From 283b06bb543f513d964dc3a6953115df897a93a1 Mon Sep 17 00:00:00 2001 From: Maurizio Pesce Date: Wed, 12 Mar 2025 09:50:04 +0100 Subject: [PATCH 01/43] Add MTP class device --- examples/device/mtp/CMakeLists.txt | 39 + examples/device/mtp/CMakePresets.json | 6 + examples/device/mtp/Makefile | 11 + examples/device/mtp/prj.conf | 6 + examples/device/mtp/skip.txt | 0 examples/device/mtp/src/main.c | 113 ++ examples/device/mtp/src/mtp_fs_example.c | 572 ++++++++++ examples/device/mtp/src/tusb_config.h | 112 ++ examples/device/mtp/src/usb_descriptors.c | 193 ++++ .../components/tinyusb_src/CMakeLists.txt | 1 + hw/bsp/rp2040/family.cmake | 1 + lib/rt-thread/SConscript | 2 + src/CMakeLists.txt | 1 + src/class/mtp/mtp.h | 438 ++++++++ src/class/mtp/mtp_device.c | 997 ++++++++++++++++++ src/class/mtp/mtp_device.h | 75 ++ src/class/mtp/mtp_device_storage.h | 149 +++ src/device/usbd.c | 13 + src/device/usbd.h | 19 + src/tinyusb.mk | 1 + src/tusb.h | 4 + src/tusb_option.h | 4 + test/fuzz/rules.mk | 1 + tools/iar_template.ipcf | 6 + 24 files changed, 2764 insertions(+) create mode 100644 examples/device/mtp/CMakeLists.txt create mode 100644 examples/device/mtp/CMakePresets.json create mode 100644 examples/device/mtp/Makefile create mode 100644 examples/device/mtp/prj.conf create mode 100644 examples/device/mtp/skip.txt create mode 100644 examples/device/mtp/src/main.c create mode 100644 examples/device/mtp/src/mtp_fs_example.c create mode 100644 examples/device/mtp/src/tusb_config.h create mode 100644 examples/device/mtp/src/usb_descriptors.c create mode 100644 src/class/mtp/mtp.h create mode 100644 src/class/mtp/mtp_device.c create mode 100644 src/class/mtp/mtp_device.h create mode 100644 src/class/mtp/mtp_device_storage.h diff --git a/examples/device/mtp/CMakeLists.txt b/examples/device/mtp/CMakeLists.txt new file mode 100644 index 000000000..e91eb8fd9 --- /dev/null +++ b/examples/device/mtp/CMakeLists.txt @@ -0,0 +1,39 @@ +cmake_minimum_required(VERSION 3.20) + +include(${CMAKE_CURRENT_SOURCE_DIR}/../../../hw/bsp/family_support.cmake) + +# gets PROJECT name for the example (e.g. -) +family_get_project_name(PROJECT ${CMAKE_CURRENT_LIST_DIR}) + +project(${PROJECT} C CXX ASM) + +# Checks this example is valid for the family and initializes the project +family_initialize_project(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}) + +# Espressif has its own cmake build system +if(FAMILY STREQUAL "espressif") + return() +endif() + +if (RTOS STREQUAL zephyr) + set(EXE_NAME app) +else() + set(EXE_NAME ${PROJECT}) + add_executable(${EXE_NAME}) +endif() + +# Example source +target_sources(${EXE_NAME} PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/src/main.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/mtp_fs_example.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/usb_descriptors.c + ) + +# Example include +target_include_directories(${EXE_NAME} PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/src + ) + +# Configure compilation flags and libraries for the example without RTOS. +# See the corresponding function in hw/bsp/FAMILY/family.cmake for details. +family_configure_device_example(${EXE_NAME} ${RTOS}) diff --git a/examples/device/mtp/CMakePresets.json b/examples/device/mtp/CMakePresets.json new file mode 100644 index 000000000..5cd8971e9 --- /dev/null +++ b/examples/device/mtp/CMakePresets.json @@ -0,0 +1,6 @@ +{ + "version": 6, + "include": [ + "../../../hw/bsp/BoardPresets.json" + ] +} diff --git a/examples/device/mtp/Makefile b/examples/device/mtp/Makefile new file mode 100644 index 000000000..7fa475da5 --- /dev/null +++ b/examples/device/mtp/Makefile @@ -0,0 +1,11 @@ +include ../../build_system/make/make.mk + +INC += \ + src \ + $(TOP)/hw \ + +# Example source +EXAMPLE_SOURCE += $(wildcard src/*.c) +SRC_C += $(addprefix $(CURRENT_PATH)/, $(EXAMPLE_SOURCE)) + +include ../../build_system/make/rules.mk diff --git a/examples/device/mtp/prj.conf b/examples/device/mtp/prj.conf new file mode 100644 index 000000000..2f5139d9d --- /dev/null +++ b/examples/device/mtp/prj.conf @@ -0,0 +1,6 @@ +CONFIG_GPIO=y +CONFIG_FPU=y +CONFIG_NO_OPTIMIZATIONS=y +CONFIG_UART_INTERRUPT_DRIVEN=y +CONFIG_NRFX_POWER=y +CONFIG_NRFX_UARTE0=y diff --git a/examples/device/mtp/skip.txt b/examples/device/mtp/skip.txt new file mode 100644 index 000000000..e69de29bb diff --git a/examples/device/mtp/src/main.c b/examples/device/mtp/src/main.c new file mode 100644 index 000000000..709aeb16f --- /dev/null +++ b/examples/device/mtp/src/main.c @@ -0,0 +1,113 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2025 Ennebi Elettronica (https://ennebielettronica.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#include +#include +#include + +#include "bsp/board_api.h" +#include "tusb.h" + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF PROTYPES +//--------------------------------------------------------------------+ + +/* Blink pattern + * - 250 ms : device not mounted + * - 1000 ms : device mounted + * - 2500 ms : device is suspended + */ +enum { + BLINK_NOT_MOUNTED = 250, + BLINK_MOUNTED = 1000, + BLINK_SUSPENDED = 2500, +}; + +static uint32_t blink_interval_ms = BLINK_NOT_MOUNTED; + +void led_blinking_task(void); + +/*------------- MAIN -------------*/ +int main(void) { + board_init(); + + // init device stack on configured roothub port + tusb_rhport_init_t dev_init = { + .role = TUSB_ROLE_DEVICE, + .speed = TUSB_SPEED_AUTO + }; + tusb_init(BOARD_TUD_RHPORT, &dev_init); + + if (board_init_after_tusb) { + board_init_after_tusb(); + } + + while (1) { + tud_task(); // tinyusb device task + led_blinking_task(); + } +} + +//--------------------------------------------------------------------+ +// Device callbacks +//--------------------------------------------------------------------+ + +// Invoked when device is mounted +void tud_mount_cb(void) { + blink_interval_ms = BLINK_MOUNTED; +} + +// Invoked when device is unmounted +void tud_umount_cb(void) { + blink_interval_ms = BLINK_NOT_MOUNTED; +} + +// Invoked when usb bus is suspended +// remote_wakeup_en : if host allow us to perform remote wakeup +// Within 7ms, device must draw an average of current less than 2.5 mA from bus +void tud_suspend_cb(bool remote_wakeup_en) { + (void) remote_wakeup_en; + blink_interval_ms = BLINK_SUSPENDED; +} + +// Invoked when usb bus is resumed +void tud_resume_cb(void) { + blink_interval_ms = tud_mounted() ? BLINK_MOUNTED : BLINK_NOT_MOUNTED; +} + +//--------------------------------------------------------------------+ +// BLINKING TASK +//--------------------------------------------------------------------+ +void led_blinking_task(void) { + static uint32_t start_ms = 0; + static bool led_state = false; + + // Blink every interval ms + if (board_millis() - start_ms < blink_interval_ms) return; // not enough time + start_ms += blink_interval_ms; + + board_led_write(led_state); + led_state = 1 - led_state; // toggle +} diff --git a/examples/device/mtp/src/mtp_fs_example.c b/examples/device/mtp/src/mtp_fs_example.c new file mode 100644 index 000000000..41673ea53 --- /dev/null +++ b/examples/device/mtp/src/mtp_fs_example.c @@ -0,0 +1,572 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2025 Ennebi Elettronica (https://ennebielettronica.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#include "class/mtp/mtp_device_storage.h" +#include "tusb.h" + +#define MTPD_STORAGE_DESCRIPTION "storage" +#define MTPD_VOLUME_IDENTIFIER "volume" + +//--------------------------------------------------------------------+ +// RAM FILESYSTEM +//--------------------------------------------------------------------+ +#define FS_MAX_NODES 5UL +#define FS_MAX_NODE_BYTES 128UL +#define FS_MAX_NODE_NAME_LEN 64UL +#define FS_ISODATETIME_LEN 26UL + +typedef struct +{ + uint32_t handle; + uint32_t parent; + uint32_t size; + bool allocated; + bool association; + char name[FS_MAX_NODE_NAME_LEN]; + char created[FS_ISODATETIME_LEN]; + char modified[FS_ISODATETIME_LEN]; + uint8_t data[FS_MAX_NODE_BYTES]; +} fs_object_info_t; + +// Sample object file +static fs_object_info_t _fs_objects[FS_MAX_NODES] = { + { + .handle = 1, + .parent = 0, + .allocated = true, + .association = false, + .name = "readme.txt", + .created = "20240104T111134.0", + .modified = "20241214T121110.0", + .data = "USB MTP on RAM Filesystem example\n", + .size = 34 + } +}; + +//--------------------------------------------------------------------+ +// OPERATING STATUS +//--------------------------------------------------------------------+ +typedef struct +{ + // Session + uint32_t session_id; + // Association traversal + uint32_t traversal_parent; + uint32_t traversal_index; + // Object open for reading + uint32_t read_handle; + uint32_t read_pos; + // Object open for writing + uint32_t write_handle; + uint32_t write_pos; + // Unique identifier + uint32_t last_handle; +} fs_operation_t; + +static fs_operation_t _fs_operation = { + .last_handle = 1 +}; + +//--------------------------------------------------------------------+ +// INTERNAL FUNCTIONS +//--------------------------------------------------------------------+ + +// Get pointer to object info from handle +fs_object_info_t *fs_object_get_from_handle(uint32_t handle); + +// Get the number of allocated nodes in filesystem +unsigned int fs_get_object_count(void); + +fs_object_info_t *fs_object_get_from_handle(uint32_t handle) +{ + fs_object_info_t *obj; + for (unsigned int i=0; iallocated && obj->handle == handle) + return obj; + } + return NULL; +} + +unsigned int fs_get_object_count(void) +{ + unsigned int s = 0; + for (unsigned int i = 0; istorage_type = MTP_STORAGE_TYPE_FIXED_RAM; + info->filesystem_type = MTP_FILESYSTEM_TYPE_GENERIC_HIERARCHICAL; + info->access_capability = MTP_ACCESS_CAPABILITY_READ_WRITE; + info->max_capacity_in_bytes = FS_MAX_NODES * FS_MAX_NODE_BYTES; + info->free_space_in_objects = FS_MAX_NODES - fs_get_object_count(); + info->free_space_in_bytes = info->free_space_in_objects * FS_MAX_NODE_BYTES; + mtpd_gct_append_wstring(MTPD_STORAGE_DESCRIPTION); + mtpd_gct_append_wstring(MTPD_VOLUME_IDENTIFIER); + return MTP_RESC_OK; +} + +mtp_response_t tud_mtp_storage_format(uint32_t storage_id) +{ + if (_fs_operation.session_id == 0) + { + TU_LOG1("ERR: Session not open\r\n"); + return MTP_RESC_SESSION_NOT_OPEN; + } + if (storage_id != STORAGE_ID(0x0001, 0x0001)) + { + TU_LOG1("ERR: Unexpected storage id %ld\r\n", storage_id); + return MTP_RESC_INVALID_STORAGE_ID; + } + + // Simply deallocate all entries + for (unsigned int i=0; iallocated && obj->parent == parent_object_handle) + { + _fs_operation.traversal_index = i+1; + *next_child_handle = obj->handle; + TU_LOG1("Association %ld -> child %ld\r\n", parent_object_handle, obj->handle); + return MTP_RESC_OK; + } + } + TU_LOG1("Association traversal completed\r\n"); + _fs_operation.traversal_index = 0; + *next_child_handle = 0; + return MTP_RESC_OK; +} + +mtp_response_t tud_mtp_storage_object_write_info(uint32_t storage_id, uint32_t parent_object, uint32_t *new_object_handle, const mtp_object_info_t *info) +{ + fs_object_info_t *obj = NULL; + + if (_fs_operation.session_id == 0) + { + TU_LOG1("ERR: Session not open\r\n"); + return MTP_RESC_SESSION_NOT_OPEN; + } + // Accept command on default storage + if (storage_id != 0xFFFFFFFF && storage_id != STORAGE_ID(0x0001, 0x0001)) + { + TU_LOG1("ERR: Unexpected storage id %ld\r\n", storage_id); + return MTP_RESC_INVALID_STORAGE_ID; + } + + if (info->object_compressed_size > FS_MAX_NODE_BYTES) + { + TU_LOG1("Object size %ld is more than maximum %ld\r\n", info->object_compressed_size, FS_MAX_NODE_BYTES); + return MTP_RESC_STORE_FULL; + } + + // Request for objects with no parent (0xFFFFFFFF) are considered root objects + if (parent_object == 0xFFFFFFFF) + parent_object = 0; + + // Ensure we are not creating an orphaned object outside root + if (parent_object != 0) + { + obj = fs_object_get_from_handle(parent_object); + if (obj == NULL) + { + TU_LOG1("Parent %ld does not exist\r\n", parent_object); + return MTP_RESC_INVALID_PARENT_OBJECT; + } + if (!obj->association) + { + TU_LOG1("Parent %ld is not an association\r\n", parent_object); + return MTP_RESC_INVALID_PARENT_OBJECT; + } + } + + // Search for first free object + for (unsigned int i=0; iallocated = true; + obj->handle = ++_fs_operation.last_handle; + obj->parent = parent_object; + obj->size = info->object_compressed_size; + obj->association = info->object_format == MTP_OBJF_ASSOCIATION; + + // Extract variable data + uint16_t offset_data = sizeof(mtp_object_info_t); + mtpd_gct_get_string(&offset_data, obj->name, FS_MAX_NODE_NAME_LEN); + mtpd_gct_get_string(&offset_data, obj->created, FS_ISODATETIME_LEN); + mtpd_gct_get_string(&offset_data, obj->modified, FS_ISODATETIME_LEN); + + TU_LOG1("Create %s %s with handle %ld, parent %ld and size %ld\r\n", + obj->association ? "association" : "object", + obj->name, obj->handle, obj->parent, obj->size); + *new_object_handle = obj->handle; + // Initialize operation + _fs_operation.write_handle = obj->handle; + _fs_operation.write_pos = 0; + return MTP_RESC_OK; +} + +mtp_response_t tud_mtp_storage_object_read_info(uint32_t object_handle, mtp_object_info_t *info) +{ + const fs_object_info_t *obj; + + if (_fs_operation.session_id == 0) + { + TU_LOG1("ERR: Session not open\r\n"); + return MTP_RESC_SESSION_NOT_OPEN; + } + + obj = fs_object_get_from_handle(object_handle); + if (obj == NULL) + { + TU_LOG1("ERR: Object with handle %ld does not exist\r\n", object_handle); + return MTP_RESC_INVALID_OBJECT_HANDLE; + } + + memset(info, 0, sizeof(mtp_object_info_t)); + info->storage_id = STORAGE_ID(0x0001, 0x0001); + if (obj->association) + { + info->object_format = MTP_OBJF_ASSOCIATION; + info->protection_status = MTP_PROTECTION_STATUS_NO_PROTECTION; + info->object_compressed_size = 0; + info->association_type = MTP_ASSOCIATION_UNDEFINED; + } + else + { + info->object_format = MTP_OBJF_UNDEFINED; + info->protection_status = MTP_PROTECTION_STATUS_NO_PROTECTION; + info->object_compressed_size = obj->size; + info->association_type = MTP_ASSOCIATION_UNDEFINED; + } + info->thumb_format = MTP_OBJF_UNDEFINED; + info->parent_object = obj->parent; + + mtpd_gct_append_wstring(obj->name); + mtpd_gct_append_wstring(obj->created); // date_created + mtpd_gct_append_wstring(obj->modified); // date_modified + mtpd_gct_append_wstring(""); // keywords, not used + + TU_LOG1("Retrieve object %s with handle %ld\r\n", obj->name, obj->handle); + + return MTP_RESC_OK; +} + +mtp_response_t tud_mtp_storage_object_write(uint32_t object_handle, const uint8_t *buffer, uint32_t size) +{ + fs_object_info_t *obj; + + obj = fs_object_get_from_handle(object_handle); + if (obj == NULL) + { + TU_LOG1("ERR: Object with handle %ld does not exist\r\n", object_handle); + return MTP_RESC_INVALID_OBJECT_HANDLE; + } + // It's a requirement that this command is preceded by a write info + if (object_handle != _fs_operation.write_handle) + { + TU_LOG1("ERR: Object %ld not open for write\r\n", object_handle); + return MTP_RESC_NO_VALID_OBJECTINFO; + } + + TU_LOG1("Write object %ld: data chunk at %ld/%ld bytes at offset %ld\r\n", object_handle, _fs_operation.write_pos, obj->size, size); + TU_ASSERT(obj->size >= _fs_operation.write_pos + size, MTP_RESC_INCOMPLETE_TRANSFER); + if (_fs_operation.write_pos + size < FS_MAX_NODE_BYTES) + memcpy(&obj->data[_fs_operation.write_pos], buffer, size); + _fs_operation.write_pos += size; + // Write operation completed + if (_fs_operation.write_pos == obj->size) + { + _fs_operation.write_handle = 0; + _fs_operation.write_pos = 0; + } + return MTP_RESC_OK; +} + +mtp_response_t tud_mtp_storage_object_size(uint32_t object_handle, uint32_t *size) +{ + const fs_object_info_t *obj; + obj = fs_object_get_from_handle(object_handle); + if (obj == NULL) + { + TU_LOG1("ERR: Object with handle %ld does not exist\r\n", object_handle); + return MTP_RESC_INVALID_OBJECT_HANDLE; + } + *size = obj->size; + return MTP_RESC_OK; +} + +mtp_response_t tud_mtp_storage_object_read(uint32_t object_handle, void *buffer, uint32_t buffer_size, uint32_t *read_count) +{ + const fs_object_info_t *obj; + + obj = fs_object_get_from_handle(object_handle); + + if (obj == NULL) + { + TU_LOG1("ERR: Object with handle %ld does not exist\r\n", object_handle); + return MTP_RESC_INVALID_OBJECT_HANDLE; + } + // It's not a requirement that this command is preceded by a read info + if (object_handle != _fs_operation.read_handle) + { + TU_LOG1("ERR: Object %ld not open for read\r\n", object_handle); + _fs_operation.read_handle = object_handle; + _fs_operation.read_pos = 0; + } + + if (obj->size - _fs_operation.read_pos > buffer_size) + { + TU_LOG1("Read object %ld: %ld bytes at offset %ld\r\n", object_handle, buffer_size, _fs_operation.read_pos); + *read_count = buffer_size; + if (_fs_operation.read_pos + buffer_size < FS_MAX_NODE_BYTES) + memcpy(buffer, &obj->data[_fs_operation.read_pos], *read_count); + _fs_operation.read_pos += *read_count; + } + else + { + TU_LOG1("Read object %ld: %ld bytes at offset %ld\r\n", object_handle, obj->size - _fs_operation.read_pos, _fs_operation.read_pos); + *read_count = obj->size - _fs_operation.read_pos; + if (_fs_operation.read_pos + buffer_size < FS_MAX_NODE_BYTES) + memcpy(buffer, &obj->data[_fs_operation.read_pos], *read_count); + // Read operation completed + _fs_operation.read_handle = 0; + _fs_operation.read_pos = 0; + } + return MTP_RESC_OK; +} + +mtp_response_t tud_mtp_storage_object_move(uint32_t object_handle, uint32_t new_parent_object_handle) +{ + fs_object_info_t *obj; + + if (new_parent_object_handle == 0xFFFFFFFF) + new_parent_object_handle = 0; + + // Ensure we are not moving to an nonexisting parent + if (new_parent_object_handle != 0) + { + obj = fs_object_get_from_handle(new_parent_object_handle); + if (obj == NULL) + { + TU_LOG1("Parent %ld does not exist\r\n", new_parent_object_handle); + return MTP_RESC_INVALID_PARENT_OBJECT; + } + if (!obj->association) + { + TU_LOG1("Parent %ld is not an association\r\n", new_parent_object_handle); + return MTP_RESC_INVALID_PARENT_OBJECT; + } + } + + obj = fs_object_get_from_handle(object_handle); + + if (obj == NULL) + { + TU_LOG1("ERR: Object with handle %ld does not exist\r\n", object_handle); + return MTP_RESC_INVALID_OBJECT_HANDLE; + } + TU_LOG1("Move object %ld to new parent %ld\r\n", object_handle, new_parent_object_handle); + obj->parent = new_parent_object_handle; + return MTP_RESC_OK; +} + +mtp_response_t tud_mtp_storage_object_delete(uint32_t object_handle) +{ + fs_object_info_t *obj; + + if (_fs_operation.session_id == 0) + { + TU_LOG1("ERR: Session not open\r\n"); + return MTP_RESC_SESSION_NOT_OPEN; + } + + if (object_handle == 0xFFFFFFFF) + object_handle = 0; + + if (object_handle != 0) + { + obj = fs_object_get_from_handle(object_handle); + + if (obj == NULL) + { + TU_LOG1("ERR: Object with handle %ld does not exist\r\n", object_handle); + return MTP_RESC_INVALID_OBJECT_HANDLE; + } + obj->allocated = false; + TU_LOG1("Delete object with handle %ld\r\n", object_handle); + } + + if (object_handle == 0 || obj->association) + { + // Delete also children + for (unsigned int i=0; iallocated && obj->parent == object_handle) + { + tud_mtp_storage_object_delete(obj->handle); + } + } + } + + return MTP_RESC_OK; +} + +void tud_mtp_storage_object_done(void) +{ +} + +void tud_mtp_storage_cancel(void) +{ + fs_object_info_t *obj; + + _fs_operation.traversal_parent = 0; + _fs_operation.traversal_index = 0; + _fs_operation.read_handle = 0; + _fs_operation.read_pos = 0; + // If write operation is canceled, discard object + if (_fs_operation.write_handle) + { + obj = fs_object_get_from_handle(_fs_operation.write_handle); + if (obj) + obj->allocated = false; + } + _fs_operation.write_handle = 0; + _fs_operation.write_pos = 0; +} + +void tud_mtp_storage_reset(void) +{ + tud_mtp_storage_cancel(); + _fs_operation.session_id = 0; +} diff --git a/examples/device/mtp/src/tusb_config.h b/examples/device/mtp/src/tusb_config.h new file mode 100644 index 000000000..251a99fd3 --- /dev/null +++ b/examples/device/mtp/src/tusb_config.h @@ -0,0 +1,112 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2025 Ennebi Elettronica (https://ennebielettronica.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#ifndef _TUSB_CONFIG_H_ +#define _TUSB_CONFIG_H_ + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Board Specific Configuration +//--------------------------------------------------------------------+ + +// RHPort number used for device can be defined by board.mk, default to port 0 +#ifndef BOARD_TUD_RHPORT +#define BOARD_TUD_RHPORT 0 +#endif + +// RHPort max operational speed can defined by board.mk +#ifndef BOARD_TUD_MAX_SPEED +#define BOARD_TUD_MAX_SPEED OPT_MODE_DEFAULT_SPEED +#endif + +//-------------------------------------------------------------------- +// COMMON CONFIGURATION +//-------------------------------------------------------------------- + +// defined by compiler flags for flexibility +#ifndef CFG_TUSB_MCU +#error CFG_TUSB_MCU must be defined +#endif + +#ifndef CFG_TUSB_OS +#define CFG_TUSB_OS OPT_OS_NONE +#endif + +#ifndef CFG_TUSB_DEBUG +#define CFG_TUSB_DEBUG 0 +#endif + +// Enable Device stack +#define CFG_TUD_ENABLED 1 + +// Default is max speed that hardware controller could support with on-chip PHY +#define CFG_TUD_MAX_SPEED BOARD_TUD_MAX_SPEED + +/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. + * Tinyusb use follows macros to declare transferring memory so that they can be put + * into those specific section. + * e.g + * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") )) + * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4))) + */ +#ifndef CFG_TUSB_MEM_SECTION +#define CFG_TUSB_MEM_SECTION +#endif + +#ifndef CFG_TUSB_MEM_ALIGN +#define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4))) +#endif + +//-------------------------------------------------------------------- +// DEVICE CONFIGURATION +//-------------------------------------------------------------------- + +#ifndef CFG_TUD_ENDPOINT0_SIZE +#define CFG_TUD_ENDPOINT0_SIZE 64 +#endif + +//------------- CLASS -------------// +#define CFG_TUD_MTP 1 + +#define CFG_TUD_MANUFACTURER "TinyUsb Manufacturer" +#define CFG_TUD_MODEL "TinyUsb Device" + +#define CFG_MTP_EP_SIZE 64 +#define CFG_MTP_EVT_EP_SIZE 64 +#define CFG_MTP_EVT_INTERVAL 100 + +#define CFG_MTP_DEVICE_VERSION "1.0" +#define CFG_MTP_SERIAL_NUMBER "0" +#define CFG_MTP_INTERFACE (CFG_TUD_MODEL " MTP") +#define CFG_MTP_STORAGE_ID_COUNT 1 + +#ifdef __cplusplus + } +#endif + +#endif /* _TUSB_CONFIG_H_ */ diff --git a/examples/device/mtp/src/usb_descriptors.c b/examples/device/mtp/src/usb_descriptors.c new file mode 100644 index 000000000..73685c3fd --- /dev/null +++ b/examples/device/mtp/src/usb_descriptors.c @@ -0,0 +1,193 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#include "bsp/board_api.h" +#include "tusb.h" + +/* A combination of interfaces must have a unique product id, since PC will save device driver after the first plug. + * Same VID/PID with different interface e.g MSC (first), then CDC (later) will possibly cause system error on PC. + * + * Auto ProductID layout's Bitmap: + * [MSB] MTP | VENDOR | MIDI | HID | MSC | CDC [LSB] + */ +#define _PID_MAP(itf, n) ( (CFG_TUD_##itf) << (n) ) +#define USB_PID (0x4000 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) | _PID_MAP(HID, 2) | \ + _PID_MAP(MIDI, 3) | _PID_MAP(VENDOR, 4) | _PID_MAP(MTP, 5)) + +#define USB_VID 0xCafe +#define USB_BCD 0x0200 + +//--------------------------------------------------------------------+ +// Device Descriptors +//--------------------------------------------------------------------+ +tusb_desc_device_t const desc_device = +{ + .bLength = sizeof(tusb_desc_device_t), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = USB_BCD, + .bDeviceClass = TUSB_CLASS_UNSPECIFIED, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + + .idVendor = USB_VID, + .idProduct = USB_PID, + .bcdDevice = 0x0100, + + .iManufacturer = 0x01, + .iProduct = 0x02, + .iSerialNumber = 0x03, + + .bNumConfigurations = 0x01 +}; + +// Invoked when received GET DEVICE DESCRIPTOR +// Application return pointer to descriptor +uint8_t const *tud_descriptor_device_cb(void) +{ + return (uint8_t const *) &desc_device; +} + +//--------------------------------------------------------------------+ +// Configuration Descriptor +//--------------------------------------------------------------------+ + +enum +{ + ITF_NUM_MTP = 0, + ITF_NUM_TOTAL +}; + +#if CFG_TUSB_MCU == OPT_MCU_LPC175X_6X || CFG_TUSB_MCU == OPT_MCU_LPC177X_8X || CFG_TUSB_MCU == OPT_MCU_LPC40XX + // LPC 17xx and 40xx endpoint type (bulk/interrupt/iso) are fixed by its number + // 0 control, 1 In, 2 Bulk, 3 Iso, 4 In, 5 Bulk etc ... + #define EPNUM_MTP_EVT 0x81 + #define EPNUM_MTP_OUT 0x02 + #define EPNUM_MTP_IN 0x82 + +#elif CFG_TUSB_MCU == OPT_MCU_CXD56 + // CXD56 USB driver has fixed endpoint type (bulk/interrupt/iso) and direction (IN/OUT) by its number + // 0 control (IN/OUT), 1 Bulk (IN), 2 Bulk (OUT), 3 In (IN), 4 Bulk (IN), 5 Bulk (OUT), 6 In (IN) + #define EPNUM_MTP_EVT 0x83 + #define EPNUM_MTP_OUT 0x02 + #define EPNUM_MTP_IN 0x81 + +#elif defined(TUD_ENDPOINT_ONE_DIRECTION_ONLY) + // MCUs that don't support a same endpoint number with different direction IN and OUT defined in tusb_mcu.h + // e.g EP1 OUT & EP1 IN cannot exist together + #define EPNUM_MTP_EVT 0x81 + #define EPNUM_MTP_OUT 0x03 + #define EPNUM_MTP_IN 0x82 + +#else + #define EPNUM_MTP_EVT 0x81 + #define EPNUM_MTP_OUT 0x02 + #define EPNUM_MTP_IN 0x82 +#endif + +#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_MTP_DESC_LEN) + +uint8_t const desc_fs_configuration[] = +{ + // Config number, interface count, string index, total length, attribute, power in mA + TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0x00, 100), + TUD_MTP_DESCRIPTOR(ITF_NUM_MTP, 4, EPNUM_MTP_EVT, CFG_MTP_EVT_EP_SIZE, CFG_MTP_EVT_INTERVAL, EPNUM_MTP_OUT, EPNUM_MTP_IN, CFG_MTP_EP_SIZE), +}; + +// Invoked when received GET CONFIGURATION DESCRIPTOR +// Application return pointer to descriptor +// Descriptor contents must exist long enough for transfer to complete +uint8_t const *tud_descriptor_configuration_cb(uint8_t index) +{ + (void) index; // for multiple configurations + return desc_fs_configuration; +} + +//--------------------------------------------------------------------+ +// String Descriptors +//--------------------------------------------------------------------+ + +// String Descriptor Index +enum { + STRID_LANGID = 0, + STRID_MANUFACTURER, + STRID_PRODUCT, + STRID_SERIAL, + STRID_MTP, +}; + +// array of pointer to string descriptors +char const *string_desc_arr[] = +{ + (const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409) + CFG_TUD_MANUFACTURER, // 1: Manufacturer + CFG_TUD_MODEL, // 2: Product + NULL, // 3: Serials will use unique ID if possible + CFG_MTP_INTERFACE, // 4: MTP Interface +}; + +static uint16_t _desc_str[32 + 1]; + +// Invoked when received GET STRING DESCRIPTOR request +// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete +uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid) { + (void) langid; + size_t chr_count; + + switch ( index ) { + case STRID_LANGID: + memcpy(&_desc_str[1], string_desc_arr[0], 2); + chr_count = 1; + break; + + case STRID_SERIAL: + chr_count = board_usb_get_serial(_desc_str + 1, 32); + break; + + default: + // Note: the 0xEE index string is a Microsoft OS 1.0 Descriptors. + // https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors + + if ( !(index < sizeof(string_desc_arr) / sizeof(string_desc_arr[0])) ) return NULL; + + const char *str = string_desc_arr[index]; + + // Cap at max char + chr_count = strlen(str); + size_t const max_count = sizeof(_desc_str) / sizeof(_desc_str[0]) - 1; // -1 for string type + if ( chr_count > max_count ) chr_count = max_count; + + // Convert ASCII string into UTF-16 + for ( size_t i = 0; i < chr_count; i++ ) { + _desc_str[1 + i] = str[i]; + } + break; + } + + // first byte is length (including header), second byte is string type + _desc_str[0] = (uint16_t) ((TUSB_DESC_STRING << 8) | (2 * chr_count + 2)); + + return _desc_str; +} diff --git a/hw/bsp/espressif/components/tinyusb_src/CMakeLists.txt b/hw/bsp/espressif/components/tinyusb_src/CMakeLists.txt index 00e288bad..beabcad9c 100644 --- a/hw/bsp/espressif/components/tinyusb_src/CMakeLists.txt +++ b/hw/bsp/espressif/components/tinyusb_src/CMakeLists.txt @@ -37,6 +37,7 @@ list(APPEND srcs ${tusb_src}/class/hid/hid_device.c ${tusb_src}/class/midi/midi_device.c ${tusb_src}/class/msc/msc_device.c + ${tusb_src}/class/mtp/mtp_device.c ${tusb_src}/class/net/ecm_rndis_device.c ${tusb_src}/class/net/ncm_device.c ${tusb_src}/class/usbtmc/usbtmc_device.c diff --git a/hw/bsp/rp2040/family.cmake b/hw/bsp/rp2040/family.cmake index 2182e9ad1..3bec5bf70 100644 --- a/hw/bsp/rp2040/family.cmake +++ b/hw/bsp/rp2040/family.cmake @@ -95,6 +95,7 @@ target_sources(tinyusb_device_base INTERFACE ${TOP}/src/class/hid/hid_device.c ${TOP}/src/class/midi/midi_device.c ${TOP}/src/class/msc/msc_device.c + ${TOP}/src/class/mtp/mtp_device.c ${TOP}/src/class/net/ecm_rndis_device.c ${TOP}/src/class/net/ncm_device.c ${TOP}/src/class/usbtmc/usbtmc_device.c diff --git a/lib/rt-thread/SConscript b/lib/rt-thread/SConscript index 34399fd45..99517a090 100644 --- a/lib/rt-thread/SConscript +++ b/lib/rt-thread/SConscript @@ -34,6 +34,8 @@ if GetDepend(["PKG_TINYUSB_DEVICE_ENABLE"]): src += ["../../src/class/cdc/cdc_device.c"] if GetDepend(["PKG_TINYUSB_DEVICE_MSC"]): src += ["../../src/class/msc/msc_device.c", "port/msc_device_port.c"] + if GetDepend(["PKG_TINYUSB_DEVICE_MTP"]): + src += ["../../src/class/mtp/mtp_device.c"] if GetDepend(["PKG_TINYUSB_DEVICE_HID"]): src += ["../../src/class/hid/hid_device.c"] diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 55c52033c..9918407b3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -19,6 +19,7 @@ function(tinyusb_target_add TARGET) ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/hid/hid_device.c ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/midi/midi_device.c ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/msc/msc_device.c + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/mtp/mtp_device.c ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/net/ecm_rndis_device.c ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/net/ncm_device.c ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/usbtmc/usbtmc_device.c diff --git a/src/class/mtp/mtp.h b/src/class/mtp/mtp.h new file mode 100644 index 000000000..33c6bb552 --- /dev/null +++ b/src/class/mtp/mtp.h @@ -0,0 +1,438 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2025 Ennebi Elettronica (https://ennebielettronica.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef _TUSB_MTP_H_ +#define _TUSB_MTP_H_ + +#include +#include +#include +#include "tusb_option.h" + +#if (CFG_TUD_ENABLED && CFG_TUD_MTP) + +#ifdef __cplusplus + extern "C" { +#endif + +#define TU_ARRAY_LEN(a) (sizeof(a)/sizeof(a[0])) +#define STORAGE_ID(physical_id, logical_id) ( (((uint32_t)physical_id & 0xFFFF) << 16) | ((uint32_t)logical_id & 0x0000FFFF) ) +typedef uint16_t wchar16_t; + +//--------------------------------------------------------------------+ +// Media Transfer Protocol Class Constant +//--------------------------------------------------------------------+ + +// Media Transfer Protocol Subclass +typedef enum +{ + MTP_SUBCLASS = 1 +} mtp_subclass_type_t; + +// MTP Protocol. +typedef enum +{ + MTP_PROTOCOL_STILL_IMAGE = 1, +} mtp_protocol_type_t; + +// PTP/MTP protocol phases +typedef enum +{ + MTP_PHASE_IDLE = 0, + MTP_PHASE_COMMAND, + MTP_PHASE_DATA_IN, + MTP_PHASE_DATA_OUT, + MTP_PHASE_RESPONSE, + MTP_PHASE_ERROR, + MTP_PHASE_NONE, +} mtp_phase_type_t; + +// PTP/MTP Class requests +typedef enum +{ + MTP_REQ_CANCEL = 0x64, + MTP_REQ_GET_EXT_EVENT_DATA = 0x65, + MTP_REQ_RESET = 0x66, + MTP_REQ_GET_DEVICE_STATUS = 0x67, +} mtp_class_request_t; + +#define MTP_GENERIC_DATA_BLOCK_LENGTH 12 +#define MTP_MAX_PACKET_SIZE 512 + +// PTP/MTP Generic container +typedef struct TU_ATTR_PACKED +{ + uint32_t container_length; + uint16_t container_type; + uint16_t code; + uint32_t transaction_id; + uint32_t data[MTP_MAX_PACKET_SIZE / sizeof(uint32_t)]; +} mtp_generic_container_t; + +// PTP/MTP Container type +typedef enum +{ + MTP_CONTAINER_TYPE_UNDEFINED = 0, + MTP_CONTAINER_TYPE_COMMAND_BLOCK = 1, + MTP_CONTAINER_TYPE_DATA_BLOCK = 2, + MTP_CONTAINER_TYPE_RESPONSE_BLOCK = 3, + MTP_CONTAINER_TYPE_EVENT_BLOCK = 4, +} mtp_container_type_t; + +// Supported OperationCode +typedef enum +{ + MTP_OPEC_GET_DEVICE_INFO = 0x1001u, + MTP_OPEC_OPEN_SESSION = 0x1002u, + MTP_OPEC_CLOSE_SESSION = 0x1003u, + MTP_OPEC_GET_STORAGE_IDS = 0x1004u, + MTP_OPEC_GET_STORAGE_INFO = 0x1005u, + MTP_OPEC_GET_NUM_OBJECTS = 0x1006u, + MTP_OPEC_GET_OBJECT_HANDLES = 0x1007u, + MTP_OPEC_GET_OBJECT_INFO = 0x1008u, + MTP_OPEC_GET_OBJECT = 0x1009u, + MTP_OPEC_GET_THUMB = 0x100Au, + MTP_OPEC_DELETE_OBJECT = 0x100Bu, + MTP_OPEC_SEND_OBJECT_INFO = 0x100Cu, + MTP_OPEC_SEND_OBJECT = 0x100Du, + MTP_OPEC_INITIAL_CAPTURE = 0x100Eu, + MTP_OPEC_FORMAT_STORE = 0x100Fu, + MTP_OPEC_RESET_DEVICE = 0x1010u, + MTP_OPEC_SELF_TEST = 0x1011u, + MTP_OPEC_SET_OBJECT_PROTECTION = 0x1012u, + MTP_OPEC_POWER_DOWN = 0x1013u, + MTP_OPEC_GET_DEVICE_PROP_DESC = 0x1014u, + MTP_OPEC_GET_DEVICE_PROP_VALUE = 0x1015u, + MTP_OPEC_SET_DEVICE_PROP_VALUE = 0x1016u, + MTP_OPEC_RESET_DEVICE_PROP_VALUE = 0x1017u, + MTP_OPEC_TERMINATE_OPEN_CAPTURE = 0x1018u, + MTP_OPEC_MOVE_OBJECT = 0x1019u, + MTP_OPEC_COPY_OBJECT = 0x101Au, + MTP_OPEC_GET_PARTIAL_OBJECT = 0x101Bu, + MTP_OPEC_INITIATE_OPEN_CAPTURE = 0x101Bu, + MTP_OPEC_GET_OBJECT_PROPS_SUPPORTED = 0x9801u, + MTP_OPEC_GET_OBJECT_PROP_DESC = 0x9802u, + MTP_OPEC_GET_OBJECT_PROP_VALUE = 0x9803u, + MTP_OPEC_SET_OBJECT_PROP_VALUE = 0x9804u, + MTP_OPEC_GET_OBJECT_PROPLIST = 0x9805u, + MTP_OPEC_GET_OBJECT_PROP_REFERENCES = 0x9810u, + MTP_OPEC_GETSERVICEIDS = 0x9301u, + MTP_OPEC_GETSERVICEINFO = 0x9302u, + MTP_OPEC_GETSERVICECAPABILITIES = 0x9303u, + MTP_OPEC_GETSERVICEPROPDESC = 0x9304u, +} mtp_operation_code_t; + +// Supported EventCode +typedef enum +{ + MTP_EVTC_OBJECT_ADDED = 0x4002, +} mtp_event_code_t; + + +// Supported Device Properties +typedef enum +{ + MTP_DEVP_UNDEFINED = 0x5000u, + MTP_DEVP_BATTERY_LEVEL = 0x5001u, + MTP_DEVP_DEVICE_FRIENDLY_NAME = 0xD402u, +} mtp_event_properties_t; + +// Supported Object Properties +typedef enum +{ + MTP_OBJP_STORAGE_ID = 0xDC01u, + MTP_OBJP_OBJECT_FORMAT = 0xDC02u, + MTP_OBJP_PROTECTION_STATUS = 0xDC03u, + MTP_OBJP_OBJECT_SIZE = 0xDC04u, + MTP_OBJP_ASSOCIATION_TYPE = 0xDC05u, + MTP_OBJP_OBJECT_FILE_NAME = 0xDC07u, + MTP_OBJP_PARENT_OBJECT = 0xDC0Bu, + MTP_OBJP_PERSISTENT_UNIQUE_OBJECT_IDENTIFIER = 0xDC41u, + MTP_OBJP_NAME = 0xDC44u, +} mtp_object_properties_t; + +// Object formats +typedef enum +{ + MTP_OBJF_UNDEFINED = 0x3000u, + MTP_OBJF_ASSOCIATION = 0x3001u, + MTP_OBJF_TEXT = 0x3004u, +} mtp_object_formats_t; + +// Predefined Object handles +typedef enum +{ + MTP_OBJH_ROOT = 0x0000, +} mtp_object_handles_t; + +// Datatypes +typedef enum +{ + MTP_TYPE_UNDEFINED = 0x0000u, + MTP_TYPE_INT8 = 0x0001u, + MTP_TYPE_UINT8 = 0x0002u, + MTP_TYPE_INT16 = 0x0003u, + MTP_TYPE_UINT16 = 0x0004u, + MTP_TYPE_INT32 = 0x0005u, + MTP_TYPE_UINT32 = 0x0006u, + MTP_TYPE_INT64 = 0x0007u, + MTP_TYPE_UINT64 = 0x0008u, + MTP_TYPE_STR = 0xFFFFu, +} mtp_datatypes_t; + +// Get/Set +typedef enum +{ + MTP_MODE_GET = 0x00u, + MTP_MODE_GET_SET = 0x01u, +} mtp_mode_get_set_t; + +tu_static const uint16_t mtp_operations_supported[] = { + MTP_OPEC_GET_DEVICE_INFO, + MTP_OPEC_OPEN_SESSION, + MTP_OPEC_CLOSE_SESSION, + MTP_OPEC_GET_STORAGE_IDS, + MTP_OPEC_GET_STORAGE_INFO, + MTP_OPEC_GET_NUM_OBJECTS, + MTP_OPEC_GET_OBJECT_HANDLES, + MTP_OPEC_GET_OBJECT_INFO, + MTP_OPEC_GET_OBJECT, + MTP_OPEC_DELETE_OBJECT, + MTP_OPEC_SEND_OBJECT_INFO, + MTP_OPEC_SEND_OBJECT, + MTP_OPEC_FORMAT_STORE, + MTP_OPEC_RESET_DEVICE, + MTP_OPEC_GET_DEVICE_PROP_DESC, + MTP_OPEC_GET_DEVICE_PROP_VALUE, + MTP_OPEC_SET_DEVICE_PROP_VALUE, +}; + +tu_static const uint16_t mtp_events_supported[] = { + MTP_EVTC_OBJECT_ADDED, +}; + +tu_static const uint16_t mtp_device_properties_supported[] = { + MTP_DEVP_DEVICE_FRIENDLY_NAME, +}; + +tu_static const uint16_t mtp_capture_formats[] = { + MTP_OBJF_UNDEFINED, + MTP_OBJF_ASSOCIATION, + MTP_OBJF_TEXT, +}; + +tu_static const uint16_t mtp_playback_formats[] = { + MTP_OBJF_UNDEFINED, + MTP_OBJF_ASSOCIATION, + MTP_OBJF_TEXT, +}; + +//--------------------------------------------------------------------+ +// Data structures +//--------------------------------------------------------------------+ + +// DeviceInfo Dataset +#define MTP_EXTENSIONS "microsoft.com: 1.0; " +typedef struct TU_ATTR_PACKED { + uint16_t standard_version; + uint32_t mtp_vendor_extension_id; + uint16_t mtp_version; + uint8_t mtp_extensions_len; + wchar16_t mtp_extensions[TU_ARRAY_LEN(MTP_EXTENSIONS)] TU_ATTR_PACKED; + + uint16_t functional_mode; + /* Operations supported */ + uint32_t operations_supported_len; + uint16_t operations_supported[TU_ARRAY_LEN(mtp_operations_supported)] TU_ATTR_PACKED; + /* Events supported */ + uint32_t events_supported_len; + uint16_t events_supported[TU_ARRAY_LEN(mtp_events_supported)] TU_ATTR_PACKED; + /* Device properties supported */ + uint32_t device_properties_supported_len; + uint16_t device_properties_supported[TU_ARRAY_LEN(mtp_device_properties_supported)] TU_ATTR_PACKED; + /* Capture formats */ + uint32_t capture_formats_len; + uint16_t capture_formats[TU_ARRAY_LEN(mtp_capture_formats)] TU_ATTR_PACKED; + /* Playback formats */ + uint32_t playback_formats_len; + uint16_t playback_formats[TU_ARRAY_LEN(mtp_playback_formats)] TU_ATTR_PACKED; +} mtp_device_info_t; +// The following fields will be dynamically added to the struct at runtime: +// - wstring manufacturer +// - wstring model +// - wstring device_version +// - wstring serial_number + + +#define MTP_STRING_DEF(name, string) \ + uint8_t name##_len; \ + wchar16_t name[TU_ARRAY_LEN(string)]; + +#define MTP_ARRAY_DEF(name, array) \ + uint16_t name##_len; \ + typeof(name) name[TU_ARRAY_LEN(array)]; + +// StorageInfo dataset +typedef struct TU_ATTR_PACKED { + uint16_t storage_type; + uint16_t filesystem_type; + uint16_t access_capability; + uint64_t max_capacity_in_bytes; + uint64_t free_space_in_bytes; + uint32_t free_space_in_objects; +} mtp_storage_info_t; +// The following fields will be dynamically added to the struct at runtime: +// - wstring storage_description +// - wstring volume_identifier + +// ObjectInfo Dataset +typedef struct TU_ATTR_PACKED { + uint32_t storage_id; + uint16_t object_format; + uint16_t protection_status; + uint32_t object_compressed_size; + uint16_t thumb_format; // unused + uint32_t thumb_compressed_size; // unused + uint32_t thumb_pix_width; // unused + uint32_t thumb_pix_height; // unused + uint32_t image_pix_width; // unused + uint32_t image_pix_height; // unused + uint32_t image_bit_depth; // unused + uint32_t parent_object; // 0: root + uint16_t association_type; + uint32_t association_description; // not used + uint32_t sequence_number; // not used +} mtp_object_info_t; +// The following fields will be dynamically added to the struct at runtime: +// - wstring filename; +// - datetime_wstring date_created; +// - datetime_wstring date_modified; +// - wstring keywords; + +// Storage IDs +typedef struct TU_ATTR_PACKED { + uint32_t storage_ids_len; + uint32_t storage_ids[CFG_MTP_STORAGE_ID_COUNT]; +} mtp_storage_ids_t; + +// DevicePropDesc Dataset +typedef struct TU_ATTR_PACKED { + uint16_t device_property_code; + uint16_t datatype; + uint8_t get_set; +} mtp_device_prop_desc_t; +// The following fields will be dynamically added to the struct at runtime: +// - wstring factory_def_value; +// - wstring current_value_len; +// - uint8_t form_flag; + +typedef struct TU_ATTR_PACKED { + uint16_t wLength; + uint16_t code; +} mtp_device_status_res_t; + +typedef struct TU_ATTR_PACKED { + uint32_t object_handle; + uint32_t storage_id; + uint32_t parent_object_handle; +} mtp_basic_object_info_t; + +//--------------------------------------------------------------------+ +// Definitions +//--------------------------------------------------------------------+ + +typedef enum { + MTP_STORAGE_TYPE_UNDEFINED = 0x0000u, + MTP_STORAGE_TYPE_FIXED_ROM = 0x0001u, + MTP_STORAGE_TYPE_REMOVABLE_ROM = 0x0002u, + MTP_STORAGE_TYPE_FIXED_RAM = 0x0003u, + MTP_STORAGE_TYPE_REMOVABLE_RAM = 0x0004u, +} mtp_storage_type_t; + +typedef enum { + MTP_FILESYSTEM_TYPE_UNDEFINED = 0x0000u, + MTP_FILESYSTEM_TYPE_GENERIC_FLAT = 0x0001u, + MTP_FILESYSTEM_TYPE_GENERIC_HIERARCHICAL = 0x0002u, + MTP_FILESYSTEM_TYPE_DCF = 0x0003u, +} mtp_filesystem_type_t; + +typedef enum { + MTP_ACCESS_CAPABILITY_READ_WRITE = 0x0000u, + MTP_ACCESS_CAPABILITY_READ_ONLY_WITHOUT_OBJECT_DELETION = 0x0001u, + MTP_ACCESS_CAPABILITY_READ_ONLY_WITH_OBJECT_DELETION = 0x0002u, +} mtp_access_capability_t; + +typedef enum { + MTP_PROTECTION_STATUS_NO_PROTECTION = 0x0000u, + MTP_PROTECTION_STATUS_READ_ONLY = 0x0001u, + MTP_PROTECTION_STATUS_READ_ONLY_DATA = 0x8002u, + MTP_PROTECTION_NON_TRANSFERABLE_DATA = 0x8003u, +} mtp_protection_status_t; + +typedef enum { + MTP_ASSOCIATION_UNDEFINED = 0x0000u, + MTP_ASSOCIATION_GENERIC_FOLDER = 0x0001u, + MTP_ASSOCIATION_GENERIC_ALBUM = 0x0002u, + MTP_ASSOCIATION_TIME_SEQUENCE = 0x0003u, + MTP_ASSOCIATION_HORIZONTAL_PANORAMIC = 0x0004u, + MTP_ASSOCIATION_VERTICAL_PANORAMIC = 0x0005u, + MTP_ASSOCIATION_2D_PANORAMIC = 0x0006u, +} mtp_association_t; + +// Responses +typedef enum { +// Supported ResponseCode + MTP_RESC_UNDEFINED = 0x2000u, + MTP_RESC_OK = 0x2001u, + MTP_RESC_GENERAL_ERROR = 0x2002u, + MTP_RESC_SESSION_NOT_OPEN = 0x2003u, + MTP_RESC_INVALID_TRANSACTION_ID = 0x2004u, + MTP_RESC_OPERATION_NOT_SUPPORTED = 0x2005u, + MTP_RESC_PARAMETER_NOT_SUPPORTED = 0x2006u, + MTP_RESC_INCOMPLETE_TRANSFER = 0x2007u, + MTP_RESC_INVALID_STORAGE_ID = 0x2008u, + MTP_RESC_INVALID_OBJECT_HANDLE = 0x2009u, + MTP_RESC_STORE_FULL = 0x200Cu, + MTP_RESC_OBJECT_WRITE_PROTECTED = 0x200Du, + MTP_RESC_STORE_NOT_AVAILABLE = 0x2013u, + MTP_RESC_SPECIFICATION_BY_FORMAT_UNSUPPORTED = 0x2014u, + MTP_RESC_NO_VALID_OBJECTINFO = 0x2015u, + MTP_RESC_DEVICE_BUSY = 0x2019u, + MTP_RESC_INVALID_PARENT_OBJECT = 0x201Au, + MTP_RESC_INVALID_DEVICE_PROP_FORMAT = 0x201Bu, + MTP_RESC_INVALID_DEVICE_PROP_VALUE = 0x201Cu, + MTP_RESC_INVALID_PARAMETER = 0x201Du, + MTP_RESC_SESSION_ALREADY_OPEN = 0x201Eu, + MTP_RESC_TRANSACTION_CANCELLED = 0x201Fu, +} mtp_response_t; + +#ifdef __cplusplus + } +#endif + +#endif /* CFG_TUD_ENABLED && CFG_TUD_MTP */ + +#endif /* _TUSB_MTP_H_ */ diff --git a/src/class/mtp/mtp_device.c b/src/class/mtp/mtp_device.c new file mode 100644 index 000000000..854d4f10e --- /dev/null +++ b/src/class/mtp/mtp_device.c @@ -0,0 +1,997 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2025 Ennebi Elettronica (https://ennebielettronica.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +#include "tusb_option.h" + +#if (CFG_TUD_ENABLED && CFG_TUD_MTP) + +//--------------------------------------------------------------------+ +// INCLUDE +//--------------------------------------------------------------------+ +#include "device/dcd.h" // for faking dcd_event_xfer_complete +#include "device/usbd.h" +#include "device/usbd_pvt.h" + +#include "mtp_device.h" +#include "mtp_device_storage.h" + +// Level where CFG_TUSB_DEBUG must be at least for this driver is logged +#ifndef CFG_TUD_MTP_LOG_LEVEL + #define CFG_TUD_MTP_LOG_LEVEL CFG_TUD_LOG_LEVEL +#endif + +#define TU_LOG_DRV(...) TU_LOG(CFG_TUD_MTP_LOG_LEVEL, __VA_ARGS__) + +//--------------------------------------------------------------------+ +// STRUCT +//--------------------------------------------------------------------+ +typedef struct +{ + uint8_t itf_num; + uint8_t ep_in; + uint8_t ep_out; + uint8_t ep_evt; + + // Bulk Only Transfer (BOT) Protocol + uint8_t phase; + + uint32_t queued_len; // number of bytes queued from the DataIN Stage + uint32_t total_len; // byte to be transferred, can be smaller than total_bytes in cbw + uint32_t xferred_len; // number of bytes transferred so far in the Data Stage + uint32_t handled_len; // number of bytes already handled in the Data Stage + bool xfer_completed; // true when DATA-IN/DATA-OUT transfer is completed + +} mtpd_interface_t; + +typedef struct +{ + uint32_t session_id; + uint32_t transaction_id; +} mtpd_context_t; + +//--------------------------------------------------------------------+ +// INTERNAL FUNCTION DECLARATION +//--------------------------------------------------------------------+ +// Checker +tu_static mtp_phase_type_t mtpd_chk_generic(const char *func_name, const bool err_cd, const uint16_t ret_code, const char *message); +tu_static mtp_phase_type_t mtpd_chk_session_open(const char *func_name); + +// MTP commands +tu_static mtp_phase_type_t mtpd_handle_cmd(void); +tu_static mtp_phase_type_t mtpd_handle_data(void); +tu_static mtp_phase_type_t mtpd_handle_cmd_get_device_info(void); +tu_static mtp_phase_type_t mtpd_handle_cmd_open_session(void); +tu_static mtp_phase_type_t mtpd_handle_cmd_close_session(void); +tu_static mtp_phase_type_t mtpd_handle_cmd_get_storage_info(void); +tu_static mtp_phase_type_t mtpd_handle_cmd_get_storage_ids(void); +tu_static mtp_phase_type_t mtpd_handle_cmd_get_object_handles(void); +tu_static mtp_phase_type_t mtpd_handle_cmd_get_object_info(void); +tu_static mtp_phase_type_t mtpd_handle_cmd_get_object(void); +tu_static mtp_phase_type_t mtpd_handle_dti_get_object(void); +tu_static mtp_phase_type_t mtpd_handle_cmd_delete_object(void); +tu_static mtp_phase_type_t mtpd_handle_cmd_get_device_prop_desc(void); +tu_static mtp_phase_type_t mtpd_handle_cmd_get_device_prop_value(void); +tu_static mtp_phase_type_t mtpd_handle_cmd_send_object_info(void); +tu_static mtp_phase_type_t mtpd_handle_dto_send_object_info(void); +tu_static mtp_phase_type_t mtpd_handle_cmd_send_object(void); +tu_static mtp_phase_type_t mtpd_handle_dto_send_object(void); +tu_static mtp_phase_type_t mtpd_handle_cmd_format_store(void); + +//--------------------------------------------------------------------+ +// MTP variable declaration +//--------------------------------------------------------------------+ +CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN tu_static mtpd_interface_t _mtpd_itf; +CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN tu_static mtp_generic_container_t _mtpd_gct; +CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN tu_static mtpd_context_t _mtpd_ctx; +CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN tu_static mtp_device_status_res_t _mtpd_device_status_res; +CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN tu_static uint32_t _mtpd_get_object_handle; +CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN tu_static mtp_basic_object_info_t _mtpd_soi; +CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN tu_static char _mtp_datestr[20]; + +//--------------------------------------------------------------------+ +// USBD Driver API +//--------------------------------------------------------------------+ +void mtpd_init(void) { + TU_LOG_DRV(" MTP mtpd_init\n"); + tu_memclr(&_mtpd_itf, sizeof(mtpd_interface_t)); + tu_memclr(&_mtpd_ctx, sizeof(mtpd_context_t)); + _mtpd_get_object_handle = 0; + tu_memclr(&_mtpd_soi, sizeof(mtp_basic_object_info_t)); +} + +bool mtpd_deinit(void) { + TU_LOG_DRV(" MTP mtpd_deinit\n"); + // nothing to do + return true; +} + +void mtpd_reset(uint8_t rhport) +{ + TU_LOG_DRV(" MTP mtpd_reset\n"); + (void) rhport; + + // Close all endpoints + dcd_edpt_close_all(rhport); + tu_memclr(&_mtpd_itf, sizeof(mtpd_interface_t)); + tu_memclr(&_mtpd_ctx, sizeof(mtpd_context_t)); + tu_memclr(&_mtpd_gct, sizeof(mtp_generic_container_t)); + _mtpd_get_object_handle = 0; + tu_memclr(&_mtpd_soi, sizeof(mtp_basic_object_info_t)); +} + +uint16_t mtpd_open(uint8_t rhport, tusb_desc_interface_t const *itf_desc, uint16_t max_len) +{ + TU_LOG_DRV(" MTP mtpd_open\n"); + tusb_desc_endpoint_t const *ep_desc; + // only support SCSI's BOT protocol + TU_VERIFY(TUSB_CLASS_IMAGE == itf_desc->bInterfaceClass && + MTP_SUBCLASS == itf_desc->bInterfaceSubClass && + MTP_PROTOCOL_STILL_IMAGE == itf_desc->bInterfaceProtocol, 0); + + // mtp driver length is fixed + uint16_t const mtpd_itf_size = sizeof(tusb_desc_interface_t) + 3 * sizeof(tusb_desc_endpoint_t); + + // Max length must be at least 1 interface + 3 endpoints + TU_ASSERT(itf_desc->bNumEndpoints == 3 && max_len >= mtpd_itf_size); + + _mtpd_itf.itf_num = itf_desc->bInterfaceNumber; + + // Open interrupt IN endpoint + ep_desc = (tusb_desc_endpoint_t const *)tu_desc_next(itf_desc); + TU_ASSERT(ep_desc->bDescriptorType == TUSB_DESC_ENDPOINT && ep_desc->bmAttributes.xfer == TUSB_XFER_INTERRUPT, 0); + TU_ASSERT(usbd_edpt_open(rhport, ep_desc), 0); + _mtpd_itf.ep_evt = ep_desc->bEndpointAddress; + + // Open endpoint pair + TU_ASSERT( usbd_open_edpt_pair(rhport, tu_desc_next(ep_desc), 2, TUSB_XFER_BULK, &_mtpd_itf.ep_out, &_mtpd_itf.ep_in), 0 ); + + // Prepare rx on bulk out EP + TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_out, ((uint8_t *)(&_mtpd_gct)), CFG_MTP_EP_SIZE), 0); + + return mtpd_itf_size; +} + +// Invoked when a control transfer occurred on an interface of this class +// Driver response accordingly to the request and the transfer stage (setup/data/ack) +// return false to stall control endpoint (e.g unsupported request) +bool mtpd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request) +{ + TU_LOG_DRV(" MTP mtpd_control_xfer_cb: bmRequest=0x%2x, bRequest=0x%2x\n", request->bmRequestType, request->bRequest); + // nothing to do with DATA & ACK stage + if (stage != CONTROL_STAGE_SETUP) return true; + + uint16_t len = 0; + + switch ( request->bRequest ) + { + case MTP_REQ_CANCEL: + TU_LOG_DRV(" MTP request: MTP_REQ_CANCEL\n"); + tud_mtp_storage_cancel(); + break; + case MTP_REQ_GET_EXT_EVENT_DATA: + TU_LOG_DRV(" MTP request: MTP_REQ_GET_EXT_EVENT_DATA\n"); + break; + case MTP_REQ_RESET: + TU_LOG_DRV(" MTP request: MTP_REQ_RESET\n"); + tud_mtp_storage_reset(); + // Prepare for a new command + TU_ASSERT( usbd_edpt_xfer(rhport, _mtpd_itf.ep_out, ((uint8_t *)(&_mtpd_gct)), CFG_MTP_EP_SIZE) ); + break; + case MTP_REQ_GET_DEVICE_STATUS: + TU_LOG_DRV(" MTP request: MTP_REQ_GET_DEVICE_STATUS\n"); + len = 4; + _mtpd_device_status_res.wLength = len; + // Cancel is synchronous, always answer OK + _mtpd_device_status_res.code = MTP_RESC_OK; + TU_ASSERT( tud_control_xfer(rhport, request, (uint8_t *)&_mtpd_device_status_res , len) ); + break; + + default: + TU_LOG_DRV(" MTP request: invalid request\r\n"); + return false; // stall unsupported request + } + return true; +} + +// Transfer on bulk endpoints +bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes) +{ + const unsigned dir = tu_edpt_dir(ep_addr); + + if (event != XFER_RESULT_SUCCESS) + return false; + + // IN transfer completed + if (dir == TUSB_DIR_IN) + { + if (_mtpd_itf.phase == MTP_PHASE_RESPONSE) + { + // IN transfer completed, prepare for a new command + TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_out, ((uint8_t *)(&_mtpd_gct)), CFG_MTP_EP_SIZE), 0); + _mtpd_itf.phase = MTP_PHASE_IDLE; + } + else if (_mtpd_itf.phase == MTP_PHASE_DATA_IN) + { + _mtpd_itf.xferred_len += xferred_bytes; + _mtpd_itf.handled_len = _mtpd_itf.xferred_len; + + // Check if transfer completed + if (_mtpd_itf.xferred_len >= _mtpd_itf.total_len && (xferred_bytes == 0 || (xferred_bytes % CFG_MTP_EP_SIZE) != 0)) + { + _mtpd_itf.phase = MTP_PHASE_RESPONSE; + _mtpd_gct.container_type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; + _mtpd_gct.code = MTP_RESC_OK; + _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH; + _mtpd_gct.transaction_id = _mtpd_ctx.transaction_id; + if (_mtpd_ctx.session_id != 0) + { + _mtpd_gct.data[0] = _mtpd_ctx.session_id; + _mtpd_gct.container_length += sizeof(uint32_t); + } + TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_in, ((uint8_t *)(&_mtpd_gct)), (uint16_t)_mtpd_gct.container_length), 0); + } + else + // Send next block of DATA + { + // Send Zero-Lenght Packet + if (_mtpd_itf.xferred_len == _mtpd_itf.total_len) + { + TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_in, ((uint8_t *)(&_mtpd_gct.data)), 0 )); + } + else + { + _mtpd_itf.phase = mtpd_handle_data(); + if (_mtpd_itf.phase == MTP_PHASE_RESPONSE) + TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_in, ((uint8_t *)(&_mtpd_gct)), (uint16_t)_mtpd_gct.container_length)); + else + TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_in, ((uint8_t *)(&_mtpd_gct.data)), (uint16_t)_mtpd_itf.queued_len)); + } + } + } + else + { + return false; + } + } + + if (dir == TUSB_DIR_OUT) + { + if (_mtpd_itf.phase == MTP_PHASE_IDLE) + { + // A new command has been received. Ensure this is the last of the sequence. + _mtpd_itf.total_len = _mtpd_gct.container_length; + // Stall in case of unexpected block + if (_mtpd_gct.container_type != MTP_CONTAINER_TYPE_COMMAND_BLOCK) + { + return false; + } + _mtpd_itf.phase = MTP_PHASE_COMMAND; + _mtpd_itf.total_len = _mtpd_gct.container_length; + _mtpd_itf.xferred_len = xferred_bytes; + _mtpd_itf.handled_len = 0; + _mtpd_itf.xfer_completed = false; + TU_ASSERT(_mtpd_itf.total_len < sizeof(mtp_generic_container_t)); + } + + if (_mtpd_itf.phase == MTP_PHASE_COMMAND) + { + // A zero-length or a short packet termination is expected + if (xferred_bytes == CFG_MTP_EP_SIZE || (_mtpd_itf.total_len - _mtpd_itf.xferred_len) > 0 ) + { + TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_out, ((uint8_t *)(&_mtpd_gct)) + _mtpd_itf.xferred_len, (uint16_t)(_mtpd_itf.total_len - _mtpd_itf.xferred_len))); + } + else + { + // Handle command block + _mtpd_itf.phase = mtpd_handle_cmd(); + if (_mtpd_itf.phase == MTP_PHASE_RESPONSE) + { + TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_in, ((uint8_t *)(&_mtpd_gct)), (uint16_t)_mtpd_gct.container_length)); + } + else if (_mtpd_itf.phase == MTP_PHASE_DATA_IN) + { + TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_in, ((uint8_t *)(&_mtpd_gct)), (uint16_t)_mtpd_itf.queued_len)); + _mtpd_itf.total_len = _mtpd_gct.container_length; + _mtpd_itf.xferred_len = 0; + _mtpd_itf.handled_len = 0; + _mtpd_itf.xfer_completed = false; + } + else if (_mtpd_itf.phase == MTP_PHASE_DATA_OUT) + { + TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_out, ((uint8_t *)(&_mtpd_gct)), CFG_MTP_EP_SIZE), 0); + _mtpd_itf.xferred_len = 0; + _mtpd_itf.handled_len = 0; + _mtpd_itf.xfer_completed = false; + } + else + { + usbd_edpt_stall(rhport, _mtpd_itf.ep_out); + usbd_edpt_stall(rhport, _mtpd_itf.ep_in); + } + } + return true; + } + + if (_mtpd_itf.phase == MTP_PHASE_DATA_OUT) + { + // First block of data + if (_mtpd_itf.xferred_len == 0) + { + _mtpd_itf.total_len = _mtpd_gct.container_length; + _mtpd_itf.handled_len = 0; + _mtpd_itf.xfer_completed = false; + } + _mtpd_itf.xferred_len += xferred_bytes; + // Stall in case of unexpected block + if (_mtpd_gct.container_type != MTP_CONTAINER_TYPE_DATA_BLOCK) + { + return false; + } + + // A zero-length or a short packet termination + if (xferred_bytes < CFG_MTP_EP_SIZE) + { + _mtpd_itf.xfer_completed = true; + // Handle data block + _mtpd_itf.phase = mtpd_handle_data(); + if (_mtpd_itf.phase == MTP_PHASE_DATA_IN || _mtpd_itf.phase == MTP_PHASE_RESPONSE) + { + TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_in, ((uint8_t *)(&_mtpd_gct)), (uint16_t)_mtpd_gct.container_length)); + } + else if (_mtpd_itf.phase == MTP_PHASE_DATA_OUT) + { + TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_out, ((uint8_t *)(&_mtpd_gct)), CFG_MTP_EP_SIZE), 0); + _mtpd_itf.xferred_len = 0; + _mtpd_itf.xfer_completed = false; + } + else + { + usbd_edpt_stall(rhport, _mtpd_itf.ep_out); + usbd_edpt_stall(rhport, _mtpd_itf.ep_in); + } + } + else + { + // Handle data block when container is full + if (_mtpd_itf.xferred_len - _mtpd_itf.handled_len >= MTP_MAX_PACKET_SIZE - CFG_MTP_EP_SIZE) + { + _mtpd_itf.phase = mtpd_handle_data(); + _mtpd_itf.handled_len = _mtpd_itf.xferred_len; + } + // Transfer completed: wait for zero-lenght packet + // Some platforms may not respect EP size and xferred_bytes may be more than CFG_MTP_EP_SIZE if + // the OUT EP is waiting for more data. Ensure we are not waiting for more than CFG_MTP_EP_SIZE. + if (_mtpd_itf.total_len == _mtpd_itf.xferred_len) + { + TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_out, ((uint8_t *)(&_mtpd_gct.data)), CFG_MTP_EP_SIZE), 0); + } + // First data block includes container header + container data + else if (_mtpd_itf.handled_len == 0) + { + TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_out, ((uint8_t *)(&_mtpd_gct)) + _mtpd_itf.xferred_len, (uint16_t)TU_MIN(_mtpd_itf.total_len - _mtpd_itf.xferred_len, CFG_MTP_EP_SIZE))); + } + else + // Successive data block includes only container data + { + TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_out, ((uint8_t *)(&_mtpd_gct.data)) + _mtpd_itf.xferred_len - _mtpd_itf.handled_len, (uint16_t)TU_MIN(_mtpd_itf.total_len - _mtpd_itf.xferred_len, CFG_MTP_EP_SIZE))); + } + } + } + } + return true; +} + +//--------------------------------------------------------------------+ +// MTPD Internal functionality +//--------------------------------------------------------------------+ + +// Decode command and prepare response +mtp_phase_type_t mtpd_handle_cmd(void) +{ + TU_ASSERT(_mtpd_gct.container_type == MTP_CONTAINER_TYPE_COMMAND_BLOCK); + _mtpd_ctx.transaction_id = _mtpd_gct.transaction_id; + if (_mtpd_gct.code != MTP_OPEC_SEND_OBJECT) + _mtpd_soi.object_handle = 0; + + switch(_mtpd_gct.code) + { + case MTP_OPEC_GET_DEVICE_INFO: + TU_LOG_DRV(" MTP command: MTP_OPEC_GET_DEVICE_INFO\n"); + return mtpd_handle_cmd_get_device_info(); + case MTP_OPEC_OPEN_SESSION: + TU_LOG_DRV(" MTP command: MTP_OPEC_OPEN_SESSION\n"); + return mtpd_handle_cmd_open_session(); + case MTP_OPEC_CLOSE_SESSION: + TU_LOG_DRV(" MTP command: MTP_OPEC_CLOSE_SESSION\n"); + return mtpd_handle_cmd_close_session(); + case MTP_OPEC_GET_STORAGE_IDS: + TU_LOG_DRV(" MTP command: MTP_OPEC_GET_STORAGE_IDS\n"); + return mtpd_handle_cmd_get_storage_ids(); + case MTP_OPEC_GET_STORAGE_INFO: + TU_LOG_DRV(" MTP command: MTP_OPEC_GET_STORAGE_INFO for ID=%lu\n", _mtpd_gct.data[0]); + return mtpd_handle_cmd_get_storage_info(); + case MTP_OPEC_GET_OBJECT_HANDLES: + TU_LOG_DRV(" MTP command: MTP_OPEC_GET_OBJECT_HANDLES\n"); + return mtpd_handle_cmd_get_object_handles(); + case MTP_OPEC_GET_OBJECT_INFO: + TU_LOG_DRV(" MTP command: MTP_OPEC_GET_OBJECT_INFO\n"); + return mtpd_handle_cmd_get_object_info(); + case MTP_OPEC_GET_OBJECT: + TU_LOG_DRV(" MTP command: MTP_OPEC_GET_OBJECT\n"); + return mtpd_handle_cmd_get_object(); + case MTP_OPEC_DELETE_OBJECT: + TU_LOG_DRV(" MTP command: MTP_OPEC_DELETE_OBJECT\n"); + return mtpd_handle_cmd_delete_object(); + case MTP_OPEC_GET_DEVICE_PROP_DESC: + TU_LOG_DRV(" MTP command: MTP_OPEC_GET_DEVICE_PROP_DESC\n"); + return mtpd_handle_cmd_get_device_prop_desc(); + case MTP_OPEC_GET_DEVICE_PROP_VALUE: + TU_LOG_DRV(" MTP command: MTP_OPEC_GET_DEVICE_PROP_VALUE\n"); + return mtpd_handle_cmd_get_device_prop_value(); + case MTP_OPEC_SEND_OBJECT_INFO: + TU_LOG_DRV(" MTP command: MTP_OPEC_SEND_OBJECT_INFO\n"); + return mtpd_handle_cmd_send_object_info(); + case MTP_OPEC_SEND_OBJECT: + TU_LOG_DRV(" MTP command: MTP_OPEC_SEND_OBJECT\n"); + return mtpd_handle_cmd_send_object(); + case MTP_OPEC_FORMAT_STORE: + TU_LOG_DRV(" MTP command: MTP_OPEC_FORMAT_STORE\n"); + return mtpd_handle_cmd_format_store(); + default: + TU_LOG_DRV(" MTP command: MTP_OPEC_UNKNOWN_COMMAND %x!!!!\n", _mtpd_gct.code); + return false; + } + return true; +} + +mtp_phase_type_t mtpd_handle_data(void) +{ + TU_ASSERT(_mtpd_gct.container_type == MTP_CONTAINER_TYPE_DATA_BLOCK); + _mtpd_ctx.transaction_id = _mtpd_gct.transaction_id; + + switch(_mtpd_gct.code) + { + case MTP_OPEC_GET_OBJECT: + TU_LOG_DRV(" MTP command: MTP_OPEC_GET_OBJECT-DATA_IN\n"); + return mtpd_handle_dti_get_object(); + case MTP_OPEC_SEND_OBJECT_INFO: + TU_LOG_DRV(" MTP command: MTP_OPEC_SEND_OBJECT_INFO-DATA_OUT\n"); + return mtpd_handle_dto_send_object_info(); + case MTP_OPEC_SEND_OBJECT: + TU_LOG_DRV(" MTP command: MTP_OPEC_SEND_OBJECT-DATA_OUT\n"); + return mtpd_handle_dto_send_object(); + default: + TU_LOG_DRV(" MTP command: MTP_OPEC_UNKNOWN_COMMAND %x!!!!\n", _mtpd_gct.code); + return false; + } + return true; +} + +mtp_phase_type_t mtpd_handle_cmd_get_device_info(void) +{ + TU_VERIFY_STATIC(sizeof(mtp_device_info_t) < MTP_MAX_PACKET_SIZE, "mtp_device_info_t shall fit in MTP_MAX_PACKET_SIZE"); + + _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH + sizeof(mtp_device_info_t); + _mtpd_gct.container_type = MTP_CONTAINER_TYPE_DATA_BLOCK; + _mtpd_gct.code = MTP_OPEC_GET_DEVICE_INFO; + mtp_device_info_t *d = (mtp_device_info_t *)_mtpd_gct.data; + d->standard_version = 100; + d->mtp_vendor_extension_id = 0x06; + d->mtp_version = 100; + d->mtp_extensions_len = TU_ARRAY_LEN(MTP_EXTENSIONS); + mtpd_wc16cpy((uint8_t *)d->mtp_extensions, MTP_EXTENSIONS); + d->functional_mode = 0x0000; + d->operations_supported_len = TU_ARRAY_LEN(mtp_operations_supported); + memcpy(d->operations_supported, mtp_operations_supported, sizeof(mtp_operations_supported)); + d->events_supported_len = TU_ARRAY_LEN(mtp_events_supported); + memcpy(d->events_supported, mtp_events_supported, sizeof(mtp_events_supported)); + d->device_properties_supported_len = TU_ARRAY_LEN(mtp_device_properties_supported); + memcpy(d->device_properties_supported, mtp_device_properties_supported, sizeof(mtp_device_properties_supported)); + d->capture_formats_len = TU_ARRAY_LEN(mtp_capture_formats); + memcpy(d->capture_formats, mtp_capture_formats, sizeof(mtp_capture_formats)); + d->playback_formats_len = TU_ARRAY_LEN(mtp_playback_formats); + memcpy(d->playback_formats, mtp_playback_formats, sizeof(mtp_playback_formats)); + mtpd_gct_append_wstring(CFG_TUD_MANUFACTURER); + mtpd_gct_append_wstring(CFG_TUD_MODEL); + mtpd_gct_append_wstring(CFG_MTP_DEVICE_VERSION); + mtpd_gct_append_wstring(CFG_MTP_SERIAL_NUMBER); + + _mtpd_itf.queued_len = _mtpd_gct.container_length; + return MTP_PHASE_DATA_IN; +} + +mtp_phase_type_t mtpd_handle_cmd_open_session(void) +{ + uint32_t session_id = _mtpd_gct.data[0]; + + mtp_response_t res = tud_mtp_storage_open_session(&session_id); + if (res == MTP_RESC_SESSION_ALREADY_OPEN) + { + _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH; + _mtpd_gct.container_type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; + _mtpd_gct.code = res; + _mtpd_gct.container_length += sizeof(_mtpd_gct.data[0]); + _mtpd_gct.data[0] = session_id; + _mtpd_ctx.session_id = session_id; + return MTP_PHASE_RESPONSE; + } + + mtp_phase_type_t phase; + if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESC_OK), res, "")) != MTP_PHASE_NONE) return phase; + + _mtpd_ctx.session_id = session_id; + + _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH; + _mtpd_gct.container_type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; + _mtpd_gct.code = MTP_RESC_OK; + + return MTP_PHASE_RESPONSE; +} + +mtp_phase_type_t mtpd_handle_cmd_close_session(void) +{ + uint32_t session_id = _mtpd_gct.data[0]; + + mtp_response_t res = tud_mtp_storage_close_session(session_id); + + _mtpd_ctx.session_id = session_id; + + _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH; + _mtpd_gct.container_type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; + _mtpd_gct.code = res; + + return MTP_PHASE_RESPONSE; +} + +mtp_phase_type_t mtpd_handle_cmd_get_storage_ids(void) +{ + TU_VERIFY_STATIC(sizeof(mtp_storage_ids_t) < MTP_MAX_PACKET_SIZE, "mtp_storage_ids_t shall fit in MTP_MAX_PACKET_SIZE"); + + uint32_t storage_id; + mtp_response_t res = tud_mtp_get_storage_id(&storage_id); + mtp_phase_type_t phase; + if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESC_OK), res, "")) != MTP_PHASE_NONE) return phase; + + _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH + sizeof(mtp_storage_ids_t); + _mtpd_gct.container_type = MTP_CONTAINER_TYPE_DATA_BLOCK; + _mtpd_gct.code = MTP_OPEC_GET_STORAGE_IDS; + mtp_storage_ids_t *d = (mtp_storage_ids_t *)_mtpd_gct.data; + if (storage_id == 0) + { + // Storage not accessible + d->storage_ids_len = 0; + d->storage_ids[0] = 0; + } + else + { + d->storage_ids_len = 1; + d->storage_ids[0] = storage_id; + } + + _mtpd_itf.queued_len = _mtpd_gct.container_length; + return MTP_PHASE_DATA_IN; +} + +mtp_phase_type_t mtpd_handle_cmd_get_storage_info(void) +{ + TU_VERIFY_STATIC(sizeof(mtp_storage_info_t) < MTP_MAX_PACKET_SIZE, "mtp_storage_info_t shall fit in MTP_MAX_PACKET_SIZE"); + + uint32_t storage_id = _mtpd_gct.data[0]; + + _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH + sizeof(mtp_storage_info_t); + _mtpd_gct.container_type = MTP_CONTAINER_TYPE_DATA_BLOCK; + _mtpd_gct.code = MTP_OPEC_GET_STORAGE_INFO; + + mtp_response_t res = tud_mtp_get_storage_info(storage_id, (mtp_storage_info_t *)_mtpd_gct.data); + mtp_phase_type_t phase; + if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESC_OK), res, "")) != MTP_PHASE_NONE) return phase; + + _mtpd_itf.queued_len = _mtpd_gct.container_length; + return MTP_PHASE_DATA_IN; +} + +mtp_phase_type_t mtpd_handle_cmd_get_object_handles(void) +{ + uint32_t storage_id = _mtpd_gct.data[0]; + uint32_t object_format_code = _mtpd_gct.data[1]; // optional, not managed + uint32_t parent_object_handle = _mtpd_gct.data[2]; // folder specification, 0xffffffff=objects with no parent + + _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH + sizeof(uint32_t); + _mtpd_gct.container_type = MTP_CONTAINER_TYPE_DATA_BLOCK; + _mtpd_gct.code = MTP_OPEC_GET_OBJECT_HANDLES; + _mtpd_gct.data[0] = 0; + + mtp_phase_type_t phase; + if ((phase = mtpd_chk_generic(__func__, (object_format_code != 0), MTP_RESC_SPECIFICATION_BY_FORMAT_UNSUPPORTED, "specification by format unsupported")) != MTP_PHASE_NONE) return phase; + //list of all object handles on all storages, not managed + if ((phase = mtpd_chk_generic(__func__, (storage_id == 0xFFFFFFFF), MTP_RESC_OPERATION_NOT_SUPPORTED, "list of all object handles on all storages unsupported")) != MTP_PHASE_NONE) return phase; + + tud_mtp_storage_object_done(); + uint32_t next_child_handle = 0; + while(true) + { + mtp_response_t res = tud_mtp_storage_association_get_object_handle(storage_id, parent_object_handle, &next_child_handle); + if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESC_OK), res, "")) != MTP_PHASE_NONE) return phase; + if (next_child_handle == 0) + break; + mtpd_gct_append_object_handle(next_child_handle); + } + tud_mtp_storage_object_done(); + + _mtpd_itf.queued_len = _mtpd_gct.container_length; + return MTP_PHASE_DATA_IN; +} + +mtp_phase_type_t mtpd_handle_cmd_get_object_info(void) +{ + TU_VERIFY_STATIC(sizeof(mtp_object_info_t) < MTP_MAX_PACKET_SIZE, "mtp_object_info_t shall fit in MTP_MAX_PACKET_SIZE"); + + uint32_t object_handle = _mtpd_gct.data[0]; + + _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH + sizeof(mtp_object_info_t); + _mtpd_gct.container_type = MTP_CONTAINER_TYPE_DATA_BLOCK; + _mtpd_gct.code = MTP_OPEC_GET_OBJECT_INFO; + mtp_response_t res = tud_mtp_storage_object_read_info(object_handle, (mtp_object_info_t *)_mtpd_gct.data); + mtp_phase_type_t phase; + if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESC_OK), res, "")) != MTP_PHASE_NONE) return phase; + + _mtpd_itf.queued_len = _mtpd_gct.container_length; + return MTP_PHASE_DATA_IN; +} + +mtp_phase_type_t mtpd_handle_cmd_get_object(void) +{ + _mtpd_get_object_handle = _mtpd_gct.data[0]; + + // Continue with DATA-IN + return mtpd_handle_dti_get_object(); +} + +mtp_phase_type_t mtpd_handle_dti_get_object(void) +{ + mtp_response_t res; + mtp_phase_type_t phase; + uint32_t file_size = 0; + res = tud_mtp_storage_object_size(_mtpd_get_object_handle, &file_size); + if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESC_OK), res, "")) != MTP_PHASE_NONE) return phase; + _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH + file_size; + _mtpd_gct.container_type = MTP_CONTAINER_TYPE_DATA_BLOCK; + _mtpd_gct.code = MTP_OPEC_GET_OBJECT; + + uint32_t buffer_size; + uint32_t read_count; + // Data block must be multiple of EP size + if (_mtpd_itf.handled_len == 0) + { + // First data block: include container header + buffer_size = ((MTP_MAX_PACKET_SIZE + MTP_GENERIC_DATA_BLOCK_LENGTH) / CFG_MTP_EP_SIZE) * CFG_MTP_EP_SIZE - MTP_GENERIC_DATA_BLOCK_LENGTH; + res = tud_mtp_storage_object_read(_mtpd_get_object_handle, (void *)&_mtpd_gct.data, buffer_size, &read_count); + if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESC_OK), res, "")) != MTP_PHASE_NONE) return phase; + _mtpd_itf.queued_len = MTP_GENERIC_DATA_BLOCK_LENGTH + read_count; + } + else + { + // Successive data block: consider only container data + buffer_size = (MTP_MAX_PACKET_SIZE / CFG_MTP_EP_SIZE) * CFG_MTP_EP_SIZE; + res = tud_mtp_storage_object_read(_mtpd_get_object_handle, (void *)&_mtpd_gct.data, buffer_size, &read_count); + if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESC_OK), res, "")) != MTP_PHASE_NONE) return phase; + _mtpd_itf.queued_len = read_count; + } + + // File completed + if (read_count < buffer_size) + { + tud_mtp_storage_object_done(); + } + + return MTP_PHASE_DATA_IN; +} + +mtp_phase_type_t mtpd_handle_cmd_delete_object(void) +{ + uint32_t object_handle = _mtpd_gct.data[0]; + uint32_t object_code_format = _mtpd_gct.data[1]; // not used + (void) object_code_format; + + mtp_response_t res = tud_mtp_storage_object_delete(object_handle); + mtp_phase_type_t phase; + if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESC_OK), res, "")) != MTP_PHASE_NONE) return phase; + + _mtpd_gct.container_type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; + _mtpd_gct.code = MTP_RESC_OK; + _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH; + return MTP_PHASE_RESPONSE; +} + +mtp_phase_type_t mtpd_handle_cmd_get_device_prop_desc(void) +{ + uint32_t device_prop_code = _mtpd_gct.data[0]; + + mtp_phase_type_t rt; + if ((rt = mtpd_chk_session_open(__func__)) != MTP_PHASE_NONE) return rt; + + switch(device_prop_code) + { + case MTP_DEVP_DEVICE_FRIENDLY_NAME: + { + TU_VERIFY_STATIC(sizeof(mtp_device_prop_desc_t) < MTP_MAX_PACKET_SIZE, "mtp_device_info_t shall fit in MTP_MAX_PACKET_SIZE"); + _mtpd_gct.container_type = MTP_CONTAINER_TYPE_DATA_BLOCK; + _mtpd_gct.code = MTP_OPEC_GET_DEVICE_PROP_DESC; + _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH + sizeof(mtp_device_prop_desc_t); + mtp_device_prop_desc_t *d = (mtp_device_prop_desc_t *)_mtpd_gct.data; + d->device_property_code = (uint16_t)(device_prop_code); + d->datatype = MTP_TYPE_STR; + d->get_set = MTP_MODE_GET; + mtpd_gct_append_wstring(CFG_TUD_MODEL); // factory_def_value + mtpd_gct_append_wstring(CFG_TUD_MODEL); // current_value_len + mtpd_gct_append_uint8(0x00); // form_flag + _mtpd_itf.queued_len = _mtpd_gct.container_length; + return MTP_PHASE_DATA_IN; + } + default: + break; + } + + _mtpd_gct.container_type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; + _mtpd_gct.code = MTP_RESC_PARAMETER_NOT_SUPPORTED; + _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH; + return MTP_PHASE_RESPONSE; +} + +mtp_phase_type_t mtpd_handle_cmd_get_device_prop_value(void) +{ + uint32_t device_prop_code = _mtpd_gct.data[0]; + + mtp_phase_type_t rt; + if ((rt = mtpd_chk_session_open(__func__)) != MTP_PHASE_NONE) return rt; + + _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH; + _mtpd_gct.container_type = MTP_CONTAINER_TYPE_DATA_BLOCK; + _mtpd_gct.code = MTP_OPEC_GET_DEVICE_PROP_VALUE; + + switch(device_prop_code) + { + // TODO support more device properties + case MTP_DEVP_DEVICE_FRIENDLY_NAME: + mtpd_gct_append_wstring(CFG_TUD_MODEL); + _mtpd_itf.queued_len = _mtpd_gct.container_length; + return MTP_PHASE_DATA_IN; + default: + _mtpd_gct.container_type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; + _mtpd_gct.code = MTP_RESC_PARAMETER_NOT_SUPPORTED; + return MTP_PHASE_RESPONSE; + } +} + +mtp_phase_type_t mtpd_handle_cmd_send_object_info(void) +{ + _mtpd_soi.storage_id = _mtpd_gct.data[0]; + _mtpd_soi.parent_object_handle = (_mtpd_gct.data[1] == 0xFFFFFFFF ? 0 : _mtpd_gct.data[1]); + + // Enter OUT phase and wait for DATA BLOCK + return MTP_PHASE_DATA_OUT; +} + +mtp_phase_type_t mtpd_handle_dto_send_object_info(void) +{ + uint32_t new_object_handle = 0; + mtp_response_t res = tud_mtp_storage_object_write_info(_mtpd_soi.storage_id, _mtpd_soi.parent_object_handle, &new_object_handle, (mtp_object_info_t *)_mtpd_gct.data); + mtp_phase_type_t phase; + if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESC_OK), res, "")) != MTP_PHASE_NONE) return phase; + + // Save send_object_info + _mtpd_soi.object_handle = new_object_handle; + + // Response + _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH + 3 * sizeof(uint32_t); + _mtpd_gct.container_type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; + _mtpd_gct.code = MTP_RESC_OK; + _mtpd_gct.data[0] = _mtpd_soi.storage_id; + _mtpd_gct.data[1] = _mtpd_soi.parent_object_handle; + _mtpd_gct.data[2] = _mtpd_soi.object_handle; + return MTP_PHASE_RESPONSE; +} + +mtp_phase_type_t mtpd_handle_cmd_send_object(void) +{ + // Enter OUT phase and wait for DATA BLOCK + return MTP_PHASE_DATA_OUT; +} + +mtp_phase_type_t mtpd_handle_dto_send_object(void) +{ + uint8_t *buffer = (uint8_t *)&_mtpd_gct.data; + uint32_t buffer_size = _mtpd_itf.xferred_len - _mtpd_itf.handled_len; + // First block of DATA + if (_mtpd_itf.handled_len == 0) + { + buffer_size -= MTP_GENERIC_DATA_BLOCK_LENGTH; + } + + if (buffer_size > 0) + { + mtp_response_t res = tud_mtp_storage_object_write(_mtpd_soi.object_handle, buffer, buffer_size); + mtp_phase_type_t phase; + if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESC_OK), res, "")) != MTP_PHASE_NONE) return phase; + } + + if (!_mtpd_itf.xfer_completed) + { + // Continue with next DATA BLOCK + return MTP_PHASE_DATA_OUT; + } + + // Send completed + tud_mtp_storage_object_done(); + + _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH; + _mtpd_gct.container_type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; + _mtpd_gct.code = MTP_RESC_OK; + return MTP_PHASE_RESPONSE; +} + +mtp_phase_type_t mtpd_handle_cmd_format_store(void) +{ + uint32_t storage_id = _mtpd_gct.data[0]; + uint32_t file_system_format = _mtpd_gct.data[1]; // not used + (void) file_system_format; + + mtp_response_t res = tud_mtp_storage_format(storage_id); + + _mtpd_gct.container_type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; + _mtpd_gct.code = res; + _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH; + return MTP_PHASE_RESPONSE; +} + +//--------------------------------------------------------------------+ +// Checker +//--------------------------------------------------------------------+ +mtp_phase_type_t mtpd_chk_session_open(const char *func_name) +{ + (void)func_name; + if (_mtpd_ctx.session_id == 0) + { + TU_LOG_DRV(" MTP error: %s session not open\n", func_name); + _mtpd_gct.container_type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; + _mtpd_gct.code = MTP_RESC_SESSION_NOT_OPEN; + _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH; + return MTP_PHASE_RESPONSE; + } + return MTP_PHASE_NONE; +} + +mtp_phase_type_t mtpd_chk_generic(const char *func_name, const bool err_cd, const uint16_t ret_code, const char *message) +{ + (void)func_name; + (void)message; + if (err_cd) + { + TU_LOG_DRV(" MTP error in %s: (%x) %s\n", func_name, ret_code, message); + _mtpd_gct.container_type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; + _mtpd_gct.code = ret_code; + _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH; + return MTP_PHASE_RESPONSE; + } + return MTP_PHASE_NONE; +} + +//--------------------------------------------------------------------+ +// Generic container data +//--------------------------------------------------------------------+ +void mtpd_wc16cpy(uint8_t *dest, const char *src) +{ + wchar16_t s; + while(true) + { + s = *src; + memcpy(dest, &s, sizeof(wchar16_t)); + if (*src == 0) break; + ++src; + dest += sizeof(wchar16_t); + } +} + +//--------------------------------------------------------------------+ +// Generic container function +//--------------------------------------------------------------------+ +bool mtpd_gct_append_uint8(const uint8_t value) +{ + uint8_t *p_value = ((uint8_t *)&_mtpd_gct) + _mtpd_gct.container_length; + _mtpd_gct.container_length += sizeof(uint8_t); + // Verify space requirement (8 bit string length, number of wide characters including terminator) + TU_ASSERT(_mtpd_gct.container_length < sizeof(mtp_generic_container_t)); + *p_value = value; + return true; +} + +bool mtpd_gct_append_object_handle(const uint32_t object_handle) +{ + _mtpd_gct.container_length += sizeof(uint32_t); + TU_ASSERT(_mtpd_gct.container_length < sizeof(mtp_generic_container_t)); + _mtpd_gct.data[0]++; + _mtpd_gct.data[_mtpd_gct.data[0]] = object_handle; + return true; +} + +bool mtpd_gct_append_wstring(const char *s) +{ + size_t len = strlen(s) + 1; + TU_ASSERT(len <= UINT8_MAX); + uint8_t *p_len = ((uint8_t *)&_mtpd_gct)+_mtpd_gct.container_length; + _mtpd_gct.container_length += sizeof(uint8_t) + sizeof(wchar16_t) * len; + // Verify space requirement (8 bit string length, number of wide characters including terminator) + TU_ASSERT(_mtpd_gct.container_length < sizeof(mtp_generic_container_t)); + *p_len = (uint8_t)len; + uint8_t *p_str = p_len + sizeof(uint8_t); + mtpd_wc16cpy(p_str, s); + return true; +} + +bool mtpd_gct_get_string(uint16_t *offset_data, char *string, const uint16_t max_size) +{ + uint16_t size = *(((uint8_t *)&_mtpd_gct.data) + *offset_data); + if (size > max_size) + size = max_size; + TU_ASSERT(*offset_data + size < sizeof(_mtpd_gct.data)); + + uint8_t *s = ((uint8_t *)&_mtpd_gct.data) + *offset_data + sizeof(uint8_t); + for(uint16_t i = 0; i < size; i++) + { + string[i] = *s; + s += sizeof(wchar16_t); + } + *offset_data += (uint16_t)(sizeof(uint8_t) + size * sizeof(wchar16_t)); + return true; +} + +bool mtpd_gct_append_array(uint32_t array_size, const void *data, size_t type_size) +{ + TU_ASSERT(_mtpd_gct.container_length + sizeof(uint32_t) + array_size * type_size < sizeof(_mtpd_gct.data)); + uint8_t *p = ((uint8_t *)&_mtpd_gct) + _mtpd_gct.container_length; + memcpy(p, &array_size, sizeof(uint32_t)); + p += sizeof(uint32_t); + memcpy(p, data, array_size * type_size); + _mtpd_gct.container_length += sizeof(uint32_t) + array_size * type_size; + return true; +} + +bool mtpd_gct_append_date(struct tm *timeinfo) +{ + // strftime is not supported by all platform, this implementation is just for reference + int len = snprintf(_mtp_datestr, sizeof(_mtpd_gct.data) - _mtpd_gct.container_length, "%04d%02d%02dT%02d%02d%02dZ", + timeinfo->tm_year + 1900, + timeinfo->tm_mon + 1, + timeinfo->tm_mday, + timeinfo->tm_hour, + timeinfo->tm_min, + timeinfo->tm_sec); + if (len == 0) + return false; + return mtpd_gct_append_wstring(_mtp_datestr); +} + +#endif // (CFG_TUD_ENABLED && CFG_TUD_MTP) diff --git a/src/class/mtp/mtp_device.h b/src/class/mtp/mtp_device.h new file mode 100644 index 000000000..6531b36df --- /dev/null +++ b/src/class/mtp/mtp_device.h @@ -0,0 +1,75 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2025 Ennebi Elettronica (https://ennebielettronica.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef _TUSB_MTP_DEVICE_H_ +#define _TUSB_MTP_DEVICE_H_ + +#include "common/tusb_common.h" +#include "mtp.h" + +#if (CFG_TUD_ENABLED && CFG_TUD_MTP) + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Internal Class Driver API +//--------------------------------------------------------------------+ +void mtpd_init (void); +bool mtpd_deinit (void); +void mtpd_reset (uint8_t rhport); +uint16_t mtpd_open (uint8_t rhport, tusb_desc_interface_t const *itf_desc, uint16_t max_len); +bool mtpd_control_xfer_cb (uint8_t rhport, uint8_t stage, tusb_control_request_t const *p_request); +bool mtpd_xfer_cb (uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes); + +//--------------------------------------------------------------------+ +// Helper functions +//--------------------------------------------------------------------+ +// Generic container function +void mtpd_wc16cpy(uint8_t *dest, const char *src); +bool mtpd_gct_append_uint8(const uint8_t value); +bool mtpd_gct_append_object_handle(const uint32_t object_handle); +bool mtpd_gct_append_wstring(const char *s); +bool mtpd_gct_get_string(uint16_t *offset_data, char *string, const uint16_t max_size); + +// Append the given array to the global context buffer +// The function returns true if the data fits in the available buffer space. +bool mtpd_gct_append_array(uint32_t array_size, const void *data, size_t type_size); + +// Append an UTC date string to the global context buffer +// Required format is 'YYYYMMDDThhmmss.s' optionally added 'Z' for UTC or +/-hhmm for time zone +// This function is provided for reference and only supports UTC format without partial seconds +// The function returns true if the data fits in the available buffer space. +bool mtpd_gct_append_date(struct tm *timeinfo); + +#ifdef __cplusplus + } +#endif + +#endif /* CFG_TUD_ENABLED && CFG_TUD_MTP */ + +#endif /* _TUSB_MTP_DEVICE_H_ */ diff --git a/src/class/mtp/mtp_device_storage.h b/src/class/mtp/mtp_device_storage.h new file mode 100644 index 000000000..a9bbc9b90 --- /dev/null +++ b/src/class/mtp/mtp_device_storage.h @@ -0,0 +1,149 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2025 Ennebi Elettronica (https://ennebielettronica.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef _TUSB_MTP_DEVICE_STORAGE_H_ +#define _TUSB_MTP_DEVICE_STORAGE_H_ + +#include "common/tusb_common.h" +#include "mtp.h" + +#if (CFG_TUD_ENABLED && CFG_TUD_MTP) + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Storage Application Callbacks +//--------------------------------------------------------------------+ + +/* + * The entire MTP functionality is based on object handles, as described in MTP Specs v. 1.1 under 3.4.1. + * The major weakness of the protocol is that those handles are supposed to be unique within a session + * and for every enumerated object. There is no specified lifetime limit or way to control the expiration: + * once given, they have to persist for an indefinite time and number of iterations. + * If the filesystem does not provide unique persistent object handle for every entry, the best approach + * would be to keep a full association between generated handles and full file paths. The suggested + * approach with memory constrained devices is to keep a hard ID associated with each file or a volatile + * ID generated on the fly and invalidated on each operation that may rearrange the order. + * In order to invalidate existing IDS, it might be necessary to invalidate the whole session from + * the device side. + * Depending on the application, the handle could be also be the file name or a tag (i.e. host-only file access) + */ + + +// Initialize MTP storage subsystem +// +// The function shall check if the session is already opened and, in case, set session_id to the +// ID of the current session. +mtp_response_t tud_mtp_storage_open_session(uint32_t *session_id); + +// Close an open session +mtp_response_t tud_mtp_storage_close_session(uint32_t session_id); + +// Get a storage ID valid within the current session +// +// TODO: while multiple storage IDs could be used, the implementation currently supports only 1. +mtp_response_t tud_mtp_get_storage_id(uint32_t *storage_id); + +// Get storage information for the given ID +// +// The implementation shall fill all the fields required by the specification. +// Note that the variable information (e.g. wstring file name, dates and tags shall be written by using the library functions) +// In addition to the fixed mtp_storage_info_t structure, the function shall add storage descriptor string and +// volume identifier string via tud_mtp_gct_append_wstring function. +mtp_response_t tud_mtp_get_storage_info(uint32_t storage_id, mtp_storage_info_t *info); + +// Format the specified storage +mtp_response_t tud_mtp_storage_format(uint32_t storage_id); + +// Traverse the given parent object handle and return a child handle for each call +// +// If the parent object has not been opened (or closed before) the function returns the first handle. +// When next_child_handle is 0 all the handles have been listed. +// TODO: traverse by ObjectFormatCode and ObjectHandle association. For now they are unsupported. +mtp_response_t tud_mtp_storage_association_get_object_handle(uint32_t session_handle, uint32_t parent_object_handle, uint32_t *next_child_handle); + +// Called with the creation of a new object is requested. +// The handle of the new object shall be returned in new_object_handle. +// The structure info contains the information to be used for file creation, as passted by the host. +// Note that the variable information (e.g. wstring file name, dates and tags shall be retrieved by using the library functions) +mtp_response_t tud_mtp_storage_object_write_info(uint32_t storage_id, uint32_t parent_object, uint32_t *new_object_handle, const mtp_object_info_t *info); + +// Get object information related to a given object handle +// +// The structure info shall be filled according to MTP specifications. Note that +// in addition to filling the fixed mtp_object_info_t structure, the caller must add the following fields via +// library calls +// - Filename (string, use tud_mtp_gct_append_wstring) +// - Date created (string, use tud_gct_append_date or empty string) +// - Date modified (string, use tud_gct_append_date or empty string) +// - Keywords (string containing list of kw, separated by space, use tud_mtp_gct_append_wstring) +// Note that the variable information (e.g. wstring file name, dates and tags shall be written by using the library functions) +mtp_response_t tud_mtp_storage_object_read_info(uint32_t object_handle, mtp_object_info_t *info); + +// Get the object size. +// +// The object may be already open when this function is called. +// The implementation shall not assume a specific call order between this function and tud_mtp_storage_object_read. +// The function may leave the file open. +mtp_response_t tud_mtp_storage_object_size(uint32_t object_handle, uint32_t *size); + +// Write object data +// +// The function shall open the object for writing if not already open. +// The binary data shall be written to the file in full before this function is returned. +mtp_response_t tud_mtp_storage_object_write(uint32_t object_handle, const uint8_t *buffer, uint32_t buffer_size); + +// Get object data +// +// The function shall open the object for reading if not already open. +// The amount of data returned shall be the given size parameter. +// read_count shall contain the effective number of bytes written. Iteration is terminated when read_count < buffer_size. +mtp_response_t tud_mtp_storage_object_read(uint32_t object_handle, void *buffer, uint32_t buffer_size, uint32_t *read_count); + +// Move an object to a new parent +mtp_response_t tud_mtp_storage_object_move(uint32_t object_handle, uint32_t new_parent_object_handle); + +// Delete the specified object +mtp_response_t tud_mtp_storage_object_delete(uint32_t object_handle); + +// Issued when IO operation has been terminated (e.g. read, traverse), close open file handles +void tud_mtp_storage_object_done(void); + +// Cancel any pending operation. Current operation shall be discarded. +void tud_mtp_storage_cancel(void); + +// Restore the operation out of reset. Cancel any pending operation and close the session. +void tud_mtp_storage_reset(void); + +#ifdef __cplusplus + } +#endif + +#endif /* CFG_TUD_ENABLED && CFG_TUD_MTP */ + +#endif /* _TUSB_MTP_DEVICE_STORAGE_H_ */ diff --git a/src/device/usbd.c b/src/device/usbd.c index fb5cec49d..8f50d815f 100644 --- a/src/device/usbd.c +++ b/src/device/usbd.c @@ -309,6 +309,19 @@ tu_static usbd_class_driver_t const _usbd_driver[] = { .sof = NULL }, #endif + + #if CFG_TUD_MTP + { + .name = DRIVER_NAME("MTP"), + .init = mtpd_init, + .deinit = mtpd_deinit, + .reset = mtpd_reset, + .open = mtpd_open, + .control_xfer_cb = mtpd_control_xfer_cb, + .xfer_cb = mtpd_xfer_cb, + .sof = NULL + }, + #endif }; enum { BUILTIN_DRIVER_COUNT = TU_ARRAY_SIZE(_usbd_driver) }; diff --git a/src/device/usbd.h b/src/device/usbd.h index de6007fb3..b89a0200b 100644 --- a/src/device/usbd.h +++ b/src/device/usbd.h @@ -269,6 +269,25 @@ bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_requ 7, TUSB_DESC_ENDPOINT, _epin, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0 +//--------------------------------------------------------------------+ +// MTP Descriptor Templates +//--------------------------------------------------------------------+ + +// Length of template descriptor: 30 bytes +#define TUD_MTP_DESC_LEN (9 + 7 + 7 + 7) + +// Interface number, string index, EP Out & EP In address, EP size +#define TUD_MTP_DESCRIPTOR(_itfnum, _stridx, _ep_evt, _ep_evt_size, _ep_evt_polling_interval, _epout, _epin, _epsize) \ + /* Interface */\ + 9, TUSB_DESC_INTERFACE, _itfnum, 0, 3, TUSB_CLASS_IMAGE, MTP_SUBCLASS, MTP_PROTOCOL_STILL_IMAGE, _stridx,\ + /* Endpoint Interrupt */\ + 7, TUSB_DESC_ENDPOINT, _ep_evt, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(_ep_evt_size), _ep_evt_polling_interval,\ + /* Endpoint Out */\ + 7, TUSB_DESC_ENDPOINT, _epout, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0,\ + /* Endpoint In */\ + 7, TUSB_DESC_ENDPOINT, _epin, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0 + + //--------------------------------------------------------------------+ // HID Descriptor Templates //--------------------------------------------------------------------+ diff --git a/src/tinyusb.mk b/src/tinyusb.mk index a9f623c24..8f9d52de9 100644 --- a/src/tinyusb.mk +++ b/src/tinyusb.mk @@ -12,6 +12,7 @@ TINYUSB_SRC_C += \ src/class/hid/hid_device.c \ src/class/midi/midi_device.c \ src/class/msc/msc_device.c \ + src/class/mtp/mtp_device.c \ src/class/net/ecm_rndis_device.c \ src/class/net/ncm_device.c \ src/class/usbtmc/usbtmc_device.c \ diff --git a/src/tusb.h b/src/tusb.h index dfba21ddf..abe3e3c9b 100644 --- a/src/tusb.h +++ b/src/tusb.h @@ -88,6 +88,10 @@ #include "class/msc/msc_device.h" #endif + #if CFG_TUD_MTP + #include "class/mtp/mtp_device.h" + #endif + #if CFG_TUD_AUDIO #include "class/audio/audio_device.h" #endif diff --git a/src/tusb_option.h b/src/tusb_option.h index 29fdcb0d6..e36a56de9 100644 --- a/src/tusb_option.h +++ b/src/tusb_option.h @@ -493,6 +493,10 @@ #define CFG_TUD_MSC 0 #endif +#ifndef CFG_TUD_MTP + #define CFG_TUD_MTP 0 +#endif + #ifndef CFG_TUD_HID #define CFG_TUD_HID 0 #endif diff --git a/test/fuzz/rules.mk b/test/fuzz/rules.mk index ee91c706d..24d6cc0d7 100644 --- a/test/fuzz/rules.mk +++ b/test/fuzz/rules.mk @@ -31,6 +31,7 @@ SRC_C += \ src/class/hid/hid_device.c \ src/class/midi/midi_device.c \ src/class/msc/msc_device.c \ + src/class/mtp/mtp_device.c \ src/class/net/ecm_rndis_device.c \ src/class/net/ncm_device.c \ src/class/usbtmc/usbtmc_device.c \ diff --git a/tools/iar_template.ipcf b/tools/iar_template.ipcf index 33a6ef045..c93795b9c 100644 --- a/tools/iar_template.ipcf +++ b/tools/iar_template.ipcf @@ -58,6 +58,12 @@ $TUSB_DIR$/src/class/msc/msc_device.h $TUSB_DIR$/src/class/msc/msc_host.h + + $TUSB_DIR$/src/class/mtp/mtp_device.c + $TUSB_DIR$/src/class/mtp/mtp.h + $TUSB_DIR$/src/class/mtp/mtp_device.h + $TUSB_DIR$/src/class/mtp/mtp_device_storage.h + $TUSB_DIR$/src/class/net/ecm_rndis_device.c $TUSB_DIR$/src/class/net/ncm_device.c From 802819d271314298ea5a786a37ec6a1e65ef31d6 Mon Sep 17 00:00:00 2001 From: hathach Date: Fri, 12 Sep 2025 17:13:52 +0700 Subject: [PATCH 02/43] add all constant from MTP specs appendix, rename some e.g OPEC to OP, EVTC to EVENT --- examples/device/CMakeLists.txt | 1 + examples/device/mtp/src/mtp_fs_example.c | 92 +-- src/class/mtp/mtp.h | 748 +++++++++++++++++------ src/class/mtp/mtp_device.c | 146 ++--- src/device/usbd.h | 2 +- 5 files changed, 682 insertions(+), 307 deletions(-) diff --git a/examples/device/CMakeLists.txt b/examples/device/CMakeLists.txt index bb7dd0a0f..eb625ea51 100644 --- a/examples/device/CMakeLists.txt +++ b/examples/device/CMakeLists.txt @@ -26,6 +26,7 @@ family_add_subdirectory(hid_generic_inout) family_add_subdirectory(hid_multiple_interface) family_add_subdirectory(midi_test) family_add_subdirectory(msc_dual_lun) +family_add_subdirectory(mtp) family_add_subdirectory(net_lwip_webserver) family_add_subdirectory(uac2_headset) family_add_subdirectory(uac2_speaker_fb) diff --git a/examples/device/mtp/src/mtp_fs_example.c b/examples/device/mtp/src/mtp_fs_example.c index 41673ea53..87730bcd3 100644 --- a/examples/device/mtp/src/mtp_fs_example.c +++ b/examples/device/mtp/src/mtp_fs_example.c @@ -130,17 +130,17 @@ mtp_response_t tud_mtp_storage_open_session(uint32_t *session_id) if (*session_id == 0) { TU_LOG1("Invalid session ID\r\n"); - return MTP_RESC_INVALID_PARAMETER; + return MTP_RESP_INVALID_PARAMETER; } if (_fs_operation.session_id != 0) { *session_id = _fs_operation.session_id; TU_LOG1("ERR: Session %ld already open\r\n", _fs_operation.session_id); - return MTP_RESC_SESSION_ALREADY_OPEN; + return MTP_RESP_SESSION_ALREADY_OPEN; } _fs_operation.session_id = *session_id; TU_LOG1("Open session with id %ld\r\n", _fs_operation.session_id); - return MTP_RESC_OK; + return MTP_RESP_OK; } mtp_response_t tud_mtp_storage_close_session(uint32_t session_id) @@ -148,11 +148,11 @@ mtp_response_t tud_mtp_storage_close_session(uint32_t session_id) if (session_id != _fs_operation.session_id) { TU_LOG1("ERR: Session %ld not open\r\n", session_id); - return MTP_RESC_SESSION_NOT_OPEN; + return MTP_RESP_SESSION_NOT_OPEN; } _fs_operation.session_id = 0; TU_LOG1("Session closed\r\n"); - return MTP_RESC_OK; + return MTP_RESP_OK; } mtp_response_t tud_mtp_get_storage_id(uint32_t *storage_id) @@ -160,11 +160,11 @@ mtp_response_t tud_mtp_get_storage_id(uint32_t *storage_id) if (_fs_operation.session_id == 0) { TU_LOG1("ERR: Session not open\r\n"); - return MTP_RESC_SESSION_NOT_OPEN; + return MTP_RESP_SESSION_NOT_OPEN; } *storage_id = STORAGE_ID(0x0001, 0x0001); TU_LOG1("Retrieved storage identifier %ld\r\n", *storage_id); - return MTP_RESC_OK; + return MTP_RESP_OK; } mtp_response_t tud_mtp_get_storage_info(uint32_t storage_id, mtp_storage_info_t *info) @@ -172,12 +172,12 @@ mtp_response_t tud_mtp_get_storage_info(uint32_t storage_id, mtp_storage_info_t if (_fs_operation.session_id == 0) { TU_LOG1("ERR: Session not open\r\n"); - return MTP_RESC_SESSION_NOT_OPEN; + return MTP_RESP_SESSION_NOT_OPEN; } if (storage_id != STORAGE_ID(0x0001, 0x0001)) { TU_LOG1("ERR: Unexpected storage id %ld\r\n", storage_id); - return MTP_RESC_INVALID_STORAGE_ID; + return MTP_RESP_INVALID_STORAGE_ID; } info->storage_type = MTP_STORAGE_TYPE_FIXED_RAM; info->filesystem_type = MTP_FILESYSTEM_TYPE_GENERIC_HIERARCHICAL; @@ -187,7 +187,7 @@ mtp_response_t tud_mtp_get_storage_info(uint32_t storage_id, mtp_storage_info_t info->free_space_in_bytes = info->free_space_in_objects * FS_MAX_NODE_BYTES; mtpd_gct_append_wstring(MTPD_STORAGE_DESCRIPTION); mtpd_gct_append_wstring(MTPD_VOLUME_IDENTIFIER); - return MTP_RESC_OK; + return MTP_RESP_OK; } mtp_response_t tud_mtp_storage_format(uint32_t storage_id) @@ -195,19 +195,19 @@ mtp_response_t tud_mtp_storage_format(uint32_t storage_id) if (_fs_operation.session_id == 0) { TU_LOG1("ERR: Session not open\r\n"); - return MTP_RESC_SESSION_NOT_OPEN; + return MTP_RESP_SESSION_NOT_OPEN; } if (storage_id != STORAGE_ID(0x0001, 0x0001)) { TU_LOG1("ERR: Unexpected storage id %ld\r\n", storage_id); - return MTP_RESC_INVALID_STORAGE_ID; + return MTP_RESP_INVALID_STORAGE_ID; } // Simply deallocate all entries for (unsigned int i=0; ihandle; TU_LOG1("Association %ld -> child %ld\r\n", parent_object_handle, obj->handle); - return MTP_RESC_OK; + return MTP_RESP_OK; } } TU_LOG1("Association traversal completed\r\n"); _fs_operation.traversal_index = 0; *next_child_handle = 0; - return MTP_RESC_OK; + return MTP_RESP_OK; } mtp_response_t tud_mtp_storage_object_write_info(uint32_t storage_id, uint32_t parent_object, uint32_t *new_object_handle, const mtp_object_info_t *info) @@ -261,19 +261,19 @@ mtp_response_t tud_mtp_storage_object_write_info(uint32_t storage_id, uint32_t p if (_fs_operation.session_id == 0) { TU_LOG1("ERR: Session not open\r\n"); - return MTP_RESC_SESSION_NOT_OPEN; + return MTP_RESP_SESSION_NOT_OPEN; } // Accept command on default storage if (storage_id != 0xFFFFFFFF && storage_id != STORAGE_ID(0x0001, 0x0001)) { TU_LOG1("ERR: Unexpected storage id %ld\r\n", storage_id); - return MTP_RESC_INVALID_STORAGE_ID; + return MTP_RESP_INVALID_STORAGE_ID; } if (info->object_compressed_size > FS_MAX_NODE_BYTES) { TU_LOG1("Object size %ld is more than maximum %ld\r\n", info->object_compressed_size, FS_MAX_NODE_BYTES); - return MTP_RESC_STORE_FULL; + return MTP_RESP_STORE_FULL; } // Request for objects with no parent (0xFFFFFFFF) are considered root objects @@ -287,12 +287,12 @@ mtp_response_t tud_mtp_storage_object_write_info(uint32_t storage_id, uint32_t p if (obj == NULL) { TU_LOG1("Parent %ld does not exist\r\n", parent_object); - return MTP_RESC_INVALID_PARENT_OBJECT; + return MTP_RESP_INVALID_PARENT_OBJECT; } if (!obj->association) { TU_LOG1("Parent %ld is not an association\r\n", parent_object); - return MTP_RESC_INVALID_PARENT_OBJECT; + return MTP_RESP_INVALID_PARENT_OBJECT; } } @@ -309,7 +309,7 @@ mtp_response_t tud_mtp_storage_object_write_info(uint32_t storage_id, uint32_t p if (obj == NULL) { TU_LOG1("No space left on device\r\n"); - return MTP_RESC_STORE_FULL; + return MTP_RESP_STORE_FULL; } // Fill-in structure @@ -317,7 +317,7 @@ mtp_response_t tud_mtp_storage_object_write_info(uint32_t storage_id, uint32_t p obj->handle = ++_fs_operation.last_handle; obj->parent = parent_object; obj->size = info->object_compressed_size; - obj->association = info->object_format == MTP_OBJF_ASSOCIATION; + obj->association = info->object_format == MTP_OBJ_FORMAT_ASSOCIATION; // Extract variable data uint16_t offset_data = sizeof(mtp_object_info_t); @@ -332,7 +332,7 @@ mtp_response_t tud_mtp_storage_object_write_info(uint32_t storage_id, uint32_t p // Initialize operation _fs_operation.write_handle = obj->handle; _fs_operation.write_pos = 0; - return MTP_RESC_OK; + return MTP_RESP_OK; } mtp_response_t tud_mtp_storage_object_read_info(uint32_t object_handle, mtp_object_info_t *info) @@ -342,33 +342,33 @@ mtp_response_t tud_mtp_storage_object_read_info(uint32_t object_handle, mtp_obje if (_fs_operation.session_id == 0) { TU_LOG1("ERR: Session not open\r\n"); - return MTP_RESC_SESSION_NOT_OPEN; + return MTP_RESP_SESSION_NOT_OPEN; } obj = fs_object_get_from_handle(object_handle); if (obj == NULL) { TU_LOG1("ERR: Object with handle %ld does not exist\r\n", object_handle); - return MTP_RESC_INVALID_OBJECT_HANDLE; + return MTP_RESP_INVALID_OBJECT_HANDLE; } memset(info, 0, sizeof(mtp_object_info_t)); info->storage_id = STORAGE_ID(0x0001, 0x0001); if (obj->association) { - info->object_format = MTP_OBJF_ASSOCIATION; + info->object_format = MTP_OBJ_FORMAT_ASSOCIATION; info->protection_status = MTP_PROTECTION_STATUS_NO_PROTECTION; info->object_compressed_size = 0; info->association_type = MTP_ASSOCIATION_UNDEFINED; } else { - info->object_format = MTP_OBJF_UNDEFINED; + info->object_format = MTP_OBJ_FORMAT_UNDEFINED; info->protection_status = MTP_PROTECTION_STATUS_NO_PROTECTION; info->object_compressed_size = obj->size; info->association_type = MTP_ASSOCIATION_UNDEFINED; } - info->thumb_format = MTP_OBJF_UNDEFINED; + info->thumb_format = MTP_OBJ_FORMAT_UNDEFINED; info->parent_object = obj->parent; mtpd_gct_append_wstring(obj->name); @@ -378,7 +378,7 @@ mtp_response_t tud_mtp_storage_object_read_info(uint32_t object_handle, mtp_obje TU_LOG1("Retrieve object %s with handle %ld\r\n", obj->name, obj->handle); - return MTP_RESC_OK; + return MTP_RESP_OK; } mtp_response_t tud_mtp_storage_object_write(uint32_t object_handle, const uint8_t *buffer, uint32_t size) @@ -389,17 +389,17 @@ mtp_response_t tud_mtp_storage_object_write(uint32_t object_handle, const uint8_ if (obj == NULL) { TU_LOG1("ERR: Object with handle %ld does not exist\r\n", object_handle); - return MTP_RESC_INVALID_OBJECT_HANDLE; + return MTP_RESP_INVALID_OBJECT_HANDLE; } // It's a requirement that this command is preceded by a write info if (object_handle != _fs_operation.write_handle) { TU_LOG1("ERR: Object %ld not open for write\r\n", object_handle); - return MTP_RESC_NO_VALID_OBJECTINFO; + return MTP_RESP_NO_VALID_OBJECTINFO; } TU_LOG1("Write object %ld: data chunk at %ld/%ld bytes at offset %ld\r\n", object_handle, _fs_operation.write_pos, obj->size, size); - TU_ASSERT(obj->size >= _fs_operation.write_pos + size, MTP_RESC_INCOMPLETE_TRANSFER); + TU_ASSERT(obj->size >= _fs_operation.write_pos + size, MTP_RESP_INCOMPLETE_TRANSFER); if (_fs_operation.write_pos + size < FS_MAX_NODE_BYTES) memcpy(&obj->data[_fs_operation.write_pos], buffer, size); _fs_operation.write_pos += size; @@ -409,7 +409,7 @@ mtp_response_t tud_mtp_storage_object_write(uint32_t object_handle, const uint8_ _fs_operation.write_handle = 0; _fs_operation.write_pos = 0; } - return MTP_RESC_OK; + return MTP_RESP_OK; } mtp_response_t tud_mtp_storage_object_size(uint32_t object_handle, uint32_t *size) @@ -419,10 +419,10 @@ mtp_response_t tud_mtp_storage_object_size(uint32_t object_handle, uint32_t *siz if (obj == NULL) { TU_LOG1("ERR: Object with handle %ld does not exist\r\n", object_handle); - return MTP_RESC_INVALID_OBJECT_HANDLE; + return MTP_RESP_INVALID_OBJECT_HANDLE; } *size = obj->size; - return MTP_RESC_OK; + return MTP_RESP_OK; } mtp_response_t tud_mtp_storage_object_read(uint32_t object_handle, void *buffer, uint32_t buffer_size, uint32_t *read_count) @@ -434,7 +434,7 @@ mtp_response_t tud_mtp_storage_object_read(uint32_t object_handle, void *buffer, if (obj == NULL) { TU_LOG1("ERR: Object with handle %ld does not exist\r\n", object_handle); - return MTP_RESC_INVALID_OBJECT_HANDLE; + return MTP_RESP_INVALID_OBJECT_HANDLE; } // It's not a requirement that this command is preceded by a read info if (object_handle != _fs_operation.read_handle) @@ -462,7 +462,7 @@ mtp_response_t tud_mtp_storage_object_read(uint32_t object_handle, void *buffer, _fs_operation.read_handle = 0; _fs_operation.read_pos = 0; } - return MTP_RESC_OK; + return MTP_RESP_OK; } mtp_response_t tud_mtp_storage_object_move(uint32_t object_handle, uint32_t new_parent_object_handle) @@ -479,12 +479,12 @@ mtp_response_t tud_mtp_storage_object_move(uint32_t object_handle, uint32_t new_ if (obj == NULL) { TU_LOG1("Parent %ld does not exist\r\n", new_parent_object_handle); - return MTP_RESC_INVALID_PARENT_OBJECT; + return MTP_RESP_INVALID_PARENT_OBJECT; } if (!obj->association) { TU_LOG1("Parent %ld is not an association\r\n", new_parent_object_handle); - return MTP_RESC_INVALID_PARENT_OBJECT; + return MTP_RESP_INVALID_PARENT_OBJECT; } } @@ -493,11 +493,11 @@ mtp_response_t tud_mtp_storage_object_move(uint32_t object_handle, uint32_t new_ if (obj == NULL) { TU_LOG1("ERR: Object with handle %ld does not exist\r\n", object_handle); - return MTP_RESC_INVALID_OBJECT_HANDLE; + return MTP_RESP_INVALID_OBJECT_HANDLE; } TU_LOG1("Move object %ld to new parent %ld\r\n", object_handle, new_parent_object_handle); obj->parent = new_parent_object_handle; - return MTP_RESC_OK; + return MTP_RESP_OK; } mtp_response_t tud_mtp_storage_object_delete(uint32_t object_handle) @@ -507,7 +507,7 @@ mtp_response_t tud_mtp_storage_object_delete(uint32_t object_handle) if (_fs_operation.session_id == 0) { TU_LOG1("ERR: Session not open\r\n"); - return MTP_RESC_SESSION_NOT_OPEN; + return MTP_RESP_SESSION_NOT_OPEN; } if (object_handle == 0xFFFFFFFF) @@ -520,7 +520,7 @@ mtp_response_t tud_mtp_storage_object_delete(uint32_t object_handle) if (obj == NULL) { TU_LOG1("ERR: Object with handle %ld does not exist\r\n", object_handle); - return MTP_RESC_INVALID_OBJECT_HANDLE; + return MTP_RESP_INVALID_OBJECT_HANDLE; } obj->allocated = false; TU_LOG1("Delete object with handle %ld\r\n", object_handle); @@ -539,7 +539,7 @@ mtp_response_t tud_mtp_storage_object_delete(uint32_t object_handle) } } - return MTP_RESC_OK; + return MTP_RESP_OK; } void tud_mtp_storage_object_done(void) diff --git a/src/class/mtp/mtp.h b/src/class/mtp/mtp.h index 33c6bb552..b5db2cd6c 100644 --- a/src/class/mtp/mtp.h +++ b/src/class/mtp/mtp.h @@ -47,20 +47,17 @@ typedef uint16_t wchar16_t; //--------------------------------------------------------------------+ // Media Transfer Protocol Subclass -typedef enum -{ - MTP_SUBCLASS = 1 +typedef enum { + MTP_SUBCLASS_STILL_IMAGE = 1 } mtp_subclass_type_t; // MTP Protocol. -typedef enum -{ - MTP_PROTOCOL_STILL_IMAGE = 1, +typedef enum { + MTP_PROTOCOL_PIMA_15470 = 1, ///< Picture Transfer Protocol (PIMA 15470) } mtp_protocol_type_t; // PTP/MTP protocol phases -typedef enum -{ +typedef enum { MTP_PHASE_IDLE = 0, MTP_PHASE_COMMAND, MTP_PHASE_DATA_IN, @@ -70,7 +67,7 @@ typedef enum MTP_PHASE_NONE, } mtp_phase_type_t; -// PTP/MTP Class requests +// PTP/MTP Class requests, PIMA 15740-2000: D.5.2 typedef enum { MTP_REQ_CANCEL = 0x64, @@ -79,19 +76,6 @@ typedef enum MTP_REQ_GET_DEVICE_STATUS = 0x67, } mtp_class_request_t; -#define MTP_GENERIC_DATA_BLOCK_LENGTH 12 -#define MTP_MAX_PACKET_SIZE 512 - -// PTP/MTP Generic container -typedef struct TU_ATTR_PACKED -{ - uint32_t container_length; - uint16_t container_type; - uint16_t code; - uint32_t transaction_id; - uint32_t data[MTP_MAX_PACKET_SIZE / sizeof(uint32_t)]; -} mtp_generic_container_t; - // PTP/MTP Container type typedef enum { @@ -102,85 +86,493 @@ typedef enum MTP_CONTAINER_TYPE_EVENT_BLOCK = 4, } mtp_container_type_t; -// Supported OperationCode +// MTP 1.1 Appendix A: Object formats typedef enum { - MTP_OPEC_GET_DEVICE_INFO = 0x1001u, - MTP_OPEC_OPEN_SESSION = 0x1002u, - MTP_OPEC_CLOSE_SESSION = 0x1003u, - MTP_OPEC_GET_STORAGE_IDS = 0x1004u, - MTP_OPEC_GET_STORAGE_INFO = 0x1005u, - MTP_OPEC_GET_NUM_OBJECTS = 0x1006u, - MTP_OPEC_GET_OBJECT_HANDLES = 0x1007u, - MTP_OPEC_GET_OBJECT_INFO = 0x1008u, - MTP_OPEC_GET_OBJECT = 0x1009u, - MTP_OPEC_GET_THUMB = 0x100Au, - MTP_OPEC_DELETE_OBJECT = 0x100Bu, - MTP_OPEC_SEND_OBJECT_INFO = 0x100Cu, - MTP_OPEC_SEND_OBJECT = 0x100Du, - MTP_OPEC_INITIAL_CAPTURE = 0x100Eu, - MTP_OPEC_FORMAT_STORE = 0x100Fu, - MTP_OPEC_RESET_DEVICE = 0x1010u, - MTP_OPEC_SELF_TEST = 0x1011u, - MTP_OPEC_SET_OBJECT_PROTECTION = 0x1012u, - MTP_OPEC_POWER_DOWN = 0x1013u, - MTP_OPEC_GET_DEVICE_PROP_DESC = 0x1014u, - MTP_OPEC_GET_DEVICE_PROP_VALUE = 0x1015u, - MTP_OPEC_SET_DEVICE_PROP_VALUE = 0x1016u, - MTP_OPEC_RESET_DEVICE_PROP_VALUE = 0x1017u, - MTP_OPEC_TERMINATE_OPEN_CAPTURE = 0x1018u, - MTP_OPEC_MOVE_OBJECT = 0x1019u, - MTP_OPEC_COPY_OBJECT = 0x101Au, - MTP_OPEC_GET_PARTIAL_OBJECT = 0x101Bu, - MTP_OPEC_INITIATE_OPEN_CAPTURE = 0x101Bu, - MTP_OPEC_GET_OBJECT_PROPS_SUPPORTED = 0x9801u, - MTP_OPEC_GET_OBJECT_PROP_DESC = 0x9802u, - MTP_OPEC_GET_OBJECT_PROP_VALUE = 0x9803u, - MTP_OPEC_SET_OBJECT_PROP_VALUE = 0x9804u, - MTP_OPEC_GET_OBJECT_PROPLIST = 0x9805u, - MTP_OPEC_GET_OBJECT_PROP_REFERENCES = 0x9810u, - MTP_OPEC_GETSERVICEIDS = 0x9301u, - MTP_OPEC_GETSERVICEINFO = 0x9302u, - MTP_OPEC_GETSERVICECAPABILITIES = 0x9303u, - MTP_OPEC_GETSERVICEPROPDESC = 0x9304u, -} mtp_operation_code_t; + // ---- Base formats ---- + MTP_OBJ_FORMAT_UNDEFINED = 0x3000u, // Undefined object + MTP_OBJ_FORMAT_ASSOCIATION = 0x3001u, // Association (for example, a folder) + MTP_OBJ_FORMAT_SCRIPT = 0x3002u, // Device model-specific script + MTP_OBJ_FORMAT_EXECUTABLE = 0x3003u, // Device model-specific binary executable + MTP_OBJ_FORMAT_TEXT = 0x3004u, // Text file + MTP_OBJ_FORMAT_HTML = 0x3005u, // Hypertext Markup Language file (text) + MTP_OBJ_FORMAT_DPOF = 0x3006u, // Digital Print Order Format file (text) + MTP_OBJ_FORMAT_AIFF = 0x3007u, // Audio clip (AIFF) + MTP_OBJ_FORMAT_WAV = 0x3008u, // Audio clip (WAV) + MTP_OBJ_FORMAT_MP3 = 0x3009u, // MPEG-1 Layer III audio (ISO/IEC 13818-3) + MTP_OBJ_FORMAT_AVI = 0x300Au, // Video clip (AVI) + MTP_OBJ_FORMAT_MPEG = 0x300Bu, // Video clip (MPEG) + MTP_OBJ_FORMAT_ASF = 0x300Cu, // Microsoft Advanced Streaming Format (video) -// Supported EventCode -typedef enum -{ - MTP_EVTC_OBJECT_ADDED = 0x4002, -} mtp_event_code_t; + // ---- Image formats ---- + MTP_OBJ_FORMAT_UNDEFINED_IMAGE = 0x3800u, // Undefined image object + MTP_OBJ_FORMAT_EXIF_JPEG = 0x3801u, // Exchangeable Image Format, JEIDA standard + MTP_OBJ_FORMAT_TIFF_EP = 0x3802u, // Tag Image File Format for Electronic Photography + MTP_OBJ_FORMAT_FLASHPIX = 0x3803u, // Structured Storage Image Format (FlashPix) + MTP_OBJ_FORMAT_BMP = 0x3804u, // Microsoft Windows Bitmap file + MTP_OBJ_FORMAT_CIFF = 0x3805u, // Canon Camera Image File Format + MTP_OBJ_FORMAT_UNDEFINED_3806 = 0x3806u, // Reserved / Undefined + MTP_OBJ_FORMAT_GIF = 0x3807u, // Graphics Interchange Format + MTP_OBJ_FORMAT_JFIF = 0x3808u, // JPEG File Interchange Format + MTP_OBJ_FORMAT_CD = 0x3809u, // PhotoCD Image Pac + MTP_OBJ_FORMAT_PICT = 0x380Au, // Quickdraw Image Format + MTP_OBJ_FORMAT_PNG = 0x380Bu, // Portable Network Graphics + MTP_OBJ_FORMAT_UNDEFINED_380C = 0x380Cu, // Reserved / Undefined + MTP_OBJ_FORMAT_TIFF = 0x380Du, // Tag Image File Format (baseline) + MTP_OBJ_FORMAT_TIFF_IT = 0x380Eu, // Tag Image File Format for IT (graphic arts) + MTP_OBJ_FORMAT_JP2 = 0x380Fu, // JPEG2000 Baseline File Format + MTP_OBJ_FORMAT_JPX = 0x3810u, // JPEG2000 Extended File Format + // ---- Firmware & misc ---- + MTP_OBJ_FORMAT_UNDEFINED_FIRMWARE = 0xB802u, // Undefined Firmware + MTP_OBJ_FORMAT_WBMP = 0xB803u, // Wireless Application Protocol Bitmap Format (.wbmp) + MTP_OBJ_FORMAT_WINDOWS_IMAGE = 0xB881u, // Windows Image Format + MTP_OBJ_FORMAT_JPEGXR = 0xB804u, // JPEG XR (.hdp, .jxr, .wdp) -// Supported Device Properties -typedef enum -{ - MTP_DEVP_UNDEFINED = 0x5000u, - MTP_DEVP_BATTERY_LEVEL = 0x5001u, - MTP_DEVP_DEVICE_FRIENDLY_NAME = 0xD402u, -} mtp_event_properties_t; + // ---- Audio formats ---- + MTP_OBJ_FORMAT_UNDEFINED_AUDIO = 0xB900u, // Undefined audio object + MTP_OBJ_FORMAT_WMA = 0xB901u, // Windows Media Audio + MTP_OBJ_FORMAT_OGG = 0xB902u, // OGG container + MTP_OBJ_FORMAT_AAC = 0xB903u, // Advanced Audio Coding (.aac) + MTP_OBJ_FORMAT_AUDIBLE = 0xB904u, // Audible format + MTP_OBJ_FORMAT_FLAC = 0xB906u, // Free Lossless Audio Codec + MTP_OBJ_FORMAT_QCELP = 0xB907u, // Qualcomm Code Excited Linear Prediction (.qcp) + MTP_OBJ_FORMAT_AMR = 0xB908u, // Adaptive Multi-Rate audio (.amr) -// Supported Object Properties -typedef enum -{ - MTP_OBJP_STORAGE_ID = 0xDC01u, - MTP_OBJP_OBJECT_FORMAT = 0xDC02u, - MTP_OBJP_PROTECTION_STATUS = 0xDC03u, - MTP_OBJP_OBJECT_SIZE = 0xDC04u, - MTP_OBJP_ASSOCIATION_TYPE = 0xDC05u, - MTP_OBJP_OBJECT_FILE_NAME = 0xDC07u, - MTP_OBJP_PARENT_OBJECT = 0xDC0Bu, - MTP_OBJP_PERSISTENT_UNIQUE_OBJECT_IDENTIFIER = 0xDC41u, - MTP_OBJP_NAME = 0xDC44u, + // ---- Video formats ---- + MTP_OBJ_FORMAT_UNDEFINED_VIDEO = 0xB980u, // Undefined video object + MTP_OBJ_FORMAT_WMV = 0xB981u, // Windows Media Video + MTP_OBJ_FORMAT_MP4 = 0xB982u, // MP4 Container (ISO 14496-1) + MTP_OBJ_FORMAT_MP2 = 0xB983u, // MPEG-1 Layer II audio + MTP_OBJ_FORMAT_3GP = 0xB984u, // 3GP Container + MTP_OBJ_FORMAT_3G2 = 0xB985u, // 3GPP2 Container + MTP_OBJ_FORMAT_AVCHD = 0xB986u, // AVCHD (MPEG-4 AVC + Dolby Digital) + MTP_OBJ_FORMAT_ATSC_TS = 0xB987u, // ATSC-compliant MPEG-2 Transport Stream + MTP_OBJ_FORMAT_DVB_TS = 0xB988u, // DVB-compliant MPEG-2 Transport Stream + + // ---- Collections ---- + MTP_OBJ_FORMAT_UNDEFINED_COLLECTION = 0xBA00u, // Undefined collection + MTP_OBJ_FORMAT_ABSTRACT_MULTIMEDIA_ALBUM = 0xBA01u, // Abstract Multimedia Album + MTP_OBJ_FORMAT_ABSTRACT_IMAGE_ALBUM = 0xBA02u, // Abstract Image Album + MTP_OBJ_FORMAT_ABSTRACT_AUDIO_ALBUM = 0xBA03u, // Abstract Audio Album + MTP_OBJ_FORMAT_ABSTRACT_VIDEO_ALBUM = 0xBA04u, // Abstract Video Album + MTP_OBJ_FORMAT_ABSTRACT_AV_PLAYLIST = 0xBA05u, // Abstract Audio & Video Playlist + MTP_OBJ_FORMAT_ABSTRACT_CONTACT_GROUP = 0xBA06u, // Abstract Contact Group + MTP_OBJ_FORMAT_ABSTRACT_MESSAGE_FOLDER = 0xBA07u, // Abstract Message Folder + MTP_OBJ_FORMAT_ABSTRACT_CHAPTERED_PRODUCTION = 0xBA08u, // Abstract Chaptered Production + MTP_OBJ_FORMAT_ABSTRACT_AUDIO_PLAYLIST = 0xBA09u, // Abstract Audio Playlist + MTP_OBJ_FORMAT_ABSTRACT_VIDEO_PLAYLIST = 0xBA0Au, // Abstract Video Playlist + MTP_OBJ_FORMAT_ABSTRACT_MEDIACAST = 0xBA0Bu, // Abstract Mediacast (RSS enclosure) + + // ---- Playlist formats ---- + MTP_OBJ_FORMAT_WPL_PLAYLIST = 0xBA10u, // Windows Media Player Playlist (.wpl) + MTP_OBJ_FORMAT_M3U_PLAYLIST = 0xBA11u, // M3U Playlist + MTP_OBJ_FORMAT_MPL_PLAYLIST = 0xBA12u, // MPL Playlist + MTP_OBJ_FORMAT_ASX_PLAYLIST = 0xBA13u, // ASX Playlist + MTP_OBJ_FORMAT_PLS_PLAYLIST = 0xBA14u, // PLS Playlist + + // ---- Document formats ---- + MTP_OBJ_FORMAT_UNDEFINED_DOC = 0xBA80u, // Undefined Document + MTP_OBJ_FORMAT_ABSTRACT_DOC = 0xBA81u, // Abstract Document + MTP_OBJ_FORMAT_XML_DOC = 0xBA82u, // XML Document + MTP_OBJ_FORMAT_DOC = 0xBA83u, // Microsoft Word Document + MTP_OBJ_FORMAT_MHT_DOC = 0xBA84u, // MHT Compiled HTML Document + MTP_OBJ_FORMAT_XLS = 0xBA85u, // Microsoft Excel Spreadsheet + MTP_OBJ_FORMAT_PPT = 0xBA86u, // Microsoft PowerPoint Presentation + + // ---- Messaging ---- + MTP_OBJ_FORMAT_UNDEFINED_MSG = 0xBB00u, // Undefined Message + MTP_OBJ_FORMAT_ABSTRACT_MSG = 0xBB01u, // Abstract Message + + // ---- Bookmarks ---- + MTP_OBJ_FORMAT_UNDEFINED_BOOKMARK = 0xBB10u, // Undefined Bookmark + MTP_OBJ_FORMAT_ABSTRACT_BOOKMARK = 0xBB11u, // Abstract Bookmark + + // ---- Appointments ---- + MTP_OBJ_FORMAT_UNDEFINED_APPT = 0xBB20u, // Undefined Appointment + MTP_OBJ_FORMAT_ABSTRACT_APPT = 0xBB21u, // Abstract Appointment + MTP_OBJ_FORMAT_VCALENDAR1 = 0xBB22u, // vCalendar 1.0 + + // ---- Tasks ---- + MTP_OBJ_FORMAT_UNDEFINED_TASK = 0xBB40u, // Undefined Task + MTP_OBJ_FORMAT_ABSTRACT_TASK = 0xBB41u, // Abstract Task + MTP_OBJ_FORMAT_ICALENDAR = 0xBB42u, // iCalendar + + // ---- Notes ---- + MTP_OBJ_FORMAT_UNDEFINED_NOTE = 0xBB60u, // Undefined Note + MTP_OBJ_FORMAT_ABSTRACT_NOTE = 0xBB61u, // Abstract Note + + // ---- Contacts ---- + MTP_OBJ_FORMAT_UNDEFINED_CONTACT= 0xBB80u, // Undefined Contact + MTP_OBJ_FORMAT_ABSTRACT_CONTACT = 0xBB81u, // Abstract Contact + MTP_OBJ_FORMAT_VCARD2 = 0xBB82u, // vCard 2.1 + MTP_OBJ_FORMAT_VCARD3 = 0xBB83u, // vCard 3.0 +} mtp_object_formats_t; + +// MTP 1.1 Appendix B: Object Properties +typedef enum { + MTP_OBJ_PROP_STORAGE_ID = 0xDC01u, // StorageID + MTP_OBJ_PROP_OBJECT_FORMAT = 0xDC02u, // Object Format + MTP_OBJ_PROP_PROTECTION_STATUS = 0xDC03u, // Protection Status + MTP_OBJ_PROP_OBJECT_SIZE = 0xDC04u, // Object Size + MTP_OBJ_PROP_ASSOCIATION_TYPE = 0xDC05u, // Association Type + MTP_OBJ_PROP_ASSOCIATION_DESC = 0xDC06u, // Association Description + MTP_OBJ_PROP_OBJECT_FILE_NAME = 0xDC07u, // Object File Name + MTP_OBJ_PROP_DATE_CREATED = 0xDC08u, // Date Created + MTP_OBJ_PROP_DATE_MODIFIED = 0xDC09u, // Date Modified + MTP_OBJ_PROP_KEYWORDS = 0xDC0Au, // Keywords + MTP_OBJ_PROP_PARENT_OBJECT = 0xDC0Bu, // Parent Object + MTP_OBJ_PROP_ALLOWED_FOLDER_CONTENTS = 0xDC0Cu, // Allowed Folder Contents + MTP_OBJ_PROP_HIDDEN = 0xDC0Du, // Hidden + MTP_OBJ_PROP_SYSTEM_OBJECT = 0xDC0Eu, // System Object + // 0xDC0F-0xDC40 is reserved + + MTP_OBJ_PROP_PERSISTENT_UID = 0xDC41u, // Persistent Unique Object Identifier + MTP_OBJ_PROP_SYNC_ID = 0xDC42u, // SyncID + MTP_OBJ_PROP_PROPERTY_BAG = 0xDC43u, // Property Bag + MTP_OBJ_PROP_NAME = 0xDC44u, // Name + MTP_OBJ_PROP_CREATED_BY = 0xDC45u, // Created By + MTP_OBJ_PROP_ARTIST = 0xDC46u, // Artist + MTP_OBJ_PROP_DATE_AUTHORED = 0xDC47u, // Date Authored + MTP_OBJ_PROP_DESCRIPTION = 0xDC48u, // Description + MTP_OBJ_PROP_URL_REFERENCE = 0xDC49u, // URL Reference + MTP_OBJ_PROP_LANGUAGE_LOCALE = 0xDC4Au, // Language-Locale + MTP_OBJ_PROP_COPYRIGHT_INFO = 0xDC4Bu, // Copyright Information + MTP_OBJ_PROP_SOURCE = 0xDC4Cu, // Source + MTP_OBJ_PROP_ORIGIN_LOCATION = 0xDC4Du, // Origin Location + MTP_OBJ_PROP_DATE_ADDED = 0xDC4Eu, // Date Added + MTP_OBJ_PROP_NON_CONSUMABLE = 0xDC4Fu, // Non-Consumable + MTP_OBJ_PROP_CORRUPT_UNPLAYABLE = 0xDC50u, // Corrupt/Unplayable + MTP_OBJ_PROP_PRODUCER_SERIAL_NUMBER = 0xDC51u, // ProducerSerialNumber + // 0xDC52-0xDC80 is reserved + + MTP_OBJ_PROP_REP_SAMPLE_FORMAT = 0xDC81u, // Representative Sample Format + MTP_OBJ_PROP_REP_SAMPLE_SIZE = 0xDC82u, // Representative Sample Size + MTP_OBJ_PROP_REP_SAMPLE_HEIGHT = 0xDC83u, // Representative Sample Height + MTP_OBJ_PROP_REP_SAMPLE_WIDTH = 0xDC84u, // Representative Sample Width + MTP_OBJ_PROP_REP_SAMPLE_DURATION = 0xDC85u, // Representative Sample Duration + MTP_OBJ_PROP_REP_SAMPLE_DATA = 0xDC86u, // Representative Sample Data + MTP_OBJ_PROP_WIDTH = 0xDC87u, // Width + MTP_OBJ_PROP_HEIGHT = 0xDC88u, // Height + MTP_OBJ_PROP_DURATION = 0xDC89u, // Duration + MTP_OBJ_PROP_RATING = 0xDC8Au, // Rating + MTP_OBJ_PROP_TRACK = 0xDC8Bu, // Track + MTP_OBJ_PROP_GENRE = 0xDC8Cu, // Genre + MTP_OBJ_PROP_CREDITS = 0xDC8Du, // Credits + MTP_OBJ_PROP_LYRICS = 0xDC8Eu, // Lyrics + MTP_OBJ_PROP_SUBSCRIPTION_CONTENT_ID = 0xDC8Fu, // Subscription Content ID + MTP_OBJ_PROP_PRODUCED_BY = 0xDC90u, // Produced By + MTP_OBJ_PROP_USE_COUNT = 0xDC91u, // Use Count + MTP_OBJ_PROP_SKIP_COUNT = 0xDC92u, // Skip Count + MTP_OBJ_PROP_LAST_ACCESSED = 0xDC93u, // Last Accessed + MTP_OBJ_PROP_PARENTAL_RATING = 0xDC94u, // Parental Rating + MTP_OBJ_PROP_META_GENRE = 0xDC95u, // Meta Genre + MTP_OBJ_PROP_COMPOSER = 0xDC96u, // Composer + MTP_OBJ_PROP_EFFECTIVE_RATING = 0xDC97u, // Effective Rating + MTP_OBJ_PROP_SUBTITLE = 0xDC98u, // Subtitle + MTP_OBJ_PROP_ORIGINAL_RELEASE_DATE = 0xDC99u, // Original Release Date + MTP_OBJ_PROP_ALBUM_NAME = 0xDC9Au, // Album Name + MTP_OBJ_PROP_ALBUM_ARTIST = 0xDC9Bu, // Album Artist + MTP_OBJ_PROP_MOOD = 0xDC9Cu, // Mood + MTP_OBJ_PROP_DRM_STATUS = 0xDC9Du, // DRM Status + MTP_OBJ_PROP_SUB_DESCRIPTION = 0xDC9Eu, // Sub Description + // 0xDC9F-0xDCD0 is reserved + + MTP_OBJ_PROP_IS_CROPPED = 0xDCD1u, // Is Cropped + MTP_OBJ_PROP_IS_COLOUR_CORRECTED = 0xDCD2u, // Is Colour Corrected + MTP_OBJ_PROP_IMAGE_BIT_DEPTH = 0xDCD3u, // Image Bit Depth + MTP_OBJ_PROP_FNUMBER = 0xDCD4u, // Fnumber (aperture ×100) + MTP_OBJ_PROP_EXPOSURE_TIME = 0xDCD5u, // Exposure Time (sec ×10,000) + MTP_OBJ_PROP_EXPOSURE_INDEX = 0xDCD6u, // Exposure Index (ISO) + // 0xDCD7-0xDCDF is reserved + + MTP_OBJ_PROP_DISPLAY_NAME = 0xDCE0u, // Display Name + MTP_OBJ_PROP_BODY_TEXT = 0xDCE1u, // Body Text + MTP_OBJ_PROP_SUBJECT = 0xDCE2u, // Subject + MTP_OBJ_PROP_PRIORITY = 0xDCE3u, // Priority + // 0xDCE4-0xDCFF is reserved + + MTP_OBJ_PROP_GIVEN_NAME = 0xDD00u, // Given Name + MTP_OBJ_PROP_MIDDLE_NAMES = 0xDD01u, // Middle Names + MTP_OBJ_PROP_FAMILY_NAME = 0xDD02u, // Family Name + MTP_OBJ_PROP_PREFIX = 0xDD03u, // Prefix + MTP_OBJ_PROP_SUFFIX = 0xDD04u, // Suffix + MTP_OBJ_PROP_PHONETIC_GIVEN_NAME = 0xDD05u, // Phonetic Given Name + MTP_OBJ_PROP_PHONETIC_FAMILY_NAME = 0xDD06u, // Phonetic Family Name + MTP_OBJ_PROP_EMAIL_PRIMARY = 0xDD07u, // Email Primary + MTP_OBJ_PROP_EMAIL_PERSONAL_1 = 0xDD08u, // Email Personal 1 + MTP_OBJ_PROP_EMAIL_PERSONAL_2 = 0xDD09u, // Email Personal 2 + MTP_OBJ_PROP_EMAIL_BUSINESS_1 = 0xDD0Au, // Email Business 1 + MTP_OBJ_PROP_EMAIL_BUSINESS_2 = 0xDD0Bu, // Email Business 2 + MTP_OBJ_PROP_EMAIL_OTHERS = 0xDD0Cu, // Email Others + MTP_OBJ_PROP_PHONE_PRIMARY = 0xDD0Du, // Phone Number Primary + MTP_OBJ_PROP_PHONE_PERSONAL_1 = 0xDD0Eu, // Phone Number Personal + MTP_OBJ_PROP_PHONE_PERSONAL_2 = 0xDD0Fu, // Phone Number Personal 2 + MTP_OBJ_PROP_PHONE_BUSINESS_1 = 0xDD10u, // Phone Number Business + MTP_OBJ_PROP_PHONE_BUSINESS_2 = 0xDD11u, // Phone Number Business 2 + MTP_OBJ_PROP_PHONE_MOBILE_1 = 0xDD12u, // Phone Number Mobile + MTP_OBJ_PROP_PHONE_MOBILE_2 = 0xDD13u, // Phone Number Mobile 2 + MTP_OBJ_PROP_FAX_PRIMARY = 0xDD14u, // Fax Number Primary + MTP_OBJ_PROP_FAX_PERSONAL = 0xDD15u, // Fax Number Personal + MTP_OBJ_PROP_FAX_BUSINESS = 0xDD16u, // Fax Number Business + MTP_OBJ_PROP_PAGER_NUMBER = 0xDD17u, // Pager Number + MTP_OBJ_PROP_PHONE_OTHERS = 0xDD18u, // Phone Number Others + MTP_OBJ_PROP_WEB_PRIMARY = 0xDD19u, // Primary Web Address + MTP_OBJ_PROP_WEB_PERSONAL = 0xDD1Au, // Personal Web Address + MTP_OBJ_PROP_WEB_BUSINESS = 0xDD1Bu, // Business Web Address + MTP_OBJ_PROP_IM_ADDRESS_1 = 0xDD1Cu, // Instant Messenger Address + MTP_OBJ_PROP_IM_ADDRESS_2 = 0xDD1Du, // Instant Messenger Address 2 + MTP_OBJ_PROP_IM_ADDRESS_3 = 0xDD1Eu, // Instant Messenger Address 3 + MTP_OBJ_PROP_ADDR_PERSONAL_FULL = 0xDD1Fu, // Postal Address Personal Full + MTP_OBJ_PROP_ADDR_PERSONAL_LINE1 = 0xDD20u, // Postal Address Personal Line 1 + MTP_OBJ_PROP_ADDR_PERSONAL_LINE2 = 0xDD21u, // Postal Address Personal Line 2 + MTP_OBJ_PROP_ADDR_PERSONAL_CITY = 0xDD22u, // Postal Address Personal City + MTP_OBJ_PROP_ADDR_PERSONAL_REGION = 0xDD23u, // Postal Address Personal Region + MTP_OBJ_PROP_ADDR_PERSONAL_POSTAL_CODE = 0xDD24u, // Postal Address Personal Postal Code + MTP_OBJ_PROP_ADDR_PERSONAL_COUNTRY = 0xDD25u, // Postal Address Personal Country + MTP_OBJ_PROP_ADDR_BUSINESS_FULL = 0xDD26u, // Postal Address Business Full + MTP_OBJ_PROP_ADDR_BUSINESS_LINE1 = 0xDD27u, // Postal Address Business Line 1 + MTP_OBJ_PROP_ADDR_BUSINESS_LINE2 = 0xDD28u, // Postal Address Business Line 2 + MTP_OBJ_PROP_ADDR_BUSINESS_CITY = 0xDD29u, // Postal Address Business City + MTP_OBJ_PROP_ADDR_BUSINESS_REGION = 0xDD2Au, // Postal Address Business Region + MTP_OBJ_PROP_ADDR_BUSINESS_POSTAL_CODE = 0xDD2Bu, // Postal Address Business Postal Code + MTP_OBJ_PROP_ADDR_BUSINESS_COUNTRY = 0xDD2Cu, // Postal Address Business Country + MTP_OBJ_PROP_ADDR_OTHER_FULL = 0xDD2Du, // Postal Address Other Full + MTP_OBJ_PROP_ADDR_OTHER_LINE1 = 0xDD2Eu, // Postal Address Other Line 1 + MTP_OBJ_PROP_ADDR_OTHER_LINE2 = 0xDD2Fu, // Postal Address Other Line 2 + MTP_OBJ_PROP_ADDR_OTHER_CITY = 0xDD30u, // Postal Address Other City + MTP_OBJ_PROP_ADDR_OTHER_REGION = 0xDD31u, // Postal Address Other Region + MTP_OBJ_PROP_ADDR_OTHER_POSTAL_CODE = 0xDD32u, // Postal Address Other Postal Code + MTP_OBJ_PROP_ADDR_OTHER_COUNTRY = 0xDD33u, // Postal Address Other Country + MTP_OBJ_PROP_ORGANIZATION_NAME = 0xDD34u, // Organization Name + MTP_OBJ_PROP_PHONETIC_ORG_NAME = 0xDD35u, // Phonetic Organization Name + MTP_OBJ_PROP_ROLE = 0xDD36u, // Role + MTP_OBJ_PROP_BIRTHDATE = 0xDD37u, // Birthdate + // 0xDD38-0xDD3F is reserved + + MTP_OBJ_PROP_MESSAGE_TO = 0xDD40u, // Message To + MTP_OBJ_PROP_MESSAGE_CC = 0xDD41u, // Message CC + MTP_OBJ_PROP_MESSAGE_BCC = 0xDD42u, // Message BCC + MTP_OBJ_PROP_MESSAGE_READ = 0xDD43u, // Message Read + MTP_OBJ_PROP_MESSAGE_RECEIVED_TIME = 0xDD44u, // Message Received Time + MTP_OBJ_PROP_MESSAGE_SENDER = 0xDD45u, // Message Sender + // 0xDD46-0xDD4F is reserved + + MTP_OBJ_PROP_ACTIVITY_BEGIN_TIME = 0xDD50u, // Activity Begin Time + MTP_OBJ_PROP_ACTIVITY_END_TIME = 0xDD51u, // Activity End Time + MTP_OBJ_PROP_ACTIVITY_LOCATION = 0xDD52u, // Activity Location + // 0xDD53 is reserved + MTP_OBJ_PROP_ACTIVITY_REQUIRED_ATTENDEES= 0xDD54u, // Activity Required Attendees + MTP_OBJ_PROP_ACTIVITY_OPTIONAL_ATTENDEES= 0xDD55u, // Activity Optional Attendees + MTP_OBJ_PROP_ACTIVITY_RESOURCES = 0xDD56u, // Activity Resources + MTP_OBJ_PROP_ACTIVITY_ACCEPTED = 0xDD57u, // Activity Accepted + MTP_OBJ_PROP_ACTIVITY_TENTATIVE = 0xDD58u, // Activity Tentative + MTP_OBJ_PROP_ACTIVITY_DECLINED = 0xDD59u, // Activity Declined + MTP_OBJ_PROP_ACTIVITY_REMINDER_TIME = 0xDD5Au, // Activity Reminder Time + MTP_OBJ_PROP_ACTIVITY_OWNER = 0xDD5Bu, // Activity Owner + MTP_OBJ_PROP_ACTIVITY_STATUS = 0xDD5Cu, // Activity Status + MTP_OBJ_PROP_OWNER = 0xDD5Du, // Owner + MTP_OBJ_PROP_EDITOR = 0xDD5Eu, // Editor + MTP_OBJ_PROP_WEBMASTER = 0xDD5Fu, // Webmaster + + MTP_OBJ_PROP_URL_SOURCE = 0xDD60u, // URL Source + MTP_OBJ_PROP_URL_DESTINATION = 0xDD61u, // URL Destination + MTP_OBJ_PROP_TIME_BOOKMARK = 0xDD62u, // Time Bookmark + MTP_OBJ_PROP_OBJECT_BOOKMARK = 0xDD63u, // Object Bookmark + MTP_OBJ_PROP_BYTE_BOOKMARK = 0xDD64u, // Byte Bookmark + // 0xDD65-0xDD6F is reserved + + MTP_OBJ_PROP_LAST_BUILD_DATE = 0xDD70u, // Last Build Date + MTP_OBJ_PROP_TIME_TO_LIVE = 0xDD71u, // Time to Live (minutes) + MTP_OBJ_PROP_MEDIA_GUID = 0xDD72u, // Media GUID + // 0xDD73-0xDDFF is reserved + + // media encoding + MTP_OBJ_PROP_TOTAL_BITRATE = 0xDE91u, // Total BitRate + MTP_OBJ_PROP_BITRATE_TYPE = 0xDE92u, // Bitrate Type + MTP_OBJ_PROP_SAMPLE_RATE = 0xDE93u, // Sample Rate + MTP_OBJ_PROP_NUM_CHANNELS = 0xDE94u, // Number Of Channels + MTP_OBJ_PROP_AUDIO_BITDEPTH = 0xDE95u, // Audio BitDepth + // 0xDE96 is reserved + MTP_OBJ_PROP_SCAN_TYPE = 0xDE97u, // Scan Type + // 0xDE98 is reserved + MTP_OBJ_PROP_AUDIO_WAVE_CODEC = 0xDE99u, // Audio WAVE Codec + MTP_OBJ_PROP_AUDIO_BITRATE = 0xDE9Au, // Audio BitRate + MTP_OBJ_PROP_VIDEO_FOURCC_CODEC = 0xDE9Bu, // Video FourCC Codec + MTP_OBJ_PROP_VIDEO_BITRATE = 0xDE9Cu, // Video BitRate + MTP_OBJ_PROP_FRAMES_PER_KSEC = 0xDE9Du, // Frames Per Thousand Seconds + MTP_OBJ_PROP_KEYFRAME_DISTANCE = 0xDE9Eu, // KeyFrame Distance (ms) + MTP_OBJ_PROP_BUFFER_SIZE = 0xDE9Fu, // Buffer Size + MTP_OBJ_PROP_ENCODING_QUALITY = 0xDEA0u, // Encoding Quality + MTP_OBJ_PROP_ENCODING_PROFILE = 0xDEA1u // Encoding Profile } mtp_object_properties_t; -// Object formats -typedef enum -{ - MTP_OBJF_UNDEFINED = 0x3000u, - MTP_OBJF_ASSOCIATION = 0x3001u, - MTP_OBJF_TEXT = 0x3004u, -} mtp_object_formats_t; + +// MTP 1.1 Appendeix C: Device Properties +typedef enum { + MTP_DEV_PROP_UNDEFINED = 0x5000u, + MTP_DEV_PROP_BATTERY_LEVEL = 0x5001u, + MTP_DEV_PROP_FUNCTIONAL_MODE = 0x5002u, + MTP_DEV_PROP_IMAGE_SIZE = 0x5003u, + MTP_DEV_PROP_COMPRESSION_SETTING = 0x5004u, + MTP_DEV_PROP_WHITE_BALANCE = 0x5005u, + MTP_DEV_PROP_RGB_GAIN = 0x5006u, + MTP_DEV_PROP_F_NUMBER = 0x5007u, + MTP_DEV_PROP_FOCAL_LENGTH = 0x5008u, + MTP_DEV_PROP_FOCUS_DISTANCE = 0x5009u, + MTP_DEV_PROP_FOCUS_MODE = 0x500Au, + MTP_DEV_PROP_EXPOSURE_METERING_MODE = 0x500Bu, + MTP_DEV_PROP_FLASH_MODE = 0x500Cu, + MTP_DEV_PROP_EXPOSURE_TIME = 0x500Du, + MTP_DEV_PROP_EXPOSURE_PROGRAM_MODE = 0x500Eu, + MTP_DEV_PROP_EXPOSURE_INDEX = 0x500Fu, + MTP_DEV_PROP_EXPOSURE_BIAS_COMPENSATION = 0x5010u, + MTP_DEV_PROP_DATE_TIME = 0x5011u, + MTP_DEV_PROP_CAPTURE_DELAY = 0x5012u, + MTP_DEV_PROP_STILL_CAPTURE_MODE = 0x5013u, + MTP_DEV_PROP_CONTRAST = 0x5014u, + MTP_DEV_PROP_SHARPNESS = 0x5015u, + MTP_DEV_PROP_DIGITAL_ZOOM = 0x5016u, + MTP_DEV_PROP_EFFECT_MODE = 0x5017u, + MTP_DEV_PROP_BURST_NUMBER = 0x5018u, + MTP_DEV_PROP_BURST_INTERVAL = 0x5019u, + MTP_DEV_PROP_TIMELAPSE_NUMBER = 0x501Au, + MTP_DEV_PROP_TIMELAPSE_INTERVAL = 0x501Bu, + MTP_DEV_PROP_FOCUS_METERING_MODE = 0x501Cu, + MTP_DEV_PROP_UPLOAD_URL = 0x501Du, + MTP_DEV_PROP_ARTIST = 0x501Eu, + MTP_DEV_PROP_COPYRIGHT_INFO = 0x501Fu, + MTP_DEV_PROP_SYNCHRONIZTION_PARTNER = 0xD401, + MTP_DEV_PROP_DEVICE_FRIENDLY_NAME = 0xD402u, + MTP_DEV_PROP_VOLUME = 0xD403u, + MTP_DEV_PROP_SUPPORTED_FORMATS_ORDERED = 0xD404u, + MTP_DEV_PROP_DEVICE_ICON = 0xD405u, + MTP_DEV_PROP_SECTION_INITIATOR_VERSION_INFO = 0xD406u, + MTP_DEV_PROP_PERCEIVED_DEVICE_TYPE = 0xD407u, + MTP_DEV_PROP_PLAYBACK_RATE = 0xD410u, + MTP_DEV_PROP_PLAYBACK_OBJECT = 0xD411u, + MTP_DEV_PROP_PLAYBACK_CONTAINER_INDEX = 0xD412u, +} mtp_event_properties_t; + +// MTP 1.1 Appendix D: Operations +typedef enum { + MTP_OP_UNDEFINED = 0x1000u, + MTP_OP_GET_DEVICE_INFO = 0x1001u, + MTP_OP_OPEN_SESSION = 0x1002u, + MTP_OP_CLOSE_SESSION = 0x1003u, + MTP_OP_GET_STORAGE_IDS = 0x1004u, + MTP_OP_GET_STORAGE_INFO = 0x1005u, + MTP_OP_GET_NUM_OBJECTS = 0x1006u, + MTP_OP_GET_OBJECT_HANDLES = 0x1007u, + MTP_OP_GET_OBJECT_INFO = 0x1008u, + MTP_OP_GET_OBJECT = 0x1009u, + MTP_OP_GET_THUMB = 0x100Au, + MTP_OP_DELETE_OBJECT = 0x100Bu, + MTP_OP_SEND_OBJECT_INFO = 0x100Cu, + MTP_OP_SEND_OBJECT = 0x100Du, + MTP_OP_INITIAL_CAPTURE = 0x100Eu, + MTP_OP_FORMAT_STORE = 0x100Fu, + MTP_OP_RESET_DEVICE = 0x1010u, + MTP_OP_SELF_TEST = 0x1011u, + MTP_OP_SET_OBJECT_PROTECTION = 0x1012u, + MTP_OP_POWER_DOWN = 0x1013u, + MTP_OP_GET_DEVICE_PROP_DESC = 0x1014u, + MTP_OP_GET_DEVICE_PROP_VALUE = 0x1015u, + MTP_OP_SET_DEVICE_PROP_VALUE = 0x1016u, + MTP_OP_RESET_DEVICE_PROP_VALUE = 0x1017u, + MTP_OP_TERMINATE_OPEN_CAPTURE = 0x1018u, + MTP_OP_MOVE_OBJECT = 0x1019u, + MTP_OP_COPY_OBJECT = 0x101Au, + MTP_OP_GET_PARTIAL_OBJECT = 0x101Bu, + MTP_OP_INITIATE_OPEN_CAPTURE = 0x101Bu, + MTP_OP_GET_OBJECT_PROPS_SUPPORTED = 0x9801u, + MTP_OP_GET_OBJECT_PROP_DESC = 0x9802u, + MTP_OP_GET_OBJECT_PROP_VALUE = 0x9803u, + MTP_OP_SET_OBJECT_PROP_VALUE = 0x9804u, + MTP_OP_GET_OBJECT_PROPLIST = 0x9805u, + MTP_OP_GET_OBJECT_PROP_REFERENCES = 0x9810u, + + MTP_OP_GET_SERVICE_IDS = 0x9301u, + MTP_OP_GET_SERVICE_INFO = 0x9302u, + MTP_OP_GET_SERVICE_CAPABILITIES = 0x9303u, + MTP_OP_GET_SERVICE_PROP_DESC = 0x9304u, + + // Appendix E: Enhanced Operations + MTP_OP_GET_OBJECT_PROP_LIST = 0x9805u, + MTP_OP_SET_OBJECT_PROP_LIST = 0x9806u, + MTP_OP_GET_INTERDEPENDENT_PROP_DESC = 0x9807u, + MTP_OP_SEND_OBJECT_PROP_LIST = 0x9808u, +} mtp_operation_code_t; + +// Appendix F: Responses +typedef enum { + MTP_RESP_UNDEFINED = 0x2000u, + MTP_RESP_OK = 0x2001u, + MTP_RESP_GENERAL_ERROR = 0x2002u, + MTP_RESP_SESSION_NOT_OPEN = 0x2003u, + MTP_RESP_INVALID_TRANSACTION_ID = 0x2004u, + MTP_RESP_OPERATION_NOT_SUPPORTED = 0x2005u, + MTP_RESP_PARAMETER_NOT_SUPPORTED = 0x2006u, + MTP_RESP_INCOMPLETE_TRANSFER = 0x2007u, + MTP_RESP_INVALID_STORAGE_ID = 0x2008u, + MTP_RESP_INVALID_OBJECT_HANDLE = 0x2009u, + MTP_RESP_DEVICE_PROP_NOT_SUPPORTED = 0x200Au, + MTP_RESP_INVALID_OBJECT_FORMAT_CODE = 0x200Bu, + MTP_RESP_STORE_FULL = 0x200Cu, + MTP_RESP_OBJECT_WRITE_PROTECTED = 0x200Du, + MPT_RESC_STORE_READ_ONLY = 0x200Eu, + MTP_RESP_ACCESS_DENIED = 0x200Fu, + MTP_RESP_NO_THUMBNAIL_PRESENT = 0x2010u, + MTP_RESP_SELF_TEST_FAILED = 0x2011u, + MTP_RESP_PARTIAL_DELETION = 0x2012u, + MTP_RESP_STORE_NOT_AVAILABLE = 0x2013u, + MTP_RESP_SPECIFICATION_BY_FORMAT_UNSUPPORTED = 0x2014u, + MTP_RESP_NO_VALID_OBJECTINFO = 0x2015u, + MTP_RESP_INVALID_CODE_FORMAT = 0x2016u, + MTP_RESP_UNKNOWN_VENDOR_CODE = 0x2017u, + MTP_RESP_CAPTURE_ALREADY_TERMINATED = 0x2018u, + MTP_RESP_DEVICE_BUSY = 0x2019u, + MTP_RESP_INVALID_PARENT_OBJECT = 0x201Au, + MTP_RESP_INVALID_DEVICE_PROP_FORMAT = 0x201Bu, + MTP_RESP_INVALID_DEVICE_PROP_VALUE = 0x201Cu, + MTP_RESP_INVALID_PARAMETER = 0x201Du, + MTP_RESP_SESSION_ALREADY_OPEN = 0x201Eu, + MTP_RESP_TRANSACTION_CANCELLED = 0x201Fu, + MTP_RESP_SPEC_OF_DESTINATION_UNSUPPORTED = 0x2020u, + + MTP_RESP_INVALID_OBJECT_PROP_CODE = 0xA801u, + MTP_RESP_INVALID_OBJECT_PROP_FORMAT = 0xA802u, + MTP_RESP_INVALID_OBJECT_PROP_VALUE = 0xA803u, + MTP_RESP_INVALID_OBJECT_REFERENCE = 0xA804u, + MTP_RESP_GROUP_NOT_SUPPORTED = 0xA805u, + MTP_RESP_INVALID_DATASET = 0xA806u, + MTP_RESP_SPEC_BY_GROUP_UNSUPPORTED = 0xA807u, + MTP_RESP_SPEC_BY_DEPTH_UNSUPPORTED = 0xA808u, + MTP_RESP_OBJECT_TOO_LARGE = 0xA809u, + MTP_RESP_OBJECT_PROP_NOT_SUPPORTED = 0xA80Au, +} mtp_response_t; + +// Appendix G: Events +typedef enum { + MTP_EVENT_UNDEFINED = 0x4000, + MTP_EVENT_CANCEL_TRANSACTION = 0x4001, + MTP_EVENT_OBJECT_ADDED = 0x4002, + MTP_EVENT_OBJECT_REMOVED = 0x4003, + MTP_EVENT_STORE_ADDED = 0x4004, + MTP_EVENT_STORE_REMOVED = 0x4005, + MTP_EVENT_DEVICE_PROP_CHANGED = 0x4006, + MTP_EVENT_OBJECT_INFO_CHANGED = 0x4007, + MTP_EVENT_DEVICE_INFO_CHANGED = 0x4008, + MTP_EVENT_REQUEST_OBJECT_TRANSFER = 0x4009, + MTP_EVENT_STORE_FULL = 0x400Au, + MTP_EVENT_DEVICE_RESET = 0x400Bu, + MTP_EVENT_STORAGE_INFO_CHANGED = 0x400Cu, + MTP_EVENT_CAPTURE_COMPLETE = 0x400Du, + MTP_EVENT_UNREPORTED_STATUS = 0x400Eu, + MTP_EVENT_OBJECT_PROP_CHANGED = 0xC801u, + MTP_EVENT_OBJECT_PROP_DESC_CHANGED = 0xC802u, + MTP_EVENT_OBJECT_REFERENCES_CHANGED = 0xC803u, +} mtp_event_code_t; // Predefined Object handles typedef enum @@ -210,50 +602,101 @@ typedef enum MTP_MODE_GET_SET = 0x01u, } mtp_mode_get_set_t; +typedef enum { + MTP_STORAGE_TYPE_UNDEFINED = 0x0000u, + MTP_STORAGE_TYPE_FIXED_ROM = 0x0001u, + MTP_STORAGE_TYPE_REMOVABLE_ROM = 0x0002u, + MTP_STORAGE_TYPE_FIXED_RAM = 0x0003u, + MTP_STORAGE_TYPE_REMOVABLE_RAM = 0x0004u, +} mtp_storage_type_t; + +typedef enum { + MTP_FILESYSTEM_TYPE_UNDEFINED = 0x0000u, + MTP_FILESYSTEM_TYPE_GENERIC_FLAT = 0x0001u, + MTP_FILESYSTEM_TYPE_GENERIC_HIERARCHICAL = 0x0002u, + MTP_FILESYSTEM_TYPE_DCF = 0x0003u, +} mtp_filesystem_type_t; + +typedef enum { + MTP_ACCESS_CAPABILITY_READ_WRITE = 0x0000u, + MTP_ACCESS_CAPABILITY_READ_ONLY_WITHOUT_OBJECT_DELETION = 0x0001u, + MTP_ACCESS_CAPABILITY_READ_ONLY_WITH_OBJECT_DELETION = 0x0002u, +} mtp_access_capability_t; + +typedef enum { + MTP_PROTECTION_STATUS_NO_PROTECTION = 0x0000u, + MTP_PROTECTION_STATUS_READ_ONLY = 0x0001u, + MTP_PROTECTION_STATUS_READ_ONLY_DATA = 0x8002u, + MTP_PROTECTION_NON_TRANSFERABLE_DATA = 0x8003u, +} mtp_protection_status_t; + +typedef enum { + MTP_ASSOCIATION_UNDEFINED = 0x0000u, + MTP_ASSOCIATION_GENERIC_FOLDER = 0x0001u, + MTP_ASSOCIATION_GENERIC_ALBUM = 0x0002u, + MTP_ASSOCIATION_TIME_SEQUENCE = 0x0003u, + MTP_ASSOCIATION_HORIZONTAL_PANORAMIC = 0x0004u, + MTP_ASSOCIATION_VERTICAL_PANORAMIC = 0x0005u, + MTP_ASSOCIATION_2D_PANORAMIC = 0x0006u, +} mtp_association_t; + + tu_static const uint16_t mtp_operations_supported[] = { - MTP_OPEC_GET_DEVICE_INFO, - MTP_OPEC_OPEN_SESSION, - MTP_OPEC_CLOSE_SESSION, - MTP_OPEC_GET_STORAGE_IDS, - MTP_OPEC_GET_STORAGE_INFO, - MTP_OPEC_GET_NUM_OBJECTS, - MTP_OPEC_GET_OBJECT_HANDLES, - MTP_OPEC_GET_OBJECT_INFO, - MTP_OPEC_GET_OBJECT, - MTP_OPEC_DELETE_OBJECT, - MTP_OPEC_SEND_OBJECT_INFO, - MTP_OPEC_SEND_OBJECT, - MTP_OPEC_FORMAT_STORE, - MTP_OPEC_RESET_DEVICE, - MTP_OPEC_GET_DEVICE_PROP_DESC, - MTP_OPEC_GET_DEVICE_PROP_VALUE, - MTP_OPEC_SET_DEVICE_PROP_VALUE, + MTP_OP_GET_DEVICE_INFO, + MTP_OP_OPEN_SESSION, + MTP_OP_CLOSE_SESSION, + MTP_OP_GET_STORAGE_IDS, + MTP_OP_GET_STORAGE_INFO, + MTP_OP_GET_NUM_OBJECTS, + MTP_OP_GET_OBJECT_HANDLES, + MTP_OP_GET_OBJECT_INFO, + MTP_OP_GET_OBJECT, + MTP_OP_DELETE_OBJECT, + MTP_OP_SEND_OBJECT_INFO, + MTP_OP_SEND_OBJECT, + MTP_OP_FORMAT_STORE, + MTP_OP_RESET_DEVICE, + MTP_OP_GET_DEVICE_PROP_DESC, + MTP_OP_GET_DEVICE_PROP_VALUE, + MTP_OP_SET_DEVICE_PROP_VALUE, }; tu_static const uint16_t mtp_events_supported[] = { - MTP_EVTC_OBJECT_ADDED, + MTP_EVENT_OBJECT_ADDED, }; tu_static const uint16_t mtp_device_properties_supported[] = { - MTP_DEVP_DEVICE_FRIENDLY_NAME, + MTP_DEV_PROP_DEVICE_FRIENDLY_NAME, }; tu_static const uint16_t mtp_capture_formats[] = { - MTP_OBJF_UNDEFINED, - MTP_OBJF_ASSOCIATION, - MTP_OBJF_TEXT, + MTP_OBJ_FORMAT_UNDEFINED, + MTP_OBJ_FORMAT_ASSOCIATION, + MTP_OBJ_FORMAT_TEXT, }; tu_static const uint16_t mtp_playback_formats[] = { - MTP_OBJF_UNDEFINED, - MTP_OBJF_ASSOCIATION, - MTP_OBJF_TEXT, + MTP_OBJ_FORMAT_UNDEFINED, + MTP_OBJ_FORMAT_ASSOCIATION, + MTP_OBJ_FORMAT_TEXT, }; //--------------------------------------------------------------------+ // Data structures //--------------------------------------------------------------------+ +#define MTP_GENERIC_DATA_BLOCK_LENGTH 12 +#define MTP_MAX_PACKET_SIZE 512 + +// PTP/MTP Generic container +typedef struct TU_ATTR_PACKED { + uint32_t container_length; + uint16_t container_type; + uint16_t code; + uint32_t transaction_id; + uint32_t data[MTP_MAX_PACKET_SIZE / sizeof(uint32_t)]; +} mtp_generic_container_t; + // DeviceInfo Dataset #define MTP_EXTENSIONS "microsoft.com: 1.0; " typedef struct TU_ATTR_PACKED { @@ -360,75 +803,6 @@ typedef struct TU_ATTR_PACKED { uint32_t parent_object_handle; } mtp_basic_object_info_t; -//--------------------------------------------------------------------+ -// Definitions -//--------------------------------------------------------------------+ - -typedef enum { - MTP_STORAGE_TYPE_UNDEFINED = 0x0000u, - MTP_STORAGE_TYPE_FIXED_ROM = 0x0001u, - MTP_STORAGE_TYPE_REMOVABLE_ROM = 0x0002u, - MTP_STORAGE_TYPE_FIXED_RAM = 0x0003u, - MTP_STORAGE_TYPE_REMOVABLE_RAM = 0x0004u, -} mtp_storage_type_t; - -typedef enum { - MTP_FILESYSTEM_TYPE_UNDEFINED = 0x0000u, - MTP_FILESYSTEM_TYPE_GENERIC_FLAT = 0x0001u, - MTP_FILESYSTEM_TYPE_GENERIC_HIERARCHICAL = 0x0002u, - MTP_FILESYSTEM_TYPE_DCF = 0x0003u, -} mtp_filesystem_type_t; - -typedef enum { - MTP_ACCESS_CAPABILITY_READ_WRITE = 0x0000u, - MTP_ACCESS_CAPABILITY_READ_ONLY_WITHOUT_OBJECT_DELETION = 0x0001u, - MTP_ACCESS_CAPABILITY_READ_ONLY_WITH_OBJECT_DELETION = 0x0002u, -} mtp_access_capability_t; - -typedef enum { - MTP_PROTECTION_STATUS_NO_PROTECTION = 0x0000u, - MTP_PROTECTION_STATUS_READ_ONLY = 0x0001u, - MTP_PROTECTION_STATUS_READ_ONLY_DATA = 0x8002u, - MTP_PROTECTION_NON_TRANSFERABLE_DATA = 0x8003u, -} mtp_protection_status_t; - -typedef enum { - MTP_ASSOCIATION_UNDEFINED = 0x0000u, - MTP_ASSOCIATION_GENERIC_FOLDER = 0x0001u, - MTP_ASSOCIATION_GENERIC_ALBUM = 0x0002u, - MTP_ASSOCIATION_TIME_SEQUENCE = 0x0003u, - MTP_ASSOCIATION_HORIZONTAL_PANORAMIC = 0x0004u, - MTP_ASSOCIATION_VERTICAL_PANORAMIC = 0x0005u, - MTP_ASSOCIATION_2D_PANORAMIC = 0x0006u, -} mtp_association_t; - -// Responses -typedef enum { -// Supported ResponseCode - MTP_RESC_UNDEFINED = 0x2000u, - MTP_RESC_OK = 0x2001u, - MTP_RESC_GENERAL_ERROR = 0x2002u, - MTP_RESC_SESSION_NOT_OPEN = 0x2003u, - MTP_RESC_INVALID_TRANSACTION_ID = 0x2004u, - MTP_RESC_OPERATION_NOT_SUPPORTED = 0x2005u, - MTP_RESC_PARAMETER_NOT_SUPPORTED = 0x2006u, - MTP_RESC_INCOMPLETE_TRANSFER = 0x2007u, - MTP_RESC_INVALID_STORAGE_ID = 0x2008u, - MTP_RESC_INVALID_OBJECT_HANDLE = 0x2009u, - MTP_RESC_STORE_FULL = 0x200Cu, - MTP_RESC_OBJECT_WRITE_PROTECTED = 0x200Du, - MTP_RESC_STORE_NOT_AVAILABLE = 0x2013u, - MTP_RESC_SPECIFICATION_BY_FORMAT_UNSUPPORTED = 0x2014u, - MTP_RESC_NO_VALID_OBJECTINFO = 0x2015u, - MTP_RESC_DEVICE_BUSY = 0x2019u, - MTP_RESC_INVALID_PARENT_OBJECT = 0x201Au, - MTP_RESC_INVALID_DEVICE_PROP_FORMAT = 0x201Bu, - MTP_RESC_INVALID_DEVICE_PROP_VALUE = 0x201Cu, - MTP_RESC_INVALID_PARAMETER = 0x201Du, - MTP_RESC_SESSION_ALREADY_OPEN = 0x201Eu, - MTP_RESC_TRANSACTION_CANCELLED = 0x201Fu, -} mtp_response_t; - #ifdef __cplusplus } #endif diff --git a/src/class/mtp/mtp_device.c b/src/class/mtp/mtp_device.c index 854d4f10e..68a7c6c2a 100644 --- a/src/class/mtp/mtp_device.c +++ b/src/class/mtp/mtp_device.c @@ -147,9 +147,9 @@ uint16_t mtpd_open(uint8_t rhport, tusb_desc_interface_t const *itf_desc, uint16 TU_LOG_DRV(" MTP mtpd_open\n"); tusb_desc_endpoint_t const *ep_desc; // only support SCSI's BOT protocol - TU_VERIFY(TUSB_CLASS_IMAGE == itf_desc->bInterfaceClass && - MTP_SUBCLASS == itf_desc->bInterfaceSubClass && - MTP_PROTOCOL_STILL_IMAGE == itf_desc->bInterfaceProtocol, 0); + TU_VERIFY(TUSB_CLASS_IMAGE == itf_desc->bInterfaceClass && + MTP_SUBCLASS_STILL_IMAGE == itf_desc->bInterfaceSubClass && + MTP_PROTOCOL_PIMA_15470 == itf_desc->bInterfaceProtocol, 0); // mtp driver length is fixed uint16_t const mtpd_itf_size = sizeof(tusb_desc_interface_t) + 3 * sizeof(tusb_desc_endpoint_t); @@ -205,7 +205,7 @@ bool mtpd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t len = 4; _mtpd_device_status_res.wLength = len; // Cancel is synchronous, always answer OK - _mtpd_device_status_res.code = MTP_RESC_OK; + _mtpd_device_status_res.code = MTP_RESP_OK; TU_ASSERT( tud_control_xfer(rhport, request, (uint8_t *)&_mtpd_device_status_res , len) ); break; @@ -243,7 +243,7 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t { _mtpd_itf.phase = MTP_PHASE_RESPONSE; _mtpd_gct.container_type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; - _mtpd_gct.code = MTP_RESC_OK; + _mtpd_gct.code = MTP_RESP_OK; _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH; _mtpd_gct.transaction_id = _mtpd_ctx.transaction_id; if (_mtpd_ctx.session_id != 0) @@ -413,55 +413,55 @@ mtp_phase_type_t mtpd_handle_cmd(void) { TU_ASSERT(_mtpd_gct.container_type == MTP_CONTAINER_TYPE_COMMAND_BLOCK); _mtpd_ctx.transaction_id = _mtpd_gct.transaction_id; - if (_mtpd_gct.code != MTP_OPEC_SEND_OBJECT) + if (_mtpd_gct.code != MTP_OP_SEND_OBJECT) _mtpd_soi.object_handle = 0; switch(_mtpd_gct.code) { - case MTP_OPEC_GET_DEVICE_INFO: - TU_LOG_DRV(" MTP command: MTP_OPEC_GET_DEVICE_INFO\n"); + case MTP_OP_GET_DEVICE_INFO: + TU_LOG_DRV(" MTP command: MTP_OP_GET_DEVICE_INFO\n"); return mtpd_handle_cmd_get_device_info(); - case MTP_OPEC_OPEN_SESSION: - TU_LOG_DRV(" MTP command: MTP_OPEC_OPEN_SESSION\n"); + case MTP_OP_OPEN_SESSION: + TU_LOG_DRV(" MTP command: MTP_OP_OPEN_SESSION\n"); return mtpd_handle_cmd_open_session(); - case MTP_OPEC_CLOSE_SESSION: - TU_LOG_DRV(" MTP command: MTP_OPEC_CLOSE_SESSION\n"); + case MTP_OP_CLOSE_SESSION: + TU_LOG_DRV(" MTP command: MTP_OP_CLOSE_SESSION\n"); return mtpd_handle_cmd_close_session(); - case MTP_OPEC_GET_STORAGE_IDS: - TU_LOG_DRV(" MTP command: MTP_OPEC_GET_STORAGE_IDS\n"); + case MTP_OP_GET_STORAGE_IDS: + TU_LOG_DRV(" MTP command: MTP_OP_GET_STORAGE_IDS\n"); return mtpd_handle_cmd_get_storage_ids(); - case MTP_OPEC_GET_STORAGE_INFO: - TU_LOG_DRV(" MTP command: MTP_OPEC_GET_STORAGE_INFO for ID=%lu\n", _mtpd_gct.data[0]); + case MTP_OP_GET_STORAGE_INFO: + TU_LOG_DRV(" MTP command: MTP_OP_GET_STORAGE_INFO for ID=%lu\n", _mtpd_gct.data[0]); return mtpd_handle_cmd_get_storage_info(); - case MTP_OPEC_GET_OBJECT_HANDLES: - TU_LOG_DRV(" MTP command: MTP_OPEC_GET_OBJECT_HANDLES\n"); + case MTP_OP_GET_OBJECT_HANDLES: + TU_LOG_DRV(" MTP command: MTP_OP_GET_OBJECT_HANDLES\n"); return mtpd_handle_cmd_get_object_handles(); - case MTP_OPEC_GET_OBJECT_INFO: - TU_LOG_DRV(" MTP command: MTP_OPEC_GET_OBJECT_INFO\n"); + case MTP_OP_GET_OBJECT_INFO: + TU_LOG_DRV(" MTP command: MTP_OP_GET_OBJECT_INFO\n"); return mtpd_handle_cmd_get_object_info(); - case MTP_OPEC_GET_OBJECT: - TU_LOG_DRV(" MTP command: MTP_OPEC_GET_OBJECT\n"); + case MTP_OP_GET_OBJECT: + TU_LOG_DRV(" MTP command: MTP_OP_GET_OBJECT\n"); return mtpd_handle_cmd_get_object(); - case MTP_OPEC_DELETE_OBJECT: - TU_LOG_DRV(" MTP command: MTP_OPEC_DELETE_OBJECT\n"); + case MTP_OP_DELETE_OBJECT: + TU_LOG_DRV(" MTP command: MTP_OP_DELETE_OBJECT\n"); return mtpd_handle_cmd_delete_object(); - case MTP_OPEC_GET_DEVICE_PROP_DESC: - TU_LOG_DRV(" MTP command: MTP_OPEC_GET_DEVICE_PROP_DESC\n"); + case MTP_OP_GET_DEVICE_PROP_DESC: + TU_LOG_DRV(" MTP command: MTP_OP_GET_DEVICE_PROP_DESC\n"); return mtpd_handle_cmd_get_device_prop_desc(); - case MTP_OPEC_GET_DEVICE_PROP_VALUE: - TU_LOG_DRV(" MTP command: MTP_OPEC_GET_DEVICE_PROP_VALUE\n"); + case MTP_OP_GET_DEVICE_PROP_VALUE: + TU_LOG_DRV(" MTP command: MTP_OP_GET_DEVICE_PROP_VALUE\n"); return mtpd_handle_cmd_get_device_prop_value(); - case MTP_OPEC_SEND_OBJECT_INFO: - TU_LOG_DRV(" MTP command: MTP_OPEC_SEND_OBJECT_INFO\n"); + case MTP_OP_SEND_OBJECT_INFO: + TU_LOG_DRV(" MTP command: MTP_OP_SEND_OBJECT_INFO\n"); return mtpd_handle_cmd_send_object_info(); - case MTP_OPEC_SEND_OBJECT: - TU_LOG_DRV(" MTP command: MTP_OPEC_SEND_OBJECT\n"); + case MTP_OP_SEND_OBJECT: + TU_LOG_DRV(" MTP command: MTP_OP_SEND_OBJECT\n"); return mtpd_handle_cmd_send_object(); - case MTP_OPEC_FORMAT_STORE: - TU_LOG_DRV(" MTP command: MTP_OPEC_FORMAT_STORE\n"); + case MTP_OP_FORMAT_STORE: + TU_LOG_DRV(" MTP command: MTP_OP_FORMAT_STORE\n"); return mtpd_handle_cmd_format_store(); default: - TU_LOG_DRV(" MTP command: MTP_OPEC_UNKNOWN_COMMAND %x!!!!\n", _mtpd_gct.code); + TU_LOG_DRV(" MTP command: MTP_OP_UNKNOWN_COMMAND %x!!!!\n", _mtpd_gct.code); return false; } return true; @@ -474,17 +474,17 @@ mtp_phase_type_t mtpd_handle_data(void) switch(_mtpd_gct.code) { - case MTP_OPEC_GET_OBJECT: - TU_LOG_DRV(" MTP command: MTP_OPEC_GET_OBJECT-DATA_IN\n"); + case MTP_OP_GET_OBJECT: + TU_LOG_DRV(" MTP command: MTP_OP_GET_OBJECT-DATA_IN\n"); return mtpd_handle_dti_get_object(); - case MTP_OPEC_SEND_OBJECT_INFO: - TU_LOG_DRV(" MTP command: MTP_OPEC_SEND_OBJECT_INFO-DATA_OUT\n"); + case MTP_OP_SEND_OBJECT_INFO: + TU_LOG_DRV(" MTP command: MTP_OP_SEND_OBJECT_INFO-DATA_OUT\n"); return mtpd_handle_dto_send_object_info(); - case MTP_OPEC_SEND_OBJECT: - TU_LOG_DRV(" MTP command: MTP_OPEC_SEND_OBJECT-DATA_OUT\n"); + case MTP_OP_SEND_OBJECT: + TU_LOG_DRV(" MTP command: MTP_OP_SEND_OBJECT-DATA_OUT\n"); return mtpd_handle_dto_send_object(); default: - TU_LOG_DRV(" MTP command: MTP_OPEC_UNKNOWN_COMMAND %x!!!!\n", _mtpd_gct.code); + TU_LOG_DRV(" MTP command: MTP_OP_UNKNOWN_COMMAND %x!!!!\n", _mtpd_gct.code); return false; } return true; @@ -496,7 +496,7 @@ mtp_phase_type_t mtpd_handle_cmd_get_device_info(void) _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH + sizeof(mtp_device_info_t); _mtpd_gct.container_type = MTP_CONTAINER_TYPE_DATA_BLOCK; - _mtpd_gct.code = MTP_OPEC_GET_DEVICE_INFO; + _mtpd_gct.code = MTP_OP_GET_DEVICE_INFO; mtp_device_info_t *d = (mtp_device_info_t *)_mtpd_gct.data; d->standard_version = 100; d->mtp_vendor_extension_id = 0x06; @@ -528,7 +528,7 @@ mtp_phase_type_t mtpd_handle_cmd_open_session(void) uint32_t session_id = _mtpd_gct.data[0]; mtp_response_t res = tud_mtp_storage_open_session(&session_id); - if (res == MTP_RESC_SESSION_ALREADY_OPEN) + if (res == MTP_RESP_SESSION_ALREADY_OPEN) { _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH; _mtpd_gct.container_type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; @@ -540,13 +540,13 @@ mtp_phase_type_t mtpd_handle_cmd_open_session(void) } mtp_phase_type_t phase; - if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESC_OK), res, "")) != MTP_PHASE_NONE) return phase; + if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESP_OK), res, "")) != MTP_PHASE_NONE) return phase; _mtpd_ctx.session_id = session_id; _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH; _mtpd_gct.container_type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; - _mtpd_gct.code = MTP_RESC_OK; + _mtpd_gct.code = MTP_RESP_OK; return MTP_PHASE_RESPONSE; } @@ -573,11 +573,11 @@ mtp_phase_type_t mtpd_handle_cmd_get_storage_ids(void) uint32_t storage_id; mtp_response_t res = tud_mtp_get_storage_id(&storage_id); mtp_phase_type_t phase; - if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESC_OK), res, "")) != MTP_PHASE_NONE) return phase; + if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESP_OK), res, "")) != MTP_PHASE_NONE) return phase; _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH + sizeof(mtp_storage_ids_t); _mtpd_gct.container_type = MTP_CONTAINER_TYPE_DATA_BLOCK; - _mtpd_gct.code = MTP_OPEC_GET_STORAGE_IDS; + _mtpd_gct.code = MTP_OP_GET_STORAGE_IDS; mtp_storage_ids_t *d = (mtp_storage_ids_t *)_mtpd_gct.data; if (storage_id == 0) { @@ -603,11 +603,11 @@ mtp_phase_type_t mtpd_handle_cmd_get_storage_info(void) _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH + sizeof(mtp_storage_info_t); _mtpd_gct.container_type = MTP_CONTAINER_TYPE_DATA_BLOCK; - _mtpd_gct.code = MTP_OPEC_GET_STORAGE_INFO; + _mtpd_gct.code = MTP_OP_GET_STORAGE_INFO; mtp_response_t res = tud_mtp_get_storage_info(storage_id, (mtp_storage_info_t *)_mtpd_gct.data); mtp_phase_type_t phase; - if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESC_OK), res, "")) != MTP_PHASE_NONE) return phase; + if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESP_OK), res, "")) != MTP_PHASE_NONE) return phase; _mtpd_itf.queued_len = _mtpd_gct.container_length; return MTP_PHASE_DATA_IN; @@ -621,20 +621,20 @@ mtp_phase_type_t mtpd_handle_cmd_get_object_handles(void) _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH + sizeof(uint32_t); _mtpd_gct.container_type = MTP_CONTAINER_TYPE_DATA_BLOCK; - _mtpd_gct.code = MTP_OPEC_GET_OBJECT_HANDLES; + _mtpd_gct.code = MTP_OP_GET_OBJECT_HANDLES; _mtpd_gct.data[0] = 0; mtp_phase_type_t phase; - if ((phase = mtpd_chk_generic(__func__, (object_format_code != 0), MTP_RESC_SPECIFICATION_BY_FORMAT_UNSUPPORTED, "specification by format unsupported")) != MTP_PHASE_NONE) return phase; + if ((phase = mtpd_chk_generic(__func__, (object_format_code != 0), MTP_RESP_SPECIFICATION_BY_FORMAT_UNSUPPORTED, "specification by format unsupported")) != MTP_PHASE_NONE) return phase; //list of all object handles on all storages, not managed - if ((phase = mtpd_chk_generic(__func__, (storage_id == 0xFFFFFFFF), MTP_RESC_OPERATION_NOT_SUPPORTED, "list of all object handles on all storages unsupported")) != MTP_PHASE_NONE) return phase; + if ((phase = mtpd_chk_generic(__func__, (storage_id == 0xFFFFFFFF), MTP_RESP_OPERATION_NOT_SUPPORTED, "list of all object handles on all storages unsupported")) != MTP_PHASE_NONE) return phase; tud_mtp_storage_object_done(); uint32_t next_child_handle = 0; while(true) { mtp_response_t res = tud_mtp_storage_association_get_object_handle(storage_id, parent_object_handle, &next_child_handle); - if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESC_OK), res, "")) != MTP_PHASE_NONE) return phase; + if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESP_OK), res, "")) != MTP_PHASE_NONE) return phase; if (next_child_handle == 0) break; mtpd_gct_append_object_handle(next_child_handle); @@ -653,10 +653,10 @@ mtp_phase_type_t mtpd_handle_cmd_get_object_info(void) _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH + sizeof(mtp_object_info_t); _mtpd_gct.container_type = MTP_CONTAINER_TYPE_DATA_BLOCK; - _mtpd_gct.code = MTP_OPEC_GET_OBJECT_INFO; + _mtpd_gct.code = MTP_OP_GET_OBJECT_INFO; mtp_response_t res = tud_mtp_storage_object_read_info(object_handle, (mtp_object_info_t *)_mtpd_gct.data); mtp_phase_type_t phase; - if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESC_OK), res, "")) != MTP_PHASE_NONE) return phase; + if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESP_OK), res, "")) != MTP_PHASE_NONE) return phase; _mtpd_itf.queued_len = _mtpd_gct.container_length; return MTP_PHASE_DATA_IN; @@ -676,10 +676,10 @@ mtp_phase_type_t mtpd_handle_dti_get_object(void) mtp_phase_type_t phase; uint32_t file_size = 0; res = tud_mtp_storage_object_size(_mtpd_get_object_handle, &file_size); - if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESC_OK), res, "")) != MTP_PHASE_NONE) return phase; + if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESP_OK), res, "")) != MTP_PHASE_NONE) return phase; _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH + file_size; _mtpd_gct.container_type = MTP_CONTAINER_TYPE_DATA_BLOCK; - _mtpd_gct.code = MTP_OPEC_GET_OBJECT; + _mtpd_gct.code = MTP_OP_GET_OBJECT; uint32_t buffer_size; uint32_t read_count; @@ -689,7 +689,7 @@ mtp_phase_type_t mtpd_handle_dti_get_object(void) // First data block: include container header buffer_size = ((MTP_MAX_PACKET_SIZE + MTP_GENERIC_DATA_BLOCK_LENGTH) / CFG_MTP_EP_SIZE) * CFG_MTP_EP_SIZE - MTP_GENERIC_DATA_BLOCK_LENGTH; res = tud_mtp_storage_object_read(_mtpd_get_object_handle, (void *)&_mtpd_gct.data, buffer_size, &read_count); - if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESC_OK), res, "")) != MTP_PHASE_NONE) return phase; + if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESP_OK), res, "")) != MTP_PHASE_NONE) return phase; _mtpd_itf.queued_len = MTP_GENERIC_DATA_BLOCK_LENGTH + read_count; } else @@ -697,7 +697,7 @@ mtp_phase_type_t mtpd_handle_dti_get_object(void) // Successive data block: consider only container data buffer_size = (MTP_MAX_PACKET_SIZE / CFG_MTP_EP_SIZE) * CFG_MTP_EP_SIZE; res = tud_mtp_storage_object_read(_mtpd_get_object_handle, (void *)&_mtpd_gct.data, buffer_size, &read_count); - if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESC_OK), res, "")) != MTP_PHASE_NONE) return phase; + if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESP_OK), res, "")) != MTP_PHASE_NONE) return phase; _mtpd_itf.queued_len = read_count; } @@ -718,10 +718,10 @@ mtp_phase_type_t mtpd_handle_cmd_delete_object(void) mtp_response_t res = tud_mtp_storage_object_delete(object_handle); mtp_phase_type_t phase; - if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESC_OK), res, "")) != MTP_PHASE_NONE) return phase; + if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESP_OK), res, "")) != MTP_PHASE_NONE) return phase; _mtpd_gct.container_type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; - _mtpd_gct.code = MTP_RESC_OK; + _mtpd_gct.code = MTP_RESP_OK; _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH; return MTP_PHASE_RESPONSE; } @@ -735,11 +735,11 @@ mtp_phase_type_t mtpd_handle_cmd_get_device_prop_desc(void) switch(device_prop_code) { - case MTP_DEVP_DEVICE_FRIENDLY_NAME: + case MTP_DEV_PROP_DEVICE_FRIENDLY_NAME: { TU_VERIFY_STATIC(sizeof(mtp_device_prop_desc_t) < MTP_MAX_PACKET_SIZE, "mtp_device_info_t shall fit in MTP_MAX_PACKET_SIZE"); _mtpd_gct.container_type = MTP_CONTAINER_TYPE_DATA_BLOCK; - _mtpd_gct.code = MTP_OPEC_GET_DEVICE_PROP_DESC; + _mtpd_gct.code = MTP_OP_GET_DEVICE_PROP_DESC; _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH + sizeof(mtp_device_prop_desc_t); mtp_device_prop_desc_t *d = (mtp_device_prop_desc_t *)_mtpd_gct.data; d->device_property_code = (uint16_t)(device_prop_code); @@ -756,7 +756,7 @@ mtp_phase_type_t mtpd_handle_cmd_get_device_prop_desc(void) } _mtpd_gct.container_type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; - _mtpd_gct.code = MTP_RESC_PARAMETER_NOT_SUPPORTED; + _mtpd_gct.code = MTP_RESP_PARAMETER_NOT_SUPPORTED; _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH; return MTP_PHASE_RESPONSE; } @@ -770,18 +770,18 @@ mtp_phase_type_t mtpd_handle_cmd_get_device_prop_value(void) _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH; _mtpd_gct.container_type = MTP_CONTAINER_TYPE_DATA_BLOCK; - _mtpd_gct.code = MTP_OPEC_GET_DEVICE_PROP_VALUE; + _mtpd_gct.code = MTP_OP_GET_DEVICE_PROP_VALUE; switch(device_prop_code) { // TODO support more device properties - case MTP_DEVP_DEVICE_FRIENDLY_NAME: + case MTP_DEV_PROP_DEVICE_FRIENDLY_NAME: mtpd_gct_append_wstring(CFG_TUD_MODEL); _mtpd_itf.queued_len = _mtpd_gct.container_length; return MTP_PHASE_DATA_IN; default: _mtpd_gct.container_type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; - _mtpd_gct.code = MTP_RESC_PARAMETER_NOT_SUPPORTED; + _mtpd_gct.code = MTP_RESP_PARAMETER_NOT_SUPPORTED; return MTP_PHASE_RESPONSE; } } @@ -800,7 +800,7 @@ mtp_phase_type_t mtpd_handle_dto_send_object_info(void) uint32_t new_object_handle = 0; mtp_response_t res = tud_mtp_storage_object_write_info(_mtpd_soi.storage_id, _mtpd_soi.parent_object_handle, &new_object_handle, (mtp_object_info_t *)_mtpd_gct.data); mtp_phase_type_t phase; - if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESC_OK), res, "")) != MTP_PHASE_NONE) return phase; + if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESP_OK), res, "")) != MTP_PHASE_NONE) return phase; // Save send_object_info _mtpd_soi.object_handle = new_object_handle; @@ -808,7 +808,7 @@ mtp_phase_type_t mtpd_handle_dto_send_object_info(void) // Response _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH + 3 * sizeof(uint32_t); _mtpd_gct.container_type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; - _mtpd_gct.code = MTP_RESC_OK; + _mtpd_gct.code = MTP_RESP_OK; _mtpd_gct.data[0] = _mtpd_soi.storage_id; _mtpd_gct.data[1] = _mtpd_soi.parent_object_handle; _mtpd_gct.data[2] = _mtpd_soi.object_handle; @@ -835,7 +835,7 @@ mtp_phase_type_t mtpd_handle_dto_send_object(void) { mtp_response_t res = tud_mtp_storage_object_write(_mtpd_soi.object_handle, buffer, buffer_size); mtp_phase_type_t phase; - if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESC_OK), res, "")) != MTP_PHASE_NONE) return phase; + if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESP_OK), res, "")) != MTP_PHASE_NONE) return phase; } if (!_mtpd_itf.xfer_completed) @@ -849,7 +849,7 @@ mtp_phase_type_t mtpd_handle_dto_send_object(void) _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH; _mtpd_gct.container_type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; - _mtpd_gct.code = MTP_RESC_OK; + _mtpd_gct.code = MTP_RESP_OK; return MTP_PHASE_RESPONSE; } @@ -877,7 +877,7 @@ mtp_phase_type_t mtpd_chk_session_open(const char *func_name) { TU_LOG_DRV(" MTP error: %s session not open\n", func_name); _mtpd_gct.container_type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; - _mtpd_gct.code = MTP_RESC_SESSION_NOT_OPEN; + _mtpd_gct.code = MTP_RESP_SESSION_NOT_OPEN; _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH; return MTP_PHASE_RESPONSE; } diff --git a/src/device/usbd.h b/src/device/usbd.h index b89a0200b..921d5c0d0 100644 --- a/src/device/usbd.h +++ b/src/device/usbd.h @@ -279,7 +279,7 @@ bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_requ // Interface number, string index, EP Out & EP In address, EP size #define TUD_MTP_DESCRIPTOR(_itfnum, _stridx, _ep_evt, _ep_evt_size, _ep_evt_polling_interval, _epout, _epin, _epsize) \ /* Interface */\ - 9, TUSB_DESC_INTERFACE, _itfnum, 0, 3, TUSB_CLASS_IMAGE, MTP_SUBCLASS, MTP_PROTOCOL_STILL_IMAGE, _stridx,\ + 9, TUSB_DESC_INTERFACE, _itfnum, 0, 3, TUSB_CLASS_IMAGE, MTP_SUBCLASS_STILL_IMAGE, MTP_PROTOCOL_PIMA_15470, _stridx,\ /* Endpoint Interrupt */\ 7, TUSB_DESC_ENDPOINT, _ep_evt, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(_ep_evt_size), _ep_evt_polling_interval,\ /* Endpoint Out */\ From 4ef99ecb27dae46788ef83c2c76ea48819004565 Mon Sep 17 00:00:00 2001 From: hathach Date: Mon, 15 Sep 2025 22:41:56 +0700 Subject: [PATCH 03/43] fix copying readme.txt contents --- examples/device/mtp/src/mtp_fs_example.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/device/mtp/src/mtp_fs_example.c b/examples/device/mtp/src/mtp_fs_example.c index 87730bcd3..9dbac746b 100644 --- a/examples/device/mtp/src/mtp_fs_example.c +++ b/examples/device/mtp/src/mtp_fs_example.c @@ -448,16 +448,18 @@ mtp_response_t tud_mtp_storage_object_read(uint32_t object_handle, void *buffer, { TU_LOG1("Read object %ld: %ld bytes at offset %ld\r\n", object_handle, buffer_size, _fs_operation.read_pos); *read_count = buffer_size; - if (_fs_operation.read_pos + buffer_size < FS_MAX_NODE_BYTES) + if (_fs_operation.read_pos + buffer_size < FS_MAX_NODE_BYTES) { memcpy(buffer, &obj->data[_fs_operation.read_pos], *read_count); + } _fs_operation.read_pos += *read_count; } else { TU_LOG1("Read object %ld: %ld bytes at offset %ld\r\n", object_handle, obj->size - _fs_operation.read_pos, _fs_operation.read_pos); *read_count = obj->size - _fs_operation.read_pos; - if (_fs_operation.read_pos + buffer_size < FS_MAX_NODE_BYTES) + if (_fs_operation.read_pos + *read_count < FS_MAX_NODE_BYTES) { memcpy(buffer, &obj->data[_fs_operation.read_pos], *read_count); + } // Read operation completed _fs_operation.read_handle = 0; _fs_operation.read_pos = 0; From 5fb8c57f5cea8fc4e3609f59d64496e9f49153df Mon Sep 17 00:00:00 2001 From: hathach Date: Tue, 16 Sep 2025 09:42:07 +0700 Subject: [PATCH 04/43] merge context into interface and refactor, reformat --- src/class/mtp/mtp_device.c | 304 +++++++++++++++---------------------- 1 file changed, 122 insertions(+), 182 deletions(-) diff --git a/src/class/mtp/mtp_device.c b/src/class/mtp/mtp_device.c index 68a7c6c2a..d8db17e79 100644 --- a/src/class/mtp/mtp_device.c +++ b/src/class/mtp/mtp_device.c @@ -53,7 +53,7 @@ typedef struct uint8_t itf_num; uint8_t ep_in; uint8_t ep_out; - uint8_t ep_evt; + uint8_t ep_event; // Bulk Only Transfer (BOT) Protocol uint8_t phase; @@ -64,112 +64,95 @@ typedef struct uint32_t handled_len; // number of bytes already handled in the Data Stage bool xfer_completed; // true when DATA-IN/DATA-OUT transfer is completed + struct { + uint32_t session_id; + uint32_t transaction_id; + } context; } mtpd_interface_t; -typedef struct -{ - uint32_t session_id; - uint32_t transaction_id; -} mtpd_context_t; - //--------------------------------------------------------------------+ // INTERNAL FUNCTION DECLARATION //--------------------------------------------------------------------+ // Checker -tu_static mtp_phase_type_t mtpd_chk_generic(const char *func_name, const bool err_cd, const uint16_t ret_code, const char *message); -tu_static mtp_phase_type_t mtpd_chk_session_open(const char *func_name); +static mtp_phase_type_t mtpd_chk_generic(const char *func_name, const bool err_cd, const uint16_t ret_code, const char *message); +static mtp_phase_type_t mtpd_chk_session_open(const char *func_name); // MTP commands -tu_static mtp_phase_type_t mtpd_handle_cmd(void); -tu_static mtp_phase_type_t mtpd_handle_data(void); -tu_static mtp_phase_type_t mtpd_handle_cmd_get_device_info(void); -tu_static mtp_phase_type_t mtpd_handle_cmd_open_session(void); -tu_static mtp_phase_type_t mtpd_handle_cmd_close_session(void); -tu_static mtp_phase_type_t mtpd_handle_cmd_get_storage_info(void); -tu_static mtp_phase_type_t mtpd_handle_cmd_get_storage_ids(void); -tu_static mtp_phase_type_t mtpd_handle_cmd_get_object_handles(void); -tu_static mtp_phase_type_t mtpd_handle_cmd_get_object_info(void); -tu_static mtp_phase_type_t mtpd_handle_cmd_get_object(void); -tu_static mtp_phase_type_t mtpd_handle_dti_get_object(void); -tu_static mtp_phase_type_t mtpd_handle_cmd_delete_object(void); -tu_static mtp_phase_type_t mtpd_handle_cmd_get_device_prop_desc(void); -tu_static mtp_phase_type_t mtpd_handle_cmd_get_device_prop_value(void); -tu_static mtp_phase_type_t mtpd_handle_cmd_send_object_info(void); -tu_static mtp_phase_type_t mtpd_handle_dto_send_object_info(void); -tu_static mtp_phase_type_t mtpd_handle_cmd_send_object(void); -tu_static mtp_phase_type_t mtpd_handle_dto_send_object(void); -tu_static mtp_phase_type_t mtpd_handle_cmd_format_store(void); +static mtp_phase_type_t mtpd_handle_cmd(void); +static mtp_phase_type_t mtpd_handle_data(void); +static mtp_phase_type_t mtpd_handle_cmd_get_device_info(void); +static mtp_phase_type_t mtpd_handle_cmd_open_session(void); +static mtp_phase_type_t mtpd_handle_cmd_close_session(void); +static mtp_phase_type_t mtpd_handle_cmd_get_storage_info(void); +static mtp_phase_type_t mtpd_handle_cmd_get_storage_ids(void); +static mtp_phase_type_t mtpd_handle_cmd_get_object_handles(void); +static mtp_phase_type_t mtpd_handle_cmd_get_object_info(void); +static mtp_phase_type_t mtpd_handle_cmd_get_object(void); +static mtp_phase_type_t mtpd_handle_dti_get_object(void); +static mtp_phase_type_t mtpd_handle_cmd_delete_object(void); +static mtp_phase_type_t mtpd_handle_cmd_get_device_prop_desc(void); +static mtp_phase_type_t mtpd_handle_cmd_get_device_prop_value(void); +static mtp_phase_type_t mtpd_handle_cmd_send_object_info(void); +static mtp_phase_type_t mtpd_handle_dto_send_object_info(void); +static mtp_phase_type_t mtpd_handle_cmd_send_object(void); +static mtp_phase_type_t mtpd_handle_dto_send_object(void); +static mtp_phase_type_t mtpd_handle_cmd_format_store(void); //--------------------------------------------------------------------+ // MTP variable declaration //--------------------------------------------------------------------+ -CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN tu_static mtpd_interface_t _mtpd_itf; -CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN tu_static mtp_generic_container_t _mtpd_gct; -CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN tu_static mtpd_context_t _mtpd_ctx; -CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN tu_static mtp_device_status_res_t _mtpd_device_status_res; -CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN tu_static uint32_t _mtpd_get_object_handle; -CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN tu_static mtp_basic_object_info_t _mtpd_soi; -CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN tu_static char _mtp_datestr[20]; +static mtpd_interface_t _mtpd_itf; +CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN static mtp_generic_container_t _mtpd_gct; +CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN static mtp_device_status_res_t _mtpd_device_status_res; +CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN static uint32_t _mtpd_get_object_handle; +CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN static mtp_basic_object_info_t _mtpd_soi; +CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN static char _mtp_datestr[20]; //--------------------------------------------------------------------+ // USBD Driver API //--------------------------------------------------------------------+ void mtpd_init(void) { - TU_LOG_DRV(" MTP mtpd_init\n"); tu_memclr(&_mtpd_itf, sizeof(mtpd_interface_t)); - tu_memclr(&_mtpd_ctx, sizeof(mtpd_context_t)); - _mtpd_get_object_handle = 0; tu_memclr(&_mtpd_soi, sizeof(mtp_basic_object_info_t)); + _mtpd_get_object_handle = 0; } bool mtpd_deinit(void) { - TU_LOG_DRV(" MTP mtpd_deinit\n"); - // nothing to do - return true; + return true; // nothing to do } -void mtpd_reset(uint8_t rhport) -{ - TU_LOG_DRV(" MTP mtpd_reset\n"); - (void) rhport; - - // Close all endpoints - dcd_edpt_close_all(rhport); +void mtpd_reset(uint8_t rhport) { tu_memclr(&_mtpd_itf, sizeof(mtpd_interface_t)); - tu_memclr(&_mtpd_ctx, sizeof(mtpd_context_t)); tu_memclr(&_mtpd_gct, sizeof(mtp_generic_container_t)); - _mtpd_get_object_handle = 0; tu_memclr(&_mtpd_soi, sizeof(mtp_basic_object_info_t)); + _mtpd_get_object_handle = 0; } -uint16_t mtpd_open(uint8_t rhport, tusb_desc_interface_t const *itf_desc, uint16_t max_len) -{ - TU_LOG_DRV(" MTP mtpd_open\n"); - tusb_desc_endpoint_t const *ep_desc; - // only support SCSI's BOT protocol - TU_VERIFY(TUSB_CLASS_IMAGE == itf_desc->bInterfaceClass && +uint16_t mtpd_open(uint8_t rhport, tusb_desc_interface_t const* itf_desc, uint16_t max_len) { + // only support PIMA 15470 protocol + TU_VERIFY(TUSB_CLASS_IMAGE == itf_desc->bInterfaceClass && MTP_SUBCLASS_STILL_IMAGE == itf_desc->bInterfaceSubClass && - MTP_PROTOCOL_PIMA_15470 == itf_desc->bInterfaceProtocol, 0); + MTP_PROTOCOL_PIMA_15470 == itf_desc->bInterfaceProtocol, 0); // mtp driver length is fixed uint16_t const mtpd_itf_size = sizeof(tusb_desc_interface_t) + 3 * sizeof(tusb_desc_endpoint_t); // Max length must be at least 1 interface + 3 endpoints TU_ASSERT(itf_desc->bNumEndpoints == 3 && max_len >= mtpd_itf_size); - - _mtpd_itf.itf_num = itf_desc->bInterfaceNumber; + mtpd_interface_t* p_mtp = &_mtpd_itf; + p_mtp->itf_num = itf_desc->bInterfaceNumber; // Open interrupt IN endpoint - ep_desc = (tusb_desc_endpoint_t const *)tu_desc_next(itf_desc); + const tusb_desc_endpoint_t* ep_desc = (const tusb_desc_endpoint_t*) tu_desc_next(itf_desc); TU_ASSERT(ep_desc->bDescriptorType == TUSB_DESC_ENDPOINT && ep_desc->bmAttributes.xfer == TUSB_XFER_INTERRUPT, 0); TU_ASSERT(usbd_edpt_open(rhport, ep_desc), 0); - _mtpd_itf.ep_evt = ep_desc->bEndpointAddress; + p_mtp->ep_event = ep_desc->bEndpointAddress; // Open endpoint pair - TU_ASSERT( usbd_open_edpt_pair(rhport, tu_desc_next(ep_desc), 2, TUSB_XFER_BULK, &_mtpd_itf.ep_out, &_mtpd_itf.ep_in), 0 ); + TU_ASSERT(usbd_open_edpt_pair(rhport, tu_desc_next(ep_desc), 2, TUSB_XFER_BULK, &p_mtp->ep_out, &p_mtp->ep_in), 0); // Prepare rx on bulk out EP - TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_out, ((uint8_t *)(&_mtpd_gct)), CFG_MTP_EP_SIZE), 0); + TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_out, ((uint8_t *)(&_mtpd_gct)), CFG_MTP_EP_SIZE), 0); return mtpd_itf_size; } @@ -177,115 +160,102 @@ uint16_t mtpd_open(uint8_t rhport, tusb_desc_interface_t const *itf_desc, uint16 // Invoked when a control transfer occurred on an interface of this class // Driver response accordingly to the request and the transfer stage (setup/data/ack) // return false to stall control endpoint (e.g unsupported request) -bool mtpd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request) -{ - TU_LOG_DRV(" MTP mtpd_control_xfer_cb: bmRequest=0x%2x, bRequest=0x%2x\n", request->bmRequestType, request->bRequest); - // nothing to do with DATA & ACK stage - if (stage != CONTROL_STAGE_SETUP) return true; +bool mtpd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const* request) { + if (stage != CONTROL_STAGE_SETUP) { + return true; // nothing to do with DATA & ACK stage + } - uint16_t len = 0; - - switch ( request->bRequest ) - { + switch (request->bRequest) { case MTP_REQ_CANCEL: TU_LOG_DRV(" MTP request: MTP_REQ_CANCEL\n"); tud_mtp_storage_cancel(); - break; + break; + case MTP_REQ_GET_EXT_EVENT_DATA: TU_LOG_DRV(" MTP request: MTP_REQ_GET_EXT_EVENT_DATA\n"); - break; + break; + case MTP_REQ_RESET: TU_LOG_DRV(" MTP request: MTP_REQ_RESET\n"); tud_mtp_storage_reset(); // Prepare for a new command - TU_ASSERT( usbd_edpt_xfer(rhport, _mtpd_itf.ep_out, ((uint8_t *)(&_mtpd_gct)), CFG_MTP_EP_SIZE) ); - break; - case MTP_REQ_GET_DEVICE_STATUS: + TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_out, ((uint8_t *)(&_mtpd_gct)), CFG_MTP_EP_SIZE)); + break; + + case MTP_REQ_GET_DEVICE_STATUS: { TU_LOG_DRV(" MTP request: MTP_REQ_GET_DEVICE_STATUS\n"); - len = 4; + uint16_t len = 4; _mtpd_device_status_res.wLength = len; // Cancel is synchronous, always answer OK _mtpd_device_status_res.code = MTP_RESP_OK; - TU_ASSERT( tud_control_xfer(rhport, request, (uint8_t *)&_mtpd_device_status_res , len) ); - break; + TU_ASSERT(tud_control_xfer(rhport, request, (uint8_t *)&_mtpd_device_status_res , len)); + break; + } default: TU_LOG_DRV(" MTP request: invalid request\r\n"); return false; // stall unsupported request - } + } + return true; } // Transfer on bulk endpoints -bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes) -{ - const unsigned dir = tu_edpt_dir(ep_addr); +bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes) { + TU_ASSERT(event == XFER_RESULT_SUCCESS); - if (event != XFER_RESULT_SUCCESS) - return false; + if (ep_addr == _mtpd_itf.ep_event) { + // nothing to do + return true; + } // IN transfer completed - if (dir == TUSB_DIR_IN) - { - if (_mtpd_itf.phase == MTP_PHASE_RESPONSE) - { + if (ep_addr == _mtpd_itf.ep_in) { + if (_mtpd_itf.phase == MTP_PHASE_RESPONSE) { // IN transfer completed, prepare for a new command TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_out, ((uint8_t *)(&_mtpd_gct)), CFG_MTP_EP_SIZE), 0); _mtpd_itf.phase = MTP_PHASE_IDLE; - } - else if (_mtpd_itf.phase == MTP_PHASE_DATA_IN) - { + } else if (_mtpd_itf.phase == MTP_PHASE_DATA_IN) { _mtpd_itf.xferred_len += xferred_bytes; _mtpd_itf.handled_len = _mtpd_itf.xferred_len; // Check if transfer completed - if (_mtpd_itf.xferred_len >= _mtpd_itf.total_len && (xferred_bytes == 0 || (xferred_bytes % CFG_MTP_EP_SIZE) != 0)) - { + if (_mtpd_itf.xferred_len >= _mtpd_itf.total_len && (xferred_bytes == 0 || (xferred_bytes % CFG_MTP_EP_SIZE) != 0)) { _mtpd_itf.phase = MTP_PHASE_RESPONSE; _mtpd_gct.container_type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; _mtpd_gct.code = MTP_RESP_OK; _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH; - _mtpd_gct.transaction_id = _mtpd_ctx.transaction_id; - if (_mtpd_ctx.session_id != 0) - { - _mtpd_gct.data[0] = _mtpd_ctx.session_id; + _mtpd_gct.transaction_id = _mtpd_itf.context.transaction_id; + if (_mtpd_itf.context.session_id != 0) { + _mtpd_gct.data[0] = _mtpd_itf.context.session_id; _mtpd_gct.container_length += sizeof(uint32_t); } TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_in, ((uint8_t *)(&_mtpd_gct)), (uint16_t)_mtpd_gct.container_length), 0); - } - else - // Send next block of DATA - { - // Send Zero-Lenght Packet - if (_mtpd_itf.xferred_len == _mtpd_itf.total_len) - { + } else { + // Send next block of DATA + // Send Zero-Length Packet + if (_mtpd_itf.xferred_len == _mtpd_itf.total_len) { TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_in, ((uint8_t *)(&_mtpd_gct.data)), 0 )); - } - else - { + } else { _mtpd_itf.phase = mtpd_handle_data(); - if (_mtpd_itf.phase == MTP_PHASE_RESPONSE) + if (_mtpd_itf.phase == MTP_PHASE_RESPONSE) { TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_in, ((uint8_t *)(&_mtpd_gct)), (uint16_t)_mtpd_gct.container_length)); - else + } else { TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_in, ((uint8_t *)(&_mtpd_gct.data)), (uint16_t)_mtpd_itf.queued_len)); + } } } - } - else - { + } else { return false; } } - if (dir == TUSB_DIR_OUT) - { - if (_mtpd_itf.phase == MTP_PHASE_IDLE) - { + if (ep_addr == _mtpd_itf.ep_out) { + if (_mtpd_itf.phase == MTP_PHASE_IDLE) { // A new command has been received. Ensure this is the last of the sequence. _mtpd_itf.total_len = _mtpd_gct.container_length; // Stall in case of unexpected block - if (_mtpd_gct.container_type != MTP_CONTAINER_TYPE_COMMAND_BLOCK) - { + if (_mtpd_gct.container_type != MTP_CONTAINER_TYPE_COMMAND_BLOCK) { return false; } _mtpd_itf.phase = MTP_PHASE_COMMAND; @@ -296,38 +266,27 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t TU_ASSERT(_mtpd_itf.total_len < sizeof(mtp_generic_container_t)); } - if (_mtpd_itf.phase == MTP_PHASE_COMMAND) - { + if (_mtpd_itf.phase == MTP_PHASE_COMMAND) { // A zero-length or a short packet termination is expected - if (xferred_bytes == CFG_MTP_EP_SIZE || (_mtpd_itf.total_len - _mtpd_itf.xferred_len) > 0 ) - { + if (xferred_bytes == CFG_MTP_EP_SIZE || (_mtpd_itf.total_len - _mtpd_itf.xferred_len) > 0) { TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_out, ((uint8_t *)(&_mtpd_gct)) + _mtpd_itf.xferred_len, (uint16_t)(_mtpd_itf.total_len - _mtpd_itf.xferred_len))); - } - else - { + } else { // Handle command block _mtpd_itf.phase = mtpd_handle_cmd(); - if (_mtpd_itf.phase == MTP_PHASE_RESPONSE) - { + if (_mtpd_itf.phase == MTP_PHASE_RESPONSE) { TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_in, ((uint8_t *)(&_mtpd_gct)), (uint16_t)_mtpd_gct.container_length)); - } - else if (_mtpd_itf.phase == MTP_PHASE_DATA_IN) - { + } else if (_mtpd_itf.phase == MTP_PHASE_DATA_IN) { TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_in, ((uint8_t *)(&_mtpd_gct)), (uint16_t)_mtpd_itf.queued_len)); _mtpd_itf.total_len = _mtpd_gct.container_length; _mtpd_itf.xferred_len = 0; _mtpd_itf.handled_len = 0; _mtpd_itf.xfer_completed = false; - } - else if (_mtpd_itf.phase == MTP_PHASE_DATA_OUT) - { + } else if (_mtpd_itf.phase == MTP_PHASE_DATA_OUT) { TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_out, ((uint8_t *)(&_mtpd_gct)), CFG_MTP_EP_SIZE), 0); _mtpd_itf.xferred_len = 0; _mtpd_itf.handled_len = 0; _mtpd_itf.xfer_completed = false; - } - else - { + } else { usbd_edpt_stall(rhport, _mtpd_itf.ep_out); usbd_edpt_stall(rhport, _mtpd_itf.ep_in); } @@ -335,72 +294,54 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t return true; } - if (_mtpd_itf.phase == MTP_PHASE_DATA_OUT) - { + if (_mtpd_itf.phase == MTP_PHASE_DATA_OUT) { // First block of data - if (_mtpd_itf.xferred_len == 0) - { + if (_mtpd_itf.xferred_len == 0) { _mtpd_itf.total_len = _mtpd_gct.container_length; _mtpd_itf.handled_len = 0; _mtpd_itf.xfer_completed = false; } _mtpd_itf.xferred_len += xferred_bytes; // Stall in case of unexpected block - if (_mtpd_gct.container_type != MTP_CONTAINER_TYPE_DATA_BLOCK) - { - return false; - } + if (_mtpd_gct.container_type != MTP_CONTAINER_TYPE_DATA_BLOCK) { return false; } // A zero-length or a short packet termination - if (xferred_bytes < CFG_MTP_EP_SIZE) - { + if (xferred_bytes < CFG_MTP_EP_SIZE) { _mtpd_itf.xfer_completed = true; // Handle data block _mtpd_itf.phase = mtpd_handle_data(); - if (_mtpd_itf.phase == MTP_PHASE_DATA_IN || _mtpd_itf.phase == MTP_PHASE_RESPONSE) - { + if (_mtpd_itf.phase == MTP_PHASE_DATA_IN || _mtpd_itf.phase == MTP_PHASE_RESPONSE) { TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_in, ((uint8_t *)(&_mtpd_gct)), (uint16_t)_mtpd_gct.container_length)); - } - else if (_mtpd_itf.phase == MTP_PHASE_DATA_OUT) - { + } else if (_mtpd_itf.phase == MTP_PHASE_DATA_OUT) { TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_out, ((uint8_t *)(&_mtpd_gct)), CFG_MTP_EP_SIZE), 0); _mtpd_itf.xferred_len = 0; _mtpd_itf.xfer_completed = false; - } - else - { + } else { usbd_edpt_stall(rhport, _mtpd_itf.ep_out); usbd_edpt_stall(rhport, _mtpd_itf.ep_in); } - } - else - { + } else { // Handle data block when container is full - if (_mtpd_itf.xferred_len - _mtpd_itf.handled_len >= MTP_MAX_PACKET_SIZE - CFG_MTP_EP_SIZE) - { + if (_mtpd_itf.xferred_len - _mtpd_itf.handled_len >= MTP_MAX_PACKET_SIZE - CFG_MTP_EP_SIZE) { _mtpd_itf.phase = mtpd_handle_data(); _mtpd_itf.handled_len = _mtpd_itf.xferred_len; } // Transfer completed: wait for zero-lenght packet // Some platforms may not respect EP size and xferred_bytes may be more than CFG_MTP_EP_SIZE if // the OUT EP is waiting for more data. Ensure we are not waiting for more than CFG_MTP_EP_SIZE. - if (_mtpd_itf.total_len == _mtpd_itf.xferred_len) - { + if (_mtpd_itf.total_len == _mtpd_itf.xferred_len) { TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_out, ((uint8_t *)(&_mtpd_gct.data)), CFG_MTP_EP_SIZE), 0); - } - // First data block includes container header + container data - else if (_mtpd_itf.handled_len == 0) - { + } else if (_mtpd_itf.handled_len == 0) { + // First data block includes container header + container data TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_out, ((uint8_t *)(&_mtpd_gct)) + _mtpd_itf.xferred_len, (uint16_t)TU_MIN(_mtpd_itf.total_len - _mtpd_itf.xferred_len, CFG_MTP_EP_SIZE))); - } - else - // Successive data block includes only container data - { + } else { + // Successive data block includes only container data TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_out, ((uint8_t *)(&_mtpd_gct.data)) + _mtpd_itf.xferred_len - _mtpd_itf.handled_len, (uint16_t)TU_MIN(_mtpd_itf.total_len - _mtpd_itf.xferred_len, CFG_MTP_EP_SIZE))); } } } } + return true; } @@ -409,15 +350,14 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t //--------------------------------------------------------------------+ // Decode command and prepare response -mtp_phase_type_t mtpd_handle_cmd(void) -{ +mtp_phase_type_t mtpd_handle_cmd(void) { TU_ASSERT(_mtpd_gct.container_type == MTP_CONTAINER_TYPE_COMMAND_BLOCK); - _mtpd_ctx.transaction_id = _mtpd_gct.transaction_id; - if (_mtpd_gct.code != MTP_OP_SEND_OBJECT) + _mtpd_itf.context.transaction_id = _mtpd_gct.transaction_id; + if (_mtpd_gct.code != MTP_OP_SEND_OBJECT) { _mtpd_soi.object_handle = 0; + } - switch(_mtpd_gct.code) - { + switch (_mtpd_gct.code) { case MTP_OP_GET_DEVICE_INFO: TU_LOG_DRV(" MTP command: MTP_OP_GET_DEVICE_INFO\n"); return mtpd_handle_cmd_get_device_info(); @@ -470,7 +410,7 @@ mtp_phase_type_t mtpd_handle_cmd(void) mtp_phase_type_t mtpd_handle_data(void) { TU_ASSERT(_mtpd_gct.container_type == MTP_CONTAINER_TYPE_DATA_BLOCK); - _mtpd_ctx.transaction_id = _mtpd_gct.transaction_id; + _mtpd_itf.context.transaction_id = _mtpd_gct.transaction_id; switch(_mtpd_gct.code) { @@ -535,14 +475,14 @@ mtp_phase_type_t mtpd_handle_cmd_open_session(void) _mtpd_gct.code = res; _mtpd_gct.container_length += sizeof(_mtpd_gct.data[0]); _mtpd_gct.data[0] = session_id; - _mtpd_ctx.session_id = session_id; + _mtpd_itf.context.session_id = session_id; return MTP_PHASE_RESPONSE; } mtp_phase_type_t phase; if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESP_OK), res, "")) != MTP_PHASE_NONE) return phase; - _mtpd_ctx.session_id = session_id; + _mtpd_itf.context.session_id = session_id; _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH; _mtpd_gct.container_type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; @@ -557,7 +497,7 @@ mtp_phase_type_t mtpd_handle_cmd_close_session(void) mtp_response_t res = tud_mtp_storage_close_session(session_id); - _mtpd_ctx.session_id = session_id; + _mtpd_itf.context.session_id = session_id; _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH; _mtpd_gct.container_type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; @@ -873,7 +813,7 @@ mtp_phase_type_t mtpd_handle_cmd_format_store(void) mtp_phase_type_t mtpd_chk_session_open(const char *func_name) { (void)func_name; - if (_mtpd_ctx.session_id == 0) + if (_mtpd_itf.context.session_id == 0) { TU_LOG_DRV(" MTP error: %s session not open\n", func_name); _mtpd_gct.container_type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; From 10298f0b275971db349c68049454994b15eabf46 Mon Sep 17 00:00:00 2001 From: hathach Date: Tue, 16 Sep 2025 14:14:36 +0700 Subject: [PATCH 05/43] refactor generic container to use EPBUF declaration simplify container field name --- src/class/mtp/mtp.h | 4 +- src/class/mtp/mtp_device.c | 443 ++++++++++++++++++++----------------- src/device/usbd.c | 1 + 3 files changed, 241 insertions(+), 207 deletions(-) diff --git a/src/class/mtp/mtp.h b/src/class/mtp/mtp.h index b5db2cd6c..f697193d2 100644 --- a/src/class/mtp/mtp.h +++ b/src/class/mtp/mtp.h @@ -690,8 +690,8 @@ tu_static const uint16_t mtp_playback_formats[] = { // PTP/MTP Generic container typedef struct TU_ATTR_PACKED { - uint32_t container_length; - uint16_t container_type; + uint32_t len; + uint16_t type; uint16_t code; uint32_t transaction_id; uint32_t data[MTP_MAX_PACKET_SIZE / sizeof(uint32_t)]; diff --git a/src/class/mtp/mtp_device.c b/src/class/mtp/mtp_device.c index d8db17e79..5aa2bf1f1 100644 --- a/src/class/mtp/mtp_device.c +++ b/src/class/mtp/mtp_device.c @@ -70,6 +70,10 @@ typedef struct } context; } mtpd_interface_t; +typedef struct { + TUD_EPBUF_TYPE_DEF(mtp_generic_container_t, container); +} mtpd_epbuf_t; + //--------------------------------------------------------------------+ // INTERNAL FUNCTION DECLARATION //--------------------------------------------------------------------+ @@ -102,7 +106,8 @@ static mtp_phase_type_t mtpd_handle_cmd_format_store(void); // MTP variable declaration //--------------------------------------------------------------------+ static mtpd_interface_t _mtpd_itf; -CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN static mtp_generic_container_t _mtpd_gct; +CFG_TUD_MEM_SECTION static mtpd_epbuf_t _mtpd_epbuf; + CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN static mtp_device_status_res_t _mtpd_device_status_res; CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN static uint32_t _mtpd_get_object_handle; CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN static mtp_basic_object_info_t _mtpd_soi; @@ -122,8 +127,9 @@ bool mtpd_deinit(void) { } void mtpd_reset(uint8_t rhport) { + (void) rhport; tu_memclr(&_mtpd_itf, sizeof(mtpd_interface_t)); - tu_memclr(&_mtpd_gct, sizeof(mtp_generic_container_t)); + tu_memclr(&_mtpd_epbuf, sizeof(mtpd_epbuf_t)); tu_memclr(&_mtpd_soi, sizeof(mtp_basic_object_info_t)); _mtpd_get_object_handle = 0; } @@ -135,7 +141,7 @@ uint16_t mtpd_open(uint8_t rhport, tusb_desc_interface_t const* itf_desc, uint16 MTP_PROTOCOL_PIMA_15470 == itf_desc->bInterfaceProtocol, 0); // mtp driver length is fixed - uint16_t const mtpd_itf_size = sizeof(tusb_desc_interface_t) + 3 * sizeof(tusb_desc_endpoint_t); + const uint16_t mtpd_itf_size = sizeof(tusb_desc_interface_t) + 3 * sizeof(tusb_desc_endpoint_t); // Max length must be at least 1 interface + 3 endpoints TU_ASSERT(itf_desc->bNumEndpoints == 3 && max_len >= mtpd_itf_size); @@ -152,7 +158,7 @@ uint16_t mtpd_open(uint8_t rhport, tusb_desc_interface_t const* itf_desc, uint16 TU_ASSERT(usbd_open_edpt_pair(rhport, tu_desc_next(ep_desc), 2, TUSB_XFER_BULK, &p_mtp->ep_out, &p_mtp->ep_in), 0); // Prepare rx on bulk out EP - TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_out, ((uint8_t *)(&_mtpd_gct)), CFG_MTP_EP_SIZE), 0); + TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_out, (uint8_t *)(&_mtpd_epbuf.container), CFG_MTP_EP_SIZE), 0); return mtpd_itf_size; } @@ -179,7 +185,7 @@ bool mtpd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t TU_LOG_DRV(" MTP request: MTP_REQ_RESET\n"); tud_mtp_storage_reset(); // Prepare for a new command - TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_out, ((uint8_t *)(&_mtpd_gct)), CFG_MTP_EP_SIZE)); + TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_out, (uint8_t *)(&_mtpd_epbuf.container), CFG_MTP_EP_SIZE)); break; case MTP_REQ_GET_DEVICE_STATUS: { @@ -209,39 +215,42 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t return true; } + mtpd_interface_t* p_mtp = &_mtpd_itf; + mtp_generic_container_t* p_container = &_mtpd_epbuf.container; + // IN transfer completed - if (ep_addr == _mtpd_itf.ep_in) { - if (_mtpd_itf.phase == MTP_PHASE_RESPONSE) { + if (ep_addr == p_mtp->ep_in) { + if (p_mtp->phase == MTP_PHASE_RESPONSE) { // IN transfer completed, prepare for a new command - TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_out, ((uint8_t *)(&_mtpd_gct)), CFG_MTP_EP_SIZE), 0); - _mtpd_itf.phase = MTP_PHASE_IDLE; - } else if (_mtpd_itf.phase == MTP_PHASE_DATA_IN) { - _mtpd_itf.xferred_len += xferred_bytes; - _mtpd_itf.handled_len = _mtpd_itf.xferred_len; + TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_out, (uint8_t*) &_mtpd_epbuf.container, CFG_MTP_EP_SIZE), 0); + p_mtp->phase = MTP_PHASE_IDLE; + } else if (p_mtp->phase == MTP_PHASE_DATA_IN) { + p_mtp->xferred_len += xferred_bytes; + p_mtp->handled_len = p_mtp->xferred_len; // Check if transfer completed - if (_mtpd_itf.xferred_len >= _mtpd_itf.total_len && (xferred_bytes == 0 || (xferred_bytes % CFG_MTP_EP_SIZE) != 0)) { - _mtpd_itf.phase = MTP_PHASE_RESPONSE; - _mtpd_gct.container_type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; - _mtpd_gct.code = MTP_RESP_OK; - _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH; - _mtpd_gct.transaction_id = _mtpd_itf.context.transaction_id; - if (_mtpd_itf.context.session_id != 0) { - _mtpd_gct.data[0] = _mtpd_itf.context.session_id; - _mtpd_gct.container_length += sizeof(uint32_t); + if (p_mtp->xferred_len >= p_mtp->total_len && (xferred_bytes == 0 || (xferred_bytes % CFG_MTP_EP_SIZE) != 0)) { + p_mtp->phase = MTP_PHASE_RESPONSE; + p_container->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; + p_container->code = MTP_RESP_OK; + p_container->len = MTP_GENERIC_DATA_BLOCK_LENGTH; + p_container->transaction_id = p_mtp->context.transaction_id; + if (p_mtp->context.session_id != 0) { + p_container->data[0] = p_mtp->context.session_id; + p_container->len += sizeof(uint32_t); } - TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_in, ((uint8_t *)(&_mtpd_gct)), (uint16_t)_mtpd_gct.container_length), 0); + TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_in, (uint8_t*) &_mtpd_epbuf.container, (uint16_t)p_container->len), 0); } else { // Send next block of DATA // Send Zero-Length Packet - if (_mtpd_itf.xferred_len == _mtpd_itf.total_len) { - TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_in, ((uint8_t *)(&_mtpd_gct.data)), 0 )); + if (p_mtp->xferred_len == p_mtp->total_len) { + TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_in, ((uint8_t *)(&p_container->data)), 0 )); } else { - _mtpd_itf.phase = mtpd_handle_data(); - if (_mtpd_itf.phase == MTP_PHASE_RESPONSE) { - TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_in, ((uint8_t *)(&_mtpd_gct)), (uint16_t)_mtpd_gct.container_length)); + p_mtp->phase = mtpd_handle_data(); + if (p_mtp->phase == MTP_PHASE_RESPONSE) { + TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_in, (uint8_t*) &_mtpd_epbuf.container, (uint16_t)p_container->len)); } else { - TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_in, ((uint8_t *)(&_mtpd_gct.data)), (uint16_t)_mtpd_itf.queued_len)); + TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_in, ((uint8_t *)(&p_container->data)), (uint16_t)p_mtp->queued_len)); } } } @@ -250,93 +259,93 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t } } - if (ep_addr == _mtpd_itf.ep_out) { - if (_mtpd_itf.phase == MTP_PHASE_IDLE) { + if (ep_addr == p_mtp->ep_out) { + if (p_mtp->phase == MTP_PHASE_IDLE) { // A new command has been received. Ensure this is the last of the sequence. - _mtpd_itf.total_len = _mtpd_gct.container_length; + p_mtp->total_len = p_container->len; // Stall in case of unexpected block - if (_mtpd_gct.container_type != MTP_CONTAINER_TYPE_COMMAND_BLOCK) { + if (p_container->type != MTP_CONTAINER_TYPE_COMMAND_BLOCK) { return false; } - _mtpd_itf.phase = MTP_PHASE_COMMAND; - _mtpd_itf.total_len = _mtpd_gct.container_length; - _mtpd_itf.xferred_len = xferred_bytes; - _mtpd_itf.handled_len = 0; - _mtpd_itf.xfer_completed = false; - TU_ASSERT(_mtpd_itf.total_len < sizeof(mtp_generic_container_t)); + p_mtp->phase = MTP_PHASE_COMMAND; + p_mtp->total_len = p_container->len; + p_mtp->xferred_len = xferred_bytes; + p_mtp->handled_len = 0; + p_mtp->xfer_completed = false; + TU_ASSERT(p_mtp->total_len < sizeof(mtp_generic_container_t)); } - if (_mtpd_itf.phase == MTP_PHASE_COMMAND) { + if (p_mtp->phase == MTP_PHASE_COMMAND) { // A zero-length or a short packet termination is expected - if (xferred_bytes == CFG_MTP_EP_SIZE || (_mtpd_itf.total_len - _mtpd_itf.xferred_len) > 0) { - TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_out, ((uint8_t *)(&_mtpd_gct)) + _mtpd_itf.xferred_len, (uint16_t)(_mtpd_itf.total_len - _mtpd_itf.xferred_len))); + if (xferred_bytes == CFG_MTP_EP_SIZE || (p_mtp->total_len - p_mtp->xferred_len) > 0) { + TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_out, (uint8_t*) &_mtpd_epbuf.container + p_mtp->xferred_len, (uint16_t)(p_mtp->total_len - p_mtp->xferred_len))); } else { // Handle command block - _mtpd_itf.phase = mtpd_handle_cmd(); - if (_mtpd_itf.phase == MTP_PHASE_RESPONSE) { - TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_in, ((uint8_t *)(&_mtpd_gct)), (uint16_t)_mtpd_gct.container_length)); - } else if (_mtpd_itf.phase == MTP_PHASE_DATA_IN) { - TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_in, ((uint8_t *)(&_mtpd_gct)), (uint16_t)_mtpd_itf.queued_len)); - _mtpd_itf.total_len = _mtpd_gct.container_length; - _mtpd_itf.xferred_len = 0; - _mtpd_itf.handled_len = 0; - _mtpd_itf.xfer_completed = false; - } else if (_mtpd_itf.phase == MTP_PHASE_DATA_OUT) { - TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_out, ((uint8_t *)(&_mtpd_gct)), CFG_MTP_EP_SIZE), 0); - _mtpd_itf.xferred_len = 0; - _mtpd_itf.handled_len = 0; - _mtpd_itf.xfer_completed = false; + p_mtp->phase = mtpd_handle_cmd(); + if (p_mtp->phase == MTP_PHASE_RESPONSE) { + TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_in, (uint8_t*) &_mtpd_epbuf.container, (uint16_t)p_container->len)); + } else if (p_mtp->phase == MTP_PHASE_DATA_IN) { + TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_in, (uint8_t*) &_mtpd_epbuf.container, (uint16_t)p_mtp->queued_len)); + p_mtp->total_len = p_container->len; + p_mtp->xferred_len = 0; + p_mtp->handled_len = 0; + p_mtp->xfer_completed = false; + } else if (p_mtp->phase == MTP_PHASE_DATA_OUT) { + TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_out, (uint8_t*) &_mtpd_epbuf.container, CFG_MTP_EP_SIZE), 0); + p_mtp->xferred_len = 0; + p_mtp->handled_len = 0; + p_mtp->xfer_completed = false; } else { - usbd_edpt_stall(rhport, _mtpd_itf.ep_out); - usbd_edpt_stall(rhport, _mtpd_itf.ep_in); + usbd_edpt_stall(rhport, p_mtp->ep_out); + usbd_edpt_stall(rhport, p_mtp->ep_in); } } return true; } - if (_mtpd_itf.phase == MTP_PHASE_DATA_OUT) { + if (p_mtp->phase == MTP_PHASE_DATA_OUT) { // First block of data - if (_mtpd_itf.xferred_len == 0) { - _mtpd_itf.total_len = _mtpd_gct.container_length; - _mtpd_itf.handled_len = 0; - _mtpd_itf.xfer_completed = false; + if (p_mtp->xferred_len == 0) { + p_mtp->total_len = p_container->len; + p_mtp->handled_len = 0; + p_mtp->xfer_completed = false; } - _mtpd_itf.xferred_len += xferred_bytes; + p_mtp->xferred_len += xferred_bytes; // Stall in case of unexpected block - if (_mtpd_gct.container_type != MTP_CONTAINER_TYPE_DATA_BLOCK) { return false; } + if (p_container->type != MTP_CONTAINER_TYPE_DATA_BLOCK) { return false; } // A zero-length or a short packet termination if (xferred_bytes < CFG_MTP_EP_SIZE) { - _mtpd_itf.xfer_completed = true; + p_mtp->xfer_completed = true; // Handle data block - _mtpd_itf.phase = mtpd_handle_data(); - if (_mtpd_itf.phase == MTP_PHASE_DATA_IN || _mtpd_itf.phase == MTP_PHASE_RESPONSE) { - TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_in, ((uint8_t *)(&_mtpd_gct)), (uint16_t)_mtpd_gct.container_length)); - } else if (_mtpd_itf.phase == MTP_PHASE_DATA_OUT) { - TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_out, ((uint8_t *)(&_mtpd_gct)), CFG_MTP_EP_SIZE), 0); - _mtpd_itf.xferred_len = 0; - _mtpd_itf.xfer_completed = false; + p_mtp->phase = mtpd_handle_data(); + if (p_mtp->phase == MTP_PHASE_DATA_IN || p_mtp->phase == MTP_PHASE_RESPONSE) { + TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_in, (uint8_t*) &_mtpd_epbuf.container, (uint16_t)p_container->len)); + } else if (p_mtp->phase == MTP_PHASE_DATA_OUT) { + TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_out, (uint8_t*) &_mtpd_epbuf.container, CFG_MTP_EP_SIZE), 0); + p_mtp->xferred_len = 0; + p_mtp->xfer_completed = false; } else { - usbd_edpt_stall(rhport, _mtpd_itf.ep_out); - usbd_edpt_stall(rhport, _mtpd_itf.ep_in); + usbd_edpt_stall(rhport, p_mtp->ep_out); + usbd_edpt_stall(rhport, p_mtp->ep_in); } } else { // Handle data block when container is full - if (_mtpd_itf.xferred_len - _mtpd_itf.handled_len >= MTP_MAX_PACKET_SIZE - CFG_MTP_EP_SIZE) { - _mtpd_itf.phase = mtpd_handle_data(); - _mtpd_itf.handled_len = _mtpd_itf.xferred_len; + if (p_mtp->xferred_len - p_mtp->handled_len >= MTP_MAX_PACKET_SIZE - CFG_MTP_EP_SIZE) { + p_mtp->phase = mtpd_handle_data(); + p_mtp->handled_len = p_mtp->xferred_len; } // Transfer completed: wait for zero-lenght packet // Some platforms may not respect EP size and xferred_bytes may be more than CFG_MTP_EP_SIZE if // the OUT EP is waiting for more data. Ensure we are not waiting for more than CFG_MTP_EP_SIZE. - if (_mtpd_itf.total_len == _mtpd_itf.xferred_len) { - TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_out, ((uint8_t *)(&_mtpd_gct.data)), CFG_MTP_EP_SIZE), 0); - } else if (_mtpd_itf.handled_len == 0) { + if (p_mtp->total_len == p_mtp->xferred_len) { + TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_out, ((uint8_t *)(&p_container->data)), CFG_MTP_EP_SIZE), 0); + } else if (p_mtp->handled_len == 0) { // First data block includes container header + container data - TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_out, ((uint8_t *)(&_mtpd_gct)) + _mtpd_itf.xferred_len, (uint16_t)TU_MIN(_mtpd_itf.total_len - _mtpd_itf.xferred_len, CFG_MTP_EP_SIZE))); + TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_out, (uint8_t*) &_mtpd_epbuf.container + p_mtp->xferred_len, (uint16_t)TU_MIN(p_mtp->total_len - p_mtp->xferred_len, CFG_MTP_EP_SIZE))); } else { // Successive data block includes only container data - TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_out, ((uint8_t *)(&_mtpd_gct.data)) + _mtpd_itf.xferred_len - _mtpd_itf.handled_len, (uint16_t)TU_MIN(_mtpd_itf.total_len - _mtpd_itf.xferred_len, CFG_MTP_EP_SIZE))); + TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_out, ((uint8_t *)(&p_container->data)) + p_mtp->xferred_len - p_mtp->handled_len, (uint16_t)TU_MIN(p_mtp->total_len - p_mtp->xferred_len, CFG_MTP_EP_SIZE))); } } } @@ -351,13 +360,14 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t // Decode command and prepare response mtp_phase_type_t mtpd_handle_cmd(void) { - TU_ASSERT(_mtpd_gct.container_type == MTP_CONTAINER_TYPE_COMMAND_BLOCK); - _mtpd_itf.context.transaction_id = _mtpd_gct.transaction_id; - if (_mtpd_gct.code != MTP_OP_SEND_OBJECT) { + mtp_generic_container_t* p_container = &_mtpd_epbuf.container; + TU_ASSERT(p_container->type == MTP_CONTAINER_TYPE_COMMAND_BLOCK); + _mtpd_itf.context.transaction_id = p_container->transaction_id; + if (p_container->code != MTP_OP_SEND_OBJECT) { _mtpd_soi.object_handle = 0; } - switch (_mtpd_gct.code) { + switch (p_container->code) { case MTP_OP_GET_DEVICE_INFO: TU_LOG_DRV(" MTP command: MTP_OP_GET_DEVICE_INFO\n"); return mtpd_handle_cmd_get_device_info(); @@ -371,7 +381,7 @@ mtp_phase_type_t mtpd_handle_cmd(void) { TU_LOG_DRV(" MTP command: MTP_OP_GET_STORAGE_IDS\n"); return mtpd_handle_cmd_get_storage_ids(); case MTP_OP_GET_STORAGE_INFO: - TU_LOG_DRV(" MTP command: MTP_OP_GET_STORAGE_INFO for ID=%lu\n", _mtpd_gct.data[0]); + TU_LOG_DRV(" MTP command: MTP_OP_GET_STORAGE_INFO for ID=%lu\n", p_container->data[0]); return mtpd_handle_cmd_get_storage_info(); case MTP_OP_GET_OBJECT_HANDLES: TU_LOG_DRV(" MTP command: MTP_OP_GET_OBJECT_HANDLES\n"); @@ -401,7 +411,7 @@ mtp_phase_type_t mtpd_handle_cmd(void) { TU_LOG_DRV(" MTP command: MTP_OP_FORMAT_STORE\n"); return mtpd_handle_cmd_format_store(); default: - TU_LOG_DRV(" MTP command: MTP_OP_UNKNOWN_COMMAND %x!!!!\n", _mtpd_gct.code); + TU_LOG_DRV(" MTP command: MTP_OP_UNKNOWN_COMMAND %x!!!!\n", p_container->code); return false; } return true; @@ -409,10 +419,11 @@ mtp_phase_type_t mtpd_handle_cmd(void) { mtp_phase_type_t mtpd_handle_data(void) { - TU_ASSERT(_mtpd_gct.container_type == MTP_CONTAINER_TYPE_DATA_BLOCK); - _mtpd_itf.context.transaction_id = _mtpd_gct.transaction_id; + mtp_generic_container_t* p_container = &_mtpd_epbuf.container; + TU_ASSERT(p_container->type == MTP_CONTAINER_TYPE_DATA_BLOCK); + _mtpd_itf.context.transaction_id = p_container->transaction_id; - switch(_mtpd_gct.code) + switch(p_container->code) { case MTP_OP_GET_OBJECT: TU_LOG_DRV(" MTP command: MTP_OP_GET_OBJECT-DATA_IN\n"); @@ -424,7 +435,7 @@ mtp_phase_type_t mtpd_handle_data(void) TU_LOG_DRV(" MTP command: MTP_OP_SEND_OBJECT-DATA_OUT\n"); return mtpd_handle_dto_send_object(); default: - TU_LOG_DRV(" MTP command: MTP_OP_UNKNOWN_COMMAND %x!!!!\n", _mtpd_gct.code); + TU_LOG_DRV(" MTP command: MTP_OP_UNKNOWN_COMMAND %x!!!!\n", p_container->code); return false; } return true; @@ -433,11 +444,12 @@ mtp_phase_type_t mtpd_handle_data(void) mtp_phase_type_t mtpd_handle_cmd_get_device_info(void) { TU_VERIFY_STATIC(sizeof(mtp_device_info_t) < MTP_MAX_PACKET_SIZE, "mtp_device_info_t shall fit in MTP_MAX_PACKET_SIZE"); + mtp_generic_container_t* p_container = &_mtpd_epbuf.container; - _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH + sizeof(mtp_device_info_t); - _mtpd_gct.container_type = MTP_CONTAINER_TYPE_DATA_BLOCK; - _mtpd_gct.code = MTP_OP_GET_DEVICE_INFO; - mtp_device_info_t *d = (mtp_device_info_t *)_mtpd_gct.data; + p_container->len = MTP_GENERIC_DATA_BLOCK_LENGTH + sizeof(mtp_device_info_t); + p_container->type = MTP_CONTAINER_TYPE_DATA_BLOCK; + p_container->code = MTP_OP_GET_DEVICE_INFO; + mtp_device_info_t *d = (mtp_device_info_t *)p_container->data; d->standard_version = 100; d->mtp_vendor_extension_id = 0x06; d->mtp_version = 100; @@ -459,22 +471,23 @@ mtp_phase_type_t mtpd_handle_cmd_get_device_info(void) mtpd_gct_append_wstring(CFG_MTP_DEVICE_VERSION); mtpd_gct_append_wstring(CFG_MTP_SERIAL_NUMBER); - _mtpd_itf.queued_len = _mtpd_gct.container_length; + _mtpd_itf.queued_len = p_container->len; return MTP_PHASE_DATA_IN; } mtp_phase_type_t mtpd_handle_cmd_open_session(void) { - uint32_t session_id = _mtpd_gct.data[0]; + mtp_generic_container_t* p_container = &_mtpd_epbuf.container; + uint32_t session_id = p_container->data[0]; mtp_response_t res = tud_mtp_storage_open_session(&session_id); if (res == MTP_RESP_SESSION_ALREADY_OPEN) { - _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH; - _mtpd_gct.container_type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; - _mtpd_gct.code = res; - _mtpd_gct.container_length += sizeof(_mtpd_gct.data[0]); - _mtpd_gct.data[0] = session_id; + p_container->len = MTP_GENERIC_DATA_BLOCK_LENGTH; + p_container->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; + p_container->code = res; + p_container->len += sizeof(p_container->data[0]); + p_container->data[0] = session_id; _mtpd_itf.context.session_id = session_id; return MTP_PHASE_RESPONSE; } @@ -484,24 +497,25 @@ mtp_phase_type_t mtpd_handle_cmd_open_session(void) _mtpd_itf.context.session_id = session_id; - _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH; - _mtpd_gct.container_type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; - _mtpd_gct.code = MTP_RESP_OK; + p_container->len = MTP_GENERIC_DATA_BLOCK_LENGTH; + p_container->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; + p_container->code = MTP_RESP_OK; return MTP_PHASE_RESPONSE; } mtp_phase_type_t mtpd_handle_cmd_close_session(void) { - uint32_t session_id = _mtpd_gct.data[0]; + mtp_generic_container_t* p_container = &_mtpd_epbuf.container; + uint32_t session_id = p_container->data[0]; mtp_response_t res = tud_mtp_storage_close_session(session_id); _mtpd_itf.context.session_id = session_id; - _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH; - _mtpd_gct.container_type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; - _mtpd_gct.code = res; + p_container->len = MTP_GENERIC_DATA_BLOCK_LENGTH; + p_container->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; + p_container->code = res; return MTP_PHASE_RESPONSE; } @@ -509,16 +523,17 @@ mtp_phase_type_t mtpd_handle_cmd_close_session(void) mtp_phase_type_t mtpd_handle_cmd_get_storage_ids(void) { TU_VERIFY_STATIC(sizeof(mtp_storage_ids_t) < MTP_MAX_PACKET_SIZE, "mtp_storage_ids_t shall fit in MTP_MAX_PACKET_SIZE"); + mtp_generic_container_t* p_container = &_mtpd_epbuf.container; uint32_t storage_id; mtp_response_t res = tud_mtp_get_storage_id(&storage_id); mtp_phase_type_t phase; if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESP_OK), res, "")) != MTP_PHASE_NONE) return phase; - _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH + sizeof(mtp_storage_ids_t); - _mtpd_gct.container_type = MTP_CONTAINER_TYPE_DATA_BLOCK; - _mtpd_gct.code = MTP_OP_GET_STORAGE_IDS; - mtp_storage_ids_t *d = (mtp_storage_ids_t *)_mtpd_gct.data; + p_container->len = MTP_GENERIC_DATA_BLOCK_LENGTH + sizeof(mtp_storage_ids_t); + p_container->type = MTP_CONTAINER_TYPE_DATA_BLOCK; + p_container->code = MTP_OP_GET_STORAGE_IDS; + mtp_storage_ids_t *d = (mtp_storage_ids_t *)p_container->data; if (storage_id == 0) { // Storage not accessible @@ -531,38 +546,39 @@ mtp_phase_type_t mtpd_handle_cmd_get_storage_ids(void) d->storage_ids[0] = storage_id; } - _mtpd_itf.queued_len = _mtpd_gct.container_length; + _mtpd_itf.queued_len = p_container->len; return MTP_PHASE_DATA_IN; } mtp_phase_type_t mtpd_handle_cmd_get_storage_info(void) { TU_VERIFY_STATIC(sizeof(mtp_storage_info_t) < MTP_MAX_PACKET_SIZE, "mtp_storage_info_t shall fit in MTP_MAX_PACKET_SIZE"); + mtp_generic_container_t* p_container = &_mtpd_epbuf.container; + uint32_t storage_id = p_container->data[0]; - uint32_t storage_id = _mtpd_gct.data[0]; + p_container->len = MTP_GENERIC_DATA_BLOCK_LENGTH + sizeof(mtp_storage_info_t); + p_container->type = MTP_CONTAINER_TYPE_DATA_BLOCK; + p_container->code = MTP_OP_GET_STORAGE_INFO; - _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH + sizeof(mtp_storage_info_t); - _mtpd_gct.container_type = MTP_CONTAINER_TYPE_DATA_BLOCK; - _mtpd_gct.code = MTP_OP_GET_STORAGE_INFO; - - mtp_response_t res = tud_mtp_get_storage_info(storage_id, (mtp_storage_info_t *)_mtpd_gct.data); + mtp_response_t res = tud_mtp_get_storage_info(storage_id, (mtp_storage_info_t *)p_container->data); mtp_phase_type_t phase; if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESP_OK), res, "")) != MTP_PHASE_NONE) return phase; - _mtpd_itf.queued_len = _mtpd_gct.container_length; + _mtpd_itf.queued_len = p_container->len; return MTP_PHASE_DATA_IN; } mtp_phase_type_t mtpd_handle_cmd_get_object_handles(void) { - uint32_t storage_id = _mtpd_gct.data[0]; - uint32_t object_format_code = _mtpd_gct.data[1]; // optional, not managed - uint32_t parent_object_handle = _mtpd_gct.data[2]; // folder specification, 0xffffffff=objects with no parent + mtp_generic_container_t* p_container = &_mtpd_epbuf.container; + uint32_t storage_id = p_container->data[0]; + uint32_t object_format_code = p_container->data[1]; // optional, not managed + uint32_t parent_object_handle = p_container->data[2]; // folder specification, 0xffffffff=objects with no parent - _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH + sizeof(uint32_t); - _mtpd_gct.container_type = MTP_CONTAINER_TYPE_DATA_BLOCK; - _mtpd_gct.code = MTP_OP_GET_OBJECT_HANDLES; - _mtpd_gct.data[0] = 0; + p_container->len = MTP_GENERIC_DATA_BLOCK_LENGTH + sizeof(uint32_t); + p_container->type = MTP_CONTAINER_TYPE_DATA_BLOCK; + p_container->code = MTP_OP_GET_OBJECT_HANDLES; + p_container->data[0] = 0; mtp_phase_type_t phase; if ((phase = mtpd_chk_generic(__func__, (object_format_code != 0), MTP_RESP_SPECIFICATION_BY_FORMAT_UNSUPPORTED, "specification by format unsupported")) != MTP_PHASE_NONE) return phase; @@ -581,30 +597,31 @@ mtp_phase_type_t mtpd_handle_cmd_get_object_handles(void) } tud_mtp_storage_object_done(); - _mtpd_itf.queued_len = _mtpd_gct.container_length; + _mtpd_itf.queued_len = p_container->len; return MTP_PHASE_DATA_IN; } mtp_phase_type_t mtpd_handle_cmd_get_object_info(void) { TU_VERIFY_STATIC(sizeof(mtp_object_info_t) < MTP_MAX_PACKET_SIZE, "mtp_object_info_t shall fit in MTP_MAX_PACKET_SIZE"); + mtp_generic_container_t* p_container = &_mtpd_epbuf.container; + uint32_t object_handle = p_container->data[0]; - uint32_t object_handle = _mtpd_gct.data[0]; - - _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH + sizeof(mtp_object_info_t); - _mtpd_gct.container_type = MTP_CONTAINER_TYPE_DATA_BLOCK; - _mtpd_gct.code = MTP_OP_GET_OBJECT_INFO; - mtp_response_t res = tud_mtp_storage_object_read_info(object_handle, (mtp_object_info_t *)_mtpd_gct.data); + p_container->len = MTP_GENERIC_DATA_BLOCK_LENGTH + sizeof(mtp_object_info_t); + p_container->type = MTP_CONTAINER_TYPE_DATA_BLOCK; + p_container->code = MTP_OP_GET_OBJECT_INFO; + mtp_response_t res = tud_mtp_storage_object_read_info(object_handle, (mtp_object_info_t *)p_container->data); mtp_phase_type_t phase; if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESP_OK), res, "")) != MTP_PHASE_NONE) return phase; - _mtpd_itf.queued_len = _mtpd_gct.container_length; + _mtpd_itf.queued_len = p_container->len; return MTP_PHASE_DATA_IN; } mtp_phase_type_t mtpd_handle_cmd_get_object(void) { - _mtpd_get_object_handle = _mtpd_gct.data[0]; + mtp_generic_container_t* p_container = &_mtpd_epbuf.container; + _mtpd_get_object_handle = p_container->data[0]; // Continue with DATA-IN return mtpd_handle_dti_get_object(); @@ -615,11 +632,12 @@ mtp_phase_type_t mtpd_handle_dti_get_object(void) mtp_response_t res; mtp_phase_type_t phase; uint32_t file_size = 0; + mtp_generic_container_t* p_container = &_mtpd_epbuf.container; res = tud_mtp_storage_object_size(_mtpd_get_object_handle, &file_size); if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESP_OK), res, "")) != MTP_PHASE_NONE) return phase; - _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH + file_size; - _mtpd_gct.container_type = MTP_CONTAINER_TYPE_DATA_BLOCK; - _mtpd_gct.code = MTP_OP_GET_OBJECT; + p_container->len = MTP_GENERIC_DATA_BLOCK_LENGTH + file_size; + p_container->type = MTP_CONTAINER_TYPE_DATA_BLOCK; + p_container->code = MTP_OP_GET_OBJECT; uint32_t buffer_size; uint32_t read_count; @@ -628,7 +646,7 @@ mtp_phase_type_t mtpd_handle_dti_get_object(void) { // First data block: include container header buffer_size = ((MTP_MAX_PACKET_SIZE + MTP_GENERIC_DATA_BLOCK_LENGTH) / CFG_MTP_EP_SIZE) * CFG_MTP_EP_SIZE - MTP_GENERIC_DATA_BLOCK_LENGTH; - res = tud_mtp_storage_object_read(_mtpd_get_object_handle, (void *)&_mtpd_gct.data, buffer_size, &read_count); + res = tud_mtp_storage_object_read(_mtpd_get_object_handle, (void *)&p_container->data, buffer_size, &read_count); if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESP_OK), res, "")) != MTP_PHASE_NONE) return phase; _mtpd_itf.queued_len = MTP_GENERIC_DATA_BLOCK_LENGTH + read_count; } @@ -636,7 +654,7 @@ mtp_phase_type_t mtpd_handle_dti_get_object(void) { // Successive data block: consider only container data buffer_size = (MTP_MAX_PACKET_SIZE / CFG_MTP_EP_SIZE) * CFG_MTP_EP_SIZE; - res = tud_mtp_storage_object_read(_mtpd_get_object_handle, (void *)&_mtpd_gct.data, buffer_size, &read_count); + res = tud_mtp_storage_object_read(_mtpd_get_object_handle, (void *)&p_container->data, buffer_size, &read_count); if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESP_OK), res, "")) != MTP_PHASE_NONE) return phase; _mtpd_itf.queued_len = read_count; } @@ -652,23 +670,25 @@ mtp_phase_type_t mtpd_handle_dti_get_object(void) mtp_phase_type_t mtpd_handle_cmd_delete_object(void) { - uint32_t object_handle = _mtpd_gct.data[0]; - uint32_t object_code_format = _mtpd_gct.data[1]; // not used + mtp_generic_container_t* p_container = &_mtpd_epbuf.container; + uint32_t object_handle = p_container->data[0]; + uint32_t object_code_format = p_container->data[1]; // not used (void) object_code_format; mtp_response_t res = tud_mtp_storage_object_delete(object_handle); mtp_phase_type_t phase; if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESP_OK), res, "")) != MTP_PHASE_NONE) return phase; - _mtpd_gct.container_type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; - _mtpd_gct.code = MTP_RESP_OK; - _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH; + p_container->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; + p_container->code = MTP_RESP_OK; + p_container->len = MTP_GENERIC_DATA_BLOCK_LENGTH; return MTP_PHASE_RESPONSE; } mtp_phase_type_t mtpd_handle_cmd_get_device_prop_desc(void) { - uint32_t device_prop_code = _mtpd_gct.data[0]; + mtp_generic_container_t* p_container = &_mtpd_epbuf.container; + uint32_t device_prop_code = p_container->data[0]; mtp_phase_type_t rt; if ((rt = mtpd_chk_session_open(__func__)) != MTP_PHASE_NONE) return rt; @@ -678,58 +698,60 @@ mtp_phase_type_t mtpd_handle_cmd_get_device_prop_desc(void) case MTP_DEV_PROP_DEVICE_FRIENDLY_NAME: { TU_VERIFY_STATIC(sizeof(mtp_device_prop_desc_t) < MTP_MAX_PACKET_SIZE, "mtp_device_info_t shall fit in MTP_MAX_PACKET_SIZE"); - _mtpd_gct.container_type = MTP_CONTAINER_TYPE_DATA_BLOCK; - _mtpd_gct.code = MTP_OP_GET_DEVICE_PROP_DESC; - _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH + sizeof(mtp_device_prop_desc_t); - mtp_device_prop_desc_t *d = (mtp_device_prop_desc_t *)_mtpd_gct.data; + p_container->type = MTP_CONTAINER_TYPE_DATA_BLOCK; + p_container->code = MTP_OP_GET_DEVICE_PROP_DESC; + p_container->len = MTP_GENERIC_DATA_BLOCK_LENGTH + sizeof(mtp_device_prop_desc_t); + mtp_device_prop_desc_t *d = (mtp_device_prop_desc_t *)p_container->data; d->device_property_code = (uint16_t)(device_prop_code); d->datatype = MTP_TYPE_STR; d->get_set = MTP_MODE_GET; mtpd_gct_append_wstring(CFG_TUD_MODEL); // factory_def_value mtpd_gct_append_wstring(CFG_TUD_MODEL); // current_value_len mtpd_gct_append_uint8(0x00); // form_flag - _mtpd_itf.queued_len = _mtpd_gct.container_length; + _mtpd_itf.queued_len = p_container->len; return MTP_PHASE_DATA_IN; } default: break; } - _mtpd_gct.container_type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; - _mtpd_gct.code = MTP_RESP_PARAMETER_NOT_SUPPORTED; - _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH; + p_container->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; + p_container->code = MTP_RESP_PARAMETER_NOT_SUPPORTED; + p_container->len = MTP_GENERIC_DATA_BLOCK_LENGTH; return MTP_PHASE_RESPONSE; } mtp_phase_type_t mtpd_handle_cmd_get_device_prop_value(void) { - uint32_t device_prop_code = _mtpd_gct.data[0]; + mtp_generic_container_t* p_container = &_mtpd_epbuf.container; + uint32_t device_prop_code = p_container->data[0]; mtp_phase_type_t rt; if ((rt = mtpd_chk_session_open(__func__)) != MTP_PHASE_NONE) return rt; - _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH; - _mtpd_gct.container_type = MTP_CONTAINER_TYPE_DATA_BLOCK; - _mtpd_gct.code = MTP_OP_GET_DEVICE_PROP_VALUE; + p_container->len = MTP_GENERIC_DATA_BLOCK_LENGTH; + p_container->type = MTP_CONTAINER_TYPE_DATA_BLOCK; + p_container->code = MTP_OP_GET_DEVICE_PROP_VALUE; switch(device_prop_code) { // TODO support more device properties case MTP_DEV_PROP_DEVICE_FRIENDLY_NAME: mtpd_gct_append_wstring(CFG_TUD_MODEL); - _mtpd_itf.queued_len = _mtpd_gct.container_length; + _mtpd_itf.queued_len = p_container->len; return MTP_PHASE_DATA_IN; default: - _mtpd_gct.container_type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; - _mtpd_gct.code = MTP_RESP_PARAMETER_NOT_SUPPORTED; + p_container->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; + p_container->code = MTP_RESP_PARAMETER_NOT_SUPPORTED; return MTP_PHASE_RESPONSE; } } mtp_phase_type_t mtpd_handle_cmd_send_object_info(void) { - _mtpd_soi.storage_id = _mtpd_gct.data[0]; - _mtpd_soi.parent_object_handle = (_mtpd_gct.data[1] == 0xFFFFFFFF ? 0 : _mtpd_gct.data[1]); + mtp_generic_container_t* p_container = &_mtpd_epbuf.container; + _mtpd_soi.storage_id = p_container->data[0]; + _mtpd_soi.parent_object_handle = (p_container->data[1] == 0xFFFFFFFF ? 0 : p_container->data[1]); // Enter OUT phase and wait for DATA BLOCK return MTP_PHASE_DATA_OUT; @@ -737,8 +759,9 @@ mtp_phase_type_t mtpd_handle_cmd_send_object_info(void) mtp_phase_type_t mtpd_handle_dto_send_object_info(void) { + mtp_generic_container_t* p_container = &_mtpd_epbuf.container; uint32_t new_object_handle = 0; - mtp_response_t res = tud_mtp_storage_object_write_info(_mtpd_soi.storage_id, _mtpd_soi.parent_object_handle, &new_object_handle, (mtp_object_info_t *)_mtpd_gct.data); + mtp_response_t res = tud_mtp_storage_object_write_info(_mtpd_soi.storage_id, _mtpd_soi.parent_object_handle, &new_object_handle, (mtp_object_info_t *)p_container->data); mtp_phase_type_t phase; if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESP_OK), res, "")) != MTP_PHASE_NONE) return phase; @@ -746,12 +769,12 @@ mtp_phase_type_t mtpd_handle_dto_send_object_info(void) _mtpd_soi.object_handle = new_object_handle; // Response - _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH + 3 * sizeof(uint32_t); - _mtpd_gct.container_type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; - _mtpd_gct.code = MTP_RESP_OK; - _mtpd_gct.data[0] = _mtpd_soi.storage_id; - _mtpd_gct.data[1] = _mtpd_soi.parent_object_handle; - _mtpd_gct.data[2] = _mtpd_soi.object_handle; + p_container->len = MTP_GENERIC_DATA_BLOCK_LENGTH + 3 * sizeof(uint32_t); + p_container->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; + p_container->code = MTP_RESP_OK; + p_container->data[0] = _mtpd_soi.storage_id; + p_container->data[1] = _mtpd_soi.parent_object_handle; + p_container->data[2] = _mtpd_soi.object_handle; return MTP_PHASE_RESPONSE; } @@ -763,7 +786,8 @@ mtp_phase_type_t mtpd_handle_cmd_send_object(void) mtp_phase_type_t mtpd_handle_dto_send_object(void) { - uint8_t *buffer = (uint8_t *)&_mtpd_gct.data; + mtp_generic_container_t* p_container = &_mtpd_epbuf.container; + uint8_t *buffer = (uint8_t *)&p_container->data; uint32_t buffer_size = _mtpd_itf.xferred_len - _mtpd_itf.handled_len; // First block of DATA if (_mtpd_itf.handled_len == 0) @@ -787,23 +811,24 @@ mtp_phase_type_t mtpd_handle_dto_send_object(void) // Send completed tud_mtp_storage_object_done(); - _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH; - _mtpd_gct.container_type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; - _mtpd_gct.code = MTP_RESP_OK; + p_container->len = MTP_GENERIC_DATA_BLOCK_LENGTH; + p_container->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; + p_container->code = MTP_RESP_OK; return MTP_PHASE_RESPONSE; } mtp_phase_type_t mtpd_handle_cmd_format_store(void) { - uint32_t storage_id = _mtpd_gct.data[0]; - uint32_t file_system_format = _mtpd_gct.data[1]; // not used + mtp_generic_container_t* p_container = &_mtpd_epbuf.container; + uint32_t storage_id = p_container->data[0]; + uint32_t file_system_format = p_container->data[1]; // not used (void) file_system_format; mtp_response_t res = tud_mtp_storage_format(storage_id); - _mtpd_gct.container_type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; - _mtpd_gct.code = res; - _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH; + p_container->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; + p_container->code = res; + p_container->len = MTP_GENERIC_DATA_BLOCK_LENGTH; return MTP_PHASE_RESPONSE; } @@ -813,12 +838,13 @@ mtp_phase_type_t mtpd_handle_cmd_format_store(void) mtp_phase_type_t mtpd_chk_session_open(const char *func_name) { (void)func_name; + mtp_generic_container_t* p_container = &_mtpd_epbuf.container; if (_mtpd_itf.context.session_id == 0) { TU_LOG_DRV(" MTP error: %s session not open\n", func_name); - _mtpd_gct.container_type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; - _mtpd_gct.code = MTP_RESP_SESSION_NOT_OPEN; - _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH; + p_container->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; + p_container->code = MTP_RESP_SESSION_NOT_OPEN; + p_container->len = MTP_GENERIC_DATA_BLOCK_LENGTH; return MTP_PHASE_RESPONSE; } return MTP_PHASE_NONE; @@ -828,12 +854,13 @@ mtp_phase_type_t mtpd_chk_generic(const char *func_name, const bool err_cd, cons { (void)func_name; (void)message; + mtp_generic_container_t* p_container = &_mtpd_epbuf.container; if (err_cd) { TU_LOG_DRV(" MTP error in %s: (%x) %s\n", func_name, ret_code, message); - _mtpd_gct.container_type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; - _mtpd_gct.code = ret_code; - _mtpd_gct.container_length = MTP_GENERIC_DATA_BLOCK_LENGTH; + p_container->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; + p_container->code = ret_code; + p_container->len = MTP_GENERIC_DATA_BLOCK_LENGTH; return MTP_PHASE_RESPONSE; } return MTP_PHASE_NONE; @@ -860,31 +887,34 @@ void mtpd_wc16cpy(uint8_t *dest, const char *src) //--------------------------------------------------------------------+ bool mtpd_gct_append_uint8(const uint8_t value) { - uint8_t *p_value = ((uint8_t *)&_mtpd_gct) + _mtpd_gct.container_length; - _mtpd_gct.container_length += sizeof(uint8_t); + mtp_generic_container_t* p_container = &_mtpd_epbuf.container; + uint8_t *p_value = ((uint8_t *)p_container) + p_container->len; + p_container->len += sizeof(uint8_t); // Verify space requirement (8 bit string length, number of wide characters including terminator) - TU_ASSERT(_mtpd_gct.container_length < sizeof(mtp_generic_container_t)); + TU_ASSERT(p_container->len < sizeof(mtp_generic_container_t)); *p_value = value; return true; } bool mtpd_gct_append_object_handle(const uint32_t object_handle) { - _mtpd_gct.container_length += sizeof(uint32_t); - TU_ASSERT(_mtpd_gct.container_length < sizeof(mtp_generic_container_t)); - _mtpd_gct.data[0]++; - _mtpd_gct.data[_mtpd_gct.data[0]] = object_handle; + mtp_generic_container_t* p_container = &_mtpd_epbuf.container; + p_container->len += sizeof(uint32_t); + TU_ASSERT(p_container->len < sizeof(mtp_generic_container_t)); + p_container->data[0]++; + p_container->data[p_container->data[0]] = object_handle; return true; } bool mtpd_gct_append_wstring(const char *s) { + mtp_generic_container_t* p_container = &_mtpd_epbuf.container; size_t len = strlen(s) + 1; TU_ASSERT(len <= UINT8_MAX); - uint8_t *p_len = ((uint8_t *)&_mtpd_gct)+_mtpd_gct.container_length; - _mtpd_gct.container_length += sizeof(uint8_t) + sizeof(wchar16_t) * len; + uint8_t *p_len = ((uint8_t *)p_container)+p_container->len; + p_container->len += sizeof(uint8_t) + sizeof(wchar16_t) * len; // Verify space requirement (8 bit string length, number of wide characters including terminator) - TU_ASSERT(_mtpd_gct.container_length < sizeof(mtp_generic_container_t)); + TU_ASSERT(p_container->len < sizeof(mtp_generic_container_t)); *p_len = (uint8_t)len; uint8_t *p_str = p_len + sizeof(uint8_t); mtpd_wc16cpy(p_str, s); @@ -893,12 +923,13 @@ bool mtpd_gct_append_wstring(const char *s) bool mtpd_gct_get_string(uint16_t *offset_data, char *string, const uint16_t max_size) { - uint16_t size = *(((uint8_t *)&_mtpd_gct.data) + *offset_data); + mtp_generic_container_t* p_container = &_mtpd_epbuf.container; + uint16_t size = *(((uint8_t *)&p_container->data) + *offset_data); if (size > max_size) size = max_size; - TU_ASSERT(*offset_data + size < sizeof(_mtpd_gct.data)); + TU_ASSERT(*offset_data + size < sizeof(p_container->data)); - uint8_t *s = ((uint8_t *)&_mtpd_gct.data) + *offset_data + sizeof(uint8_t); + uint8_t *s = ((uint8_t *)&p_container->data) + *offset_data + sizeof(uint8_t); for(uint16_t i = 0; i < size; i++) { string[i] = *s; @@ -910,19 +941,21 @@ bool mtpd_gct_get_string(uint16_t *offset_data, char *string, const uint16_t max bool mtpd_gct_append_array(uint32_t array_size, const void *data, size_t type_size) { - TU_ASSERT(_mtpd_gct.container_length + sizeof(uint32_t) + array_size * type_size < sizeof(_mtpd_gct.data)); - uint8_t *p = ((uint8_t *)&_mtpd_gct) + _mtpd_gct.container_length; + mtp_generic_container_t* p_container = &_mtpd_epbuf.container; + TU_ASSERT(p_container->len + sizeof(uint32_t) + array_size * type_size < sizeof(p_container->data)); + uint8_t *p = ((uint8_t *)p_container) + p_container->len; memcpy(p, &array_size, sizeof(uint32_t)); p += sizeof(uint32_t); memcpy(p, data, array_size * type_size); - _mtpd_gct.container_length += sizeof(uint32_t) + array_size * type_size; + p_container->len += sizeof(uint32_t) + array_size * type_size; return true; } bool mtpd_gct_append_date(struct tm *timeinfo) { + mtp_generic_container_t* p_container = &_mtpd_epbuf.container; // strftime is not supported by all platform, this implementation is just for reference - int len = snprintf(_mtp_datestr, sizeof(_mtpd_gct.data) - _mtpd_gct.container_length, "%04d%02d%02dT%02d%02d%02dZ", + int len = snprintf(_mtp_datestr, sizeof(p_container->data) - p_container->len, "%04d%02d%02dT%02d%02d%02dZ", timeinfo->tm_year + 1900, timeinfo->tm_mon + 1, timeinfo->tm_mday, diff --git a/src/device/usbd.c b/src/device/usbd.c index 8620c3b6f..e5542914a 100644 --- a/src/device/usbd.c +++ b/src/device/usbd.c @@ -331,6 +331,7 @@ tu_static usbd_class_driver_t const _usbd_driver[] = { .open = mtpd_open, .control_xfer_cb = mtpd_control_xfer_cb, .xfer_cb = mtpd_xfer_cb, + .xfer_isr = NULL, .sof = NULL }, #endif From 541c62ceaee39bc5896f16c0f71251863adb7bc4 Mon Sep 17 00:00:00 2001 From: hathach Date: Tue, 16 Sep 2025 22:23:57 +0700 Subject: [PATCH 06/43] refactor mtp xfer callback --- src/class/mtp/mtp.h | 12 ++- src/class/mtp/mtp_device.c | 211 ++++++++++++++++++++++++++++++------- 2 files changed, 186 insertions(+), 37 deletions(-) diff --git a/src/class/mtp/mtp.h b/src/class/mtp/mtp.h index f697193d2..c9d2b27a9 100644 --- a/src/class/mtp/mtp.h +++ b/src/class/mtp/mtp.h @@ -60,9 +60,11 @@ typedef enum { typedef enum { MTP_PHASE_IDLE = 0, MTP_PHASE_COMMAND, + MTP_PHASE_DATA, MTP_PHASE_DATA_IN, MTP_PHASE_DATA_OUT, MTP_PHASE_RESPONSE, + MTP_PHASE_RESPONSE_QUEUED, MTP_PHASE_ERROR, MTP_PHASE_NONE, } mtp_phase_type_t; @@ -685,9 +687,17 @@ tu_static const uint16_t mtp_playback_formats[] = { // Data structures //--------------------------------------------------------------------+ -#define MTP_GENERIC_DATA_BLOCK_LENGTH 12 +#define MTP_CONTAINER_HEADER_LENGTH 12 #define MTP_MAX_PACKET_SIZE 512 +typedef struct TU_ATTR_PACKED { + uint32_t len; + uint16_t type; + uint16_t code; + uint32_t transaction_id; +} mtp_container_header_t; +TU_VERIFY_STATIC(sizeof(mtp_container_header_t) == 12, "size is not correct"); + // PTP/MTP Generic container typedef struct TU_ATTR_PACKED { uint32_t len; diff --git a/src/class/mtp/mtp_device.c b/src/class/mtp/mtp_device.c index 5aa2bf1f1..6b37f50c2 100644 --- a/src/class/mtp/mtp_device.c +++ b/src/class/mtp/mtp_device.c @@ -45,6 +45,8 @@ #define TU_LOG_DRV(...) TU_LOG(CFG_TUD_MTP_LOG_LEVEL, __VA_ARGS__) +#define BULK_PACKET_SIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) + //--------------------------------------------------------------------+ // STRUCT //--------------------------------------------------------------------+ @@ -68,6 +70,8 @@ typedef struct uint32_t session_id; uint32_t transaction_id; } context; + + mtp_container_header_t cmd_header; } mtpd_interface_t; typedef struct { @@ -113,6 +117,17 @@ CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN static uint32_t _mtpd_get_object_handle; CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN static mtp_basic_object_info_t _mtpd_soi; CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN static char _mtp_datestr[20]; + +//--------------------------------------------------------------------+ +// Helper +//--------------------------------------------------------------------+ + +static bool prepare_new_command(uint8_t rhport, mtpd_interface_t* p_mtp) { + p_mtp->phase = MTP_PHASE_IDLE; + return usbd_edpt_xfer(rhport, p_mtp->ep_out, (uint8_t *)(&_mtpd_epbuf.container), sizeof(mtp_generic_container_t)); +} + + //--------------------------------------------------------------------+ // USBD Driver API //--------------------------------------------------------------------+ @@ -157,8 +172,7 @@ uint16_t mtpd_open(uint8_t rhport, tusb_desc_interface_t const* itf_desc, uint16 // Open endpoint pair TU_ASSERT(usbd_open_edpt_pair(rhport, tu_desc_next(ep_desc), 2, TUSB_XFER_BULK, &p_mtp->ep_out, &p_mtp->ep_in), 0); - // Prepare rx on bulk out EP - TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_out, (uint8_t *)(&_mtpd_epbuf.container), CFG_MTP_EP_SIZE), 0); + TU_ASSERT(prepare_new_command(rhport, p_mtp), 0); return mtpd_itf_size; } @@ -185,7 +199,7 @@ bool mtpd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t TU_LOG_DRV(" MTP request: MTP_REQ_RESET\n"); tud_mtp_storage_reset(); // Prepare for a new command - TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_out, (uint8_t *)(&_mtpd_epbuf.container), CFG_MTP_EP_SIZE)); + TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_out, (uint8_t *)(&_mtpd_epbuf.container), sizeof(mtp_generic_container_t))); break; case MTP_REQ_GET_DEVICE_STATUS: { @@ -218,28 +232,152 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t mtpd_interface_t* p_mtp = &_mtpd_itf; mtp_generic_container_t* p_container = &_mtpd_epbuf.container; + switch (p_mtp->phase) { + case MTP_PHASE_IDLE: + // received new command + TU_VERIFY(ep_addr == p_mtp->ep_out && p_container->type == MTP_CONTAINER_TYPE_COMMAND_BLOCK); + p_mtp->phase = MTP_PHASE_COMMAND; + TU_ATTR_FALLTHROUGH; // handle in the next case + + case MTP_PHASE_COMMAND: + // Handle command block + memcpy(&p_mtp->cmd_header, p_container, sizeof(mtp_container_header_t)); + p_mtp->phase = mtpd_handle_cmd(); + if (p_mtp->phase == MTP_PHASE_DATA_IN) { + TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_in, (uint8_t*) p_container, (uint16_t)p_mtp->queued_len)); + p_mtp->total_len = p_container->len; + p_mtp->xferred_len = 0; + p_mtp->handled_len = 0; + p_mtp->xfer_completed = false; + } else if (p_mtp->phase == MTP_PHASE_DATA_OUT) { + p_mtp->xferred_len = 0; + p_mtp->handled_len = 0; + p_mtp->xfer_completed = false; + TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_out, (uint8_t*) p_container, sizeof(mtp_generic_container_t)), 0); + } + break; + + case MTP_PHASE_DATA_IN: + p_mtp->xferred_len += xferred_bytes; + p_mtp->handled_len = p_mtp->xferred_len; + + // Check if transfer completed TODO check ZLP with FS/HS bulk size + if (p_mtp->xferred_len >= p_mtp->total_len && (xferred_bytes == 0 || (xferred_bytes % CFG_MTP_EP_SIZE) != 0)) { + p_mtp->phase = MTP_PHASE_RESPONSE; + p_container->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; + p_container->code = MTP_RESP_OK; + p_container->len = MTP_CONTAINER_HEADER_LENGTH; + p_container->transaction_id = p_mtp->context.transaction_id; + if (p_mtp->context.session_id != 0) { // is this needed ? + p_container->data[0] = p_mtp->context.session_id; + p_container->len += sizeof(uint32_t); + } + } else { + // Send next block of DATA + if (p_mtp->xferred_len == p_mtp->total_len) { + // send Zero-Length Packet + TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_in, NULL, 0 )); + } else { + p_mtp->phase = mtpd_handle_data(); + if (p_mtp->phase == MTP_PHASE_DATA_IN) { + TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_in, ((uint8_t *)(&p_container->data)), (uint16_t)p_mtp->queued_len)); + } + } + } + break; + + case MTP_PHASE_DATA_OUT: + // First block of data + if (p_mtp->xferred_len == 0) { + p_mtp->total_len = p_container->len; + p_mtp->handled_len = 0; + p_mtp->xfer_completed = false; + TU_ASSERT(p_container->type == MTP_CONTAINER_TYPE_DATA_BLOCK); + } + p_mtp->xferred_len += xferred_bytes; + + // A zero-length or a short packet termination + if (xferred_bytes < CFG_MTP_EP_SIZE) { + p_mtp->xfer_completed = true; + // Handle data block + p_mtp->phase = mtpd_handle_data(); + if (p_mtp->phase == MTP_PHASE_DATA_OUT) { + TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_out, (uint8_t*) p_container, sizeof(mtp_generic_container_t)), 0); + p_mtp->xferred_len = 0; + p_mtp->xfer_completed = false; + } + } else { + // Handle data block when container is full + if (p_mtp->xferred_len - p_mtp->handled_len >= MTP_MAX_PACKET_SIZE - CFG_MTP_EP_SIZE) { + p_mtp->phase = mtpd_handle_data(); + p_mtp->handled_len = p_mtp->xferred_len; + } + // Transfer completed: wait for zero-length packet + // Some platforms may not respect EP size and xferred_bytes may be more than CFG_MTP_EP_SIZE if + // the OUT EP is waiting for more data. Ensure we are not waiting for more than CFG_MTP_EP_SIZE. + if (p_mtp->total_len == p_mtp->xferred_len) { + TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_out, ((uint8_t *)(&p_container->data)), CFG_MTP_EP_SIZE), 0); + } else if (p_mtp->handled_len == 0) { + // First data block includes container header + container data + TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_out, + (uint8_t*) p_container + p_mtp->xferred_len, + (uint16_t)TU_MIN(p_mtp->total_len - p_mtp->xferred_len, CFG_MTP_EP_SIZE))); + } else { + // Successive data block includes only container data + TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_out, + ((uint8_t *)(&p_container->data)) + p_mtp->xferred_len - p_mtp->handled_len, + (uint16_t)TU_MIN(p_mtp->total_len - p_mtp->xferred_len, CFG_MTP_EP_SIZE))); + } + } + break; + + case MTP_PHASE_RESPONSE_QUEUED: + // response phase is complete -> prepare for new command + TU_ASSERT(ep_addr == p_mtp->ep_in); + prepare_new_command(rhport, p_mtp); + break; + + case MTP_PHASE_RESPONSE: + case MTP_PHASE_ERROR: + // processed immediately after this switch, supposedly to be empty + break; + default: return false; + } + + if (p_mtp->phase == MTP_PHASE_RESPONSE) { + p_mtp->phase = MTP_PHASE_RESPONSE_QUEUED; + p_container->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; + p_container->transaction_id = p_mtp->context.transaction_id; + TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_in, (uint8_t*) p_container, (uint16_t)p_container->len), 0); + } else if (p_mtp->phase == MTP_PHASE_ERROR) { + // stall both IN & OUT endpoints + usbd_edpt_stall(rhport, p_mtp->ep_out); + usbd_edpt_stall(rhport, p_mtp->ep_in); + } + +#if 0 // IN transfer completed if (ep_addr == p_mtp->ep_in) { if (p_mtp->phase == MTP_PHASE_RESPONSE) { // IN transfer completed, prepare for a new command - TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_out, (uint8_t*) &_mtpd_epbuf.container, CFG_MTP_EP_SIZE), 0); + TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_out, (uint8_t*) p_container, CFG_MTP_EP_SIZE), 0); p_mtp->phase = MTP_PHASE_IDLE; } else if (p_mtp->phase == MTP_PHASE_DATA_IN) { p_mtp->xferred_len += xferred_bytes; p_mtp->handled_len = p_mtp->xferred_len; - // Check if transfer completed + // Check if transfer completed. if (p_mtp->xferred_len >= p_mtp->total_len && (xferred_bytes == 0 || (xferred_bytes % CFG_MTP_EP_SIZE) != 0)) { p_mtp->phase = MTP_PHASE_RESPONSE; p_container->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; p_container->code = MTP_RESP_OK; - p_container->len = MTP_GENERIC_DATA_BLOCK_LENGTH; + p_container->len = MTP_CONTAINER_HEADER_LENGTH; p_container->transaction_id = p_mtp->context.transaction_id; if (p_mtp->context.session_id != 0) { p_container->data[0] = p_mtp->context.session_id; p_container->len += sizeof(uint32_t); } - TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_in, (uint8_t*) &_mtpd_epbuf.container, (uint16_t)p_container->len), 0); + TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_in, (uint8_t*) p_container, (uint16_t)p_container->len), 0); } else { // Send next block of DATA // Send Zero-Length Packet @@ -248,7 +386,7 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t } else { p_mtp->phase = mtpd_handle_data(); if (p_mtp->phase == MTP_PHASE_RESPONSE) { - TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_in, (uint8_t*) &_mtpd_epbuf.container, (uint16_t)p_container->len)); + TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_in, (uint8_t*) p_container, (uint16_t)p_container->len)); } else { TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_in, ((uint8_t *)(&p_container->data)), (uint16_t)p_mtp->queued_len)); } @@ -278,20 +416,20 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t if (p_mtp->phase == MTP_PHASE_COMMAND) { // A zero-length or a short packet termination is expected if (xferred_bytes == CFG_MTP_EP_SIZE || (p_mtp->total_len - p_mtp->xferred_len) > 0) { - TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_out, (uint8_t*) &_mtpd_epbuf.container + p_mtp->xferred_len, (uint16_t)(p_mtp->total_len - p_mtp->xferred_len))); + TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_out, (uint8_t*) p_container + p_mtp->xferred_len, (uint16_t)(p_mtp->total_len - p_mtp->xferred_len))); } else { // Handle command block p_mtp->phase = mtpd_handle_cmd(); if (p_mtp->phase == MTP_PHASE_RESPONSE) { - TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_in, (uint8_t*) &_mtpd_epbuf.container, (uint16_t)p_container->len)); + TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_in, (uint8_t*) p_container, (uint16_t)p_container->len)); } else if (p_mtp->phase == MTP_PHASE_DATA_IN) { - TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_in, (uint8_t*) &_mtpd_epbuf.container, (uint16_t)p_mtp->queued_len)); + TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_in, (uint8_t*) p_container, (uint16_t)p_mtp->queued_len)); p_mtp->total_len = p_container->len; p_mtp->xferred_len = 0; p_mtp->handled_len = 0; p_mtp->xfer_completed = false; } else if (p_mtp->phase == MTP_PHASE_DATA_OUT) { - TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_out, (uint8_t*) &_mtpd_epbuf.container, CFG_MTP_EP_SIZE), 0); + TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_out, (uint8_t*) p_container, sizeof(mtp_generic_container_t)), 0); p_mtp->xferred_len = 0; p_mtp->handled_len = 0; p_mtp->xfer_completed = false; @@ -320,9 +458,9 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t // Handle data block p_mtp->phase = mtpd_handle_data(); if (p_mtp->phase == MTP_PHASE_DATA_IN || p_mtp->phase == MTP_PHASE_RESPONSE) { - TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_in, (uint8_t*) &_mtpd_epbuf.container, (uint16_t)p_container->len)); + TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_in, (uint8_t*) p_container, (uint16_t)p_container->len)); } else if (p_mtp->phase == MTP_PHASE_DATA_OUT) { - TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_out, (uint8_t*) &_mtpd_epbuf.container, CFG_MTP_EP_SIZE), 0); + TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_out, (uint8_t*) p_container, sizeof(mtp_generic_container_t)), 0); p_mtp->xferred_len = 0; p_mtp->xfer_completed = false; } else { @@ -342,7 +480,7 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_out, ((uint8_t *)(&p_container->data)), CFG_MTP_EP_SIZE), 0); } else if (p_mtp->handled_len == 0) { // First data block includes container header + container data - TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_out, (uint8_t*) &_mtpd_epbuf.container + p_mtp->xferred_len, (uint16_t)TU_MIN(p_mtp->total_len - p_mtp->xferred_len, CFG_MTP_EP_SIZE))); + TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_out, (uint8_t*) p_container + p_mtp->xferred_len, (uint16_t)TU_MIN(p_mtp->total_len - p_mtp->xferred_len, CFG_MTP_EP_SIZE))); } else { // Successive data block includes only container data TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_out, ((uint8_t *)(&p_container->data)) + p_mtp->xferred_len - p_mtp->handled_len, (uint16_t)TU_MIN(p_mtp->total_len - p_mtp->xferred_len, CFG_MTP_EP_SIZE))); @@ -350,6 +488,7 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t } } } +#endif return true; } @@ -446,7 +585,7 @@ mtp_phase_type_t mtpd_handle_cmd_get_device_info(void) TU_VERIFY_STATIC(sizeof(mtp_device_info_t) < MTP_MAX_PACKET_SIZE, "mtp_device_info_t shall fit in MTP_MAX_PACKET_SIZE"); mtp_generic_container_t* p_container = &_mtpd_epbuf.container; - p_container->len = MTP_GENERIC_DATA_BLOCK_LENGTH + sizeof(mtp_device_info_t); + p_container->len = MTP_CONTAINER_HEADER_LENGTH + sizeof(mtp_device_info_t); p_container->type = MTP_CONTAINER_TYPE_DATA_BLOCK; p_container->code = MTP_OP_GET_DEVICE_INFO; mtp_device_info_t *d = (mtp_device_info_t *)p_container->data; @@ -483,7 +622,7 @@ mtp_phase_type_t mtpd_handle_cmd_open_session(void) mtp_response_t res = tud_mtp_storage_open_session(&session_id); if (res == MTP_RESP_SESSION_ALREADY_OPEN) { - p_container->len = MTP_GENERIC_DATA_BLOCK_LENGTH; + p_container->len = MTP_CONTAINER_HEADER_LENGTH; p_container->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; p_container->code = res; p_container->len += sizeof(p_container->data[0]); @@ -497,7 +636,7 @@ mtp_phase_type_t mtpd_handle_cmd_open_session(void) _mtpd_itf.context.session_id = session_id; - p_container->len = MTP_GENERIC_DATA_BLOCK_LENGTH; + p_container->len = MTP_CONTAINER_HEADER_LENGTH; p_container->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; p_container->code = MTP_RESP_OK; @@ -513,7 +652,7 @@ mtp_phase_type_t mtpd_handle_cmd_close_session(void) _mtpd_itf.context.session_id = session_id; - p_container->len = MTP_GENERIC_DATA_BLOCK_LENGTH; + p_container->len = MTP_CONTAINER_HEADER_LENGTH; p_container->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; p_container->code = res; @@ -530,7 +669,7 @@ mtp_phase_type_t mtpd_handle_cmd_get_storage_ids(void) mtp_phase_type_t phase; if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESP_OK), res, "")) != MTP_PHASE_NONE) return phase; - p_container->len = MTP_GENERIC_DATA_BLOCK_LENGTH + sizeof(mtp_storage_ids_t); + p_container->len = MTP_CONTAINER_HEADER_LENGTH + sizeof(mtp_storage_ids_t); p_container->type = MTP_CONTAINER_TYPE_DATA_BLOCK; p_container->code = MTP_OP_GET_STORAGE_IDS; mtp_storage_ids_t *d = (mtp_storage_ids_t *)p_container->data; @@ -556,7 +695,7 @@ mtp_phase_type_t mtpd_handle_cmd_get_storage_info(void) mtp_generic_container_t* p_container = &_mtpd_epbuf.container; uint32_t storage_id = p_container->data[0]; - p_container->len = MTP_GENERIC_DATA_BLOCK_LENGTH + sizeof(mtp_storage_info_t); + p_container->len = MTP_CONTAINER_HEADER_LENGTH + sizeof(mtp_storage_info_t); p_container->type = MTP_CONTAINER_TYPE_DATA_BLOCK; p_container->code = MTP_OP_GET_STORAGE_INFO; @@ -575,7 +714,7 @@ mtp_phase_type_t mtpd_handle_cmd_get_object_handles(void) uint32_t object_format_code = p_container->data[1]; // optional, not managed uint32_t parent_object_handle = p_container->data[2]; // folder specification, 0xffffffff=objects with no parent - p_container->len = MTP_GENERIC_DATA_BLOCK_LENGTH + sizeof(uint32_t); + p_container->len = MTP_CONTAINER_HEADER_LENGTH + sizeof(uint32_t); p_container->type = MTP_CONTAINER_TYPE_DATA_BLOCK; p_container->code = MTP_OP_GET_OBJECT_HANDLES; p_container->data[0] = 0; @@ -607,7 +746,7 @@ mtp_phase_type_t mtpd_handle_cmd_get_object_info(void) mtp_generic_container_t* p_container = &_mtpd_epbuf.container; uint32_t object_handle = p_container->data[0]; - p_container->len = MTP_GENERIC_DATA_BLOCK_LENGTH + sizeof(mtp_object_info_t); + p_container->len = MTP_CONTAINER_HEADER_LENGTH + sizeof(mtp_object_info_t); p_container->type = MTP_CONTAINER_TYPE_DATA_BLOCK; p_container->code = MTP_OP_GET_OBJECT_INFO; mtp_response_t res = tud_mtp_storage_object_read_info(object_handle, (mtp_object_info_t *)p_container->data); @@ -635,7 +774,7 @@ mtp_phase_type_t mtpd_handle_dti_get_object(void) mtp_generic_container_t* p_container = &_mtpd_epbuf.container; res = tud_mtp_storage_object_size(_mtpd_get_object_handle, &file_size); if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESP_OK), res, "")) != MTP_PHASE_NONE) return phase; - p_container->len = MTP_GENERIC_DATA_BLOCK_LENGTH + file_size; + p_container->len = MTP_CONTAINER_HEADER_LENGTH + file_size; p_container->type = MTP_CONTAINER_TYPE_DATA_BLOCK; p_container->code = MTP_OP_GET_OBJECT; @@ -645,10 +784,10 @@ mtp_phase_type_t mtpd_handle_dti_get_object(void) if (_mtpd_itf.handled_len == 0) { // First data block: include container header - buffer_size = ((MTP_MAX_PACKET_SIZE + MTP_GENERIC_DATA_BLOCK_LENGTH) / CFG_MTP_EP_SIZE) * CFG_MTP_EP_SIZE - MTP_GENERIC_DATA_BLOCK_LENGTH; + buffer_size = ((MTP_MAX_PACKET_SIZE + MTP_CONTAINER_HEADER_LENGTH) / CFG_MTP_EP_SIZE) * CFG_MTP_EP_SIZE - MTP_CONTAINER_HEADER_LENGTH; res = tud_mtp_storage_object_read(_mtpd_get_object_handle, (void *)&p_container->data, buffer_size, &read_count); if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESP_OK), res, "")) != MTP_PHASE_NONE) return phase; - _mtpd_itf.queued_len = MTP_GENERIC_DATA_BLOCK_LENGTH + read_count; + _mtpd_itf.queued_len = MTP_CONTAINER_HEADER_LENGTH + read_count; } else { @@ -681,7 +820,7 @@ mtp_phase_type_t mtpd_handle_cmd_delete_object(void) p_container->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; p_container->code = MTP_RESP_OK; - p_container->len = MTP_GENERIC_DATA_BLOCK_LENGTH; + p_container->len = MTP_CONTAINER_HEADER_LENGTH; return MTP_PHASE_RESPONSE; } @@ -700,7 +839,7 @@ mtp_phase_type_t mtpd_handle_cmd_get_device_prop_desc(void) TU_VERIFY_STATIC(sizeof(mtp_device_prop_desc_t) < MTP_MAX_PACKET_SIZE, "mtp_device_info_t shall fit in MTP_MAX_PACKET_SIZE"); p_container->type = MTP_CONTAINER_TYPE_DATA_BLOCK; p_container->code = MTP_OP_GET_DEVICE_PROP_DESC; - p_container->len = MTP_GENERIC_DATA_BLOCK_LENGTH + sizeof(mtp_device_prop_desc_t); + p_container->len = MTP_CONTAINER_HEADER_LENGTH + sizeof(mtp_device_prop_desc_t); mtp_device_prop_desc_t *d = (mtp_device_prop_desc_t *)p_container->data; d->device_property_code = (uint16_t)(device_prop_code); d->datatype = MTP_TYPE_STR; @@ -717,7 +856,7 @@ mtp_phase_type_t mtpd_handle_cmd_get_device_prop_desc(void) p_container->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; p_container->code = MTP_RESP_PARAMETER_NOT_SUPPORTED; - p_container->len = MTP_GENERIC_DATA_BLOCK_LENGTH; + p_container->len = MTP_CONTAINER_HEADER_LENGTH; return MTP_PHASE_RESPONSE; } @@ -729,7 +868,7 @@ mtp_phase_type_t mtpd_handle_cmd_get_device_prop_value(void) mtp_phase_type_t rt; if ((rt = mtpd_chk_session_open(__func__)) != MTP_PHASE_NONE) return rt; - p_container->len = MTP_GENERIC_DATA_BLOCK_LENGTH; + p_container->len = MTP_CONTAINER_HEADER_LENGTH; p_container->type = MTP_CONTAINER_TYPE_DATA_BLOCK; p_container->code = MTP_OP_GET_DEVICE_PROP_VALUE; @@ -769,7 +908,7 @@ mtp_phase_type_t mtpd_handle_dto_send_object_info(void) _mtpd_soi.object_handle = new_object_handle; // Response - p_container->len = MTP_GENERIC_DATA_BLOCK_LENGTH + 3 * sizeof(uint32_t); + p_container->len = MTP_CONTAINER_HEADER_LENGTH + 3 * sizeof(uint32_t); p_container->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; p_container->code = MTP_RESP_OK; p_container->data[0] = _mtpd_soi.storage_id; @@ -792,7 +931,7 @@ mtp_phase_type_t mtpd_handle_dto_send_object(void) // First block of DATA if (_mtpd_itf.handled_len == 0) { - buffer_size -= MTP_GENERIC_DATA_BLOCK_LENGTH; + buffer_size -= MTP_CONTAINER_HEADER_LENGTH; } if (buffer_size > 0) @@ -811,7 +950,7 @@ mtp_phase_type_t mtpd_handle_dto_send_object(void) // Send completed tud_mtp_storage_object_done(); - p_container->len = MTP_GENERIC_DATA_BLOCK_LENGTH; + p_container->len = MTP_CONTAINER_HEADER_LENGTH; p_container->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; p_container->code = MTP_RESP_OK; return MTP_PHASE_RESPONSE; @@ -828,7 +967,7 @@ mtp_phase_type_t mtpd_handle_cmd_format_store(void) p_container->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; p_container->code = res; - p_container->len = MTP_GENERIC_DATA_BLOCK_LENGTH; + p_container->len = MTP_CONTAINER_HEADER_LENGTH; return MTP_PHASE_RESPONSE; } @@ -844,7 +983,7 @@ mtp_phase_type_t mtpd_chk_session_open(const char *func_name) TU_LOG_DRV(" MTP error: %s session not open\n", func_name); p_container->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; p_container->code = MTP_RESP_SESSION_NOT_OPEN; - p_container->len = MTP_GENERIC_DATA_BLOCK_LENGTH; + p_container->len = MTP_CONTAINER_HEADER_LENGTH; return MTP_PHASE_RESPONSE; } return MTP_PHASE_NONE; @@ -860,7 +999,7 @@ mtp_phase_type_t mtpd_chk_generic(const char *func_name, const bool err_cd, cons TU_LOG_DRV(" MTP error in %s: (%x) %s\n", func_name, ret_code, message); p_container->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; p_container->code = ret_code; - p_container->len = MTP_GENERIC_DATA_BLOCK_LENGTH; + p_container->len = MTP_CONTAINER_HEADER_LENGTH; return MTP_PHASE_RESPONSE; } return MTP_PHASE_NONE; From aea56dc7760c1a1f4caedc983e2c46b8928e7388 Mon Sep 17 00:00:00 2001 From: hathach Date: Tue, 16 Sep 2025 22:43:18 +0700 Subject: [PATCH 07/43] remove mtp context --- src/class/mtp/mtp_device.c | 63 ++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 27 deletions(-) diff --git a/src/class/mtp/mtp_device.c b/src/class/mtp/mtp_device.c index 6b37f50c2..7e9574ab1 100644 --- a/src/class/mtp/mtp_device.c +++ b/src/class/mtp/mtp_device.c @@ -66,11 +66,7 @@ typedef struct uint32_t handled_len; // number of bytes already handled in the Data Stage bool xfer_completed; // true when DATA-IN/DATA-OUT transfer is completed - struct { - uint32_t session_id; - uint32_t transaction_id; - } context; - + uint32_t session_id; mtp_container_header_t cmd_header; } mtpd_interface_t; @@ -264,12 +260,10 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t // Check if transfer completed TODO check ZLP with FS/HS bulk size if (p_mtp->xferred_len >= p_mtp->total_len && (xferred_bytes == 0 || (xferred_bytes % CFG_MTP_EP_SIZE) != 0)) { p_mtp->phase = MTP_PHASE_RESPONSE; - p_container->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; p_container->code = MTP_RESP_OK; p_container->len = MTP_CONTAINER_HEADER_LENGTH; - p_container->transaction_id = p_mtp->context.transaction_id; - if (p_mtp->context.session_id != 0) { // is this needed ? - p_container->data[0] = p_mtp->context.session_id; + if (p_mtp->session_id != 0) { // is this needed ? + p_container->data[0] = p_mtp->session_id; p_container->len += sizeof(uint32_t); } } else { @@ -347,7 +341,7 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t if (p_mtp->phase == MTP_PHASE_RESPONSE) { p_mtp->phase = MTP_PHASE_RESPONSE_QUEUED; p_container->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; - p_container->transaction_id = p_mtp->context.transaction_id; + p_container->transaction_id = p_mtp->cmd_header.transaction_id; TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_in, (uint8_t*) p_container, (uint16_t)p_container->len), 0); } else if (p_mtp->phase == MTP_PHASE_ERROR) { // stall both IN & OUT endpoints @@ -373,8 +367,8 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t p_container->code = MTP_RESP_OK; p_container->len = MTP_CONTAINER_HEADER_LENGTH; p_container->transaction_id = p_mtp->context.transaction_id; - if (p_mtp->context.session_id != 0) { - p_container->data[0] = p_mtp->context.session_id; + if (p_mtp->session_id != 0) { + p_container->data[0] = p_mtp->session_id; p_container->len += sizeof(uint32_t); } TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_in, (uint8_t*) p_container, (uint16_t)p_container->len), 0); @@ -501,7 +495,6 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t mtp_phase_type_t mtpd_handle_cmd(void) { mtp_generic_container_t* p_container = &_mtpd_epbuf.container; TU_ASSERT(p_container->type == MTP_CONTAINER_TYPE_COMMAND_BLOCK); - _mtpd_itf.context.transaction_id = p_container->transaction_id; if (p_container->code != MTP_OP_SEND_OBJECT) { _mtpd_soi.object_handle = 0; } @@ -560,7 +553,6 @@ mtp_phase_type_t mtpd_handle_data(void) { mtp_generic_container_t* p_container = &_mtpd_epbuf.container; TU_ASSERT(p_container->type == MTP_CONTAINER_TYPE_DATA_BLOCK); - _mtpd_itf.context.transaction_id = p_container->transaction_id; switch(p_container->code) { @@ -627,14 +619,14 @@ mtp_phase_type_t mtpd_handle_cmd_open_session(void) p_container->code = res; p_container->len += sizeof(p_container->data[0]); p_container->data[0] = session_id; - _mtpd_itf.context.session_id = session_id; + _mtpd_itf.session_id = session_id; return MTP_PHASE_RESPONSE; } mtp_phase_type_t phase; if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESP_OK), res, "")) != MTP_PHASE_NONE) return phase; - _mtpd_itf.context.session_id = session_id; + _mtpd_itf.session_id = session_id; p_container->len = MTP_CONTAINER_HEADER_LENGTH; p_container->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; @@ -650,7 +642,7 @@ mtp_phase_type_t mtpd_handle_cmd_close_session(void) mtp_response_t res = tud_mtp_storage_close_session(session_id); - _mtpd_itf.context.session_id = session_id; + _mtpd_itf.session_id = session_id; p_container->len = MTP_CONTAINER_HEADER_LENGTH; p_container->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; @@ -701,7 +693,9 @@ mtp_phase_type_t mtpd_handle_cmd_get_storage_info(void) mtp_response_t res = tud_mtp_get_storage_info(storage_id, (mtp_storage_info_t *)p_container->data); mtp_phase_type_t phase; - if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESP_OK), res, "")) != MTP_PHASE_NONE) return phase; + if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESP_OK), res, "")) != MTP_PHASE_NONE) { + return phase; + } _mtpd_itf.queued_len = p_container->len; return MTP_PHASE_DATA_IN; @@ -720,18 +714,25 @@ mtp_phase_type_t mtpd_handle_cmd_get_object_handles(void) p_container->data[0] = 0; mtp_phase_type_t phase; - if ((phase = mtpd_chk_generic(__func__, (object_format_code != 0), MTP_RESP_SPECIFICATION_BY_FORMAT_UNSUPPORTED, "specification by format unsupported")) != MTP_PHASE_NONE) return phase; + if ((phase = mtpd_chk_generic(__func__, (object_format_code != 0), MTP_RESP_SPECIFICATION_BY_FORMAT_UNSUPPORTED, "specification by format unsupported")) != MTP_PHASE_NONE) { + return phase; + } //list of all object handles on all storages, not managed - if ((phase = mtpd_chk_generic(__func__, (storage_id == 0xFFFFFFFF), MTP_RESP_OPERATION_NOT_SUPPORTED, "list of all object handles on all storages unsupported")) != MTP_PHASE_NONE) return phase; + if ((phase = mtpd_chk_generic(__func__, (storage_id == 0xFFFFFFFF), MTP_RESP_OPERATION_NOT_SUPPORTED, "list of all object handles on all storages unsupported")) != MTP_PHASE_NONE) { + return phase; + } tud_mtp_storage_object_done(); uint32_t next_child_handle = 0; while(true) { mtp_response_t res = tud_mtp_storage_association_get_object_handle(storage_id, parent_object_handle, &next_child_handle); - if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESP_OK), res, "")) != MTP_PHASE_NONE) return phase; - if (next_child_handle == 0) + if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESP_OK), res, "")) != MTP_PHASE_NONE) { + return phase; + } + if (next_child_handle == 0) { break; + } mtpd_gct_append_object_handle(next_child_handle); } tud_mtp_storage_object_done(); @@ -751,7 +752,9 @@ mtp_phase_type_t mtpd_handle_cmd_get_object_info(void) p_container->code = MTP_OP_GET_OBJECT_INFO; mtp_response_t res = tud_mtp_storage_object_read_info(object_handle, (mtp_object_info_t *)p_container->data); mtp_phase_type_t phase; - if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESP_OK), res, "")) != MTP_PHASE_NONE) return phase; + if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESP_OK), res, "")) != MTP_PHASE_NONE) { + return phase; + } _mtpd_itf.queued_len = p_container->len; return MTP_PHASE_DATA_IN; @@ -773,7 +776,9 @@ mtp_phase_type_t mtpd_handle_dti_get_object(void) uint32_t file_size = 0; mtp_generic_container_t* p_container = &_mtpd_epbuf.container; res = tud_mtp_storage_object_size(_mtpd_get_object_handle, &file_size); - if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESP_OK), res, "")) != MTP_PHASE_NONE) return phase; + if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESP_OK), res, "")) != MTP_PHASE_NONE) { + return phase; + } p_container->len = MTP_CONTAINER_HEADER_LENGTH + file_size; p_container->type = MTP_CONTAINER_TYPE_DATA_BLOCK; p_container->code = MTP_OP_GET_OBJECT; @@ -786,7 +791,9 @@ mtp_phase_type_t mtpd_handle_dti_get_object(void) // First data block: include container header buffer_size = ((MTP_MAX_PACKET_SIZE + MTP_CONTAINER_HEADER_LENGTH) / CFG_MTP_EP_SIZE) * CFG_MTP_EP_SIZE - MTP_CONTAINER_HEADER_LENGTH; res = tud_mtp_storage_object_read(_mtpd_get_object_handle, (void *)&p_container->data, buffer_size, &read_count); - if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESP_OK), res, "")) != MTP_PHASE_NONE) return phase; + if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESP_OK), res, "")) != MTP_PHASE_NONE) { + return phase; + } _mtpd_itf.queued_len = MTP_CONTAINER_HEADER_LENGTH + read_count; } else @@ -794,7 +801,9 @@ mtp_phase_type_t mtpd_handle_dti_get_object(void) // Successive data block: consider only container data buffer_size = (MTP_MAX_PACKET_SIZE / CFG_MTP_EP_SIZE) * CFG_MTP_EP_SIZE; res = tud_mtp_storage_object_read(_mtpd_get_object_handle, (void *)&p_container->data, buffer_size, &read_count); - if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESP_OK), res, "")) != MTP_PHASE_NONE) return phase; + if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESP_OK), res, "")) != MTP_PHASE_NONE) { + return phase; + } _mtpd_itf.queued_len = read_count; } @@ -978,7 +987,7 @@ mtp_phase_type_t mtpd_chk_session_open(const char *func_name) { (void)func_name; mtp_generic_container_t* p_container = &_mtpd_epbuf.container; - if (_mtpd_itf.context.session_id == 0) + if (_mtpd_itf.session_id == 0) { TU_LOG_DRV(" MTP error: %s session not open\n", func_name); p_container->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; From f99f203c28b79f00c5eb82e34c869efb3c5034a0 Mon Sep 17 00:00:00 2001 From: hathach Date: Fri, 19 Sep 2025 11:44:21 +0700 Subject: [PATCH 08/43] reworking MTP API, adding callback, getting GetDeviceInfo working --- examples/device/mtp/src/mtp_fs_example.c | 952 ++++++++++++----------- src/class/mtp/mtp.h | 185 +++-- src/class/mtp/mtp_device.c | 315 +++----- src/class/mtp/mtp_device.h | 71 +- 4 files changed, 794 insertions(+), 729 deletions(-) diff --git a/examples/device/mtp/src/mtp_fs_example.c b/examples/device/mtp/src/mtp_fs_example.c index 9dbac746b..68140b1ae 100644 --- a/examples/device/mtp/src/mtp_fs_example.c +++ b/examples/device/mtp/src/mtp_fs_example.c @@ -29,6 +29,58 @@ #define MTPD_STORAGE_DESCRIPTION "storage" #define MTPD_VOLUME_IDENTIFIER "volume" +//--------------------------------------------------------------------+ +// Device Info +//--------------------------------------------------------------------+ + +// device info string (including terminating null) +const uint16_t dev_info_manufacturer[] = { 'T', 'i', 'n', 'y', 'U', 'S', 'B', 0 }; +const uint16_t dev_info_model[] = { 'M', 'T', 'P', ' ', 'E', 'x', 'a', 'm', 'p', 'l', 'e', 0 }; +const uint16_t dev_info_version[] = { '1', '.', '0', 0 }; +const uint16_t dev_info_serial[] = { '1', '2', '3', '4', '5', '6', 0 }; + + +static const uint16_t supported_operations[] = { + MTP_OP_GET_DEVICE_INFO, + MTP_OP_OPEN_SESSION, + MTP_OP_CLOSE_SESSION, + MTP_OP_GET_STORAGE_IDS, + MTP_OP_GET_STORAGE_INFO, + MTP_OP_GET_NUM_OBJECTS, + MTP_OP_GET_OBJECT_HANDLES, + MTP_OP_GET_OBJECT_INFO, + MTP_OP_GET_OBJECT, + MTP_OP_DELETE_OBJECT, + MTP_OP_SEND_OBJECT_INFO, + MTP_OP_SEND_OBJECT, + MTP_OP_FORMAT_STORE, + MTP_OP_RESET_DEVICE, + MTP_OP_GET_DEVICE_PROP_DESC, + MTP_OP_GET_DEVICE_PROP_VALUE, + MTP_OP_SET_DEVICE_PROP_VALUE +}; + +static const uint16_t supported_events[] = { + MTP_EVENT_OBJECT_ADDED, +}; + +static const uint16_t supported_device_properties[] = { + MTP_DEV_PROP_DEVICE_FRIENDLY_NAME, +}; + +static const uint16_t capture_formats[] = { + MTP_OBJ_FORMAT_UNDEFINED, + MTP_OBJ_FORMAT_ASSOCIATION, + MTP_OBJ_FORMAT_TEXT, +}; + +static const uint16_t playback_formats[] = { + MTP_OBJ_FORMAT_UNDEFINED, + MTP_OBJ_FORMAT_ASSOCIATION, + MTP_OBJ_FORMAT_TEXT, +}; + + //--------------------------------------------------------------------+ // RAM FILESYSTEM //--------------------------------------------------------------------+ @@ -37,56 +89,54 @@ #define FS_MAX_NODE_NAME_LEN 64UL #define FS_ISODATETIME_LEN 26UL -typedef struct -{ - uint32_t handle; - uint32_t parent; - uint32_t size; - bool allocated; - bool association; - char name[FS_MAX_NODE_NAME_LEN]; - char created[FS_ISODATETIME_LEN]; - char modified[FS_ISODATETIME_LEN]; - uint8_t data[FS_MAX_NODE_BYTES]; +typedef struct { + uint32_t handle; + uint32_t parent; + uint32_t size; + bool allocated; + bool association; + char name[FS_MAX_NODE_NAME_LEN]; + char created[FS_ISODATETIME_LEN]; + char modified[FS_ISODATETIME_LEN]; + uint8_t data[FS_MAX_NODE_BYTES]; } fs_object_info_t; // Sample object file static fs_object_info_t _fs_objects[FS_MAX_NODES] = { - { - .handle = 1, - .parent = 0, - .allocated = true, - .association = false, - .name = "readme.txt", - .created = "20240104T111134.0", - .modified = "20241214T121110.0", - .data = "USB MTP on RAM Filesystem example\n", - .size = 34 - } + { + .handle = 1, + .parent = 0, + .allocated = true, + .association = false, + .name = "readme.txt", + .created = "20240104T111134.0", + .modified = "20241214T121110.0", + .data = "USB MTP on RAM Filesystem example\n", + .size = 34 + } }; //--------------------------------------------------------------------+ // OPERATING STATUS //--------------------------------------------------------------------+ -typedef struct -{ - // Session - uint32_t session_id; - // Association traversal - uint32_t traversal_parent; - uint32_t traversal_index; - // Object open for reading - uint32_t read_handle; - uint32_t read_pos; - // Object open for writing - uint32_t write_handle; - uint32_t write_pos; - // Unique identifier - uint32_t last_handle; +typedef struct { + // Session + uint32_t session_id; + // Association traversal + uint32_t traversal_parent; + uint32_t traversal_index; + // Object open for reading + uint32_t read_handle; + uint32_t read_pos; + // Object open for writing + uint32_t write_handle; + uint32_t write_pos; + // Unique identifier + uint32_t last_handle; } fs_operation_t; static fs_operation_t _fs_operation = { - .last_handle = 1 + .last_handle = 1 }; //--------------------------------------------------------------------+ @@ -94,481 +144,461 @@ static fs_operation_t _fs_operation = { //--------------------------------------------------------------------+ // Get pointer to object info from handle -fs_object_info_t *fs_object_get_from_handle(uint32_t handle); +fs_object_info_t* fs_object_get_from_handle(uint32_t handle); // Get the number of allocated nodes in filesystem unsigned int fs_get_object_count(void); -fs_object_info_t *fs_object_get_from_handle(uint32_t handle) -{ - fs_object_info_t *obj; - for (unsigned int i=0; iallocated && obj->handle == handle) - return obj; - } - return NULL; +fs_object_info_t* fs_object_get_from_handle(uint32_t handle) { + fs_object_info_t* obj; + for (unsigned int i = 0; i < FS_MAX_NODES; i++) { + obj = &_fs_objects[i]; + if (obj->allocated && obj->handle == handle) + return obj; + } + return NULL; } -unsigned int fs_get_object_count(void) -{ - unsigned int s = 0; - for (unsigned int i = 0; icode) { + // default: break; + // } + resp_block->len = MTP_CONTAINER_HEADER_LENGTH; + resp_block->code = (xfer_result == XFER_RESULT_SUCCESS) ? MTP_RESP_OK : MTP_RESP_GENERAL_ERROR; + + tud_mtp_response_send(resp_block); + + return 0; +} + +int32_t tud_mtp_command_received_cb(uint8_t idx, mtp_generic_container_t* cmd_block, mtp_generic_container_t* out_block) { + (void)idx; + switch (cmd_block->code) { + case MTP_OP_GET_DEVICE_INFO: { + // Device info is already prepared up to playback formats. Application need to add string fields + mtp_container_add_string(out_block, TU_ARRAY_SIZE(dev_info_manufacturer), dev_info_manufacturer); + mtp_container_add_string(out_block, TU_ARRAY_SIZE(dev_info_model), dev_info_model); + mtp_container_add_string(out_block, TU_ARRAY_SIZE(dev_info_version), dev_info_version); + mtp_container_add_string(out_block, TU_ARRAY_SIZE(dev_info_serial), dev_info_serial); + + tud_mtp_data_send(out_block); + break; } - return s; + + case MTP_OP_OPEN_SESSION: + out_block->len = MTP_CONTAINER_HEADER_LENGTH; + out_block->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; + // TODO check if session is already opened + out_block->code = MTP_RESP_OK; + + tud_mtp_response_send(out_block); + break; + + default: return -1; + } + + return 0; } //--------------------------------------------------------------------+ // API //--------------------------------------------------------------------+ -mtp_response_t tud_mtp_storage_open_session(uint32_t *session_id) -{ - if (*session_id == 0) - { - TU_LOG1("Invalid session ID\r\n"); - return MTP_RESP_INVALID_PARAMETER; - } - if (_fs_operation.session_id != 0) - { - *session_id = _fs_operation.session_id; - TU_LOG1("ERR: Session %ld already open\r\n", _fs_operation.session_id); - return MTP_RESP_SESSION_ALREADY_OPEN; - } - _fs_operation.session_id = *session_id; - TU_LOG1("Open session with id %ld\r\n", _fs_operation.session_id); - return MTP_RESP_OK; +mtp_response_t tud_mtp_storage_open_session(uint32_t* session_id) { + if (*session_id == 0) { + TU_LOG1("Invalid session ID\r\n"); + return MTP_RESP_INVALID_PARAMETER; + } + if (_fs_operation.session_id != 0) { + *session_id = _fs_operation.session_id; + TU_LOG1("ERR: Session %ld already open\r\n", _fs_operation.session_id); + return MTP_RESP_SESSION_ALREADY_OPEN; + } + _fs_operation.session_id = *session_id; + TU_LOG1("Open session with id %ld\r\n", _fs_operation.session_id); + return MTP_RESP_OK; } -mtp_response_t tud_mtp_storage_close_session(uint32_t session_id) -{ - if (session_id != _fs_operation.session_id) - { - TU_LOG1("ERR: Session %ld not open\r\n", session_id); - return MTP_RESP_SESSION_NOT_OPEN; - } - _fs_operation.session_id = 0; - TU_LOG1("Session closed\r\n"); - return MTP_RESP_OK; +mtp_response_t tud_mtp_storage_close_session(uint32_t session_id) { + if (session_id != _fs_operation.session_id) { + TU_LOG1("ERR: Session %ld not open\r\n", session_id); + return MTP_RESP_SESSION_NOT_OPEN; + } + _fs_operation.session_id = 0; + TU_LOG1("Session closed\r\n"); + return MTP_RESP_OK; } -mtp_response_t tud_mtp_get_storage_id(uint32_t *storage_id) -{ - if (_fs_operation.session_id == 0) - { - TU_LOG1("ERR: Session not open\r\n"); - return MTP_RESP_SESSION_NOT_OPEN; - } - *storage_id = STORAGE_ID(0x0001, 0x0001); - TU_LOG1("Retrieved storage identifier %ld\r\n", *storage_id); - return MTP_RESP_OK; +mtp_response_t tud_mtp_get_storage_id(uint32_t* storage_id) { + if (_fs_operation.session_id == 0) { + TU_LOG1("ERR: Session not open\r\n"); + return MTP_RESP_SESSION_NOT_OPEN; + } + *storage_id = STORAGE_ID(0x0001, 0x0001); + TU_LOG1("Retrieved storage identifier %ld\r\n", *storage_id); + return MTP_RESP_OK; } -mtp_response_t tud_mtp_get_storage_info(uint32_t storage_id, mtp_storage_info_t *info) -{ - if (_fs_operation.session_id == 0) - { - TU_LOG1("ERR: Session not open\r\n"); - return MTP_RESP_SESSION_NOT_OPEN; - } - if (storage_id != STORAGE_ID(0x0001, 0x0001)) - { - TU_LOG1("ERR: Unexpected storage id %ld\r\n", storage_id); - return MTP_RESP_INVALID_STORAGE_ID; - } - info->storage_type = MTP_STORAGE_TYPE_FIXED_RAM; - info->filesystem_type = MTP_FILESYSTEM_TYPE_GENERIC_HIERARCHICAL; - info->access_capability = MTP_ACCESS_CAPABILITY_READ_WRITE; - info->max_capacity_in_bytes = FS_MAX_NODES * FS_MAX_NODE_BYTES; - info->free_space_in_objects = FS_MAX_NODES - fs_get_object_count(); - info->free_space_in_bytes = info->free_space_in_objects * FS_MAX_NODE_BYTES; - mtpd_gct_append_wstring(MTPD_STORAGE_DESCRIPTION); - mtpd_gct_append_wstring(MTPD_VOLUME_IDENTIFIER); - return MTP_RESP_OK; +mtp_response_t tud_mtp_get_storage_info(uint32_t storage_id, mtp_storage_info_t* info) { + if (_fs_operation.session_id == 0) { + TU_LOG1("ERR: Session not open\r\n"); + return MTP_RESP_SESSION_NOT_OPEN; + } + if (storage_id != STORAGE_ID(0x0001, 0x0001)) { + TU_LOG1("ERR: Unexpected storage id %ld\r\n", storage_id); + return MTP_RESP_INVALID_STORAGE_ID; + } + info->storage_type = MTP_STORAGE_TYPE_FIXED_RAM; + info->filesystem_type = MTP_FILESYSTEM_TYPE_GENERIC_HIERARCHICAL; + info->access_capability = MTP_ACCESS_CAPABILITY_READ_WRITE; + info->max_capacity_in_bytes = FS_MAX_NODES * FS_MAX_NODE_BYTES; + info->free_space_in_objects = FS_MAX_NODES - fs_get_object_count(); + info->free_space_in_bytes = info->free_space_in_objects * FS_MAX_NODE_BYTES; + mtpd_gct_append_wstring(MTPD_STORAGE_DESCRIPTION); + mtpd_gct_append_wstring(MTPD_VOLUME_IDENTIFIER); + return MTP_RESP_OK; } -mtp_response_t tud_mtp_storage_format(uint32_t storage_id) -{ - if (_fs_operation.session_id == 0) - { - TU_LOG1("ERR: Session not open\r\n"); - return MTP_RESP_SESSION_NOT_OPEN; - } - if (storage_id != STORAGE_ID(0x0001, 0x0001)) - { - TU_LOG1("ERR: Unexpected storage id %ld\r\n", storage_id); - return MTP_RESP_INVALID_STORAGE_ID; - } +mtp_response_t tud_mtp_storage_format(uint32_t storage_id) { + if (_fs_operation.session_id == 0) { + TU_LOG1("ERR: Session not open\r\n"); + return MTP_RESP_SESSION_NOT_OPEN; + } + if (storage_id != STORAGE_ID(0x0001, 0x0001)) { + TU_LOG1("ERR: Unexpected storage id %ld\r\n", storage_id); + return MTP_RESP_INVALID_STORAGE_ID; + } - // Simply deallocate all entries - for (unsigned int i=0; iallocated && obj->parent == parent_object_handle) - { - _fs_operation.traversal_index = i+1; - *next_child_handle = obj->handle; - TU_LOG1("Association %ld -> child %ld\r\n", parent_object_handle, obj->handle); - return MTP_RESP_OK; - } - } - TU_LOG1("Association traversal completed\r\n"); + if (parent_object_handle != _fs_operation.traversal_parent) { + _fs_operation.traversal_parent = parent_object_handle; _fs_operation.traversal_index = 0; - *next_child_handle = 0; - return MTP_RESP_OK; + } + + for (unsigned int i = _fs_operation.traversal_index; i < FS_MAX_NODES; i++) { + obj = &_fs_objects[i]; + if (obj->allocated && obj->parent == parent_object_handle) { + _fs_operation.traversal_index = i + 1; + *next_child_handle = obj->handle; + TU_LOG1("Association %ld -> child %ld\r\n", parent_object_handle, obj->handle); + return MTP_RESP_OK; + } + } + TU_LOG1("Association traversal completed\r\n"); + _fs_operation.traversal_index = 0; + *next_child_handle = 0; + return MTP_RESP_OK; } -mtp_response_t tud_mtp_storage_object_write_info(uint32_t storage_id, uint32_t parent_object, uint32_t *new_object_handle, const mtp_object_info_t *info) -{ - fs_object_info_t *obj = NULL; +mtp_response_t tud_mtp_storage_object_write_info(uint32_t storage_id, uint32_t parent_object, + uint32_t* new_object_handle, const mtp_object_info_t* info) { + fs_object_info_t* obj = NULL; - if (_fs_operation.session_id == 0) - { - TU_LOG1("ERR: Session not open\r\n"); - return MTP_RESP_SESSION_NOT_OPEN; + if (_fs_operation.session_id == 0) { + TU_LOG1("ERR: Session not open\r\n"); + return MTP_RESP_SESSION_NOT_OPEN; + } + // Accept command on default storage + if (storage_id != 0xFFFFFFFF && storage_id != STORAGE_ID(0x0001, 0x0001)) { + TU_LOG1("ERR: Unexpected storage id %ld\r\n", storage_id); + return MTP_RESP_INVALID_STORAGE_ID; + } + + if (info->object_compressed_size > FS_MAX_NODE_BYTES) { + TU_LOG1("Object size %ld is more than maximum %ld\r\n", info->object_compressed_size, FS_MAX_NODE_BYTES); + return MTP_RESP_STORE_FULL; + } + + // Request for objects with no parent (0xFFFFFFFF) are considered root objects + if (parent_object == 0xFFFFFFFF) + parent_object = 0; + + // Ensure we are not creating an orphaned object outside root + if (parent_object != 0) { + obj = fs_object_get_from_handle(parent_object); + if (obj == NULL) { + TU_LOG1("Parent %ld does not exist\r\n", parent_object); + return MTP_RESP_INVALID_PARENT_OBJECT; } - // Accept command on default storage - if (storage_id != 0xFFFFFFFF && storage_id != STORAGE_ID(0x0001, 0x0001)) - { - TU_LOG1("ERR: Unexpected storage id %ld\r\n", storage_id); - return MTP_RESP_INVALID_STORAGE_ID; + if (!obj->association) { + TU_LOG1("Parent %ld is not an association\r\n", parent_object); + return MTP_RESP_INVALID_PARENT_OBJECT; } + } - if (info->object_compressed_size > FS_MAX_NODE_BYTES) - { - TU_LOG1("Object size %ld is more than maximum %ld\r\n", info->object_compressed_size, FS_MAX_NODE_BYTES); - return MTP_RESP_STORE_FULL; + // Search for first free object + for (unsigned int i = 0; i < FS_MAX_NODES; i++) { + if (!_fs_objects[i].allocated) { + obj = &_fs_objects[i]; + break; } + } - // Request for objects with no parent (0xFFFFFFFF) are considered root objects - if (parent_object == 0xFFFFFFFF) - parent_object = 0; + if (obj == NULL) { + TU_LOG1("No space left on device\r\n"); + return MTP_RESP_STORE_FULL; + } - // Ensure we are not creating an orphaned object outside root - if (parent_object != 0) - { - obj = fs_object_get_from_handle(parent_object); - if (obj == NULL) - { - TU_LOG1("Parent %ld does not exist\r\n", parent_object); - return MTP_RESP_INVALID_PARENT_OBJECT; - } - if (!obj->association) - { - TU_LOG1("Parent %ld is not an association\r\n", parent_object); - return MTP_RESP_INVALID_PARENT_OBJECT; - } - } + // Fill-in structure + obj->allocated = true; + obj->handle = ++_fs_operation.last_handle; + obj->parent = parent_object; + obj->size = info->object_compressed_size; + obj->association = info->object_format == MTP_OBJ_FORMAT_ASSOCIATION; - // Search for first free object - for (unsigned int i=0; iname, FS_MAX_NODE_NAME_LEN); + mtpd_gct_get_string(&offset_data, obj->created, FS_ISODATETIME_LEN); + mtpd_gct_get_string(&offset_data, obj->modified, FS_ISODATETIME_LEN); - if (obj == NULL) - { - TU_LOG1("No space left on device\r\n"); - return MTP_RESP_STORE_FULL; - } - - // Fill-in structure - obj->allocated = true; - obj->handle = ++_fs_operation.last_handle; - obj->parent = parent_object; - obj->size = info->object_compressed_size; - obj->association = info->object_format == MTP_OBJ_FORMAT_ASSOCIATION; - - // Extract variable data - uint16_t offset_data = sizeof(mtp_object_info_t); - mtpd_gct_get_string(&offset_data, obj->name, FS_MAX_NODE_NAME_LEN); - mtpd_gct_get_string(&offset_data, obj->created, FS_ISODATETIME_LEN); - mtpd_gct_get_string(&offset_data, obj->modified, FS_ISODATETIME_LEN); - - TU_LOG1("Create %s %s with handle %ld, parent %ld and size %ld\r\n", - obj->association ? "association" : "object", - obj->name, obj->handle, obj->parent, obj->size); - *new_object_handle = obj->handle; - // Initialize operation - _fs_operation.write_handle = obj->handle; - _fs_operation.write_pos = 0; - return MTP_RESP_OK; + TU_LOG1("Create %s %s with handle %ld, parent %ld and size %ld\r\n", + obj->association ? "association" : "object", + obj->name, obj->handle, obj->parent, obj->size); + *new_object_handle = obj->handle; + // Initialize operation + _fs_operation.write_handle = obj->handle; + _fs_operation.write_pos = 0; + return MTP_RESP_OK; } -mtp_response_t tud_mtp_storage_object_read_info(uint32_t object_handle, mtp_object_info_t *info) -{ - const fs_object_info_t *obj; +mtp_response_t tud_mtp_storage_object_read_info(uint32_t object_handle, mtp_object_info_t* info) { + const fs_object_info_t* obj; - if (_fs_operation.session_id == 0) - { - TU_LOG1("ERR: Session not open\r\n"); - return MTP_RESP_SESSION_NOT_OPEN; - } + if (_fs_operation.session_id == 0) { + TU_LOG1("ERR: Session not open\r\n"); + return MTP_RESP_SESSION_NOT_OPEN; + } - obj = fs_object_get_from_handle(object_handle); - if (obj == NULL) - { - TU_LOG1("ERR: Object with handle %ld does not exist\r\n", object_handle); - return MTP_RESP_INVALID_OBJECT_HANDLE; - } + obj = fs_object_get_from_handle(object_handle); + if (obj == NULL) { + TU_LOG1("ERR: Object with handle %ld does not exist\r\n", object_handle); + return MTP_RESP_INVALID_OBJECT_HANDLE; + } - memset(info, 0, sizeof(mtp_object_info_t)); - info->storage_id = STORAGE_ID(0x0001, 0x0001); - if (obj->association) - { - info->object_format = MTP_OBJ_FORMAT_ASSOCIATION; - info->protection_status = MTP_PROTECTION_STATUS_NO_PROTECTION; - info->object_compressed_size = 0; - info->association_type = MTP_ASSOCIATION_UNDEFINED; - } - else - { - info->object_format = MTP_OBJ_FORMAT_UNDEFINED; - info->protection_status = MTP_PROTECTION_STATUS_NO_PROTECTION; - info->object_compressed_size = obj->size; - info->association_type = MTP_ASSOCIATION_UNDEFINED; - } - info->thumb_format = MTP_OBJ_FORMAT_UNDEFINED; - info->parent_object = obj->parent; + memset(info, 0, sizeof(mtp_object_info_t)); + info->storage_id = STORAGE_ID(0x0001, 0x0001); + if (obj->association) { + info->object_format = MTP_OBJ_FORMAT_ASSOCIATION; + info->protection_status = MTP_PROTECTION_STATUS_NO_PROTECTION; + info->object_compressed_size = 0; + info->association_type = MTP_ASSOCIATION_UNDEFINED; + } else { + info->object_format = MTP_OBJ_FORMAT_UNDEFINED; + info->protection_status = MTP_PROTECTION_STATUS_NO_PROTECTION; + info->object_compressed_size = obj->size; + info->association_type = MTP_ASSOCIATION_UNDEFINED; + } + info->thumb_format = MTP_OBJ_FORMAT_UNDEFINED; + info->parent_object = obj->parent; - mtpd_gct_append_wstring(obj->name); - mtpd_gct_append_wstring(obj->created); // date_created - mtpd_gct_append_wstring(obj->modified); // date_modified - mtpd_gct_append_wstring(""); // keywords, not used + mtpd_gct_append_wstring(obj->name); + mtpd_gct_append_wstring(obj->created); // date_created + mtpd_gct_append_wstring(obj->modified); // date_modified + mtpd_gct_append_wstring(""); // keywords, not used - TU_LOG1("Retrieve object %s with handle %ld\r\n", obj->name, obj->handle); + TU_LOG1("Retrieve object %s with handle %ld\r\n", obj->name, obj->handle); - return MTP_RESP_OK; + return MTP_RESP_OK; } -mtp_response_t tud_mtp_storage_object_write(uint32_t object_handle, const uint8_t *buffer, uint32_t size) -{ - fs_object_info_t *obj; +mtp_response_t tud_mtp_storage_object_write(uint32_t object_handle, const uint8_t* buffer, uint32_t size) { + fs_object_info_t* obj; - obj = fs_object_get_from_handle(object_handle); - if (obj == NULL) - { - TU_LOG1("ERR: Object with handle %ld does not exist\r\n", object_handle); - return MTP_RESP_INVALID_OBJECT_HANDLE; - } - // It's a requirement that this command is preceded by a write info - if (object_handle != _fs_operation.write_handle) - { - TU_LOG1("ERR: Object %ld not open for write\r\n", object_handle); - return MTP_RESP_NO_VALID_OBJECTINFO; - } + obj = fs_object_get_from_handle(object_handle); + if (obj == NULL) { + TU_LOG1("ERR: Object with handle %ld does not exist\r\n", object_handle); + return MTP_RESP_INVALID_OBJECT_HANDLE; + } + // It's a requirement that this command is preceded by a write info + if (object_handle != _fs_operation.write_handle) { + TU_LOG1("ERR: Object %ld not open for write\r\n", object_handle); + return MTP_RESP_NO_VALID_OBJECTINFO; + } - TU_LOG1("Write object %ld: data chunk at %ld/%ld bytes at offset %ld\r\n", object_handle, _fs_operation.write_pos, obj->size, size); - TU_ASSERT(obj->size >= _fs_operation.write_pos + size, MTP_RESP_INCOMPLETE_TRANSFER); - if (_fs_operation.write_pos + size < FS_MAX_NODE_BYTES) - memcpy(&obj->data[_fs_operation.write_pos], buffer, size); - _fs_operation.write_pos += size; - // Write operation completed - if (_fs_operation.write_pos == obj->size) - { - _fs_operation.write_handle = 0; - _fs_operation.write_pos = 0; - } - return MTP_RESP_OK; -} - -mtp_response_t tud_mtp_storage_object_size(uint32_t object_handle, uint32_t *size) -{ - const fs_object_info_t *obj; - obj = fs_object_get_from_handle(object_handle); - if (obj == NULL) - { - TU_LOG1("ERR: Object with handle %ld does not exist\r\n", object_handle); - return MTP_RESP_INVALID_OBJECT_HANDLE; - } - *size = obj->size; - return MTP_RESP_OK; -} - -mtp_response_t tud_mtp_storage_object_read(uint32_t object_handle, void *buffer, uint32_t buffer_size, uint32_t *read_count) -{ - const fs_object_info_t *obj; - - obj = fs_object_get_from_handle(object_handle); - - if (obj == NULL) - { - TU_LOG1("ERR: Object with handle %ld does not exist\r\n", object_handle); - return MTP_RESP_INVALID_OBJECT_HANDLE; - } - // It's not a requirement that this command is preceded by a read info - if (object_handle != _fs_operation.read_handle) - { - TU_LOG1("ERR: Object %ld not open for read\r\n", object_handle); - _fs_operation.read_handle = object_handle; - _fs_operation.read_pos = 0; - } - - if (obj->size - _fs_operation.read_pos > buffer_size) - { - TU_LOG1("Read object %ld: %ld bytes at offset %ld\r\n", object_handle, buffer_size, _fs_operation.read_pos); - *read_count = buffer_size; - if (_fs_operation.read_pos + buffer_size < FS_MAX_NODE_BYTES) { - memcpy(buffer, &obj->data[_fs_operation.read_pos], *read_count); - } - _fs_operation.read_pos += *read_count; - } - else - { - TU_LOG1("Read object %ld: %ld bytes at offset %ld\r\n", object_handle, obj->size - _fs_operation.read_pos, _fs_operation.read_pos); - *read_count = obj->size - _fs_operation.read_pos; - if (_fs_operation.read_pos + *read_count < FS_MAX_NODE_BYTES) { - memcpy(buffer, &obj->data[_fs_operation.read_pos], *read_count); - } - // Read operation completed - _fs_operation.read_handle = 0; - _fs_operation.read_pos = 0; - } - return MTP_RESP_OK; -} - -mtp_response_t tud_mtp_storage_object_move(uint32_t object_handle, uint32_t new_parent_object_handle) -{ - fs_object_info_t *obj; - - if (new_parent_object_handle == 0xFFFFFFFF) - new_parent_object_handle = 0; - - // Ensure we are not moving to an nonexisting parent - if (new_parent_object_handle != 0) - { - obj = fs_object_get_from_handle(new_parent_object_handle); - if (obj == NULL) - { - TU_LOG1("Parent %ld does not exist\r\n", new_parent_object_handle); - return MTP_RESP_INVALID_PARENT_OBJECT; - } - if (!obj->association) - { - TU_LOG1("Parent %ld is not an association\r\n", new_parent_object_handle); - return MTP_RESP_INVALID_PARENT_OBJECT; - } - } - - obj = fs_object_get_from_handle(object_handle); - - if (obj == NULL) - { - TU_LOG1("ERR: Object with handle %ld does not exist\r\n", object_handle); - return MTP_RESP_INVALID_OBJECT_HANDLE; - } - TU_LOG1("Move object %ld to new parent %ld\r\n", object_handle, new_parent_object_handle); - obj->parent = new_parent_object_handle; - return MTP_RESP_OK; -} - -mtp_response_t tud_mtp_storage_object_delete(uint32_t object_handle) -{ - fs_object_info_t *obj; - - if (_fs_operation.session_id == 0) - { - TU_LOG1("ERR: Session not open\r\n"); - return MTP_RESP_SESSION_NOT_OPEN; - } - - if (object_handle == 0xFFFFFFFF) - object_handle = 0; - - if (object_handle != 0) - { - obj = fs_object_get_from_handle(object_handle); - - if (obj == NULL) - { - TU_LOG1("ERR: Object with handle %ld does not exist\r\n", object_handle); - return MTP_RESP_INVALID_OBJECT_HANDLE; - } - obj->allocated = false; - TU_LOG1("Delete object with handle %ld\r\n", object_handle); - } - - if (object_handle == 0 || obj->association) - { - // Delete also children - for (unsigned int i=0; iallocated && obj->parent == object_handle) - { - tud_mtp_storage_object_delete(obj->handle); - } - } - } - - return MTP_RESP_OK; -} - -void tud_mtp_storage_object_done(void) -{ -} - -void tud_mtp_storage_cancel(void) -{ - fs_object_info_t *obj; - - _fs_operation.traversal_parent = 0; - _fs_operation.traversal_index = 0; - _fs_operation.read_handle = 0; - _fs_operation.read_pos = 0; - // If write operation is canceled, discard object - if (_fs_operation.write_handle) - { - obj = fs_object_get_from_handle(_fs_operation.write_handle); - if (obj) - obj->allocated = false; - } + TU_LOG1("Write object %ld: data chunk at %ld/%ld bytes at offset %ld\r\n", object_handle, _fs_operation.write_pos, + obj->size, size); + TU_ASSERT(obj->size >= _fs_operation.write_pos + size, MTP_RESP_INCOMPLETE_TRANSFER); + if (_fs_operation.write_pos + size < FS_MAX_NODE_BYTES) + memcpy(&obj->data[_fs_operation.write_pos], buffer, size); + _fs_operation.write_pos += size; + // Write operation completed + if (_fs_operation.write_pos == obj->size) { _fs_operation.write_handle = 0; _fs_operation.write_pos = 0; + } + return MTP_RESP_OK; } -void tud_mtp_storage_reset(void) -{ - tud_mtp_storage_cancel(); - _fs_operation.session_id = 0; +mtp_response_t tud_mtp_storage_object_size(uint32_t object_handle, uint32_t* size) { + const fs_object_info_t* obj; + obj = fs_object_get_from_handle(object_handle); + if (obj == NULL) { + TU_LOG1("ERR: Object with handle %ld does not exist\r\n", object_handle); + return MTP_RESP_INVALID_OBJECT_HANDLE; + } + *size = obj->size; + return MTP_RESP_OK; +} + +mtp_response_t tud_mtp_storage_object_read(uint32_t object_handle, void* buffer, uint32_t buffer_size, + uint32_t* read_count) { + const fs_object_info_t* obj; + + obj = fs_object_get_from_handle(object_handle); + + if (obj == NULL) { + TU_LOG1("ERR: Object with handle %ld does not exist\r\n", object_handle); + return MTP_RESP_INVALID_OBJECT_HANDLE; + } + // It's not a requirement that this command is preceded by a read info + if (object_handle != _fs_operation.read_handle) { + TU_LOG1("ERR: Object %ld not open for read\r\n", object_handle); + _fs_operation.read_handle = object_handle; + _fs_operation.read_pos = 0; + } + + if (obj->size - _fs_operation.read_pos > buffer_size) { + TU_LOG1("Read object %ld: %ld bytes at offset %ld\r\n", object_handle, buffer_size, _fs_operation.read_pos); + *read_count = buffer_size; + if (_fs_operation.read_pos + buffer_size < FS_MAX_NODE_BYTES) { + memcpy(buffer, &obj->data[_fs_operation.read_pos], *read_count); + } + _fs_operation.read_pos += *read_count; + } else { + TU_LOG1("Read object %ld: %ld bytes at offset %ld\r\n", object_handle, obj->size - _fs_operation.read_pos, + _fs_operation.read_pos); + *read_count = obj->size - _fs_operation.read_pos; + if (_fs_operation.read_pos + *read_count < FS_MAX_NODE_BYTES) { + memcpy(buffer, &obj->data[_fs_operation.read_pos], *read_count); + } + // Read operation completed + _fs_operation.read_handle = 0; + _fs_operation.read_pos = 0; + } + return MTP_RESP_OK; +} + +mtp_response_t tud_mtp_storage_object_move(uint32_t object_handle, uint32_t new_parent_object_handle) { + fs_object_info_t* obj; + + if (new_parent_object_handle == 0xFFFFFFFF) + new_parent_object_handle = 0; + + // Ensure we are not moving to an nonexisting parent + if (new_parent_object_handle != 0) { + obj = fs_object_get_from_handle(new_parent_object_handle); + if (obj == NULL) { + TU_LOG1("Parent %ld does not exist\r\n", new_parent_object_handle); + return MTP_RESP_INVALID_PARENT_OBJECT; + } + if (!obj->association) { + TU_LOG1("Parent %ld is not an association\r\n", new_parent_object_handle); + return MTP_RESP_INVALID_PARENT_OBJECT; + } + } + + obj = fs_object_get_from_handle(object_handle); + + if (obj == NULL) { + TU_LOG1("ERR: Object with handle %ld does not exist\r\n", object_handle); + return MTP_RESP_INVALID_OBJECT_HANDLE; + } + TU_LOG1("Move object %ld to new parent %ld\r\n", object_handle, new_parent_object_handle); + obj->parent = new_parent_object_handle; + return MTP_RESP_OK; +} + +mtp_response_t tud_mtp_storage_object_delete(uint32_t object_handle) { + fs_object_info_t* obj; + + if (_fs_operation.session_id == 0) { + TU_LOG1("ERR: Session not open\r\n"); + return MTP_RESP_SESSION_NOT_OPEN; + } + + if (object_handle == 0xFFFFFFFF) + object_handle = 0; + + if (object_handle != 0) { + obj = fs_object_get_from_handle(object_handle); + + if (obj == NULL) { + TU_LOG1("ERR: Object with handle %ld does not exist\r\n", object_handle); + return MTP_RESP_INVALID_OBJECT_HANDLE; + } + obj->allocated = false; + TU_LOG1("Delete object with handle %ld\r\n", object_handle); + } + + if (object_handle == 0 || obj->association) { + // Delete also children + for (unsigned int i = 0; i < FS_MAX_NODES; i++) { + obj = &_fs_objects[i]; + if (obj->allocated && obj->parent == object_handle) { + tud_mtp_storage_object_delete(obj->handle); + } + } + } + + return MTP_RESP_OK; +} + +void tud_mtp_storage_object_done(void) { +} + +void tud_mtp_storage_cancel(void) { + fs_object_info_t* obj; + + _fs_operation.traversal_parent = 0; + _fs_operation.traversal_index = 0; + _fs_operation.read_handle = 0; + _fs_operation.read_pos = 0; + // If write operation is canceled, discard object + if (_fs_operation.write_handle) { + obj = fs_object_get_from_handle(_fs_operation.write_handle); + if (obj) + obj->allocated = false; + } + _fs_operation.write_handle = 0; + _fs_operation.write_pos = 0; +} + +void tud_mtp_storage_reset(void) { + tud_mtp_storage_cancel(); + _fs_operation.session_id = 0; } diff --git a/src/class/mtp/mtp.h b/src/class/mtp/mtp.h index c9d2b27a9..d61f329c4 100644 --- a/src/class/mtp/mtp.h +++ b/src/class/mtp/mtp.h @@ -38,7 +38,6 @@ extern "C" { #endif -#define TU_ARRAY_LEN(a) (sizeof(a)/sizeof(a[0])) #define STORAGE_ID(physical_id, logical_id) ( (((uint32_t)physical_id & 0xFFFF) << 16) | ((uint32_t)logical_id & 0x0000FFFF) ) typedef uint16_t wchar16_t; @@ -583,19 +582,32 @@ typedef enum } mtp_object_handles_t; // Datatypes -typedef enum -{ - MTP_TYPE_UNDEFINED = 0x0000u, - MTP_TYPE_INT8 = 0x0001u, - MTP_TYPE_UINT8 = 0x0002u, - MTP_TYPE_INT16 = 0x0003u, - MTP_TYPE_UINT16 = 0x0004u, - MTP_TYPE_INT32 = 0x0005u, - MTP_TYPE_UINT32 = 0x0006u, - MTP_TYPE_INT64 = 0x0007u, - MTP_TYPE_UINT64 = 0x0008u, - MTP_TYPE_STR = 0xFFFFu, -} mtp_datatypes_t; +typedef enum { + MTP_DATA_TYPE_UNDEFINED = 0x0000u, + // scalars + MTP_DATA_TYPE_INT8 = 0x0001u, + MTP_DATA_TYPE_UINT8 = 0x0002u, + MTP_DATA_TYPE_INT16 = 0x0003u, + MTP_DATA_TYPE_UINT16 = 0x0004u, + MTP_DATA_TYPE_INT32 = 0x0005u, + MTP_DATA_TYPE_UINT32 = 0x0006u, + MTP_DATA_TYPE_INT64 = 0x0007u, + MTP_DATA_TYPE_UINT64 = 0x0008u, + MTP_DATA_TYPE_INT128 = 0x0009u, + MTP_DATA_TYPE_UINT128 = 0x000Au, + // array + MTP_DATA_TYPE_AINT8 = 0x4001u, + MTP_DATA_TYPE_AUINT8 = 0x4002u, + MTP_DATA_TYPE_AINT16 = 0x4003u, + MTP_DATA_TYPE_AUINT16 = 0x4004u, + MTP_DATA_TYPE_AINT32 = 0x4005u, + MTP_DATA_TYPE_AUINT32 = 0x4006u, + MTP_DATA_TYPE_AINT64 = 0x4007u, + MTP_DATA_TYPE_AUINT64 = 0x4008u, + MTP_DATA_TYPE_AINT128 = 0x4009u, + MTP_DATA_TYPE_AUINT128 = 0x400Au, + MTP_DATA_TYPE_STR = 0xFFFFu, +} mtp_data_type_t; // Get/Set typedef enum @@ -707,46 +719,24 @@ typedef struct TU_ATTR_PACKED { uint32_t data[MTP_MAX_PACKET_SIZE / sizeof(uint32_t)]; } mtp_generic_container_t; -// DeviceInfo Dataset -#define MTP_EXTENSIONS "microsoft.com: 1.0; " +#define mtp_string_t(_nchars) \ + struct TU_ATTR_PACKED { \ + uint8_t count; /* in characters including null */ \ + uint16_t utf16[_nchars]; \ + } + +#define mtp_array_t(_type, _count) \ + struct TU_ATTR_PACKED { \ + uint32_t count; \ + _type arr[_count];\ + } + +#define mtp_auint16_t(_count) mtp_array_t(uint16_t, _count) + typedef struct TU_ATTR_PACKED { - uint16_t standard_version; - uint32_t mtp_vendor_extension_id; - uint16_t mtp_version; - uint8_t mtp_extensions_len; - wchar16_t mtp_extensions[TU_ARRAY_LEN(MTP_EXTENSIONS)] TU_ATTR_PACKED; - - uint16_t functional_mode; - /* Operations supported */ - uint32_t operations_supported_len; - uint16_t operations_supported[TU_ARRAY_LEN(mtp_operations_supported)] TU_ATTR_PACKED; - /* Events supported */ - uint32_t events_supported_len; - uint16_t events_supported[TU_ARRAY_LEN(mtp_events_supported)] TU_ATTR_PACKED; - /* Device properties supported */ - uint32_t device_properties_supported_len; - uint16_t device_properties_supported[TU_ARRAY_LEN(mtp_device_properties_supported)] TU_ATTR_PACKED; - /* Capture formats */ - uint32_t capture_formats_len; - uint16_t capture_formats[TU_ARRAY_LEN(mtp_capture_formats)] TU_ATTR_PACKED; - /* Playback formats */ - uint32_t playback_formats_len; - uint16_t playback_formats[TU_ARRAY_LEN(mtp_playback_formats)] TU_ATTR_PACKED; -} mtp_device_info_t; -// The following fields will be dynamically added to the struct at runtime: -// - wstring manufacturer -// - wstring model -// - wstring device_version -// - wstring serial_number - - -#define MTP_STRING_DEF(name, string) \ - uint8_t name##_len; \ - wchar16_t name[TU_ARRAY_LEN(string)]; - -#define MTP_ARRAY_DEF(name, array) \ - uint16_t name##_len; \ - typeof(name) name[TU_ARRAY_LEN(array)]; + uint8_t count; + uint16_t utf16[]; +} mtp_flexible_string_t; // StorageInfo dataset typedef struct TU_ATTR_PACKED { @@ -813,6 +803,95 @@ typedef struct TU_ATTR_PACKED { uint32_t parent_object_handle; } mtp_basic_object_info_t; +//--------------------------------------------------------------------+ +// Generic Container function +//--------------------------------------------------------------------+ + +TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add(mtp_generic_container_t* p_container, mtp_data_type_t type, const void* data) { + TU_VERIFY(type != MTP_DATA_TYPE_UNDEFINED, 0); + uint8_t scalar_size; // size of single scalar + uint8_t count_width; // size of count field (0, 1 or 4 bytes) + + if (type == MTP_DATA_TYPE_STR) { + scalar_size = 2; + count_width = 1; + } else { + uint8_t scalar_type = type & 0x3F; + count_width = (type & 0x4000u) ? 4 : 0; + scalar_size = 1u << ((scalar_type - 1u) >> 1); + } + + uint32_t data_len; + if (count_width) { + const uint32_t count = *(const uint32_t*) data; + data_len = count_width + count*scalar_size; + } else { + data_len = scalar_size; + } + + memcpy(((uint8_t*)p_container) + p_container->len, data, data_len); + p_container->len += data_len; + + return data_len; +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_field(mtp_generic_container_t* p_container, uint8_t scalar_size, uint32_t count, const void* data) { + const uint32_t prev_len = p_container->len; + uint8_t* container8 = (uint8_t*) p_container; + if (count == 0) { + // count = 0 means scalar + memcpy(container8 + p_container->len, data, scalar_size); + p_container->len += scalar_size; + } else { + tu_unaligned_write32(container8 + p_container->len, count); + p_container->len += 4; + memcpy(container8 + p_container->len, data, count * scalar_size); + } + + return p_container->len - prev_len; +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_string(mtp_generic_container_t* p_container, uint8_t count, uint16_t* utf16) { + const uint32_t prev_len = p_container->len; + uint8_t* container8 = (uint8_t*) p_container; + *(container8 + p_container->len) = count; + p_container->len += 1; + + memcpy(container8 + p_container->len, utf16, 2 * count); + p_container->len += 2 * count; + + return p_container->len - prev_len; +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_uint8(mtp_generic_container_t* p_container, uint8_t data) { + return mtp_container_add_field(p_container, sizeof(uint8_t), 0, &data); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_uint16(mtp_generic_container_t* p_container, uint16_t data) { + return mtp_container_add_field(p_container, sizeof(uint16_t), 0, &data); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_uint32(mtp_generic_container_t* p_container, uint32_t data) { + return mtp_container_add_field(p_container, sizeof(uint32_t), 0, &data); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_uint64(mtp_generic_container_t* p_container, uint64_t data) { + return mtp_container_add_field(p_container, 8, 0, &data); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_auint8(mtp_generic_container_t* p_container, uint32_t count, const uint8_t* data) { + return mtp_container_add_field(p_container, sizeof(uint8_t), count, data); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_auint16(mtp_generic_container_t* p_container, uint32_t count, const uint16_t* data) { + return mtp_container_add_field(p_container, sizeof(uint16_t), count, data); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_auint32(mtp_generic_container_t* p_container, uint32_t count, const uint32_t* data) { + return mtp_container_add_field(p_container, sizeof(uint32_t), count, data); +} + + #ifdef __cplusplus } #endif diff --git a/src/class/mtp/mtp_device.c b/src/class/mtp/mtp_device.c index 7e9574ab1..b64f36f2e 100644 --- a/src/class/mtp/mtp_device.c +++ b/src/class/mtp/mtp_device.c @@ -50,8 +50,8 @@ //--------------------------------------------------------------------+ // STRUCT //--------------------------------------------------------------------+ -typedef struct -{ +typedef struct { + uint8_t rhport; uint8_t itf_num; uint8_t ep_in; uint8_t ep_out; @@ -82,7 +82,7 @@ static mtp_phase_type_t mtpd_chk_generic(const char *func_name, const bool err_c static mtp_phase_type_t mtpd_chk_session_open(const char *func_name); // MTP commands -static mtp_phase_type_t mtpd_handle_cmd(void); +static mtp_phase_type_t mtpd_handle_cmd(mtpd_interface_t* p_mtp); static mtp_phase_type_t mtpd_handle_data(void); static mtp_phase_type_t mtpd_handle_cmd_get_device_info(void); static mtp_phase_type_t mtpd_handle_cmd_open_session(void); @@ -118,9 +118,9 @@ CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN static char _mtp_datestr[20]; // Helper //--------------------------------------------------------------------+ -static bool prepare_new_command(uint8_t rhport, mtpd_interface_t* p_mtp) { +static bool prepare_new_command(mtpd_interface_t* p_mtp) { p_mtp->phase = MTP_PHASE_IDLE; - return usbd_edpt_xfer(rhport, p_mtp->ep_out, (uint8_t *)(&_mtpd_epbuf.container), sizeof(mtp_generic_container_t)); + return usbd_edpt_xfer(p_mtp->rhport, p_mtp->ep_out, (uint8_t *)(&_mtpd_epbuf.container), sizeof(mtp_generic_container_t)); } @@ -157,6 +157,8 @@ uint16_t mtpd_open(uint8_t rhport, tusb_desc_interface_t const* itf_desc, uint16 // Max length must be at least 1 interface + 3 endpoints TU_ASSERT(itf_desc->bNumEndpoints == 3 && max_len >= mtpd_itf_size); mtpd_interface_t* p_mtp = &_mtpd_itf; + tu_memclr(p_mtp, sizeof(mtpd_interface_t)); + p_mtp->rhport = rhport; p_mtp->itf_num = itf_desc->bInterfaceNumber; // Open interrupt IN endpoint @@ -168,7 +170,7 @@ uint16_t mtpd_open(uint8_t rhport, tusb_desc_interface_t const* itf_desc, uint16 // Open endpoint pair TU_ASSERT(usbd_open_edpt_pair(rhport, tu_desc_next(ep_desc), 2, TUSB_XFER_BULK, &p_mtp->ep_out, &p_mtp->ep_in), 0); - TU_ASSERT(prepare_new_command(rhport, p_mtp), 0); + TU_ASSERT(prepare_new_command(p_mtp), 0); return mtpd_itf_size; } @@ -216,6 +218,29 @@ bool mtpd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t return true; } +bool tud_mtp_data_send(mtp_generic_container_t* data_block) { + mtpd_interface_t* p_mtp = &_mtpd_itf; + p_mtp->phase = MTP_PHASE_DATA; + p_mtp->total_len = data_block->len; + p_mtp->xferred_len = 0; + p_mtp->handled_len = 0; + p_mtp->xfer_completed = false; + + data_block->type = MTP_CONTAINER_TYPE_DATA_BLOCK; + data_block->transaction_id = p_mtp->cmd_header.transaction_id; + TU_ASSERT(usbd_edpt_xfer(p_mtp->rhport, p_mtp->ep_in, (uint8_t*) data_block, (uint16_t)data_block->len)); + return true; +} + +bool tud_mtp_response_send(mtp_generic_container_t* resp_block) { + mtpd_interface_t* p_mtp = &_mtpd_itf; + p_mtp->phase = MTP_PHASE_RESPONSE_QUEUED; + resp_block->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; + resp_block->transaction_id = p_mtp->cmd_header.transaction_id; + TU_ASSERT(usbd_edpt_xfer(p_mtp->rhport, p_mtp->ep_in, (uint8_t*) resp_block, (uint16_t)resp_block->len)); + return true; +} + // Transfer on bulk endpoints bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes) { TU_ASSERT(event == XFER_RESULT_SUCCESS); @@ -235,23 +260,25 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t p_mtp->phase = MTP_PHASE_COMMAND; TU_ATTR_FALLTHROUGH; // handle in the next case - case MTP_PHASE_COMMAND: - // Handle command block - memcpy(&p_mtp->cmd_header, p_container, sizeof(mtp_container_header_t)); - p_mtp->phase = mtpd_handle_cmd(); - if (p_mtp->phase == MTP_PHASE_DATA_IN) { - TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_in, (uint8_t*) p_container, (uint16_t)p_mtp->queued_len)); - p_mtp->total_len = p_container->len; - p_mtp->xferred_len = 0; - p_mtp->handled_len = 0; - p_mtp->xfer_completed = false; - } else if (p_mtp->phase == MTP_PHASE_DATA_OUT) { - p_mtp->xferred_len = 0; - p_mtp->handled_len = 0; - p_mtp->xfer_completed = false; - TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_out, (uint8_t*) p_container, sizeof(mtp_generic_container_t)), 0); + case MTP_PHASE_COMMAND: { + mtpd_handle_cmd(p_mtp); + break; + } + + case MTP_PHASE_DATA: { + const uint16_t bulk_mps = (tud_speed_get() == TUSB_SPEED_HIGH) ? 512 : 64; + p_mtp->xferred_len += xferred_bytes; + + // transfer complete if ZLP or short packet or overflow + if (xferred_bytes == 0 || // ZLP + (xferred_bytes & (bulk_mps - 1)) || // short packet + p_mtp->xferred_len > p_mtp->total_len) { + tud_mtp_data_complete_cb(0, &p_mtp->cmd_header, p_container, event, p_mtp->xferred_len); + } else { + TU_ASSERT(false); } break; + } case MTP_PHASE_DATA_IN: p_mtp->xferred_len += xferred_bytes; @@ -328,7 +355,7 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t case MTP_PHASE_RESPONSE_QUEUED: // response phase is complete -> prepare for new command TU_ASSERT(ep_addr == p_mtp->ep_in); - prepare_new_command(rhport, p_mtp); + prepare_new_command(p_mtp); break; case MTP_PHASE_RESPONSE: @@ -339,151 +366,16 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t } if (p_mtp->phase == MTP_PHASE_RESPONSE) { - p_mtp->phase = MTP_PHASE_RESPONSE_QUEUED; - p_container->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; - p_container->transaction_id = p_mtp->cmd_header.transaction_id; - TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_in, (uint8_t*) p_container, (uint16_t)p_container->len), 0); + // p_mtp->phase = MTP_PHASE_RESPONSE_QUEUED; + // p_container->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; + // p_container->transaction_id = p_mtp->cmd_header.transaction_id; + // TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_in, (uint8_t*) p_container, (uint16_t)p_container->len), 0); } else if (p_mtp->phase == MTP_PHASE_ERROR) { // stall both IN & OUT endpoints usbd_edpt_stall(rhport, p_mtp->ep_out); usbd_edpt_stall(rhport, p_mtp->ep_in); } -#if 0 - // IN transfer completed - if (ep_addr == p_mtp->ep_in) { - if (p_mtp->phase == MTP_PHASE_RESPONSE) { - // IN transfer completed, prepare for a new command - TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_out, (uint8_t*) p_container, CFG_MTP_EP_SIZE), 0); - p_mtp->phase = MTP_PHASE_IDLE; - } else if (p_mtp->phase == MTP_PHASE_DATA_IN) { - p_mtp->xferred_len += xferred_bytes; - p_mtp->handled_len = p_mtp->xferred_len; - - // Check if transfer completed. - if (p_mtp->xferred_len >= p_mtp->total_len && (xferred_bytes == 0 || (xferred_bytes % CFG_MTP_EP_SIZE) != 0)) { - p_mtp->phase = MTP_PHASE_RESPONSE; - p_container->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; - p_container->code = MTP_RESP_OK; - p_container->len = MTP_CONTAINER_HEADER_LENGTH; - p_container->transaction_id = p_mtp->context.transaction_id; - if (p_mtp->session_id != 0) { - p_container->data[0] = p_mtp->session_id; - p_container->len += sizeof(uint32_t); - } - TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_in, (uint8_t*) p_container, (uint16_t)p_container->len), 0); - } else { - // Send next block of DATA - // Send Zero-Length Packet - if (p_mtp->xferred_len == p_mtp->total_len) { - TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_in, ((uint8_t *)(&p_container->data)), 0 )); - } else { - p_mtp->phase = mtpd_handle_data(); - if (p_mtp->phase == MTP_PHASE_RESPONSE) { - TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_in, (uint8_t*) p_container, (uint16_t)p_container->len)); - } else { - TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_in, ((uint8_t *)(&p_container->data)), (uint16_t)p_mtp->queued_len)); - } - } - } - } else { - return false; - } - } - - if (ep_addr == p_mtp->ep_out) { - if (p_mtp->phase == MTP_PHASE_IDLE) { - // A new command has been received. Ensure this is the last of the sequence. - p_mtp->total_len = p_container->len; - // Stall in case of unexpected block - if (p_container->type != MTP_CONTAINER_TYPE_COMMAND_BLOCK) { - return false; - } - p_mtp->phase = MTP_PHASE_COMMAND; - p_mtp->total_len = p_container->len; - p_mtp->xferred_len = xferred_bytes; - p_mtp->handled_len = 0; - p_mtp->xfer_completed = false; - TU_ASSERT(p_mtp->total_len < sizeof(mtp_generic_container_t)); - } - - if (p_mtp->phase == MTP_PHASE_COMMAND) { - // A zero-length or a short packet termination is expected - if (xferred_bytes == CFG_MTP_EP_SIZE || (p_mtp->total_len - p_mtp->xferred_len) > 0) { - TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_out, (uint8_t*) p_container + p_mtp->xferred_len, (uint16_t)(p_mtp->total_len - p_mtp->xferred_len))); - } else { - // Handle command block - p_mtp->phase = mtpd_handle_cmd(); - if (p_mtp->phase == MTP_PHASE_RESPONSE) { - TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_in, (uint8_t*) p_container, (uint16_t)p_container->len)); - } else if (p_mtp->phase == MTP_PHASE_DATA_IN) { - TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_in, (uint8_t*) p_container, (uint16_t)p_mtp->queued_len)); - p_mtp->total_len = p_container->len; - p_mtp->xferred_len = 0; - p_mtp->handled_len = 0; - p_mtp->xfer_completed = false; - } else if (p_mtp->phase == MTP_PHASE_DATA_OUT) { - TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_out, (uint8_t*) p_container, sizeof(mtp_generic_container_t)), 0); - p_mtp->xferred_len = 0; - p_mtp->handled_len = 0; - p_mtp->xfer_completed = false; - } else { - usbd_edpt_stall(rhport, p_mtp->ep_out); - usbd_edpt_stall(rhport, p_mtp->ep_in); - } - } - return true; - } - - if (p_mtp->phase == MTP_PHASE_DATA_OUT) { - // First block of data - if (p_mtp->xferred_len == 0) { - p_mtp->total_len = p_container->len; - p_mtp->handled_len = 0; - p_mtp->xfer_completed = false; - } - p_mtp->xferred_len += xferred_bytes; - // Stall in case of unexpected block - if (p_container->type != MTP_CONTAINER_TYPE_DATA_BLOCK) { return false; } - - // A zero-length or a short packet termination - if (xferred_bytes < CFG_MTP_EP_SIZE) { - p_mtp->xfer_completed = true; - // Handle data block - p_mtp->phase = mtpd_handle_data(); - if (p_mtp->phase == MTP_PHASE_DATA_IN || p_mtp->phase == MTP_PHASE_RESPONSE) { - TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_in, (uint8_t*) p_container, (uint16_t)p_container->len)); - } else if (p_mtp->phase == MTP_PHASE_DATA_OUT) { - TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_out, (uint8_t*) p_container, sizeof(mtp_generic_container_t)), 0); - p_mtp->xferred_len = 0; - p_mtp->xfer_completed = false; - } else { - usbd_edpt_stall(rhport, p_mtp->ep_out); - usbd_edpt_stall(rhport, p_mtp->ep_in); - } - } else { - // Handle data block when container is full - if (p_mtp->xferred_len - p_mtp->handled_len >= MTP_MAX_PACKET_SIZE - CFG_MTP_EP_SIZE) { - p_mtp->phase = mtpd_handle_data(); - p_mtp->handled_len = p_mtp->xferred_len; - } - // Transfer completed: wait for zero-lenght packet - // Some platforms may not respect EP size and xferred_bytes may be more than CFG_MTP_EP_SIZE if - // the OUT EP is waiting for more data. Ensure we are not waiting for more than CFG_MTP_EP_SIZE. - if (p_mtp->total_len == p_mtp->xferred_len) { - TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_out, ((uint8_t *)(&p_container->data)), CFG_MTP_EP_SIZE), 0); - } else if (p_mtp->handled_len == 0) { - // First data block includes container header + container data - TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_out, (uint8_t*) p_container + p_mtp->xferred_len, (uint16_t)TU_MIN(p_mtp->total_len - p_mtp->xferred_len, CFG_MTP_EP_SIZE))); - } else { - // Successive data block includes only container data - TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_out, ((uint8_t *)(&p_container->data)) + p_mtp->xferred_len - p_mtp->handled_len, (uint16_t)TU_MIN(p_mtp->total_len - p_mtp->xferred_len, CFG_MTP_EP_SIZE))); - } - } - } - } -#endif - return true; } @@ -492,26 +384,77 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t //--------------------------------------------------------------------+ // Decode command and prepare response -mtp_phase_type_t mtpd_handle_cmd(void) { +mtp_phase_type_t mtpd_handle_cmd(mtpd_interface_t* p_mtp) { mtp_generic_container_t* p_container = &_mtpd_epbuf.container; - TU_ASSERT(p_container->type == MTP_CONTAINER_TYPE_COMMAND_BLOCK); + + mtp_generic_container_t cmd_block; // copy command block for callback + memcpy(&cmd_block, p_container, p_container->len); + memcpy(&p_mtp->cmd_header, p_container, sizeof(mtp_container_header_t)); + // p_container->len = MTP_CONTAINER_HEADER_LENGTH; // default data/response length + if (p_container->code != MTP_OP_SEND_OBJECT) { _mtpd_soi.object_handle = 0; } + mtp_phase_type_t ret = MTP_PHASE_RESPONSE; + switch (p_container->code) { - case MTP_OP_GET_DEVICE_INFO: + case MTP_OP_GET_DEVICE_INFO: { TU_LOG_DRV(" MTP command: MTP_OP_GET_DEVICE_INFO\n"); - return mtpd_handle_cmd_get_device_info(); + tud_mtp_device_info_t dev_info = { + .standard_version = 100, + .mtp_vendor_extension_id = 0xFFFFFFFFU, + .mtp_version = 100, + .mtp_extensions = { + .count = sizeof(CFG_TUD_MTP_DEVICEINFO_EXTENSIONS), + .utf16 = { 0 } + }, + .functional_mode = 0x0000, + .supported_operations = { + .count = TU_ARGS_NUM(CFG_TUD_MTP_DEVICEINFO_SUPPORTED_OPERATIONS), + .arr = { CFG_TUD_MTP_DEVICEINFO_SUPPORTED_OPERATIONS } + }, + .supported_events = { + .count = TU_ARGS_NUM(CFG_TUD_MTP_DEVICEINFO_SUPPORTED_EVENTS), + .arr = { CFG_TUD_MTP_DEVICEINFO_SUPPORTED_EVENTS } + }, + .supported_device_properties = { + .count = TU_ARGS_NUM(CFG_TUD_MTP_DEVICEINFO_SUPPORTED_DEVICE_PROPERTIES), + .arr = { CFG_TUD_MTP_DEVICEINFO_SUPPORTED_DEVICE_PROPERTIES } + }, + .capture_formats = { + .count = TU_ARGS_NUM(CFG_TUD_MTP_DEVICEINFO_CAPTURE_FORMATS), + .arr = { CFG_TUD_MTP_DEVICEINFO_CAPTURE_FORMATS } + }, + .playback_formats = { + .count = TU_ARGS_NUM(CFG_TUD_MTP_DEVICEINFO_PLAYBACK_FORMATS), + .arr = { CFG_TUD_MTP_DEVICEINFO_PLAYBACK_FORMATS } + } + }; + for (uint8_t i=0; i < dev_info.mtp_extensions.count; i++) { + dev_info.mtp_extensions.utf16[i] = (uint16_t)CFG_TUD_MTP_DEVICEINFO_EXTENSIONS[i]; + } + p_container->len = MTP_CONTAINER_HEADER_LENGTH + sizeof(tud_mtp_device_info_t); + p_container->type = MTP_CONTAINER_TYPE_DATA_BLOCK; + p_container->code = MTP_OP_GET_DEVICE_INFO; + memcpy(p_container->data, &dev_info, sizeof(tud_mtp_device_info_t)); + + ret = MTP_PHASE_RESPONSE; + break; + } + case MTP_OP_OPEN_SESSION: TU_LOG_DRV(" MTP command: MTP_OP_OPEN_SESSION\n"); - return mtpd_handle_cmd_open_session(); + break; + case MTP_OP_CLOSE_SESSION: TU_LOG_DRV(" MTP command: MTP_OP_CLOSE_SESSION\n"); return mtpd_handle_cmd_close_session(); + case MTP_OP_GET_STORAGE_IDS: TU_LOG_DRV(" MTP command: MTP_OP_GET_STORAGE_IDS\n"); return mtpd_handle_cmd_get_storage_ids(); + case MTP_OP_GET_STORAGE_INFO: TU_LOG_DRV(" MTP command: MTP_OP_GET_STORAGE_INFO for ID=%lu\n", p_container->data[0]); return mtpd_handle_cmd_get_storage_info(); @@ -546,7 +489,9 @@ mtp_phase_type_t mtpd_handle_cmd(void) { TU_LOG_DRV(" MTP command: MTP_OP_UNKNOWN_COMMAND %x!!!!\n", p_container->code); return false; } - return true; + + tud_mtp_command_received_cb(0, &cmd_block, p_container); + return ret; } mtp_phase_type_t mtpd_handle_data(void) @@ -572,40 +517,6 @@ mtp_phase_type_t mtpd_handle_data(void) return true; } -mtp_phase_type_t mtpd_handle_cmd_get_device_info(void) -{ - TU_VERIFY_STATIC(sizeof(mtp_device_info_t) < MTP_MAX_PACKET_SIZE, "mtp_device_info_t shall fit in MTP_MAX_PACKET_SIZE"); - mtp_generic_container_t* p_container = &_mtpd_epbuf.container; - - p_container->len = MTP_CONTAINER_HEADER_LENGTH + sizeof(mtp_device_info_t); - p_container->type = MTP_CONTAINER_TYPE_DATA_BLOCK; - p_container->code = MTP_OP_GET_DEVICE_INFO; - mtp_device_info_t *d = (mtp_device_info_t *)p_container->data; - d->standard_version = 100; - d->mtp_vendor_extension_id = 0x06; - d->mtp_version = 100; - d->mtp_extensions_len = TU_ARRAY_LEN(MTP_EXTENSIONS); - mtpd_wc16cpy((uint8_t *)d->mtp_extensions, MTP_EXTENSIONS); - d->functional_mode = 0x0000; - d->operations_supported_len = TU_ARRAY_LEN(mtp_operations_supported); - memcpy(d->operations_supported, mtp_operations_supported, sizeof(mtp_operations_supported)); - d->events_supported_len = TU_ARRAY_LEN(mtp_events_supported); - memcpy(d->events_supported, mtp_events_supported, sizeof(mtp_events_supported)); - d->device_properties_supported_len = TU_ARRAY_LEN(mtp_device_properties_supported); - memcpy(d->device_properties_supported, mtp_device_properties_supported, sizeof(mtp_device_properties_supported)); - d->capture_formats_len = TU_ARRAY_LEN(mtp_capture_formats); - memcpy(d->capture_formats, mtp_capture_formats, sizeof(mtp_capture_formats)); - d->playback_formats_len = TU_ARRAY_LEN(mtp_playback_formats); - memcpy(d->playback_formats, mtp_playback_formats, sizeof(mtp_playback_formats)); - mtpd_gct_append_wstring(CFG_TUD_MANUFACTURER); - mtpd_gct_append_wstring(CFG_TUD_MODEL); - mtpd_gct_append_wstring(CFG_MTP_DEVICE_VERSION); - mtpd_gct_append_wstring(CFG_MTP_SERIAL_NUMBER); - - _mtpd_itf.queued_len = p_container->len; - return MTP_PHASE_DATA_IN; -} - mtp_phase_type_t mtpd_handle_cmd_open_session(void) { mtp_generic_container_t* p_container = &_mtpd_epbuf.container; @@ -851,7 +762,7 @@ mtp_phase_type_t mtpd_handle_cmd_get_device_prop_desc(void) p_container->len = MTP_CONTAINER_HEADER_LENGTH + sizeof(mtp_device_prop_desc_t); mtp_device_prop_desc_t *d = (mtp_device_prop_desc_t *)p_container->data; d->device_property_code = (uint16_t)(device_prop_code); - d->datatype = MTP_TYPE_STR; + d->datatype = MTP_DATA_TYPE_STR; d->get_set = MTP_MODE_GET; mtpd_gct_append_wstring(CFG_TUD_MODEL); // factory_def_value mtpd_gct_append_wstring(CFG_TUD_MODEL); // current_value_len diff --git a/src/class/mtp/mtp_device.h b/src/class/mtp/mtp_device.h index 6531b36df..b39322f22 100644 --- a/src/class/mtp/mtp_device.h +++ b/src/class/mtp/mtp_device.h @@ -18,14 +18,14 @@ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN0 * THE SOFTWARE. * * This file is part of the TinyUSB stack. */ -#ifndef _TUSB_MTP_DEVICE_H_ -#define _TUSB_MTP_DEVICE_H_ +#ifndef TUSB_MTP_DEVICE_H_ +#define TUSB_MTP_DEVICE_H_ #include "common/tusb_common.h" #include "mtp.h" @@ -36,15 +36,51 @@ extern "C" { #endif +typedef struct { + const mtp_container_header_t* cmd_header; + tusb_xfer_result_t xfer_result; + uint32_t xferred_bytes; +} tud_mtp_cb_complete_data_t; + +// Number of supported operations, events, device properties, capture formats, playback formats +// and max number of characters for strings manufacturer, model, device_version, serial_number +#define MTP_DEVICE_INFO_TYPEDEF(_extension_nchars, _op_count, _event_count, _devprop_count, _capture_count, _playback_count) \ + struct TU_ATTR_PACKED { \ + uint16_t standard_version; \ + uint32_t mtp_vendor_extension_id; \ + uint16_t mtp_version; \ + mtp_string_t(_extension_nchars) mtp_extensions; \ + uint16_t functional_mode; \ + mtp_auint16_t(_op_count) supported_operations; \ + mtp_auint16_t(_event_count) supported_events; \ + mtp_auint16_t(_devprop_count) supported_device_properties; \ + mtp_auint16_t(_capture_count) capture_formats; \ + mtp_auint16_t(_playback_count) playback_formats; \ + /* string fields will be added using append function */ \ + } + +typedef MTP_DEVICE_INFO_TYPEDEF( + sizeof(CFG_TUD_MTP_DEVICEINFO_EXTENSIONS), TU_ARGS_NUM(CFG_TUD_MTP_DEVICEINFO_SUPPORTED_OPERATIONS), + TU_ARGS_NUM(CFG_TUD_MTP_DEVICEINFO_SUPPORTED_EVENTS), TU_ARGS_NUM(CFG_TUD_MTP_DEVICEINFO_SUPPORTED_DEVICE_PROPERTIES), + TU_ARGS_NUM(CFG_TUD_MTP_DEVICEINFO_CAPTURE_FORMATS), TU_ARGS_NUM(CFG_TUD_MTP_DEVICEINFO_PLAYBACK_FORMATS) + ) tud_mtp_device_info_t; + //--------------------------------------------------------------------+ -// Internal Class Driver API +// Application API //--------------------------------------------------------------------+ -void mtpd_init (void); -bool mtpd_deinit (void); -void mtpd_reset (uint8_t rhport); -uint16_t mtpd_open (uint8_t rhport, tusb_desc_interface_t const *itf_desc, uint16_t max_len); -bool mtpd_control_xfer_cb (uint8_t rhport, uint8_t stage, tusb_control_request_t const *p_request); -bool mtpd_xfer_cb (uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes); +bool tud_mtp_data_send(mtp_generic_container_t* data_block); +// bool tud_mtp_block_data_receive(); +bool tud_mtp_response_send(mtp_generic_container_t* resp_block); + +//--------------------------------------------------------------------+ +// Application Callbacks +//--------------------------------------------------------------------+ + +// Invoked when new command is received +int32_t tud_mtp_command_received_cb(uint8_t idx, mtp_generic_container_t* cmd_block, mtp_generic_container_t* out_block); + +// Invoked when data phase is complete +int32_t tud_mtp_data_complete_cb(uint8_t idx, mtp_container_header_t* cmd_header, mtp_generic_container_t* resp_block, tusb_xfer_result_t xfer_result, uint32_t xferred_bytes); //--------------------------------------------------------------------+ // Helper functions @@ -66,10 +102,19 @@ bool mtpd_gct_append_array(uint32_t array_size, const void *data, size_t type_si // The function returns true if the data fits in the available buffer space. bool mtpd_gct_append_date(struct tm *timeinfo); +//--------------------------------------------------------------------+ +// Internal Class Driver API +//--------------------------------------------------------------------+ +void mtpd_init (void); +bool mtpd_deinit (void); +void mtpd_reset (uint8_t rhport); +uint16_t mtpd_open (uint8_t rhport, tusb_desc_interface_t const *itf_desc, uint16_t max_len); +bool mtpd_control_xfer_cb (uint8_t rhport, uint8_t stage, tusb_control_request_t const *p_request); +bool mtpd_xfer_cb (uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes); + #ifdef __cplusplus } #endif -#endif /* CFG_TUD_ENABLED && CFG_TUD_MTP */ - -#endif /* _TUSB_MTP_DEVICE_H_ */ +#endif +#endif From e76d09bb4213921c8957af22033c9451fe0ed123 Mon Sep 17 00:00:00 2001 From: hathach Date: Fri, 19 Sep 2025 16:00:36 +0700 Subject: [PATCH 09/43] rework get storageIDs and get storage info --- examples/device/mtp/src/mtp_fs_example.c | 103 +++++++++++------------ src/class/mtp/mtp.h | 74 ++++++++-------- src/class/mtp/mtp_device.c | 89 +------------------- src/class/mtp/mtp_device_storage.h | 14 --- src/common/tusb_common.h | 1 + 5 files changed, 88 insertions(+), 193 deletions(-) diff --git a/examples/device/mtp/src/mtp_fs_example.c b/examples/device/mtp/src/mtp_fs_example.c index 68140b1ae..fcd01f54a 100644 --- a/examples/device/mtp/src/mtp_fs_example.c +++ b/examples/device/mtp/src/mtp_fs_example.c @@ -34,11 +34,10 @@ //--------------------------------------------------------------------+ // device info string (including terminating null) -const uint16_t dev_info_manufacturer[] = { 'T', 'i', 'n', 'y', 'U', 'S', 'B', 0 }; -const uint16_t dev_info_model[] = { 'M', 'T', 'P', ' ', 'E', 'x', 'a', 'm', 'p', 'l', 'e', 0 }; -const uint16_t dev_info_version[] = { '1', '.', '0', 0 }; -const uint16_t dev_info_serial[] = { '1', '2', '3', '4', '5', '6', 0 }; - +static const uint16_t dev_info_manufacturer[] = { 'T', 'i', 'n', 'y', 'U', 'S', 'B', 0 }; +static const uint16_t dev_info_model[] = { 'M', 'T', 'P', ' ', 'E', 'x', 'a', 'm', 'p', 'l', 'e', 0 }; +static const uint16_t dev_info_version[] = { '1', '.', '0', 0 }; +static const uint16_t dev_info_serial[] = { '1', '2', '3', '4', '5', '6', 0 }; static const uint16_t supported_operations[] = { MTP_OP_GET_DEVICE_INFO, @@ -80,7 +79,6 @@ static const uint16_t playback_formats[] = { MTP_OBJ_FORMAT_TEXT, }; - //--------------------------------------------------------------------+ // RAM FILESYSTEM //--------------------------------------------------------------------+ @@ -116,6 +114,32 @@ static fs_object_info_t _fs_objects[FS_MAX_NODES] = { } }; +//------------- Storage Info -------------// +#define STORAGE_DESCRIPTRION { 'd', 'i', 's', 'k', 0 } +#define VOLUME_IDENTIFIER { 'v', 'o', 'l', 0 } + +typedef MTP_STORAGE_INFO_TYPEDEF(TU_ARRAY_SIZE((uint16_t[]) STORAGE_DESCRIPTRION), + TU_ARRAY_SIZE(((uint16_t[])VOLUME_IDENTIFIER)) +) storage_info_t; + +storage_info_t storage_info = { + .storage_type = MTP_STORAGE_TYPE_FIXED_RAM, + .filesystem_type = MTP_FILESYSTEM_TYPE_GENERIC_HIERARCHICAL, + .access_capability = MTP_ACCESS_CAPABILITY_READ_WRITE, + .max_capacity_in_bytes = FS_MAX_NODES * FS_MAX_NODE_BYTES, + .free_space_in_bytes = FS_MAX_NODES * FS_MAX_NODE_BYTES, + .free_space_in_objects = FS_MAX_NODES, + .storage_description = { + .count = (TU_FIELD_SZIE(storage_info_t, storage_description)-1) / sizeof(uint16_t), + .utf16 = STORAGE_DESCRIPTRION + }, + .volume_identifier = { + .count = (TU_FIELD_SZIE(storage_info_t, volume_identifier)-1) / sizeof(uint16_t), + .utf16 = VOLUME_IDENTIFIER + } +}; + + //--------------------------------------------------------------------+ // OPERATING STATUS //--------------------------------------------------------------------+ @@ -185,7 +209,7 @@ int32_t tud_mtp_command_received_cb(uint8_t idx, mtp_generic_container_t* cmd_bl (void)idx; switch (cmd_block->code) { case MTP_OP_GET_DEVICE_INFO: { - // Device info is already prepared up to playback formats. Application need to add string fields + // Device info is already prepared up to playback formats. Application only need to add string fields mtp_container_add_string(out_block, TU_ARRAY_SIZE(dev_info_manufacturer), dev_info_manufacturer); mtp_container_add_string(out_block, TU_ARRAY_SIZE(dev_info_model), dev_info_model); mtp_container_add_string(out_block, TU_ARRAY_SIZE(dev_info_version), dev_info_version); @@ -196,14 +220,26 @@ int32_t tud_mtp_command_received_cb(uint8_t idx, mtp_generic_container_t* cmd_bl } case MTP_OP_OPEN_SESSION: - out_block->len = MTP_CONTAINER_HEADER_LENGTH; - out_block->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; - // TODO check if session is already opened out_block->code = MTP_RESP_OK; - tud_mtp_response_send(out_block); break; + case MTP_OP_GET_STORAGE_IDS: { + uint32_t storage_ids [] = { 0x00010001u }; // physical = 1, logical = 1 + mtp_container_add_auint32(out_block, 1, storage_ids); + tud_mtp_data_send(out_block); + break; + } + + case MTP_OP_GET_STORAGE_INFO: { + // update storage info with current free space + storage_info.free_space_in_objects = FS_MAX_NODES - fs_get_object_count(); + storage_info.free_space_in_bytes = storage_info.free_space_in_objects * FS_MAX_NODE_BYTES; + mtp_container_add_raw(out_block, &storage_info, sizeof(storage_info)); + tud_mtp_data_send(out_block); + break; + } + default: return -1; } @@ -213,21 +249,6 @@ int32_t tud_mtp_command_received_cb(uint8_t idx, mtp_generic_container_t* cmd_bl //--------------------------------------------------------------------+ // API //--------------------------------------------------------------------+ -mtp_response_t tud_mtp_storage_open_session(uint32_t* session_id) { - if (*session_id == 0) { - TU_LOG1("Invalid session ID\r\n"); - return MTP_RESP_INVALID_PARAMETER; - } - if (_fs_operation.session_id != 0) { - *session_id = _fs_operation.session_id; - TU_LOG1("ERR: Session %ld already open\r\n", _fs_operation.session_id); - return MTP_RESP_SESSION_ALREADY_OPEN; - } - _fs_operation.session_id = *session_id; - TU_LOG1("Open session with id %ld\r\n", _fs_operation.session_id); - return MTP_RESP_OK; -} - mtp_response_t tud_mtp_storage_close_session(uint32_t session_id) { if (session_id != _fs_operation.session_id) { TU_LOG1("ERR: Session %ld not open\r\n", session_id); @@ -238,36 +259,6 @@ mtp_response_t tud_mtp_storage_close_session(uint32_t session_id) { return MTP_RESP_OK; } -mtp_response_t tud_mtp_get_storage_id(uint32_t* storage_id) { - if (_fs_operation.session_id == 0) { - TU_LOG1("ERR: Session not open\r\n"); - return MTP_RESP_SESSION_NOT_OPEN; - } - *storage_id = STORAGE_ID(0x0001, 0x0001); - TU_LOG1("Retrieved storage identifier %ld\r\n", *storage_id); - return MTP_RESP_OK; -} - -mtp_response_t tud_mtp_get_storage_info(uint32_t storage_id, mtp_storage_info_t* info) { - if (_fs_operation.session_id == 0) { - TU_LOG1("ERR: Session not open\r\n"); - return MTP_RESP_SESSION_NOT_OPEN; - } - if (storage_id != STORAGE_ID(0x0001, 0x0001)) { - TU_LOG1("ERR: Unexpected storage id %ld\r\n", storage_id); - return MTP_RESP_INVALID_STORAGE_ID; - } - info->storage_type = MTP_STORAGE_TYPE_FIXED_RAM; - info->filesystem_type = MTP_FILESYSTEM_TYPE_GENERIC_HIERARCHICAL; - info->access_capability = MTP_ACCESS_CAPABILITY_READ_WRITE; - info->max_capacity_in_bytes = FS_MAX_NODES * FS_MAX_NODE_BYTES; - info->free_space_in_objects = FS_MAX_NODES - fs_get_object_count(); - info->free_space_in_bytes = info->free_space_in_objects * FS_MAX_NODE_BYTES; - mtpd_gct_append_wstring(MTPD_STORAGE_DESCRIPTION); - mtpd_gct_append_wstring(MTPD_VOLUME_IDENTIFIER); - return MTP_RESP_OK; -} - mtp_response_t tud_mtp_storage_format(uint32_t storage_id) { if (_fs_operation.session_id == 0) { TU_LOG1("ERR: Session not open\r\n"); diff --git a/src/class/mtp/mtp.h b/src/class/mtp/mtp.h index d61f329c4..a28ce40df 100644 --- a/src/class/mtp/mtp.h +++ b/src/class/mtp/mtp.h @@ -738,7 +738,16 @@ typedef struct TU_ATTR_PACKED { uint16_t utf16[]; } mtp_flexible_string_t; -// StorageInfo dataset + typedef union TU_ATTR_PACKED { + struct { + uint16_t physical; // physical location + uint16_t logical; // logical within physical + }; + + uint32_t id; +} mtp_storage_id_t; + +// StorageInfo dataset (excluding storage description and volume identifier) typedef struct TU_ATTR_PACKED { uint16_t storage_type; uint16_t filesystem_type; @@ -746,10 +755,20 @@ typedef struct TU_ATTR_PACKED { uint64_t max_capacity_in_bytes; uint64_t free_space_in_bytes; uint32_t free_space_in_objects; -} mtp_storage_info_t; -// The following fields will be dynamically added to the struct at runtime: -// - wstring storage_description -// - wstring volume_identifier + // storage description and volume identifier are added dynamically +} mtp_storage_info_nostring_t; + +#define MTP_STORAGE_INFO_TYPEDEF(_storage_desc_chars, _volume_id_chars) \ + struct TU_ATTR_PACKED { \ + uint16_t storage_type; \ + uint16_t filesystem_type; \ + uint16_t access_capability; \ + uint64_t max_capacity_in_bytes; \ + uint64_t free_space_in_bytes; \ + uint32_t free_space_in_objects; \ + mtp_string_t(_storage_desc_chars) storage_description; \ + mtp_string_t(_volume_id_chars) volume_identifier; \ + } // ObjectInfo Dataset typedef struct TU_ATTR_PACKED { @@ -807,48 +826,27 @@ typedef struct TU_ATTR_PACKED { // Generic Container function //--------------------------------------------------------------------+ -TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add(mtp_generic_container_t* p_container, mtp_data_type_t type, const void* data) { - TU_VERIFY(type != MTP_DATA_TYPE_UNDEFINED, 0); - uint8_t scalar_size; // size of single scalar - uint8_t count_width; // size of count field (0, 1 or 4 bytes) - - if (type == MTP_DATA_TYPE_STR) { - scalar_size = 2; - count_width = 1; - } else { - uint8_t scalar_type = type & 0x3F; - count_width = (type & 0x4000u) ? 4 : 0; - scalar_size = 1u << ((scalar_type - 1u) >> 1); - } - - uint32_t data_len; - if (count_width) { - const uint32_t count = *(const uint32_t*) data; - data_len = count_width + count*scalar_size; - } else { - data_len = scalar_size; - } - - memcpy(((uint8_t*)p_container) + p_container->len, data, data_len); - p_container->len += data_len; - - return data_len; +TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_raw(mtp_generic_container_t* p_container, const void* data, uint32_t len) { + memcpy((uint8_t*) p_container + p_container->len, data, len); + p_container->len += len; + return len; } TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_field(mtp_generic_container_t* p_container, uint8_t scalar_size, uint32_t count, const void* data) { - const uint32_t prev_len = p_container->len; - uint8_t* container8 = (uint8_t*) p_container; if (count == 0) { // count = 0 means scalar - memcpy(container8 + p_container->len, data, scalar_size); - p_container->len += scalar_size; + return mtp_container_add_raw(p_container, data, scalar_size); } else { + uint8_t* container8 = (uint8_t*) p_container; + tu_unaligned_write32(container8 + p_container->len, count); p_container->len += 4; - memcpy(container8 + p_container->len, data, count * scalar_size); - } - return p_container->len - prev_len; + memcpy(container8 + p_container->len, data, count * scalar_size); + p_container->len += count * scalar_size; + + return 4 + count * scalar_size; + } } TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_string(mtp_generic_container_t* p_container, uint8_t count, uint16_t* utf16) { diff --git a/src/class/mtp/mtp_device.c b/src/class/mtp/mtp_device.c index b64f36f2e..857fafd0c 100644 --- a/src/class/mtp/mtp_device.c +++ b/src/class/mtp/mtp_device.c @@ -84,11 +84,7 @@ static mtp_phase_type_t mtpd_chk_session_open(const char *func_name); // MTP commands static mtp_phase_type_t mtpd_handle_cmd(mtpd_interface_t* p_mtp); static mtp_phase_type_t mtpd_handle_data(void); -static mtp_phase_type_t mtpd_handle_cmd_get_device_info(void); -static mtp_phase_type_t mtpd_handle_cmd_open_session(void); static mtp_phase_type_t mtpd_handle_cmd_close_session(void); -static mtp_phase_type_t mtpd_handle_cmd_get_storage_info(void); -static mtp_phase_type_t mtpd_handle_cmd_get_storage_ids(void); static mtp_phase_type_t mtpd_handle_cmd_get_object_handles(void); static mtp_phase_type_t mtpd_handle_cmd_get_object_info(void); static mtp_phase_type_t mtpd_handle_cmd_get_object(void); @@ -390,7 +386,7 @@ mtp_phase_type_t mtpd_handle_cmd(mtpd_interface_t* p_mtp) { mtp_generic_container_t cmd_block; // copy command block for callback memcpy(&cmd_block, p_container, p_container->len); memcpy(&p_mtp->cmd_header, p_container, sizeof(mtp_container_header_t)); - // p_container->len = MTP_CONTAINER_HEADER_LENGTH; // default data/response length + p_container->len = MTP_CONTAINER_HEADER_LENGTH; // default data/response length if (p_container->code != MTP_OP_SEND_OBJECT) { _mtpd_soi.object_handle = 0; @@ -453,11 +449,12 @@ mtp_phase_type_t mtpd_handle_cmd(mtpd_interface_t* p_mtp) { case MTP_OP_GET_STORAGE_IDS: TU_LOG_DRV(" MTP command: MTP_OP_GET_STORAGE_IDS\n"); - return mtpd_handle_cmd_get_storage_ids(); + break; case MTP_OP_GET_STORAGE_INFO: TU_LOG_DRV(" MTP command: MTP_OP_GET_STORAGE_INFO for ID=%lu\n", p_container->data[0]); - return mtpd_handle_cmd_get_storage_info(); + break; + case MTP_OP_GET_OBJECT_HANDLES: TU_LOG_DRV(" MTP command: MTP_OP_GET_OBJECT_HANDLES\n"); return mtpd_handle_cmd_get_object_handles(); @@ -517,34 +514,6 @@ mtp_phase_type_t mtpd_handle_data(void) return true; } -mtp_phase_type_t mtpd_handle_cmd_open_session(void) -{ - mtp_generic_container_t* p_container = &_mtpd_epbuf.container; - uint32_t session_id = p_container->data[0]; - - mtp_response_t res = tud_mtp_storage_open_session(&session_id); - if (res == MTP_RESP_SESSION_ALREADY_OPEN) - { - p_container->len = MTP_CONTAINER_HEADER_LENGTH; - p_container->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; - p_container->code = res; - p_container->len += sizeof(p_container->data[0]); - p_container->data[0] = session_id; - _mtpd_itf.session_id = session_id; - return MTP_PHASE_RESPONSE; - } - - mtp_phase_type_t phase; - if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESP_OK), res, "")) != MTP_PHASE_NONE) return phase; - - _mtpd_itf.session_id = session_id; - - p_container->len = MTP_CONTAINER_HEADER_LENGTH; - p_container->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; - p_container->code = MTP_RESP_OK; - - return MTP_PHASE_RESPONSE; -} mtp_phase_type_t mtpd_handle_cmd_close_session(void) { @@ -562,56 +531,6 @@ mtp_phase_type_t mtpd_handle_cmd_close_session(void) return MTP_PHASE_RESPONSE; } -mtp_phase_type_t mtpd_handle_cmd_get_storage_ids(void) -{ - TU_VERIFY_STATIC(sizeof(mtp_storage_ids_t) < MTP_MAX_PACKET_SIZE, "mtp_storage_ids_t shall fit in MTP_MAX_PACKET_SIZE"); - mtp_generic_container_t* p_container = &_mtpd_epbuf.container; - - uint32_t storage_id; - mtp_response_t res = tud_mtp_get_storage_id(&storage_id); - mtp_phase_type_t phase; - if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESP_OK), res, "")) != MTP_PHASE_NONE) return phase; - - p_container->len = MTP_CONTAINER_HEADER_LENGTH + sizeof(mtp_storage_ids_t); - p_container->type = MTP_CONTAINER_TYPE_DATA_BLOCK; - p_container->code = MTP_OP_GET_STORAGE_IDS; - mtp_storage_ids_t *d = (mtp_storage_ids_t *)p_container->data; - if (storage_id == 0) - { - // Storage not accessible - d->storage_ids_len = 0; - d->storage_ids[0] = 0; - } - else - { - d->storage_ids_len = 1; - d->storage_ids[0] = storage_id; - } - - _mtpd_itf.queued_len = p_container->len; - return MTP_PHASE_DATA_IN; -} - -mtp_phase_type_t mtpd_handle_cmd_get_storage_info(void) -{ - TU_VERIFY_STATIC(sizeof(mtp_storage_info_t) < MTP_MAX_PACKET_SIZE, "mtp_storage_info_t shall fit in MTP_MAX_PACKET_SIZE"); - mtp_generic_container_t* p_container = &_mtpd_epbuf.container; - uint32_t storage_id = p_container->data[0]; - - p_container->len = MTP_CONTAINER_HEADER_LENGTH + sizeof(mtp_storage_info_t); - p_container->type = MTP_CONTAINER_TYPE_DATA_BLOCK; - p_container->code = MTP_OP_GET_STORAGE_INFO; - - mtp_response_t res = tud_mtp_get_storage_info(storage_id, (mtp_storage_info_t *)p_container->data); - mtp_phase_type_t phase; - if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESP_OK), res, "")) != MTP_PHASE_NONE) { - return phase; - } - - _mtpd_itf.queued_len = p_container->len; - return MTP_PHASE_DATA_IN; -} - mtp_phase_type_t mtpd_handle_cmd_get_object_handles(void) { mtp_generic_container_t* p_container = &_mtpd_epbuf.container; diff --git a/src/class/mtp/mtp_device_storage.h b/src/class/mtp/mtp_device_storage.h index a9bbc9b90..6c12d38e3 100644 --- a/src/class/mtp/mtp_device_storage.h +++ b/src/class/mtp/mtp_device_storage.h @@ -59,24 +59,10 @@ // // The function shall check if the session is already opened and, in case, set session_id to the // ID of the current session. -mtp_response_t tud_mtp_storage_open_session(uint32_t *session_id); // Close an open session mtp_response_t tud_mtp_storage_close_session(uint32_t session_id); -// Get a storage ID valid within the current session -// -// TODO: while multiple storage IDs could be used, the implementation currently supports only 1. -mtp_response_t tud_mtp_get_storage_id(uint32_t *storage_id); - -// Get storage information for the given ID -// -// The implementation shall fill all the fields required by the specification. -// Note that the variable information (e.g. wstring file name, dates and tags shall be written by using the library functions) -// In addition to the fixed mtp_storage_info_t structure, the function shall add storage descriptor string and -// volume identifier string via tud_mtp_gct_append_wstring function. -mtp_response_t tud_mtp_get_storage_info(uint32_t storage_id, mtp_storage_info_t *info); - // Format the specified storage mtp_response_t tud_mtp_storage_format(uint32_t storage_id); diff --git a/src/common/tusb_common.h b/src/common/tusb_common.h index e35d3e6fe..76764bbba 100644 --- a/src/common/tusb_common.h +++ b/src/common/tusb_common.h @@ -35,6 +35,7 @@ // Macros Helper //--------------------------------------------------------------------+ #define TU_ARRAY_SIZE(_arr) ( sizeof(_arr) / sizeof(_arr[0]) ) +#define TU_FIELD_SZIE(_type, _field) (sizeof(((_type *)0)->_field)) #define TU_MIN(_x, _y) ( ( (_x) < (_y) ) ? (_x) : (_y) ) #define TU_MAX(_x, _y) ( ( (_x) > (_y) ) ? (_x) : (_y) ) #define TU_DIV_CEIL(n, d) (((n) + (d) - 1) / (d)) From 57c5e5516a7e6711085e3d5f57d2023d6ce1b237 Mon Sep 17 00:00:00 2001 From: hathach Date: Fri, 19 Sep 2025 16:41:29 +0700 Subject: [PATCH 10/43] rework get storageIDs and get storage info --- src/class/mtp/mtp.h | 18 ------------------ src/class/mtp/mtp_device.c | 12 +++++------- src/class/mtp/mtp_device.h | 3 +++ 3 files changed, 8 insertions(+), 25 deletions(-) diff --git a/src/class/mtp/mtp.h b/src/class/mtp/mtp.h index a28ce40df..c48a2469c 100644 --- a/src/class/mtp/mtp.h +++ b/src/class/mtp/mtp.h @@ -743,21 +743,9 @@ typedef struct TU_ATTR_PACKED { uint16_t physical; // physical location uint16_t logical; // logical within physical }; - uint32_t id; } mtp_storage_id_t; -// StorageInfo dataset (excluding storage description and volume identifier) -typedef struct TU_ATTR_PACKED { - uint16_t storage_type; - uint16_t filesystem_type; - uint16_t access_capability; - uint64_t max_capacity_in_bytes; - uint64_t free_space_in_bytes; - uint32_t free_space_in_objects; - // storage description and volume identifier are added dynamically -} mtp_storage_info_nostring_t; - #define MTP_STORAGE_INFO_TYPEDEF(_storage_desc_chars, _volume_id_chars) \ struct TU_ATTR_PACKED { \ uint16_t storage_type; \ @@ -794,12 +782,6 @@ typedef struct TU_ATTR_PACKED { // - datetime_wstring date_modified; // - wstring keywords; -// Storage IDs -typedef struct TU_ATTR_PACKED { - uint32_t storage_ids_len; - uint32_t storage_ids[CFG_MTP_STORAGE_ID_COUNT]; -} mtp_storage_ids_t; - // DevicePropDesc Dataset typedef struct TU_ATTR_PACKED { uint16_t device_property_code; diff --git a/src/class/mtp/mtp_device.c b/src/class/mtp/mtp_device.c index 857fafd0c..d0a1d6c40 100644 --- a/src/class/mtp/mtp_device.c +++ b/src/class/mtp/mtp_device.c @@ -351,22 +351,18 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t case MTP_PHASE_RESPONSE_QUEUED: // response phase is complete -> prepare for new command TU_ASSERT(ep_addr == p_mtp->ep_in); + tud_mtp_response_complete_cb(0, &p_mtp->cmd_header, p_container, event, xferred_bytes); prepare_new_command(p_mtp); break; case MTP_PHASE_RESPONSE: case MTP_PHASE_ERROR: - // processed immediately after this switch, supposedly to be empty + // supposedly to be empty break; default: return false; } - if (p_mtp->phase == MTP_PHASE_RESPONSE) { - // p_mtp->phase = MTP_PHASE_RESPONSE_QUEUED; - // p_container->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; - // p_container->transaction_id = p_mtp->cmd_header.transaction_id; - // TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_in, (uint8_t*) p_container, (uint16_t)p_container->len), 0); - } else if (p_mtp->phase == MTP_PHASE_ERROR) { + if (p_mtp->phase == MTP_PHASE_ERROR) { // stall both IN & OUT endpoints usbd_edpt_stall(rhport, p_mtp->ep_out); usbd_edpt_stall(rhport, p_mtp->ep_in); @@ -467,9 +463,11 @@ mtp_phase_type_t mtpd_handle_cmd(mtpd_interface_t* p_mtp) { case MTP_OP_DELETE_OBJECT: TU_LOG_DRV(" MTP command: MTP_OP_DELETE_OBJECT\n"); return mtpd_handle_cmd_delete_object(); + case MTP_OP_GET_DEVICE_PROP_DESC: TU_LOG_DRV(" MTP command: MTP_OP_GET_DEVICE_PROP_DESC\n"); return mtpd_handle_cmd_get_device_prop_desc(); + case MTP_OP_GET_DEVICE_PROP_VALUE: TU_LOG_DRV(" MTP command: MTP_OP_GET_DEVICE_PROP_VALUE\n"); return mtpd_handle_cmd_get_device_prop_value(); diff --git a/src/class/mtp/mtp_device.h b/src/class/mtp/mtp_device.h index b39322f22..538e3c8d0 100644 --- a/src/class/mtp/mtp_device.h +++ b/src/class/mtp/mtp_device.h @@ -82,6 +82,9 @@ int32_t tud_mtp_command_received_cb(uint8_t idx, mtp_generic_container_t* cmd_bl // Invoked when data phase is complete int32_t tud_mtp_data_complete_cb(uint8_t idx, mtp_container_header_t* cmd_header, mtp_generic_container_t* resp_block, tusb_xfer_result_t xfer_result, uint32_t xferred_bytes); +// Invoked when response phase is complete +int32_t tud_mtp_response_complete_cb(uint8_t idx, mtp_container_header_t* cmd_header, mtp_generic_container_t* resp_block, tusb_xfer_result_t xfer_result, uint32_t xferred_bytes); + //--------------------------------------------------------------------+ // Helper functions //--------------------------------------------------------------------+ From b70804b0c4212f67625e945986b91bbe1d965059 Mon Sep 17 00:00:00 2001 From: hathach Date: Sat, 20 Sep 2025 00:42:06 +0700 Subject: [PATCH 11/43] implement get device properties value --- examples/device/mtp/src/mtp_fs_example.c | 86 +++++++++++------------- src/class/mtp/mtp.h | 38 +++++++++-- src/class/mtp/mtp_device.c | 11 +-- 3 files changed, 76 insertions(+), 59 deletions(-) diff --git a/examples/device/mtp/src/mtp_fs_example.c b/examples/device/mtp/src/mtp_fs_example.c index fcd01f54a..89814fb8f 100644 --- a/examples/device/mtp/src/mtp_fs_example.c +++ b/examples/device/mtp/src/mtp_fs_example.c @@ -34,50 +34,12 @@ //--------------------------------------------------------------------+ // device info string (including terminating null) -static const uint16_t dev_info_manufacturer[] = { 'T', 'i', 'n', 'y', 'U', 'S', 'B', 0 }; -static const uint16_t dev_info_model[] = { 'M', 'T', 'P', ' ', 'E', 'x', 'a', 'm', 'p', 'l', 'e', 0 }; -static const uint16_t dev_info_version[] = { '1', '.', '0', 0 }; -static const uint16_t dev_info_serial[] = { '1', '2', '3', '4', '5', '6', 0 }; +#define DEV_INFO_MANUFACTURER "TinyUSB" +#define DEV_INFO_MODEL "MTP Example" +#define DEV_INFO_VERSION "1.0" +#define DEV_INFO_SERIAL "123456" -static const uint16_t supported_operations[] = { - MTP_OP_GET_DEVICE_INFO, - MTP_OP_OPEN_SESSION, - MTP_OP_CLOSE_SESSION, - MTP_OP_GET_STORAGE_IDS, - MTP_OP_GET_STORAGE_INFO, - MTP_OP_GET_NUM_OBJECTS, - MTP_OP_GET_OBJECT_HANDLES, - MTP_OP_GET_OBJECT_INFO, - MTP_OP_GET_OBJECT, - MTP_OP_DELETE_OBJECT, - MTP_OP_SEND_OBJECT_INFO, - MTP_OP_SEND_OBJECT, - MTP_OP_FORMAT_STORE, - MTP_OP_RESET_DEVICE, - MTP_OP_GET_DEVICE_PROP_DESC, - MTP_OP_GET_DEVICE_PROP_VALUE, - MTP_OP_SET_DEVICE_PROP_VALUE -}; - -static const uint16_t supported_events[] = { - MTP_EVENT_OBJECT_ADDED, -}; - -static const uint16_t supported_device_properties[] = { - MTP_DEV_PROP_DEVICE_FRIENDLY_NAME, -}; - -static const uint16_t capture_formats[] = { - MTP_OBJ_FORMAT_UNDEFINED, - MTP_OBJ_FORMAT_ASSOCIATION, - MTP_OBJ_FORMAT_TEXT, -}; - -static const uint16_t playback_formats[] = { - MTP_OBJ_FORMAT_UNDEFINED, - MTP_OBJ_FORMAT_ASSOCIATION, - MTP_OBJ_FORMAT_TEXT, -}; +#define DEV_PROP_FRIENDLY_NAME "TinyUSB MTP" //--------------------------------------------------------------------+ // RAM FILESYSTEM @@ -139,6 +101,10 @@ storage_info_t storage_info = { } }; +enum { + SUPPORTED_STORAGE_ID = 0x00010001u // physical = 1, logical = 1 +}; + //--------------------------------------------------------------------+ // OPERATING STATUS @@ -205,15 +171,24 @@ int32_t tud_mtp_data_complete_cb(uint8_t idx, mtp_container_header_t* cmd_header return 0; } +int32_t tud_mtp_response_complete_cb(uint8_t idx, mtp_container_header_t* cmd_header, mtp_generic_container_t* resp_block, tusb_xfer_result_t xfer_result, uint32_t xferred_bytes) { + (void) idx; + (void) cmd_header; + (void) resp_block; + (void) xfer_result; + (void) xferred_bytes; + return 0; // nothing to do +} + int32_t tud_mtp_command_received_cb(uint8_t idx, mtp_generic_container_t* cmd_block, mtp_generic_container_t* out_block) { (void)idx; switch (cmd_block->code) { case MTP_OP_GET_DEVICE_INFO: { // Device info is already prepared up to playback formats. Application only need to add string fields - mtp_container_add_string(out_block, TU_ARRAY_SIZE(dev_info_manufacturer), dev_info_manufacturer); - mtp_container_add_string(out_block, TU_ARRAY_SIZE(dev_info_model), dev_info_model); - mtp_container_add_string(out_block, TU_ARRAY_SIZE(dev_info_version), dev_info_version); - mtp_container_add_string(out_block, TU_ARRAY_SIZE(dev_info_serial), dev_info_serial); + mtp_container_add_cstring(out_block, DEV_INFO_MANUFACTURER); + mtp_container_add_cstring(out_block, DEV_INFO_MODEL); + mtp_container_add_cstring(out_block, DEV_INFO_VERSION); + mtp_container_add_cstring(out_block, DEV_INFO_SERIAL); tud_mtp_data_send(out_block); break; @@ -225,13 +200,14 @@ int32_t tud_mtp_command_received_cb(uint8_t idx, mtp_generic_container_t* cmd_bl break; case MTP_OP_GET_STORAGE_IDS: { - uint32_t storage_ids [] = { 0x00010001u }; // physical = 1, logical = 1 + uint32_t storage_ids [] = { SUPPORTED_STORAGE_ID }; // physical = 1, logical = 1 mtp_container_add_auint32(out_block, 1, storage_ids); tud_mtp_data_send(out_block); break; } case MTP_OP_GET_STORAGE_INFO: { + TU_VERIFY(SUPPORTED_STORAGE_ID == cmd_block->data[0], -1); // update storage info with current free space storage_info.free_space_in_objects = FS_MAX_NODES - fs_get_object_count(); storage_info.free_space_in_bytes = storage_info.free_space_in_objects * FS_MAX_NODE_BYTES; @@ -240,6 +216,20 @@ int32_t tud_mtp_command_received_cb(uint8_t idx, mtp_generic_container_t* cmd_bl break; } + case MTP_OP_GET_DEVICE_PROP_VALUE: { + const uint16_t dev_prop_code = (uint16_t) cmd_block->data[0]; + switch (dev_prop_code) { + case MTP_DEV_PROP_DEVICE_FRIENDLY_NAME: + mtp_container_add_cstring(out_block, DEV_PROP_FRIENDLY_NAME); + tud_mtp_data_send(out_block); + break; + + default: return -1; + } + break; + } + + default: return -1; } diff --git a/src/class/mtp/mtp.h b/src/class/mtp/mtp.h index c48a2469c..138cb1b86 100644 --- a/src/class/mtp/mtp.h +++ b/src/class/mtp/mtp.h @@ -782,17 +782,30 @@ typedef struct TU_ATTR_PACKED { // - datetime_wstring date_modified; // - wstring keywords; -// DevicePropDesc Dataset +// Device property desc up to get/set typedef struct TU_ATTR_PACKED { uint16_t device_property_code; uint16_t datatype; uint8_t get_set; -} mtp_device_prop_desc_t; +} mtp_device_prop_desc_header_t; + // The following fields will be dynamically added to the struct at runtime: // - wstring factory_def_value; // - wstring current_value_len; // - uint8_t form_flag; +// no form +#define MTP_DEVICE_PROPERTIES_TYPEDEF(_type) \ + struct TU_ATTR_PACKED { \ + uint16_t device_property_code; \ + uint16_t datatype; \ + uint8_t get_set; \ + _type factory_default; \ + _type current_value; \ + uint8_t form_flag; /* 0: none, 1: range, 2: enum */ \ + }; + + typedef struct TU_ATTR_PACKED { uint16_t wLength; uint16_t code; @@ -832,15 +845,28 @@ TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_field(mtp_generic } TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_string(mtp_generic_container_t* p_container, uint8_t count, uint16_t* utf16) { - const uint32_t prev_len = p_container->len; uint8_t* container8 = (uint8_t*) p_container; - *(container8 + p_container->len) = count; - p_container->len += 1; + container8[p_container->len] = count; + p_container->len++; memcpy(container8 + p_container->len, utf16, 2 * count); p_container->len += 2 * count; - return p_container->len - prev_len; + return 1 + 2 * count; +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_cstring(mtp_generic_container_t* p_container, const char* str) { + uint8_t* container8 = (uint8_t*) p_container; + const uint8_t len = (uint8_t) (strlen(str) + 1); // include null + container8[p_container->len] = len; + p_container->len++; + + for (uint8_t i = 0; i < len; i++) { + container8[p_container->len] = str[i]; + container8[p_container->len + 1] = 0; + p_container->len += 2; + } + return 1 + 2 * len; } TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_uint8(mtp_generic_container_t* p_container, uint8_t data) { diff --git a/src/class/mtp/mtp_device.c b/src/class/mtp/mtp_device.c index d0a1d6c40..2e9ac9a92 100644 --- a/src/class/mtp/mtp_device.c +++ b/src/class/mtp/mtp_device.c @@ -466,11 +466,12 @@ mtp_phase_type_t mtpd_handle_cmd(mtpd_interface_t* p_mtp) { case MTP_OP_GET_DEVICE_PROP_DESC: TU_LOG_DRV(" MTP command: MTP_OP_GET_DEVICE_PROP_DESC\n"); - return mtpd_handle_cmd_get_device_prop_desc(); + break; case MTP_OP_GET_DEVICE_PROP_VALUE: TU_LOG_DRV(" MTP command: MTP_OP_GET_DEVICE_PROP_VALUE\n"); - return mtpd_handle_cmd_get_device_prop_value(); + break; + case MTP_OP_SEND_OBJECT_INFO: TU_LOG_DRV(" MTP command: MTP_OP_SEND_OBJECT_INFO\n"); return mtpd_handle_cmd_send_object_info(); @@ -673,11 +674,11 @@ mtp_phase_type_t mtpd_handle_cmd_get_device_prop_desc(void) { case MTP_DEV_PROP_DEVICE_FRIENDLY_NAME: { - TU_VERIFY_STATIC(sizeof(mtp_device_prop_desc_t) < MTP_MAX_PACKET_SIZE, "mtp_device_info_t shall fit in MTP_MAX_PACKET_SIZE"); + TU_VERIFY_STATIC(sizeof(mtp_device_prop_desc_header_t) < MTP_MAX_PACKET_SIZE, "mtp_device_info_t shall fit in MTP_MAX_PACKET_SIZE"); p_container->type = MTP_CONTAINER_TYPE_DATA_BLOCK; p_container->code = MTP_OP_GET_DEVICE_PROP_DESC; - p_container->len = MTP_CONTAINER_HEADER_LENGTH + sizeof(mtp_device_prop_desc_t); - mtp_device_prop_desc_t *d = (mtp_device_prop_desc_t *)p_container->data; + p_container->len = MTP_CONTAINER_HEADER_LENGTH + sizeof(mtp_device_prop_desc_header_t); + mtp_device_prop_desc_header_t *d = (mtp_device_prop_desc_header_t *)p_container->data; d->device_property_code = (uint16_t)(device_prop_code); d->datatype = MTP_DATA_TYPE_STR; d->get_set = MTP_MODE_GET; From d9c6dfbe2b2ede8c4147cef2f82765a16ec803b7 Mon Sep 17 00:00:00 2001 From: hathach Date: Sat, 20 Sep 2025 17:37:47 +0700 Subject: [PATCH 12/43] implement get device properties describer and device properties value --- examples/device/mtp/src/mtp_fs_example.c | 29 +++++++++-- src/class/mtp/mtp_device.c | 64 ------------------------ 2 files changed, 26 insertions(+), 67 deletions(-) diff --git a/examples/device/mtp/src/mtp_fs_example.c b/examples/device/mtp/src/mtp_fs_example.c index 89814fb8f..e0dafb5ab 100644 --- a/examples/device/mtp/src/mtp_fs_example.c +++ b/examples/device/mtp/src/mtp_fs_example.c @@ -216,6 +216,26 @@ int32_t tud_mtp_command_received_cb(uint8_t idx, mtp_generic_container_t* cmd_bl break; } + case MTP_OP_GET_DEVICE_PROP_DESC: { + const uint16_t dev_prop_code = (uint16_t) cmd_block->data[0]; + mtp_device_prop_desc_header_t device_prop_header; + device_prop_header.device_property_code = dev_prop_code; + switch (dev_prop_code) { + case MTP_DEV_PROP_DEVICE_FRIENDLY_NAME: + device_prop_header.datatype = MTP_DATA_TYPE_STR; + device_prop_header.get_set = MTP_MODE_GET; + mtp_container_add_raw(out_block, &device_prop_header, sizeof(device_prop_header)); + mtp_container_add_cstring(out_block, DEV_PROP_FRIENDLY_NAME); // factory + mtp_container_add_cstring(out_block, DEV_PROP_FRIENDLY_NAME); // current + mtp_container_add_uint8(out_block, 0); // no form + tud_mtp_data_send(out_block); + break; + + default: return MTP_RESP_PARAMETER_NOT_SUPPORTED; + } + break; + } + case MTP_OP_GET_DEVICE_PROP_VALUE: { const uint16_t dev_prop_code = (uint16_t) cmd_block->data[0]; switch (dev_prop_code) { @@ -224,16 +244,19 @@ int32_t tud_mtp_command_received_cb(uint8_t idx, mtp_generic_container_t* cmd_bl tud_mtp_data_send(out_block); break; - default: return -1; + default: return MTP_RESP_PARAMETER_NOT_SUPPORTED; } break; } + case MTP_OP_GET_OBJECT_HANDLES: - default: return -1; + break; + + default: return MTP_RESP_OPERATION_NOT_SUPPORTED; } - return 0; + return MTP_RESP_OK; } //--------------------------------------------------------------------+ diff --git a/src/class/mtp/mtp_device.c b/src/class/mtp/mtp_device.c index 2e9ac9a92..cb7bb9574 100644 --- a/src/class/mtp/mtp_device.c +++ b/src/class/mtp/mtp_device.c @@ -90,8 +90,6 @@ static mtp_phase_type_t mtpd_handle_cmd_get_object_info(void); static mtp_phase_type_t mtpd_handle_cmd_get_object(void); static mtp_phase_type_t mtpd_handle_dti_get_object(void); static mtp_phase_type_t mtpd_handle_cmd_delete_object(void); -static mtp_phase_type_t mtpd_handle_cmd_get_device_prop_desc(void); -static mtp_phase_type_t mtpd_handle_cmd_get_device_prop_value(void); static mtp_phase_type_t mtpd_handle_cmd_send_object_info(void); static mtp_phase_type_t mtpd_handle_dto_send_object_info(void); static mtp_phase_type_t mtpd_handle_cmd_send_object(void); @@ -662,68 +660,6 @@ mtp_phase_type_t mtpd_handle_cmd_delete_object(void) return MTP_PHASE_RESPONSE; } -mtp_phase_type_t mtpd_handle_cmd_get_device_prop_desc(void) -{ - mtp_generic_container_t* p_container = &_mtpd_epbuf.container; - uint32_t device_prop_code = p_container->data[0]; - - mtp_phase_type_t rt; - if ((rt = mtpd_chk_session_open(__func__)) != MTP_PHASE_NONE) return rt; - - switch(device_prop_code) - { - case MTP_DEV_PROP_DEVICE_FRIENDLY_NAME: - { - TU_VERIFY_STATIC(sizeof(mtp_device_prop_desc_header_t) < MTP_MAX_PACKET_SIZE, "mtp_device_info_t shall fit in MTP_MAX_PACKET_SIZE"); - p_container->type = MTP_CONTAINER_TYPE_DATA_BLOCK; - p_container->code = MTP_OP_GET_DEVICE_PROP_DESC; - p_container->len = MTP_CONTAINER_HEADER_LENGTH + sizeof(mtp_device_prop_desc_header_t); - mtp_device_prop_desc_header_t *d = (mtp_device_prop_desc_header_t *)p_container->data; - d->device_property_code = (uint16_t)(device_prop_code); - d->datatype = MTP_DATA_TYPE_STR; - d->get_set = MTP_MODE_GET; - mtpd_gct_append_wstring(CFG_TUD_MODEL); // factory_def_value - mtpd_gct_append_wstring(CFG_TUD_MODEL); // current_value_len - mtpd_gct_append_uint8(0x00); // form_flag - _mtpd_itf.queued_len = p_container->len; - return MTP_PHASE_DATA_IN; - } - default: - break; - } - - p_container->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; - p_container->code = MTP_RESP_PARAMETER_NOT_SUPPORTED; - p_container->len = MTP_CONTAINER_HEADER_LENGTH; - return MTP_PHASE_RESPONSE; -} - -mtp_phase_type_t mtpd_handle_cmd_get_device_prop_value(void) -{ - mtp_generic_container_t* p_container = &_mtpd_epbuf.container; - uint32_t device_prop_code = p_container->data[0]; - - mtp_phase_type_t rt; - if ((rt = mtpd_chk_session_open(__func__)) != MTP_PHASE_NONE) return rt; - - p_container->len = MTP_CONTAINER_HEADER_LENGTH; - p_container->type = MTP_CONTAINER_TYPE_DATA_BLOCK; - p_container->code = MTP_OP_GET_DEVICE_PROP_VALUE; - - switch(device_prop_code) - { - // TODO support more device properties - case MTP_DEV_PROP_DEVICE_FRIENDLY_NAME: - mtpd_gct_append_wstring(CFG_TUD_MODEL); - _mtpd_itf.queued_len = p_container->len; - return MTP_PHASE_DATA_IN; - default: - p_container->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; - p_container->code = MTP_RESP_PARAMETER_NOT_SUPPORTED; - return MTP_PHASE_RESPONSE; - } -} - mtp_phase_type_t mtpd_handle_cmd_send_object_info(void) { mtp_generic_container_t* p_container = &_mtpd_epbuf.container; From f5a3f25456bcfae65de9b60c8073986f4cdc8f16 Mon Sep 17 00:00:00 2001 From: hathach Date: Sun, 21 Sep 2025 14:10:36 +0700 Subject: [PATCH 13/43] implement get objection handles --- examples/device/mtp/src/mtp_fs_example.c | 37 ++++++++--- src/class/mtp/mtp.h | 81 ++++++------------------ src/class/mtp/mtp_device.c | 44 +------------ 3 files changed, 48 insertions(+), 114 deletions(-) diff --git a/examples/device/mtp/src/mtp_fs_example.c b/examples/device/mtp/src/mtp_fs_example.c index e0dafb5ab..add7d6bf5 100644 --- a/examples/device/mtp/src/mtp_fs_example.c +++ b/examples/device/mtp/src/mtp_fs_example.c @@ -62,10 +62,10 @@ typedef struct { } fs_object_info_t; // Sample object file -static fs_object_info_t _fs_objects[FS_MAX_NODES] = { +static fs_object_info_t fs_objects[FS_MAX_NODES] = { { .handle = 1, - .parent = 0, + .parent = 0xffffffff, .allocated = true, .association = false, .name = "readme.txt", @@ -142,7 +142,7 @@ unsigned int fs_get_object_count(void); fs_object_info_t* fs_object_get_from_handle(uint32_t handle) { fs_object_info_t* obj; for (unsigned int i = 0; i < FS_MAX_NODES; i++) { - obj = &_fs_objects[i]; + obj = &fs_objects[i]; if (obj->allocated && obj->handle == handle) return obj; } @@ -152,7 +152,7 @@ fs_object_info_t* fs_object_get_from_handle(uint32_t handle) { unsigned int fs_get_object_count(void) { unsigned int s = 0; for (unsigned int i = 0; i < FS_MAX_NODES; i++) { - if (_fs_objects[i].allocated) + if (fs_objects[i].allocated) s++; } return s; @@ -249,9 +249,26 @@ int32_t tud_mtp_command_received_cb(uint8_t idx, mtp_generic_container_t* cmd_bl break; } - case MTP_OP_GET_OBJECT_HANDLES: + case MTP_OP_GET_OBJECT_HANDLES: { + const uint32_t storage_id = cmd_block->data[0]; + const uint32_t obj_format = cmd_block->data[1]; // optional + (void) obj_format; + const uint32_t parent_handle = cmd_block->data[2]; // folder handle, 0xFFFFFFFF is root + if (storage_id != 0xFFFFFFFF && storage_id != SUPPORTED_STORAGE_ID) { + return MTP_RESP_INVALID_STORAGE_ID; + } + uint32_t handles[FS_MAX_NODES] = { 0 }; + uint32_t count = 0; + for (uint8_t i = 0, h = 0; i < FS_MAX_NODES; i++) { + if (fs_objects[i].allocated && parent_handle == fs_objects[i].parent) { + handles[count++] = fs_objects[i].handle; + } + } + mtp_container_add_auint32(out_block, count, handles); + tud_mtp_data_send(out_block); break; + } default: return MTP_RESP_OPERATION_NOT_SUPPORTED; } @@ -284,7 +301,7 @@ mtp_response_t tud_mtp_storage_format(uint32_t storage_id) { // Simply deallocate all entries for (unsigned int i = 0; i < FS_MAX_NODES; i++) - _fs_objects[i].allocated = false; + fs_objects[i].allocated = false; TU_LOG1("Format completed\r\n"); return MTP_RESP_OK; } @@ -314,7 +331,7 @@ mtp_response_t tud_mtp_storage_association_get_object_handle(uint32_t storage_id } for (unsigned int i = _fs_operation.traversal_index; i < FS_MAX_NODES; i++) { - obj = &_fs_objects[i]; + obj = &fs_objects[i]; if (obj->allocated && obj->parent == parent_object_handle) { _fs_operation.traversal_index = i + 1; *next_child_handle = obj->handle; @@ -366,8 +383,8 @@ mtp_response_t tud_mtp_storage_object_write_info(uint32_t storage_id, uint32_t p // Search for first free object for (unsigned int i = 0; i < FS_MAX_NODES; i++) { - if (!_fs_objects[i].allocated) { - obj = &_fs_objects[i]; + if (!fs_objects[i].allocated) { + obj = &fs_objects[i]; break; } } @@ -572,7 +589,7 @@ mtp_response_t tud_mtp_storage_object_delete(uint32_t object_handle) { if (object_handle == 0 || obj->association) { // Delete also children for (unsigned int i = 0; i < FS_MAX_NODES; i++) { - obj = &_fs_objects[i]; + obj = &fs_objects[i]; if (obj->allocated && obj->parent == object_handle) { tud_mtp_storage_object_delete(obj->handle); } diff --git a/src/class/mtp/mtp.h b/src/class/mtp/mtp.h index 138cb1b86..c6ff2946a 100644 --- a/src/class/mtp/mtp.h +++ b/src/class/mtp/mtp.h @@ -654,47 +654,6 @@ typedef enum { MTP_ASSOCIATION_2D_PANORAMIC = 0x0006u, } mtp_association_t; - -tu_static const uint16_t mtp_operations_supported[] = { - MTP_OP_GET_DEVICE_INFO, - MTP_OP_OPEN_SESSION, - MTP_OP_CLOSE_SESSION, - MTP_OP_GET_STORAGE_IDS, - MTP_OP_GET_STORAGE_INFO, - MTP_OP_GET_NUM_OBJECTS, - MTP_OP_GET_OBJECT_HANDLES, - MTP_OP_GET_OBJECT_INFO, - MTP_OP_GET_OBJECT, - MTP_OP_DELETE_OBJECT, - MTP_OP_SEND_OBJECT_INFO, - MTP_OP_SEND_OBJECT, - MTP_OP_FORMAT_STORE, - MTP_OP_RESET_DEVICE, - MTP_OP_GET_DEVICE_PROP_DESC, - MTP_OP_GET_DEVICE_PROP_VALUE, - MTP_OP_SET_DEVICE_PROP_VALUE, -}; - -tu_static const uint16_t mtp_events_supported[] = { - MTP_EVENT_OBJECT_ADDED, -}; - -tu_static const uint16_t mtp_device_properties_supported[] = { - MTP_DEV_PROP_DEVICE_FRIENDLY_NAME, -}; - -tu_static const uint16_t mtp_capture_formats[] = { - MTP_OBJ_FORMAT_UNDEFINED, - MTP_OBJ_FORMAT_ASSOCIATION, - MTP_OBJ_FORMAT_TEXT, -}; - -tu_static const uint16_t mtp_playback_formats[] = { - MTP_OBJ_FORMAT_UNDEFINED, - MTP_OBJ_FORMAT_ASSOCIATION, - MTP_OBJ_FORMAT_TEXT, -}; - //--------------------------------------------------------------------+ // Data structures //--------------------------------------------------------------------+ @@ -738,7 +697,7 @@ typedef struct TU_ATTR_PACKED { uint16_t utf16[]; } mtp_flexible_string_t; - typedef union TU_ATTR_PACKED { +typedef union TU_ATTR_PACKED { struct { uint16_t physical; // physical location uint16_t logical; // logical within physical @@ -827,21 +786,16 @@ TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_raw(mtp_generic_c return len; } -TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_field(mtp_generic_container_t* p_container, uint8_t scalar_size, uint32_t count, const void* data) { - if (count == 0) { - // count = 0 means scalar - return mtp_container_add_raw(p_container, data, scalar_size); - } else { - uint8_t* container8 = (uint8_t*) p_container; +TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_array(mtp_generic_container_t* p_container, uint8_t scalar_size, uint32_t count, const void* data) { + uint8_t* container8 = (uint8_t*)p_container; - tu_unaligned_write32(container8 + p_container->len, count); - p_container->len += 4; + tu_unaligned_write32(container8 + p_container->len, count); + p_container->len += 4; - memcpy(container8 + p_container->len, data, count * scalar_size); - p_container->len += count * scalar_size; + memcpy(container8 + p_container->len, data, count * scalar_size); + p_container->len += count * scalar_size; - return 4 + count * scalar_size; - } + return 4 + count * scalar_size; } TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_string(mtp_generic_container_t* p_container, uint8_t count, uint16_t* utf16) { @@ -870,34 +824,37 @@ TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_cstring(mtp_gener } TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_uint8(mtp_generic_container_t* p_container, uint8_t data) { - return mtp_container_add_field(p_container, sizeof(uint8_t), 0, &data); + return mtp_container_add_raw(p_container, &data, 1); } TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_uint16(mtp_generic_container_t* p_container, uint16_t data) { - return mtp_container_add_field(p_container, sizeof(uint16_t), 0, &data); + return mtp_container_add_raw(p_container, &data, 2); } TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_uint32(mtp_generic_container_t* p_container, uint32_t data) { - return mtp_container_add_field(p_container, sizeof(uint32_t), 0, &data); + return mtp_container_add_raw(p_container, &data, 4); } TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_uint64(mtp_generic_container_t* p_container, uint64_t data) { - return mtp_container_add_field(p_container, 8, 0, &data); + return mtp_container_add_raw(p_container, &data, 8); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_uint128(mtp_generic_container_t* p_container, const void* data) { + return mtp_container_add_raw(p_container, data, 16); } TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_auint8(mtp_generic_container_t* p_container, uint32_t count, const uint8_t* data) { - return mtp_container_add_field(p_container, sizeof(uint8_t), count, data); + return mtp_container_add_array(p_container, sizeof(uint8_t), count, data); } TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_auint16(mtp_generic_container_t* p_container, uint32_t count, const uint16_t* data) { - return mtp_container_add_field(p_container, sizeof(uint16_t), count, data); + return mtp_container_add_array(p_container, sizeof(uint16_t), count, data); } TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_auint32(mtp_generic_container_t* p_container, uint32_t count, const uint32_t* data) { - return mtp_container_add_field(p_container, sizeof(uint32_t), count, data); + return mtp_container_add_array(p_container, sizeof(uint32_t), count, data); } - #ifdef __cplusplus } #endif diff --git a/src/class/mtp/mtp_device.c b/src/class/mtp/mtp_device.c index cb7bb9574..8e403f9ac 100644 --- a/src/class/mtp/mtp_device.c +++ b/src/class/mtp/mtp_device.c @@ -85,7 +85,6 @@ static mtp_phase_type_t mtpd_chk_session_open(const char *func_name); static mtp_phase_type_t mtpd_handle_cmd(mtpd_interface_t* p_mtp); static mtp_phase_type_t mtpd_handle_data(void); static mtp_phase_type_t mtpd_handle_cmd_close_session(void); -static mtp_phase_type_t mtpd_handle_cmd_get_object_handles(void); static mtp_phase_type_t mtpd_handle_cmd_get_object_info(void); static mtp_phase_type_t mtpd_handle_cmd_get_object(void); static mtp_phase_type_t mtpd_handle_dti_get_object(void); @@ -451,7 +450,8 @@ mtp_phase_type_t mtpd_handle_cmd(mtpd_interface_t* p_mtp) { case MTP_OP_GET_OBJECT_HANDLES: TU_LOG_DRV(" MTP command: MTP_OP_GET_OBJECT_HANDLES\n"); - return mtpd_handle_cmd_get_object_handles(); + break; + case MTP_OP_GET_OBJECT_INFO: TU_LOG_DRV(" MTP command: MTP_OP_GET_OBJECT_INFO\n"); return mtpd_handle_cmd_get_object_info(); @@ -528,46 +528,6 @@ mtp_phase_type_t mtpd_handle_cmd_close_session(void) return MTP_PHASE_RESPONSE; } -mtp_phase_type_t mtpd_handle_cmd_get_object_handles(void) -{ - mtp_generic_container_t* p_container = &_mtpd_epbuf.container; - uint32_t storage_id = p_container->data[0]; - uint32_t object_format_code = p_container->data[1]; // optional, not managed - uint32_t parent_object_handle = p_container->data[2]; // folder specification, 0xffffffff=objects with no parent - - p_container->len = MTP_CONTAINER_HEADER_LENGTH + sizeof(uint32_t); - p_container->type = MTP_CONTAINER_TYPE_DATA_BLOCK; - p_container->code = MTP_OP_GET_OBJECT_HANDLES; - p_container->data[0] = 0; - - mtp_phase_type_t phase; - if ((phase = mtpd_chk_generic(__func__, (object_format_code != 0), MTP_RESP_SPECIFICATION_BY_FORMAT_UNSUPPORTED, "specification by format unsupported")) != MTP_PHASE_NONE) { - return phase; - } - //list of all object handles on all storages, not managed - if ((phase = mtpd_chk_generic(__func__, (storage_id == 0xFFFFFFFF), MTP_RESP_OPERATION_NOT_SUPPORTED, "list of all object handles on all storages unsupported")) != MTP_PHASE_NONE) { - return phase; - } - - tud_mtp_storage_object_done(); - uint32_t next_child_handle = 0; - while(true) - { - mtp_response_t res = tud_mtp_storage_association_get_object_handle(storage_id, parent_object_handle, &next_child_handle); - if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESP_OK), res, "")) != MTP_PHASE_NONE) { - return phase; - } - if (next_child_handle == 0) { - break; - } - mtpd_gct_append_object_handle(next_child_handle); - } - tud_mtp_storage_object_done(); - - _mtpd_itf.queued_len = p_container->len; - return MTP_PHASE_DATA_IN; -} - mtp_phase_type_t mtpd_handle_cmd_get_object_info(void) { TU_VERIFY_STATIC(sizeof(mtp_object_info_t) < MTP_MAX_PACKET_SIZE, "mtp_object_info_t shall fit in MTP_MAX_PACKET_SIZE"); From 4c818998d499a5a22c27ddfafc618e04ddcb823f Mon Sep 17 00:00:00 2001 From: hathach Date: Sun, 21 Sep 2025 21:39:27 +0700 Subject: [PATCH 14/43] implement get object info --- examples/device/mtp/src/mtp_fs_example.c | 85 +++++++++++------------- src/class/mtp/mtp.h | 35 +++++----- src/class/mtp/mtp_device.c | 25 +------ src/class/mtp/mtp_device_storage.h | 4 +- 4 files changed, 60 insertions(+), 89 deletions(-) diff --git a/examples/device/mtp/src/mtp_fs_example.c b/examples/device/mtp/src/mtp_fs_example.c index add7d6bf5..20d09508f 100644 --- a/examples/device/mtp/src/mtp_fs_example.c +++ b/examples/device/mtp/src/mtp_fs_example.c @@ -65,7 +65,7 @@ typedef struct { static fs_object_info_t fs_objects[FS_MAX_NODES] = { { .handle = 1, - .parent = 0xffffffff, + .parent = 0, .allocated = true, .association = false, .name = "readme.txt", @@ -80,7 +80,7 @@ static fs_object_info_t fs_objects[FS_MAX_NODES] = { #define STORAGE_DESCRIPTRION { 'd', 'i', 's', 'k', 0 } #define VOLUME_IDENTIFIER { 'v', 'o', 'l', 0 } -typedef MTP_STORAGE_INFO_TYPEDEF(TU_ARRAY_SIZE((uint16_t[]) STORAGE_DESCRIPTRION), +typedef MTP_STORAGE_INFO_STRUCT(TU_ARRAY_SIZE((uint16_t[]) STORAGE_DESCRIPTRION), TU_ARRAY_SIZE(((uint16_t[])VOLUME_IDENTIFIER)) ) storage_info_t; @@ -261,7 +261,8 @@ int32_t tud_mtp_command_received_cb(uint8_t idx, mtp_generic_container_t* cmd_bl uint32_t handles[FS_MAX_NODES] = { 0 }; uint32_t count = 0; for (uint8_t i = 0, h = 0; i < FS_MAX_NODES; i++) { - if (fs_objects[i].allocated && parent_handle == fs_objects[i].parent) { + if (fs_objects[i].allocated && parent_handle == fs_objects[i].parent || + (parent_handle == 0xFFFFFFFF && fs_objects[i].parent == 0)) { handles[count++] = fs_objects[i].handle; } } @@ -270,6 +271,40 @@ int32_t tud_mtp_command_received_cb(uint8_t idx, mtp_generic_container_t* cmd_bl break; } + case MTP_OP_GET_OBJECT_INFO: { + const uint32_t object_handle = cmd_block->data[0]; + fs_object_info_t* obj = fs_object_get_from_handle(object_handle); + if (obj == NULL) { + TU_LOG1("ERR: Object with handle %ld does not exist\r\n", object_handle); + return MTP_RESP_INVALID_OBJECT_HANDLE; + } + mtp_object_info_header_t object_info_header = { + .storage_id = SUPPORTED_STORAGE_ID, + .object_format = MTP_OBJ_FORMAT_TEXT, + .protection_status = MTP_PROTECTION_STATUS_NO_PROTECTION, + .object_compressed_size = obj->size, + .thumb_format = MTP_OBJ_FORMAT_UNDEFINED, + .thumb_compressed_size = 0, + .thumb_pix_width = 0, + .thumb_pix_height = 0, + .image_pix_width = 0, + .image_pix_height = 0, + .image_bit_depth = 0, + .parent_object = obj->parent, + .association_type = MTP_ASSOCIATION_UNDEFINED, + .association_desc = 0, + .sequence_number = 0 + }; + mtp_container_add_raw(out_block, &object_info_header, sizeof(object_info_header)); + mtp_container_add_cstring(out_block, obj->name); + mtp_container_add_cstring(out_block, obj->created); + mtp_container_add_cstring(out_block, obj->modified); + mtp_container_add_cstring(out_block, ""); // keywords, not used + + tud_mtp_data_send(out_block); + break; + } + default: return MTP_RESP_OPERATION_NOT_SUPPORTED; } @@ -346,7 +381,7 @@ mtp_response_t tud_mtp_storage_association_get_object_handle(uint32_t storage_id } mtp_response_t tud_mtp_storage_object_write_info(uint32_t storage_id, uint32_t parent_object, - uint32_t* new_object_handle, const mtp_object_info_t* info) { + uint32_t* new_object_handle, const mtp_object_info_header_t* info) { fs_object_info_t* obj = NULL; if (_fs_operation.session_id == 0) { @@ -402,7 +437,7 @@ mtp_response_t tud_mtp_storage_object_write_info(uint32_t storage_id, uint32_t p obj->association = info->object_format == MTP_OBJ_FORMAT_ASSOCIATION; // Extract variable data - uint16_t offset_data = sizeof(mtp_object_info_t); + uint16_t offset_data = sizeof(mtp_object_info_header_t); mtpd_gct_get_string(&offset_data, obj->name, FS_MAX_NODE_NAME_LEN); mtpd_gct_get_string(&offset_data, obj->created, FS_ISODATETIME_LEN); mtpd_gct_get_string(&offset_data, obj->modified, FS_ISODATETIME_LEN); @@ -417,46 +452,6 @@ mtp_response_t tud_mtp_storage_object_write_info(uint32_t storage_id, uint32_t p return MTP_RESP_OK; } -mtp_response_t tud_mtp_storage_object_read_info(uint32_t object_handle, mtp_object_info_t* info) { - const fs_object_info_t* obj; - - if (_fs_operation.session_id == 0) { - TU_LOG1("ERR: Session not open\r\n"); - return MTP_RESP_SESSION_NOT_OPEN; - } - - obj = fs_object_get_from_handle(object_handle); - if (obj == NULL) { - TU_LOG1("ERR: Object with handle %ld does not exist\r\n", object_handle); - return MTP_RESP_INVALID_OBJECT_HANDLE; - } - - memset(info, 0, sizeof(mtp_object_info_t)); - info->storage_id = STORAGE_ID(0x0001, 0x0001); - if (obj->association) { - info->object_format = MTP_OBJ_FORMAT_ASSOCIATION; - info->protection_status = MTP_PROTECTION_STATUS_NO_PROTECTION; - info->object_compressed_size = 0; - info->association_type = MTP_ASSOCIATION_UNDEFINED; - } else { - info->object_format = MTP_OBJ_FORMAT_UNDEFINED; - info->protection_status = MTP_PROTECTION_STATUS_NO_PROTECTION; - info->object_compressed_size = obj->size; - info->association_type = MTP_ASSOCIATION_UNDEFINED; - } - info->thumb_format = MTP_OBJ_FORMAT_UNDEFINED; - info->parent_object = obj->parent; - - mtpd_gct_append_wstring(obj->name); - mtpd_gct_append_wstring(obj->created); // date_created - mtpd_gct_append_wstring(obj->modified); // date_modified - mtpd_gct_append_wstring(""); // keywords, not used - - TU_LOG1("Retrieve object %s with handle %ld\r\n", obj->name, obj->handle); - - return MTP_RESP_OK; -} - mtp_response_t tud_mtp_storage_object_write(uint32_t object_handle, const uint8_t* buffer, uint32_t size) { fs_object_info_t* obj; diff --git a/src/class/mtp/mtp.h b/src/class/mtp/mtp.h index c6ff2946a..61adc29e4 100644 --- a/src/class/mtp/mtp.h +++ b/src/class/mtp/mtp.h @@ -647,11 +647,12 @@ typedef enum { typedef enum { MTP_ASSOCIATION_UNDEFINED = 0x0000u, MTP_ASSOCIATION_GENERIC_FOLDER = 0x0001u, - MTP_ASSOCIATION_GENERIC_ALBUM = 0x0002u, + MTP_ASSOCIATION_ALBUM = 0x0002u, MTP_ASSOCIATION_TIME_SEQUENCE = 0x0003u, MTP_ASSOCIATION_HORIZONTAL_PANORAMIC = 0x0004u, MTP_ASSOCIATION_VERTICAL_PANORAMIC = 0x0005u, MTP_ASSOCIATION_2D_PANORAMIC = 0x0006u, + MTP_ASSOCIATION_ANCILLARY_DATA = 0x0007u, } mtp_association_t; //--------------------------------------------------------------------+ @@ -705,7 +706,7 @@ typedef union TU_ATTR_PACKED { uint32_t id; } mtp_storage_id_t; -#define MTP_STORAGE_INFO_TYPEDEF(_storage_desc_chars, _volume_id_chars) \ +#define MTP_STORAGE_INFO_STRUCT(_storage_desc_chars, _volume_id_chars) \ struct TU_ATTR_PACKED { \ uint16_t storage_type; \ uint16_t filesystem_type; \ @@ -717,29 +718,24 @@ typedef union TU_ATTR_PACKED { mtp_string_t(_volume_id_chars) volume_identifier; \ } -// ObjectInfo Dataset +// Object Info Dataset without dynamic string: filename, date_created, date_modified, keywords typedef struct TU_ATTR_PACKED { uint32_t storage_id; uint16_t object_format; uint16_t protection_status; uint32_t object_compressed_size; - uint16_t thumb_format; // unused - uint32_t thumb_compressed_size; // unused - uint32_t thumb_pix_width; // unused - uint32_t thumb_pix_height; // unused - uint32_t image_pix_width; // unused - uint32_t image_pix_height; // unused - uint32_t image_bit_depth; // unused + uint16_t thumb_format; + uint32_t thumb_compressed_size; + uint32_t thumb_pix_width; + uint32_t thumb_pix_height; + uint32_t image_pix_width; + uint32_t image_pix_height; + uint32_t image_bit_depth; uint32_t parent_object; // 0: root uint16_t association_type; - uint32_t association_description; // not used - uint32_t sequence_number; // not used -} mtp_object_info_t; -// The following fields will be dynamically added to the struct at runtime: -// - wstring filename; -// - datetime_wstring date_created; -// - datetime_wstring date_modified; -// - wstring keywords; + uint32_t association_desc; + uint32_t sequence_number; +} mtp_object_info_header_t; // Device property desc up to get/set typedef struct TU_ATTR_PACKED { @@ -754,7 +750,7 @@ typedef struct TU_ATTR_PACKED { // - uint8_t form_flag; // no form -#define MTP_DEVICE_PROPERTIES_TYPEDEF(_type) \ +#define MTP_DEVICE_PROPERTIES_STRUCT(_type) \ struct TU_ATTR_PACKED { \ uint16_t device_property_code; \ uint16_t datatype; \ @@ -764,7 +760,6 @@ typedef struct TU_ATTR_PACKED { uint8_t form_flag; /* 0: none, 1: range, 2: enum */ \ }; - typedef struct TU_ATTR_PACKED { uint16_t wLength; uint16_t code; diff --git a/src/class/mtp/mtp_device.c b/src/class/mtp/mtp_device.c index 8e403f9ac..22ef45cd9 100644 --- a/src/class/mtp/mtp_device.c +++ b/src/class/mtp/mtp_device.c @@ -85,7 +85,6 @@ static mtp_phase_type_t mtpd_chk_session_open(const char *func_name); static mtp_phase_type_t mtpd_handle_cmd(mtpd_interface_t* p_mtp); static mtp_phase_type_t mtpd_handle_data(void); static mtp_phase_type_t mtpd_handle_cmd_close_session(void); -static mtp_phase_type_t mtpd_handle_cmd_get_object_info(void); static mtp_phase_type_t mtpd_handle_cmd_get_object(void); static mtp_phase_type_t mtpd_handle_dti_get_object(void); static mtp_phase_type_t mtpd_handle_cmd_delete_object(void); @@ -454,7 +453,8 @@ mtp_phase_type_t mtpd_handle_cmd(mtpd_interface_t* p_mtp) { case MTP_OP_GET_OBJECT_INFO: TU_LOG_DRV(" MTP command: MTP_OP_GET_OBJECT_INFO\n"); - return mtpd_handle_cmd_get_object_info(); + break; + case MTP_OP_GET_OBJECT: TU_LOG_DRV(" MTP command: MTP_OP_GET_OBJECT\n"); return mtpd_handle_cmd_get_object(); @@ -528,25 +528,6 @@ mtp_phase_type_t mtpd_handle_cmd_close_session(void) return MTP_PHASE_RESPONSE; } -mtp_phase_type_t mtpd_handle_cmd_get_object_info(void) -{ - TU_VERIFY_STATIC(sizeof(mtp_object_info_t) < MTP_MAX_PACKET_SIZE, "mtp_object_info_t shall fit in MTP_MAX_PACKET_SIZE"); - mtp_generic_container_t* p_container = &_mtpd_epbuf.container; - uint32_t object_handle = p_container->data[0]; - - p_container->len = MTP_CONTAINER_HEADER_LENGTH + sizeof(mtp_object_info_t); - p_container->type = MTP_CONTAINER_TYPE_DATA_BLOCK; - p_container->code = MTP_OP_GET_OBJECT_INFO; - mtp_response_t res = tud_mtp_storage_object_read_info(object_handle, (mtp_object_info_t *)p_container->data); - mtp_phase_type_t phase; - if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESP_OK), res, "")) != MTP_PHASE_NONE) { - return phase; - } - - _mtpd_itf.queued_len = p_container->len; - return MTP_PHASE_DATA_IN; -} - mtp_phase_type_t mtpd_handle_cmd_get_object(void) { mtp_generic_container_t* p_container = &_mtpd_epbuf.container; @@ -634,7 +615,7 @@ mtp_phase_type_t mtpd_handle_dto_send_object_info(void) { mtp_generic_container_t* p_container = &_mtpd_epbuf.container; uint32_t new_object_handle = 0; - mtp_response_t res = tud_mtp_storage_object_write_info(_mtpd_soi.storage_id, _mtpd_soi.parent_object_handle, &new_object_handle, (mtp_object_info_t *)p_container->data); + mtp_response_t res = tud_mtp_storage_object_write_info(_mtpd_soi.storage_id, _mtpd_soi.parent_object_handle, &new_object_handle, (mtp_object_info_header_t *)p_container->data); mtp_phase_type_t phase; if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESP_OK), res, "")) != MTP_PHASE_NONE) return phase; diff --git a/src/class/mtp/mtp_device_storage.h b/src/class/mtp/mtp_device_storage.h index 6c12d38e3..29d4b0bd5 100644 --- a/src/class/mtp/mtp_device_storage.h +++ b/src/class/mtp/mtp_device_storage.h @@ -77,7 +77,7 @@ mtp_response_t tud_mtp_storage_association_get_object_handle(uint32_t session_ha // The handle of the new object shall be returned in new_object_handle. // The structure info contains the information to be used for file creation, as passted by the host. // Note that the variable information (e.g. wstring file name, dates and tags shall be retrieved by using the library functions) -mtp_response_t tud_mtp_storage_object_write_info(uint32_t storage_id, uint32_t parent_object, uint32_t *new_object_handle, const mtp_object_info_t *info); +mtp_response_t tud_mtp_storage_object_write_info(uint32_t storage_id, uint32_t parent_object, uint32_t *new_object_handle, const mtp_object_info_header_t *info); // Get object information related to a given object handle // @@ -89,7 +89,7 @@ mtp_response_t tud_mtp_storage_object_write_info(uint32_t storage_id, uint32_t p // - Date modified (string, use tud_gct_append_date or empty string) // - Keywords (string containing list of kw, separated by space, use tud_mtp_gct_append_wstring) // Note that the variable information (e.g. wstring file name, dates and tags shall be written by using the library functions) -mtp_response_t tud_mtp_storage_object_read_info(uint32_t object_handle, mtp_object_info_t *info); +mtp_response_t tud_mtp_storage_object_read_info(uint32_t object_handle, mtp_object_info_header_t *info); // Get the object size. // From 6fa5268a9c013c9d38fc71649400d9ecc8c2dddf Mon Sep 17 00:00:00 2001 From: hathach Date: Mon, 22 Sep 2025 12:26:03 +0700 Subject: [PATCH 15/43] implement get object, close session --- examples/device/mtp/src/mtp_fs_example.c | 165 +++++++---------------- src/class/mtp/mtp.h | 1 - src/class/mtp/mtp_device.c | 116 +++------------- src/class/mtp/mtp_device.h | 32 ++++- src/class/mtp/mtp_device_storage.h | 42 ------ 5 files changed, 90 insertions(+), 266 deletions(-) diff --git a/examples/device/mtp/src/mtp_fs_example.c b/examples/device/mtp/src/mtp_fs_example.c index 20d09508f..a1c45c6e4 100644 --- a/examples/device/mtp/src/mtp_fs_example.c +++ b/examples/device/mtp/src/mtp_fs_example.c @@ -30,10 +30,10 @@ #define MTPD_VOLUME_IDENTIFIER "volume" //--------------------------------------------------------------------+ -// Device Info +// Dataset //--------------------------------------------------------------------+ -// device info string (including terminating null) +//------------- device info -------------// #define DEV_INFO_MANUFACTURER "TinyUSB" #define DEV_INFO_MODEL "MTP Example" #define DEV_INFO_VERSION "1.0" @@ -41,6 +41,14 @@ #define DEV_PROP_FRIENDLY_NAME "TinyUSB MTP" +//------------- storage info -------------// +#define STORAGE_DESCRIPTRION { 'd', 'i', 's', 'k', 0 } +#define VOLUME_IDENTIFIER { 'v', 'o', 'l', 0 } + +typedef MTP_STORAGE_INFO_STRUCT(TU_ARRAY_SIZE((uint16_t[]) STORAGE_DESCRIPTRION), + TU_ARRAY_SIZE(((uint16_t[])VOLUME_IDENTIFIER)) +) storage_info_t; + //--------------------------------------------------------------------+ // RAM FILESYSTEM //--------------------------------------------------------------------+ @@ -77,12 +85,7 @@ static fs_object_info_t fs_objects[FS_MAX_NODES] = { }; //------------- Storage Info -------------// -#define STORAGE_DESCRIPTRION { 'd', 'i', 's', 'k', 0 } -#define VOLUME_IDENTIFIER { 'v', 'o', 'l', 0 } -typedef MTP_STORAGE_INFO_STRUCT(TU_ARRAY_SIZE((uint16_t[]) STORAGE_DESCRIPTRION), - TU_ARRAY_SIZE(((uint16_t[])VOLUME_IDENTIFIER)) -) storage_info_t; storage_info_t storage_info = { .storage_type = MTP_STORAGE_TYPE_FIXED_RAM, @@ -105,6 +108,7 @@ enum { SUPPORTED_STORAGE_ID = 0x00010001u // physical = 1, logical = 1 }; +static bool is_session_opened = false; //--------------------------------------------------------------------+ // OPERATING STATUS @@ -160,14 +164,9 @@ unsigned int fs_get_object_count(void) { int32_t tud_mtp_data_complete_cb(uint8_t idx, mtp_container_header_t* cmd_header, mtp_generic_container_t* resp_block, tusb_xfer_result_t xfer_result, uint32_t xferred_bytes) { (void) idx; - // switch (cmd_header->code) { - // default: break; - // } resp_block->len = MTP_CONTAINER_HEADER_LENGTH; resp_block->code = (xfer_result == XFER_RESULT_SUCCESS) ? MTP_RESP_OK : MTP_RESP_GENERAL_ERROR; - tud_mtp_response_send(resp_block); - return 0; } @@ -195,7 +194,24 @@ int32_t tud_mtp_command_received_cb(uint8_t idx, mtp_generic_container_t* cmd_bl } case MTP_OP_OPEN_SESSION: - out_block->code = MTP_RESP_OK; + if (is_session_opened) { + //return MTP_RESP_SESSION_ALREADY_OPEN; + out_block->code = MTP_RESP_SESSION_ALREADY_OPEN; + }else { + out_block->code = MTP_RESP_OK; + } + is_session_opened = true; + tud_mtp_response_send(out_block); + break; + + case MTP_OP_CLOSE_SESSION: + if (!is_session_opened) { + // return MTP_RESP_SESSION_NOT_OPEN; + out_block->code = MTP_RESP_SESSION_NOT_OPEN; + } else { + out_block->code = MTP_RESP_OK; + } + is_session_opened = false; tud_mtp_response_send(out_block); break; @@ -272,13 +288,12 @@ int32_t tud_mtp_command_received_cb(uint8_t idx, mtp_generic_container_t* cmd_bl } case MTP_OP_GET_OBJECT_INFO: { - const uint32_t object_handle = cmd_block->data[0]; - fs_object_info_t* obj = fs_object_get_from_handle(object_handle); + const uint32_t obj_handle = cmd_block->data[0]; + fs_object_info_t* obj = fs_object_get_from_handle(obj_handle); if (obj == NULL) { - TU_LOG1("ERR: Object with handle %ld does not exist\r\n", object_handle); return MTP_RESP_INVALID_OBJECT_HANDLE; } - mtp_object_info_header_t object_info_header = { + mtp_object_info_header_t obj_info_header = { .storage_id = SUPPORTED_STORAGE_ID, .object_format = MTP_OBJ_FORMAT_TEXT, .protection_status = MTP_PROTECTION_STATUS_NO_PROTECTION, @@ -295,7 +310,7 @@ int32_t tud_mtp_command_received_cb(uint8_t idx, mtp_generic_container_t* cmd_bl .association_desc = 0, .sequence_number = 0 }; - mtp_container_add_raw(out_block, &object_info_header, sizeof(object_info_header)); + mtp_container_add_raw(out_block, &obj_info_header, sizeof(obj_info_header)); mtp_container_add_cstring(out_block, obj->name); mtp_container_add_cstring(out_block, obj->created); mtp_container_add_cstring(out_block, obj->modified); @@ -305,6 +320,18 @@ int32_t tud_mtp_command_received_cb(uint8_t idx, mtp_generic_container_t* cmd_bl break; } + case MTP_OP_GET_OBJECT: { + const uint32_t obj_handle = cmd_block->data[0]; + fs_object_info_t* obj = fs_object_get_from_handle(obj_handle); + if (obj == NULL) { + return MTP_RESP_INVALID_OBJECT_HANDLE; + } + + mtp_container_add_raw(out_block, obj->data, obj->size); + tud_mtp_data_send(out_block); + break; + } + default: return MTP_RESP_OPERATION_NOT_SUPPORTED; } @@ -314,22 +341,12 @@ int32_t tud_mtp_command_received_cb(uint8_t idx, mtp_generic_container_t* cmd_bl //--------------------------------------------------------------------+ // API //--------------------------------------------------------------------+ -mtp_response_t tud_mtp_storage_close_session(uint32_t session_id) { - if (session_id != _fs_operation.session_id) { - TU_LOG1("ERR: Session %ld not open\r\n", session_id); - return MTP_RESP_SESSION_NOT_OPEN; - } - _fs_operation.session_id = 0; - TU_LOG1("Session closed\r\n"); - return MTP_RESP_OK; -} - mtp_response_t tud_mtp_storage_format(uint32_t storage_id) { if (_fs_operation.session_id == 0) { TU_LOG1("ERR: Session not open\r\n"); return MTP_RESP_SESSION_NOT_OPEN; } - if (storage_id != STORAGE_ID(0x0001, 0x0001)) { + if (storage_id != SUPPORTED_STORAGE_ID) { TU_LOG1("ERR: Unexpected storage id %ld\r\n", storage_id); return MTP_RESP_INVALID_STORAGE_ID; } @@ -341,45 +358,6 @@ mtp_response_t tud_mtp_storage_format(uint32_t storage_id) { return MTP_RESP_OK; } -mtp_response_t tud_mtp_storage_association_get_object_handle(uint32_t storage_id, uint32_t parent_object_handle, - uint32_t* next_child_handle) { - fs_object_info_t* obj; - - if (_fs_operation.session_id == 0) { - TU_LOG1("ERR: Session not open\r\n"); - return MTP_RESP_SESSION_NOT_OPEN; - } - // We just have one storage, same reply if querying all storages - if (storage_id != 0xFFFFFFFF && storage_id != STORAGE_ID(0x0001, 0x0001)) { - TU_LOG1("ERR: Unexpected storage id %ld\r\n", storage_id); - return MTP_RESP_INVALID_STORAGE_ID; - } - - // Request for objects with no parent (0xFFFFFFFF) are considered root objects - // Note: implementation may pass 0 as parent_object_handle - if (parent_object_handle == 0xFFFFFFFF) - parent_object_handle = 0; - - if (parent_object_handle != _fs_operation.traversal_parent) { - _fs_operation.traversal_parent = parent_object_handle; - _fs_operation.traversal_index = 0; - } - - for (unsigned int i = _fs_operation.traversal_index; i < FS_MAX_NODES; i++) { - obj = &fs_objects[i]; - if (obj->allocated && obj->parent == parent_object_handle) { - _fs_operation.traversal_index = i + 1; - *next_child_handle = obj->handle; - TU_LOG1("Association %ld -> child %ld\r\n", parent_object_handle, obj->handle); - return MTP_RESP_OK; - } - } - TU_LOG1("Association traversal completed\r\n"); - _fs_operation.traversal_index = 0; - *next_child_handle = 0; - return MTP_RESP_OK; -} - mtp_response_t tud_mtp_storage_object_write_info(uint32_t storage_id, uint32_t parent_object, uint32_t* new_object_handle, const mtp_object_info_header_t* info) { fs_object_info_t* obj = NULL; @@ -389,7 +367,7 @@ mtp_response_t tud_mtp_storage_object_write_info(uint32_t storage_id, uint32_t p return MTP_RESP_SESSION_NOT_OPEN; } // Accept command on default storage - if (storage_id != 0xFFFFFFFF && storage_id != STORAGE_ID(0x0001, 0x0001)) { + if (storage_id != 0xFFFFFFFF && storage_id != SUPPORTED_STORAGE_ID) { TU_LOG1("ERR: Unexpected storage id %ld\r\n", storage_id); return MTP_RESP_INVALID_STORAGE_ID; } @@ -480,55 +458,6 @@ mtp_response_t tud_mtp_storage_object_write(uint32_t object_handle, const uint8_ return MTP_RESP_OK; } -mtp_response_t tud_mtp_storage_object_size(uint32_t object_handle, uint32_t* size) { - const fs_object_info_t* obj; - obj = fs_object_get_from_handle(object_handle); - if (obj == NULL) { - TU_LOG1("ERR: Object with handle %ld does not exist\r\n", object_handle); - return MTP_RESP_INVALID_OBJECT_HANDLE; - } - *size = obj->size; - return MTP_RESP_OK; -} - -mtp_response_t tud_mtp_storage_object_read(uint32_t object_handle, void* buffer, uint32_t buffer_size, - uint32_t* read_count) { - const fs_object_info_t* obj; - - obj = fs_object_get_from_handle(object_handle); - - if (obj == NULL) { - TU_LOG1("ERR: Object with handle %ld does not exist\r\n", object_handle); - return MTP_RESP_INVALID_OBJECT_HANDLE; - } - // It's not a requirement that this command is preceded by a read info - if (object_handle != _fs_operation.read_handle) { - TU_LOG1("ERR: Object %ld not open for read\r\n", object_handle); - _fs_operation.read_handle = object_handle; - _fs_operation.read_pos = 0; - } - - if (obj->size - _fs_operation.read_pos > buffer_size) { - TU_LOG1("Read object %ld: %ld bytes at offset %ld\r\n", object_handle, buffer_size, _fs_operation.read_pos); - *read_count = buffer_size; - if (_fs_operation.read_pos + buffer_size < FS_MAX_NODE_BYTES) { - memcpy(buffer, &obj->data[_fs_operation.read_pos], *read_count); - } - _fs_operation.read_pos += *read_count; - } else { - TU_LOG1("Read object %ld: %ld bytes at offset %ld\r\n", object_handle, obj->size - _fs_operation.read_pos, - _fs_operation.read_pos); - *read_count = obj->size - _fs_operation.read_pos; - if (_fs_operation.read_pos + *read_count < FS_MAX_NODE_BYTES) { - memcpy(buffer, &obj->data[_fs_operation.read_pos], *read_count); - } - // Read operation completed - _fs_operation.read_handle = 0; - _fs_operation.read_pos = 0; - } - return MTP_RESP_OK; -} - mtp_response_t tud_mtp_storage_object_move(uint32_t object_handle, uint32_t new_parent_object_handle) { fs_object_info_t* obj; diff --git a/src/class/mtp/mtp.h b/src/class/mtp/mtp.h index 61adc29e4..c844b08ab 100644 --- a/src/class/mtp/mtp.h +++ b/src/class/mtp/mtp.h @@ -38,7 +38,6 @@ extern "C" { #endif -#define STORAGE_ID(physical_id, logical_id) ( (((uint32_t)physical_id & 0xFFFF) << 16) | ((uint32_t)logical_id & 0x0000FFFF) ) typedef uint16_t wchar16_t; //--------------------------------------------------------------------+ diff --git a/src/class/mtp/mtp_device.c b/src/class/mtp/mtp_device.c index 22ef45cd9..ca594158e 100644 --- a/src/class/mtp/mtp_device.c +++ b/src/class/mtp/mtp_device.c @@ -79,14 +79,10 @@ typedef struct { //--------------------------------------------------------------------+ // Checker static mtp_phase_type_t mtpd_chk_generic(const char *func_name, const bool err_cd, const uint16_t ret_code, const char *message); -static mtp_phase_type_t mtpd_chk_session_open(const char *func_name); // MTP commands static mtp_phase_type_t mtpd_handle_cmd(mtpd_interface_t* p_mtp); static mtp_phase_type_t mtpd_handle_data(void); -static mtp_phase_type_t mtpd_handle_cmd_close_session(void); -static mtp_phase_type_t mtpd_handle_cmd_get_object(void); -static mtp_phase_type_t mtpd_handle_dti_get_object(void); static mtp_phase_type_t mtpd_handle_cmd_delete_object(void); static mtp_phase_type_t mtpd_handle_cmd_send_object_info(void); static mtp_phase_type_t mtpd_handle_dto_send_object_info(void); @@ -272,6 +268,7 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t break; } +#if 0 case MTP_PHASE_DATA_IN: p_mtp->xferred_len += xferred_bytes; p_mtp->handled_len = p_mtp->xferred_len; @@ -333,16 +330,17 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t } else if (p_mtp->handled_len == 0) { // First data block includes container header + container data TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_out, - (uint8_t*) p_container + p_mtp->xferred_len, + (uint8_t*) p_container + p_mtp->xferred_len, (uint16_t)TU_MIN(p_mtp->total_len - p_mtp->xferred_len, CFG_MTP_EP_SIZE))); } else { // Successive data block includes only container data TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_out, - ((uint8_t *)(&p_container->data)) + p_mtp->xferred_len - p_mtp->handled_len, - (uint16_t)TU_MIN(p_mtp->total_len - p_mtp->xferred_len, CFG_MTP_EP_SIZE))); + ((uint8_t *)(&p_container->data)) + p_mtp->xferred_len - p_mtp->handled_len, + (uint16_t)TU_MIN(p_mtp->total_len - p_mtp->xferred_len, CFG_MTP_EP_SIZE))); } } break; +#endif case MTP_PHASE_RESPONSE_QUEUED: // response phase is complete -> prepare for new command @@ -391,12 +389,16 @@ mtp_phase_type_t mtpd_handle_cmd(mtpd_interface_t* p_mtp) { TU_LOG_DRV(" MTP command: MTP_OP_GET_DEVICE_INFO\n"); tud_mtp_device_info_t dev_info = { .standard_version = 100, - .mtp_vendor_extension_id = 0xFFFFFFFFU, + .mtp_vendor_extension_id = 6, // MTP specs say 0xFFFFFFFF but libMTP check for value 6 .mtp_version = 100, +#ifdef CFG_TUD_MTP_DEVICEINFO_EXTENSIONS .mtp_extensions = { .count = sizeof(CFG_TUD_MTP_DEVICEINFO_EXTENSIONS), .utf16 = { 0 } }, +#else + .mtp_extensions = 0, +#endif .functional_mode = 0x0000, .supported_operations = { .count = TU_ARGS_NUM(CFG_TUD_MTP_DEVICEINFO_SUPPORTED_OPERATIONS), @@ -419,9 +421,11 @@ mtp_phase_type_t mtpd_handle_cmd(mtpd_interface_t* p_mtp) { .arr = { CFG_TUD_MTP_DEVICEINFO_PLAYBACK_FORMATS } } }; +#ifdef CFG_TUD_MTP_DEVICEINFO_EXTENSIONS for (uint8_t i=0; i < dev_info.mtp_extensions.count; i++) { dev_info.mtp_extensions.utf16[i] = (uint16_t)CFG_TUD_MTP_DEVICEINFO_EXTENSIONS[i]; } +#endif p_container->len = MTP_CONTAINER_HEADER_LENGTH + sizeof(tud_mtp_device_info_t); p_container->type = MTP_CONTAINER_TYPE_DATA_BLOCK; p_container->code = MTP_OP_GET_DEVICE_INFO; @@ -437,7 +441,7 @@ mtp_phase_type_t mtpd_handle_cmd(mtpd_interface_t* p_mtp) { case MTP_OP_CLOSE_SESSION: TU_LOG_DRV(" MTP command: MTP_OP_CLOSE_SESSION\n"); - return mtpd_handle_cmd_close_session(); + break; case MTP_OP_GET_STORAGE_IDS: TU_LOG_DRV(" MTP command: MTP_OP_GET_STORAGE_IDS\n"); @@ -457,7 +461,8 @@ mtp_phase_type_t mtpd_handle_cmd(mtpd_interface_t* p_mtp) { case MTP_OP_GET_OBJECT: TU_LOG_DRV(" MTP command: MTP_OP_GET_OBJECT\n"); - return mtpd_handle_cmd_get_object(); + break; + case MTP_OP_DELETE_OBJECT: TU_LOG_DRV(" MTP command: MTP_OP_DELETE_OBJECT\n"); return mtpd_handle_cmd_delete_object(); @@ -495,9 +500,6 @@ mtp_phase_type_t mtpd_handle_data(void) switch(p_container->code) { - case MTP_OP_GET_OBJECT: - TU_LOG_DRV(" MTP command: MTP_OP_GET_OBJECT-DATA_IN\n"); - return mtpd_handle_dti_get_object(); case MTP_OP_SEND_OBJECT_INFO: TU_LOG_DRV(" MTP command: MTP_OP_SEND_OBJECT_INFO-DATA_OUT\n"); return mtpd_handle_dto_send_object_info(); @@ -511,79 +513,6 @@ mtp_phase_type_t mtpd_handle_data(void) return true; } - -mtp_phase_type_t mtpd_handle_cmd_close_session(void) -{ - mtp_generic_container_t* p_container = &_mtpd_epbuf.container; - uint32_t session_id = p_container->data[0]; - - mtp_response_t res = tud_mtp_storage_close_session(session_id); - - _mtpd_itf.session_id = session_id; - - p_container->len = MTP_CONTAINER_HEADER_LENGTH; - p_container->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; - p_container->code = res; - - return MTP_PHASE_RESPONSE; -} - -mtp_phase_type_t mtpd_handle_cmd_get_object(void) -{ - mtp_generic_container_t* p_container = &_mtpd_epbuf.container; - _mtpd_get_object_handle = p_container->data[0]; - - // Continue with DATA-IN - return mtpd_handle_dti_get_object(); -} - -mtp_phase_type_t mtpd_handle_dti_get_object(void) -{ - mtp_response_t res; - mtp_phase_type_t phase; - uint32_t file_size = 0; - mtp_generic_container_t* p_container = &_mtpd_epbuf.container; - res = tud_mtp_storage_object_size(_mtpd_get_object_handle, &file_size); - if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESP_OK), res, "")) != MTP_PHASE_NONE) { - return phase; - } - p_container->len = MTP_CONTAINER_HEADER_LENGTH + file_size; - p_container->type = MTP_CONTAINER_TYPE_DATA_BLOCK; - p_container->code = MTP_OP_GET_OBJECT; - - uint32_t buffer_size; - uint32_t read_count; - // Data block must be multiple of EP size - if (_mtpd_itf.handled_len == 0) - { - // First data block: include container header - buffer_size = ((MTP_MAX_PACKET_SIZE + MTP_CONTAINER_HEADER_LENGTH) / CFG_MTP_EP_SIZE) * CFG_MTP_EP_SIZE - MTP_CONTAINER_HEADER_LENGTH; - res = tud_mtp_storage_object_read(_mtpd_get_object_handle, (void *)&p_container->data, buffer_size, &read_count); - if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESP_OK), res, "")) != MTP_PHASE_NONE) { - return phase; - } - _mtpd_itf.queued_len = MTP_CONTAINER_HEADER_LENGTH + read_count; - } - else - { - // Successive data block: consider only container data - buffer_size = (MTP_MAX_PACKET_SIZE / CFG_MTP_EP_SIZE) * CFG_MTP_EP_SIZE; - res = tud_mtp_storage_object_read(_mtpd_get_object_handle, (void *)&p_container->data, buffer_size, &read_count); - if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESP_OK), res, "")) != MTP_PHASE_NONE) { - return phase; - } - _mtpd_itf.queued_len = read_count; - } - - // File completed - if (read_count < buffer_size) - { - tud_mtp_storage_object_done(); - } - - return MTP_PHASE_DATA_IN; -} - mtp_phase_type_t mtpd_handle_cmd_delete_object(void) { mtp_generic_container_t* p_container = &_mtpd_epbuf.container; @@ -689,21 +618,6 @@ mtp_phase_type_t mtpd_handle_cmd_format_store(void) //--------------------------------------------------------------------+ // Checker //--------------------------------------------------------------------+ -mtp_phase_type_t mtpd_chk_session_open(const char *func_name) -{ - (void)func_name; - mtp_generic_container_t* p_container = &_mtpd_epbuf.container; - if (_mtpd_itf.session_id == 0) - { - TU_LOG_DRV(" MTP error: %s session not open\n", func_name); - p_container->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; - p_container->code = MTP_RESP_SESSION_NOT_OPEN; - p_container->len = MTP_CONTAINER_HEADER_LENGTH; - return MTP_PHASE_RESPONSE; - } - return MTP_PHASE_NONE; -} - mtp_phase_type_t mtpd_chk_generic(const char *func_name, const bool err_cd, const uint16_t ret_code, const char *message) { (void)func_name; diff --git a/src/class/mtp/mtp_device.h b/src/class/mtp/mtp_device.h index 538e3c8d0..b85b1706e 100644 --- a/src/class/mtp/mtp_device.h +++ b/src/class/mtp/mtp_device.h @@ -44,7 +44,7 @@ typedef struct { // Number of supported operations, events, device properties, capture formats, playback formats // and max number of characters for strings manufacturer, model, device_version, serial_number -#define MTP_DEVICE_INFO_TYPEDEF(_extension_nchars, _op_count, _event_count, _devprop_count, _capture_count, _playback_count) \ +#define MTP_DEVICE_INFO_STRUCT(_extension_nchars, _op_count, _event_count, _devprop_count, _capture_count, _playback_count) \ struct TU_ATTR_PACKED { \ uint16_t standard_version; \ uint32_t mtp_vendor_extension_id; \ @@ -59,11 +59,34 @@ typedef struct { /* string fields will be added using append function */ \ } -typedef MTP_DEVICE_INFO_TYPEDEF( - sizeof(CFG_TUD_MTP_DEVICEINFO_EXTENSIONS), TU_ARGS_NUM(CFG_TUD_MTP_DEVICEINFO_SUPPORTED_OPERATIONS), +#define MTP_DEVICE_INFO_NO_EXTENSION_STRUCT(_op_count, _event_count, _devprop_count, _capture_count, _playback_count) \ + struct TU_ATTR_PACKED { \ + uint16_t standard_version; \ + uint32_t mtp_vendor_extension_id; \ + uint16_t mtp_version; \ + uint8_t mtp_extensions; \ + uint16_t functional_mode; \ + mtp_auint16_t(_op_count) supported_operations; \ + mtp_auint16_t(_event_count) supported_events; \ + mtp_auint16_t(_devprop_count) supported_device_properties; \ + mtp_auint16_t(_capture_count) capture_formats; \ + mtp_auint16_t(_playback_count) playback_formats; \ + /* string fields will be added using append function */ \ + } + +#ifdef CFG_TUD_MTP_DEVICEINFO_EXTENSIONS + typedef MTP_DEVICE_INFO_STRUCT( + sizeof(CFG_TUD_MTP_DEVICEINFO_EXTENSIONS), TU_ARGS_NUM(CFG_TUD_MTP_DEVICEINFO_SUPPORTED_OPERATIONS), + TU_ARGS_NUM(CFG_TUD_MTP_DEVICEINFO_SUPPORTED_EVENTS), TU_ARGS_NUM(CFG_TUD_MTP_DEVICEINFO_SUPPORTED_DEVICE_PROPERTIES), + TU_ARGS_NUM(CFG_TUD_MTP_DEVICEINFO_CAPTURE_FORMATS), TU_ARGS_NUM(CFG_TUD_MTP_DEVICEINFO_PLAYBACK_FORMATS) + ) tud_mtp_device_info_t; +#else + typedef MTP_DEVICE_INFO_NO_EXTENSION_STRUCT( + TU_ARGS_NUM(CFG_TUD_MTP_DEVICEINFO_SUPPORTED_OPERATIONS), TU_ARGS_NUM(CFG_TUD_MTP_DEVICEINFO_SUPPORTED_EVENTS), TU_ARGS_NUM(CFG_TUD_MTP_DEVICEINFO_SUPPORTED_DEVICE_PROPERTIES), TU_ARGS_NUM(CFG_TUD_MTP_DEVICEINFO_CAPTURE_FORMATS), TU_ARGS_NUM(CFG_TUD_MTP_DEVICEINFO_PLAYBACK_FORMATS) ) tud_mtp_device_info_t; +#endif //--------------------------------------------------------------------+ // Application API @@ -76,7 +99,8 @@ bool tud_mtp_response_send(mtp_generic_container_t* resp_block); // Application Callbacks //--------------------------------------------------------------------+ -// Invoked when new command is received +// Invoked when new command is received. Application fill the out_block with either DATA or RESPONSE container +// return MTP response code int32_t tud_mtp_command_received_cb(uint8_t idx, mtp_generic_container_t* cmd_block, mtp_generic_container_t* out_block); // Invoked when data phase is complete diff --git a/src/class/mtp/mtp_device_storage.h b/src/class/mtp/mtp_device_storage.h index 29d4b0bd5..30038978f 100644 --- a/src/class/mtp/mtp_device_storage.h +++ b/src/class/mtp/mtp_device_storage.h @@ -54,63 +54,21 @@ * Depending on the application, the handle could be also be the file name or a tag (i.e. host-only file access) */ - -// Initialize MTP storage subsystem -// -// The function shall check if the session is already opened and, in case, set session_id to the -// ID of the current session. - -// Close an open session -mtp_response_t tud_mtp_storage_close_session(uint32_t session_id); - // Format the specified storage mtp_response_t tud_mtp_storage_format(uint32_t storage_id); -// Traverse the given parent object handle and return a child handle for each call -// -// If the parent object has not been opened (or closed before) the function returns the first handle. -// When next_child_handle is 0 all the handles have been listed. -// TODO: traverse by ObjectFormatCode and ObjectHandle association. For now they are unsupported. -mtp_response_t tud_mtp_storage_association_get_object_handle(uint32_t session_handle, uint32_t parent_object_handle, uint32_t *next_child_handle); - // Called with the creation of a new object is requested. // The handle of the new object shall be returned in new_object_handle. // The structure info contains the information to be used for file creation, as passted by the host. // Note that the variable information (e.g. wstring file name, dates and tags shall be retrieved by using the library functions) mtp_response_t tud_mtp_storage_object_write_info(uint32_t storage_id, uint32_t parent_object, uint32_t *new_object_handle, const mtp_object_info_header_t *info); -// Get object information related to a given object handle -// -// The structure info shall be filled according to MTP specifications. Note that -// in addition to filling the fixed mtp_object_info_t structure, the caller must add the following fields via -// library calls -// - Filename (string, use tud_mtp_gct_append_wstring) -// - Date created (string, use tud_gct_append_date or empty string) -// - Date modified (string, use tud_gct_append_date or empty string) -// - Keywords (string containing list of kw, separated by space, use tud_mtp_gct_append_wstring) -// Note that the variable information (e.g. wstring file name, dates and tags shall be written by using the library functions) -mtp_response_t tud_mtp_storage_object_read_info(uint32_t object_handle, mtp_object_info_header_t *info); - -// Get the object size. -// -// The object may be already open when this function is called. -// The implementation shall not assume a specific call order between this function and tud_mtp_storage_object_read. -// The function may leave the file open. -mtp_response_t tud_mtp_storage_object_size(uint32_t object_handle, uint32_t *size); - // Write object data // // The function shall open the object for writing if not already open. // The binary data shall be written to the file in full before this function is returned. mtp_response_t tud_mtp_storage_object_write(uint32_t object_handle, const uint8_t *buffer, uint32_t buffer_size); -// Get object data -// -// The function shall open the object for reading if not already open. -// The amount of data returned shall be the given size parameter. -// read_count shall contain the effective number of bytes written. Iteration is terminated when read_count < buffer_size. -mtp_response_t tud_mtp_storage_object_read(uint32_t object_handle, void *buffer, uint32_t buffer_size, uint32_t *read_count); - // Move an object to a new parent mtp_response_t tud_mtp_storage_object_move(uint32_t object_handle, uint32_t new_parent_object_handle); From 1ab45bc52557a85f488566ee89ef48b7332b7ef0 Mon Sep 17 00:00:00 2001 From: hathach Date: Tue, 23 Sep 2025 09:34:03 +0700 Subject: [PATCH 16/43] try to add logo png to mtp example --- examples/device/mtp/src/mtp_fs_example.c | 140 +++++++++-------- examples/device/mtp/src/tinyusb_logo_png.h | 174 +++++++++++++++++++++ examples/device/mtp/src/tusb_config.h | 48 +++++- src/class/mtp/mtp.h | 36 +++-- src/class/mtp/mtp_device.c | 120 +++++++------- 5 files changed, 381 insertions(+), 137 deletions(-) create mode 100644 examples/device/mtp/src/tinyusb_logo_png.h diff --git a/examples/device/mtp/src/mtp_fs_example.c b/examples/device/mtp/src/mtp_fs_example.c index a1c45c6e4..104260373 100644 --- a/examples/device/mtp/src/mtp_fs_example.c +++ b/examples/device/mtp/src/mtp_fs_example.c @@ -25,6 +25,7 @@ #include "class/mtp/mtp_device_storage.h" #include "tusb.h" +#include "tinyusb_logo_png.h" #define MTPD_STORAGE_DESCRIPTION "storage" #define MTPD_VOLUME_IDENTIFIER "volume" @@ -52,48 +53,55 @@ typedef MTP_STORAGE_INFO_STRUCT(TU_ARRAY_SIZE((uint16_t[]) STORAGE_DESCRIPTRION) //--------------------------------------------------------------------+ // RAM FILESYSTEM //--------------------------------------------------------------------+ -#define FS_MAX_NODES 5UL -#define FS_MAX_NODE_BYTES 128UL -#define FS_MAX_NODE_NAME_LEN 64UL -#define FS_ISODATETIME_LEN 26UL +#define FS_MAX_FILE_COUNT 5UL +#define FS_MAX_CAPACITY_BYTES (2 * 1024UL) +#define FS_MAX_FILENAME_LEN 16UL +#define FS_FIXED_DATETIME "20250808T173500.0" // "YYYYMMDDTHHMMSS.s" + +#define README_TXT_CONTENT "TinyUSB MTP on RAM Filesystem example" typedef struct { - uint32_t handle; + // uint32_t handle; + char name[FS_MAX_FILENAME_LEN]; + mtp_object_formats_t format; uint32_t parent; - uint32_t size; - bool allocated; bool association; - char name[FS_MAX_NODE_NAME_LEN]; - char created[FS_ISODATETIME_LEN]; - char modified[FS_ISODATETIME_LEN]; - uint8_t data[FS_MAX_NODE_BYTES]; + uint32_t size; + uint8_t* data; } fs_object_info_t; -// Sample object file -static fs_object_info_t fs_objects[FS_MAX_NODES] = { +// object data buffer (excluding 2 predefined files) +uint8_t fs_buf[FS_MAX_CAPACITY_BYTES]; +uint8_t fs_buf_head = 0; // simple allocation pointer + +// Files system, handle is index + 1 +static fs_object_info_t fs_objects[FS_MAX_FILE_COUNT] = { { - .handle = 1, - .parent = 0, - .allocated = true, - .association = false, .name = "readme.txt", - .created = "20240104T111134.0", - .modified = "20241214T121110.0", - .data = "USB MTP on RAM Filesystem example\n", - .size = 34 + .format = MTP_OBJ_FORMAT_TEXT, + .parent = 0, + .association = false, + .data = (uint8_t*) README_TXT_CONTENT, + .size = sizeof(README_TXT_CONTENT) + }, + { + .name = "tinyusb.png", + .format = MTP_OBJ_FORMAT_PNG, + .parent = 0, + .association = false, + .data = logo_bin, + .size = logo_len, } }; //------------- Storage Info -------------// - - storage_info_t storage_info = { .storage_type = MTP_STORAGE_TYPE_FIXED_RAM, .filesystem_type = MTP_FILESYSTEM_TYPE_GENERIC_HIERARCHICAL, .access_capability = MTP_ACCESS_CAPABILITY_READ_WRITE, - .max_capacity_in_bytes = FS_MAX_NODES * FS_MAX_NODE_BYTES, - .free_space_in_bytes = FS_MAX_NODES * FS_MAX_NODE_BYTES, - .free_space_in_objects = FS_MAX_NODES, + .max_capacity_in_bytes = sizeof(README_TXT_CONTENT) + logo_len + FS_MAX_CAPACITY_BYTES, + .free_space_in_bytes = 0, // calculated at runtime + .free_space_in_objects = 0, // calculated at runtime .storage_description = { .count = (TU_FIELD_SZIE(storage_info_t, storage_description)-1) / sizeof(uint16_t), .utf16 = STORAGE_DESCRIPTRION @@ -138,32 +146,27 @@ static fs_operation_t _fs_operation = { //--------------------------------------------------------------------+ // Get pointer to object info from handle -fs_object_info_t* fs_object_get_from_handle(uint32_t handle); - -// Get the number of allocated nodes in filesystem -unsigned int fs_get_object_count(void); - -fs_object_info_t* fs_object_get_from_handle(uint32_t handle) { - fs_object_info_t* obj; - for (unsigned int i = 0; i < FS_MAX_NODES; i++) { - obj = &fs_objects[i]; - if (obj->allocated && obj->handle == handle) - return obj; +static inline fs_object_info_t* fs_get_object(uint32_t handle) { + if (handle == 0 || handle > FS_MAX_FILE_COUNT) { + return NULL; } - return NULL; + return &fs_objects[handle-1]; } -unsigned int fs_get_object_count(void) { - unsigned int s = 0; - for (unsigned int i = 0; i < FS_MAX_NODES; i++) { - if (fs_objects[i].allocated) - s++; +// Get the number of allocated nodes in filesystem +uint32_t fs_get_object_count(void) { + uint32_t count = 0; + for (unsigned int i = 0; i < FS_MAX_FILE_COUNT; i++) { + if (fs_objects[i].name[0] != 0) { + count++; + } } - return s; + return count; } int32_t tud_mtp_data_complete_cb(uint8_t idx, mtp_container_header_t* cmd_header, mtp_generic_container_t* resp_block, tusb_xfer_result_t xfer_result, uint32_t xferred_bytes) { (void) idx; + (void) cmd_header; resp_block->len = MTP_CONTAINER_HEADER_LENGTH; resp_block->code = (xfer_result == XFER_RESULT_SUCCESS) ? MTP_RESP_OK : MTP_RESP_GENERAL_ERROR; tud_mtp_response_send(resp_block); @@ -225,8 +228,8 @@ int32_t tud_mtp_command_received_cb(uint8_t idx, mtp_generic_container_t* cmd_bl case MTP_OP_GET_STORAGE_INFO: { TU_VERIFY(SUPPORTED_STORAGE_ID == cmd_block->data[0], -1); // update storage info with current free space - storage_info.free_space_in_objects = FS_MAX_NODES - fs_get_object_count(); - storage_info.free_space_in_bytes = storage_info.free_space_in_objects * FS_MAX_NODE_BYTES; + storage_info.free_space_in_objects = FS_MAX_FILE_COUNT - fs_get_object_count(); + storage_info.free_space_in_bytes = FS_MAX_CAPACITY_BYTES-fs_buf_head; mtp_container_add_raw(out_block, &storage_info, sizeof(storage_info)); tud_mtp_data_send(out_block); break; @@ -274,12 +277,13 @@ int32_t tud_mtp_command_received_cb(uint8_t idx, mtp_generic_container_t* cmd_bl return MTP_RESP_INVALID_STORAGE_ID; } - uint32_t handles[FS_MAX_NODES] = { 0 }; + uint32_t handles[FS_MAX_FILE_COUNT] = { 0 }; uint32_t count = 0; - for (uint8_t i = 0, h = 0; i < FS_MAX_NODES; i++) { - if (fs_objects[i].allocated && parent_handle == fs_objects[i].parent || - (parent_handle == 0xFFFFFFFF && fs_objects[i].parent == 0)) { - handles[count++] = fs_objects[i].handle; + for (uint8_t i = 0; i < FS_MAX_FILE_COUNT; i++) { + fs_object_info_t* obj = &fs_objects[i]; + if (obj->name[0] != 0 && + (parent_handle == obj->parent || (parent_handle == 0xFFFFFFFF && obj->parent == 0))) { + handles[count++] = i + 1; // handle is index + 1 } } mtp_container_add_auint32(out_block, count, handles); @@ -289,22 +293,22 @@ int32_t tud_mtp_command_received_cb(uint8_t idx, mtp_generic_container_t* cmd_bl case MTP_OP_GET_OBJECT_INFO: { const uint32_t obj_handle = cmd_block->data[0]; - fs_object_info_t* obj = fs_object_get_from_handle(obj_handle); + fs_object_info_t* obj = fs_get_object(obj_handle); if (obj == NULL) { return MTP_RESP_INVALID_OBJECT_HANDLE; } mtp_object_info_header_t obj_info_header = { .storage_id = SUPPORTED_STORAGE_ID, - .object_format = MTP_OBJ_FORMAT_TEXT, + .object_format = obj->format, .protection_status = MTP_PROTECTION_STATUS_NO_PROTECTION, .object_compressed_size = obj->size, .thumb_format = MTP_OBJ_FORMAT_UNDEFINED, .thumb_compressed_size = 0, .thumb_pix_width = 0, .thumb_pix_height = 0, - .image_pix_width = 0, - .image_pix_height = 0, - .image_bit_depth = 0, + .image_pix_width = 128, + .image_pix_height = 64, + .image_bit_depth = 32, .parent_object = obj->parent, .association_type = MTP_ASSOCIATION_UNDEFINED, .association_desc = 0, @@ -312,8 +316,8 @@ int32_t tud_mtp_command_received_cb(uint8_t idx, mtp_generic_container_t* cmd_bl }; mtp_container_add_raw(out_block, &obj_info_header, sizeof(obj_info_header)); mtp_container_add_cstring(out_block, obj->name); - mtp_container_add_cstring(out_block, obj->created); - mtp_container_add_cstring(out_block, obj->modified); + mtp_container_add_cstring(out_block, FS_FIXED_DATETIME); + mtp_container_add_cstring(out_block, FS_FIXED_DATETIME); mtp_container_add_cstring(out_block, ""); // keywords, not used tud_mtp_data_send(out_block); @@ -322,7 +326,7 @@ int32_t tud_mtp_command_received_cb(uint8_t idx, mtp_generic_container_t* cmd_bl case MTP_OP_GET_OBJECT: { const uint32_t obj_handle = cmd_block->data[0]; - fs_object_info_t* obj = fs_object_get_from_handle(obj_handle); + fs_object_info_t* obj = fs_get_object(obj_handle); if (obj == NULL) { return MTP_RESP_INVALID_OBJECT_HANDLE; } @@ -341,6 +345,7 @@ int32_t tud_mtp_command_received_cb(uint8_t idx, mtp_generic_container_t* cmd_bl //--------------------------------------------------------------------+ // API //--------------------------------------------------------------------+ +#if 0 mtp_response_t tud_mtp_storage_format(uint32_t storage_id) { if (_fs_operation.session_id == 0) { TU_LOG1("ERR: Session not open\r\n"); @@ -383,7 +388,7 @@ mtp_response_t tud_mtp_storage_object_write_info(uint32_t storage_id, uint32_t p // Ensure we are not creating an orphaned object outside root if (parent_object != 0) { - obj = fs_object_get_from_handle(parent_object); + obj = fs_get_object(parent_object); if (obj == NULL) { TU_LOG1("Parent %ld does not exist\r\n", parent_object); return MTP_RESP_INVALID_PARENT_OBJECT; @@ -433,7 +438,7 @@ mtp_response_t tud_mtp_storage_object_write_info(uint32_t storage_id, uint32_t p mtp_response_t tud_mtp_storage_object_write(uint32_t object_handle, const uint8_t* buffer, uint32_t size) { fs_object_info_t* obj; - obj = fs_object_get_from_handle(object_handle); + obj = fs_get_object(object_handle); if (obj == NULL) { TU_LOG1("ERR: Object with handle %ld does not exist\r\n", object_handle); return MTP_RESP_INVALID_OBJECT_HANDLE; @@ -466,7 +471,7 @@ mtp_response_t tud_mtp_storage_object_move(uint32_t object_handle, uint32_t new_ // Ensure we are not moving to an nonexisting parent if (new_parent_object_handle != 0) { - obj = fs_object_get_from_handle(new_parent_object_handle); + obj = fs_get_object(new_parent_object_handle); if (obj == NULL) { TU_LOG1("Parent %ld does not exist\r\n", new_parent_object_handle); return MTP_RESP_INVALID_PARENT_OBJECT; @@ -477,7 +482,7 @@ mtp_response_t tud_mtp_storage_object_move(uint32_t object_handle, uint32_t new_ } } - obj = fs_object_get_from_handle(object_handle); + obj = fs_get_object(object_handle); if (obj == NULL) { TU_LOG1("ERR: Object with handle %ld does not exist\r\n", object_handle); @@ -500,7 +505,7 @@ mtp_response_t tud_mtp_storage_object_delete(uint32_t object_handle) { object_handle = 0; if (object_handle != 0) { - obj = fs_object_get_from_handle(object_handle); + obj = fs_get_object(object_handle); if (obj == NULL) { TU_LOG1("ERR: Object with handle %ld does not exist\r\n", object_handle); @@ -525,6 +530,7 @@ mtp_response_t tud_mtp_storage_object_delete(uint32_t object_handle) { void tud_mtp_storage_object_done(void) { } +#endif void tud_mtp_storage_cancel(void) { fs_object_info_t* obj; @@ -535,9 +541,9 @@ void tud_mtp_storage_cancel(void) { _fs_operation.read_pos = 0; // If write operation is canceled, discard object if (_fs_operation.write_handle) { - obj = fs_object_get_from_handle(_fs_operation.write_handle); - if (obj) - obj->allocated = false; + obj = fs_get_object(_fs_operation.write_handle); + // if (obj) + // obj->allocated = false; } _fs_operation.write_handle = 0; _fs_operation.write_pos = 0; diff --git a/examples/device/mtp/src/tinyusb_logo_png.h b/examples/device/mtp/src/tinyusb_logo_png.h new file mode 100644 index 000000000..d1071c6d3 --- /dev/null +++ b/examples/device/mtp/src/tinyusb_logo_png.h @@ -0,0 +1,174 @@ +const size_t logo_len = 2733; +const uint8_t logo_bin[] __attribute__((aligned(16))) = { + 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, + 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x40, 0x08, 0x06, 0x00, 0x00, 0x00, 0xd2, 0xd6, 0x7f, + 0x7f, 0x00, 0x00, 0x00, 0x06, 0x62, 0x4b, 0x47, 0x44, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0xa0, + 0xbd, 0xa7, 0x93, 0x00, 0x00, 0x0a, 0x62, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9c, 0xed, 0x9c, 0x7d, + 0x54, 0x54, 0x65, 0x1a, 0xc0, 0x7f, 0xc3, 0x00, 0x29, 0x1f, 0x31, 0x88, 0x61, 0x90, 0xd6, 0xa1, + 0xb3, 0xba, 0x1a, 0x18, 0x7e, 0xf2, 0xad, 0x40, 0x82, 0xb8, 0x9b, 0x2b, 0x43, 0x1e, 0xd2, 0x00, + 0x53, 0xc0, 0xea, 0xec, 0xc9, 0x70, 0x18, 0x3d, 0x5b, 0xb1, 0x9e, 0x4a, 0x6d, 0x13, 0x2d, 0xcb, + 0x76, 0xcd, 0x00, 0xf3, 0x8b, 0x10, 0x8a, 0x8e, 0xd6, 0xee, 0xda, 0x8a, 0x96, 0x30, 0x66, 0xa2, + 0xa0, 0x91, 0x64, 0xae, 0x16, 0x98, 0x45, 0x9d, 0x2c, 0x93, 0x8f, 0x05, 0x11, 0x92, 0x81, 0xbb, + 0x7f, 0xa8, 0x93, 0x33, 0x23, 0xcc, 0xdc, 0x61, 0x60, 0x46, 0xb8, 0xbf, 0xbf, 0x78, 0xdf, 0xfb, + 0x3c, 0xcf, 0xfb, 0xc0, 0x7d, 0xee, 0x73, 0xdf, 0xf7, 0xb9, 0xef, 0x8b, 0x8c, 0x6b, 0x08, 0x82, + 0x90, 0x08, 0x2c, 0x03, 0x02, 0x81, 0x21, 0x48, 0x0c, 0x44, 0xda, 0x81, 0x6a, 0xe0, 0x55, 0x99, + 0x4c, 0x56, 0x0c, 0x20, 0x03, 0x10, 0x04, 0x61, 0x35, 0xb0, 0xc2, 0x86, 0x8e, 0x49, 0xf4, 0x3f, + 0x6b, 0x64, 0x32, 0x59, 0x96, 0x4c, 0x10, 0x84, 0x19, 0xc0, 0x47, 0x5c, 0x0b, 0x06, 0x89, 0x41, + 0x45, 0xac, 0x03, 0xa0, 0x42, 0xba, 0xf9, 0x83, 0x15, 0x95, 0x4c, 0x10, 0x84, 0x8b, 0x80, 0x97, + 0xad, 0x3d, 0x91, 0xb0, 0x09, 0x4d, 0x32, 0x41, 0x10, 0x3a, 0x01, 0x07, 0x5b, 0x7b, 0x22, 0x61, + 0x1b, 0x1c, 0x90, 0xd2, 0xff, 0xa0, 0x46, 0x7a, 0xf2, 0x07, 0x39, 0x52, 0x00, 0x0c, 0x72, 0x1c, + 0x2d, 0x55, 0x3c, 0x52, 0x5e, 0x4e, 0x53, 0x63, 0x93, 0x28, 0x9d, 0x49, 0x93, 0x27, 0x31, 0xe2, + 0xce, 0x3b, 0x4d, 0xca, 0x5d, 0xba, 0x74, 0x89, 0xea, 0x13, 0xd5, 0x26, 0xe5, 0x46, 0x8c, 0xf0, + 0xe6, 0x77, 0xa3, 0x47, 0x8b, 0xf2, 0x41, 0x42, 0x1f, 0x99, 0x20, 0x08, 0x5d, 0x58, 0x30, 0x0f, + 0x48, 0x7c, 0x68, 0x2e, 0x9f, 0x57, 0x55, 0x89, 0xd2, 0xd9, 0x94, 0x9b, 0xc3, 0xcc, 0xb8, 0x38, + 0x93, 0x72, 0x27, 0xbf, 0x38, 0x49, 0xc2, 0x9c, 0x39, 0x78, 0x78, 0x78, 0x74, 0x2b, 0xd3, 0xd6, + 0xd6, 0x46, 0xbc, 0x52, 0xc9, 0x9a, 0x75, 0x6b, 0x45, 0xf9, 0x20, 0xa1, 0x8f, 0xc5, 0x19, 0xa0, + 0x3f, 0xa8, 0xfc, 0xec, 0x38, 0x72, 0xc7, 0x9b, 0xbb, 0xb8, 0x22, 0x2b, 0x8b, 0x4e, 0x6d, 0x67, + 0x3f, 0x7b, 0x34, 0xf0, 0x90, 0xe6, 0x00, 0x83, 0x1c, 0xbb, 0x0e, 0x80, 0xb6, 0xb6, 0x36, 0x5e, + 0x5c, 0xb5, 0x8a, 0x0f, 0x76, 0xbf, 0x0f, 0x40, 0xdd, 0x77, 0xdf, 0xf1, 0xe2, 0xaa, 0x55, 0x1c, + 0xfe, 0xf4, 0xb0, 0x8d, 0x3d, 0x1b, 0x38, 0xd8, 0x75, 0x00, 0x68, 0xb5, 0x5a, 0x34, 0xa5, 0x65, + 0x9c, 0x3a, 0x75, 0x0a, 0x80, 0xa6, 0xa6, 0xff, 0xa1, 0x29, 0x2d, 0xa3, 0xae, 0xee, 0x3b, 0x1b, + 0x7b, 0x36, 0x70, 0xb0, 0xeb, 0x39, 0x80, 0xbb, 0xbb, 0x3b, 0x1f, 0x6b, 0xca, 0x74, 0xed, 0xfb, + 0x03, 0xef, 0xd7, 0xb5, 0x57, 0x64, 0x65, 0xd9, 0xca, 0xad, 0x01, 0x85, 0x5d, 0x07, 0x80, 0xbd, + 0x70, 0xb9, 0xf5, 0x32, 0x3f, 0x5f, 0xf8, 0x19, 0xb9, 0x83, 0x03, 0x6e, 0xee, 0xee, 0x28, 0x14, + 0x0a, 0x1c, 0x1c, 0xec, 0x3a, 0x79, 0x9a, 0x8d, 0xd9, 0xcb, 0xc0, 0x8e, 0x8e, 0x0e, 0x56, 0x3e, + 0xff, 0xbc, 0xae, 0xfd, 0xf1, 0xfe, 0x8f, 0xb8, 0x78, 0xf1, 0xa2, 0xa8, 0xc1, 0x22, 0xa6, 0x4d, + 0x63, 0xe4, 0xa8, 0x91, 0x7a, 0x7d, 0xa1, 0x61, 0x61, 0x3c, 0x38, 0x7b, 0xb6, 0xae, 0xfd, 0xf7, + 0x0d, 0x1b, 0x38, 0x73, 0xfa, 0x0c, 0xfb, 0xf7, 0xed, 0xe3, 0xe1, 0xf9, 0xf3, 0xba, 0xfd, 0x43, + 0x1f, 0xab, 0x3c, 0x86, 0xd0, 0xd5, 0x45, 0x50, 0x48, 0x30, 0x69, 0x8b, 0x17, 0x73, 0xef, 0xbd, + 0xf7, 0xea, 0xae, 0x7d, 0xb4, 0x7f, 0x3f, 0x07, 0x35, 0x1a, 0x51, 0xbe, 0xad, 0x5c, 0xbd, 0x1a, + 0xb9, 0x5c, 0x0e, 0x5c, 0x9d, 0x6b, 0x94, 0xec, 0x2d, 0xe1, 0xa0, 0x46, 0x43, 0xf5, 0x89, 0x13, + 0xb4, 0xb7, 0xb7, 0xeb, 0xc9, 0xba, 0xb9, 0xb9, 0xe1, 0x1f, 0x10, 0x40, 0x4c, 0x6c, 0x0c, 0xf1, + 0x09, 0x09, 0x0c, 0x1b, 0x36, 0x4c, 0x77, 0xed, 0xc7, 0x1f, 0x7f, 0x64, 0xd3, 0xc6, 0x8d, 0xa2, + 0xc6, 0x06, 0x70, 0x90, 0x39, 0xf0, 0xec, 0x5f, 0xb3, 0x18, 0xea, 0xe2, 0x22, 0x4a, 0xef, 0xdd, + 0xa2, 0x77, 0x38, 0x79, 0xf2, 0x0b, 0xbd, 0xbe, 0xe4, 0x94, 0x14, 0xc6, 0xdd, 0x77, 0x9f, 0x59, + 0xfa, 0x66, 0x07, 0x40, 0x5b, 0x5b, 0x1b, 0xe3, 0xc7, 0x99, 0x67, 0x54, 0x0c, 0x8b, 0xd2, 0x52, + 0x59, 0xf1, 0xdc, 0x73, 0xba, 0x76, 0x5c, 0x4c, 0x2c, 0x67, 0x6b, 0x6b, 0x45, 0xd9, 0x28, 0x28, + 0x2a, 0x24, 0x24, 0x34, 0x54, 0xd7, 0x5e, 0xff, 0xf2, 0x2b, 0xbc, 0xf9, 0xc6, 0x1b, 0xa2, 0x6c, + 0x9c, 0xa9, 0xad, 0xa1, 0xa1, 0xa1, 0x81, 0x7f, 0x6c, 0x78, 0x9d, 0xe2, 0xe2, 0x77, 0xcd, 0x5e, + 0x62, 0x0e, 0x19, 0x32, 0x84, 0xa5, 0x99, 0x99, 0xa4, 0xa5, 0xa7, 0xe9, 0x96, 0xac, 0x73, 0xe3, + 0x95, 0x54, 0x57, 0x9b, 0x2e, 0x64, 0x19, 0xf2, 0xf4, 0xb3, 0xcf, 0xf2, 0xd8, 0x13, 0x8f, 0x9b, + 0x2d, 0xdf, 0xdc, 0xdc, 0x4c, 0x44, 0x68, 0x28, 0x97, 0x5b, 0x2f, 0xeb, 0xfa, 0x14, 0x0a, 0x05, + 0x87, 0xca, 0x0f, 0x9b, 0x1d, 0x48, 0x03, 0x23, 0x8f, 0x59, 0x81, 0xea, 0x13, 0x27, 0x98, 0x3d, + 0xeb, 0x0f, 0x14, 0x15, 0x16, 0x8a, 0xaa, 0x2f, 0xb4, 0xb7, 0xb7, 0xb3, 0x76, 0xcd, 0x1a, 0x32, + 0x96, 0x3c, 0x45, 0xa7, 0x56, 0x0b, 0xc0, 0x53, 0xaa, 0xa5, 0x16, 0xf9, 0x90, 0x97, 0x9b, 0xa3, + 0x77, 0x33, 0x4d, 0x51, 0xb4, 0xb3, 0xd0, 0x48, 0x3e, 0xe5, 0xd1, 0x05, 0xa2, 0xb2, 0x88, 0x14, + 0x00, 0xd7, 0x78, 0xe2, 0xb1, 0xc7, 0x69, 0x68, 0x68, 0xb0, 0x58, 0x7f, 0x5f, 0x49, 0x09, 0x6b, + 0xb3, 0xaf, 0x56, 0x25, 0xa3, 0xa2, 0xa3, 0x09, 0x9c, 0x30, 0x41, 0xb4, 0x8d, 0xc6, 0x86, 0x46, + 0x76, 0x16, 0x14, 0x98, 0x25, 0xab, 0xd5, 0x6a, 0x29, 0xc8, 0xcf, 0xd7, 0xeb, 0x73, 0x72, 0x72, + 0x22, 0x29, 0x25, 0x45, 0xd4, 0x98, 0x66, 0x4f, 0x02, 0x9d, 0x9d, 0x9d, 0xd9, 0x71, 0x83, 0x73, + 0xab, 0x57, 0xae, 0xa4, 0xb6, 0xa6, 0x46, 0xd4, 0x60, 0x19, 0x2a, 0x15, 0x93, 0xa7, 0x4c, 0xd1, + 0xeb, 0xf3, 0xf1, 0xf5, 0x11, 0x65, 0xa3, 0xaf, 0x68, 0x6a, 0x6c, 0xec, 0xb5, 0x8d, 0x1d, 0xdb, + 0xb7, 0x91, 0xf8, 0x70, 0x22, 0xa3, 0xc7, 0x8c, 0x61, 0x69, 0xa6, 0x8a, 0xb4, 0x85, 0x8b, 0x44, + 0xdb, 0xc8, 0xcb, 0xcd, 0x21, 0x39, 0x25, 0x05, 0x17, 0xd7, 0x9e, 0x9f, 0xe2, 0x0f, 0xf7, 0xec, + 0xe1, 0xfc, 0xf9, 0xf3, 0x7a, 0x7d, 0x73, 0xe2, 0xe3, 0xf1, 0xf6, 0xf6, 0x16, 0x35, 0x9e, 0xd9, + 0x01, 0x20, 0x97, 0xcb, 0x09, 0x8f, 0x08, 0xd7, 0xb5, 0xdd, 0xdd, 0xdd, 0x45, 0x0d, 0x04, 0x30, + 0x76, 0xdc, 0x58, 0x3d, 0x1b, 0xf6, 0xc4, 0x5d, 0x77, 0xdd, 0x45, 0x70, 0x48, 0x08, 0x23, 0x47, + 0x8d, 0xa4, 0xab, 0xab, 0x8b, 0xe3, 0xc7, 0x8e, 0x73, 0xf4, 0xc8, 0x11, 0x51, 0x36, 0x3a, 0xb5, + 0x9d, 0x14, 0x15, 0x16, 0xf2, 0xdc, 0x0b, 0x2f, 0x30, 0x3d, 0x32, 0x92, 0x29, 0x53, 0xa7, 0x70, + 0xfc, 0xd8, 0x71, 0x51, 0x36, 0xae, 0x67, 0x01, 0x53, 0x73, 0x81, 0xfc, 0x6d, 0xdb, 0x8d, 0xfa, + 0x16, 0xa5, 0xa5, 0x8a, 0x1a, 0x0b, 0xa4, 0x57, 0x80, 0x8e, 0x98, 0x99, 0xb1, 0xac, 0x5b, 0xff, + 0x0a, 0x19, 0x2a, 0x15, 0x2a, 0xb5, 0x9a, 0x82, 0xa2, 0x42, 0xfe, 0xfc, 0xe4, 0x93, 0xa2, 0xed, + 0x94, 0x95, 0xfe, 0x56, 0xb7, 0x58, 0x92, 0x91, 0x61, 0x91, 0x2f, 0xa6, 0xe6, 0x02, 0x95, 0x15, + 0x15, 0x46, 0x93, 0xcc, 0xb0, 0xf0, 0x70, 0xb3, 0x67, 0xfe, 0x37, 0x22, 0x05, 0x40, 0x0f, 0x2c, + 0x4a, 0x4b, 0xd5, 0x2d, 0x0d, 0xcd, 0xe5, 0xfb, 0xba, 0x3a, 0x9a, 0x9a, 0xae, 0x7e, 0x26, 0x8f, + 0x98, 0x36, 0x8d, 0xa9, 0x41, 0x53, 0x45, 0x8f, 0x6b, 0x6a, 0x2e, 0xb0, 0xe5, 0xad, 0xb7, 0x8c, + 0xfa, 0x52, 0xd3, 0xd3, 0x44, 0x8f, 0x03, 0x52, 0x00, 0xf4, 0x88, 0x97, 0x97, 0x17, 0x0a, 0x85, + 0x42, 0xb4, 0x5e, 0x7d, 0x7d, 0xbd, 0xee, 0xe7, 0x25, 0x19, 0xd6, 0x5d, 0x11, 0x7c, 0x7b, 0xee, + 0x5b, 0xca, 0x0e, 0x94, 0xea, 0xf5, 0xf9, 0xf9, 0xf9, 0x11, 0x19, 0x15, 0x65, 0xd1, 0x38, 0x76, + 0x1b, 0x00, 0x81, 0x81, 0x81, 0x6c, 0xdc, 0xb4, 0x89, 0xdc, 0xcd, 0x9b, 0x89, 0x57, 0x2a, 0x01, + 0x58, 0x98, 0xba, 0x88, 0x3d, 0x25, 0x7b, 0xd9, 0xf5, 0xcf, 0x0f, 0x48, 0x4d, 0x4b, 0x43, 0x26, + 0xeb, 0xfb, 0xed, 0x8c, 0xb7, 0xdf, 0x7e, 0xbb, 0x68, 0x9d, 0xc6, 0x86, 0xdf, 0x26, 0x94, 0xe1, + 0x11, 0xe1, 0x4c, 0x0d, 0x0a, 0xb2, 0xc8, 0xc6, 0xcd, 0xb2, 0xc0, 0xb6, 0xad, 0x5b, 0xe9, 0xea, + 0xea, 0xd2, 0xeb, 0x4b, 0x7b, 0x6c, 0xb1, 0xc5, 0x95, 0x49, 0xbb, 0x2c, 0x05, 0xfb, 0xfa, 0xfa, + 0x52, 0x50, 0x54, 0xa8, 0x5b, 0xcf, 0xce, 0x88, 0x8d, 0x61, 0x7a, 0x64, 0x24, 0xf1, 0x09, 0x4a, + 0x9d, 0x4c, 0x60, 0x60, 0x20, 0x1d, 0x37, 0x59, 0x0a, 0x59, 0x1b, 0x4b, 0x82, 0xac, 0xab, 0x4b, + 0xbf, 0x8e, 0x90, 0xa1, 0x5a, 0xca, 0x82, 0xa4, 0x64, 0xd1, 0x76, 0x0c, 0x57, 0x04, 0x4d, 0x4d, + 0x4d, 0xec, 0xde, 0xb5, 0x4b, 0x4f, 0x46, 0xa1, 0x50, 0xa0, 0x54, 0x2a, 0x6f, 0xa6, 0x6e, 0x16, + 0x76, 0x99, 0x01, 0xe6, 0x28, 0x95, 0x0c, 0x75, 0x71, 0x61, 0x5d, 0x76, 0x36, 0xe1, 0x21, 0xa1, + 0xe4, 0x6f, 0xdf, 0x41, 0x7c, 0x82, 0x92, 0xa3, 0x47, 0x8e, 0x70, 0xbf, 0xbf, 0x3f, 0x33, 0x1f, + 0x98, 0x41, 0x4b, 0x4b, 0x0b, 0xf3, 0x93, 0x1e, 0xb1, 0xb5, 0xab, 0x66, 0x11, 0x1a, 0x16, 0x46, + 0x50, 0x70, 0xb0, 0x68, 0x3d, 0xc3, 0x2c, 0x50, 0x58, 0x50, 0x40, 0xdb, 0x65, 0xfd, 0xd7, 0x42, + 0xf2, 0x82, 0x14, 0xd1, 0xe5, 0xe3, 0x1b, 0xb1, 0xcb, 0x00, 0x18, 0x35, 0x6a, 0x14, 0x70, 0x75, + 0x69, 0xf6, 0xe8, 0xc2, 0x85, 0xb8, 0xb9, 0xbb, 0x01, 0x57, 0x9f, 0xc6, 0x27, 0x97, 0x3c, 0xc5, + 0xdc, 0xc4, 0x44, 0x5a, 0x2f, 0xb5, 0x72, 0xcf, 0xdd, 0x77, 0xdb, 0xd2, 0x4d, 0x51, 0xa8, 0x97, + 0x2f, 0xb7, 0x48, 0xef, 0xfa, 0x5c, 0x40, 0xab, 0xd5, 0x52, 0xb8, 0x73, 0xa7, 0xde, 0x35, 0x27, + 0x27, 0x27, 0x92, 0x17, 0x2c, 0xe8, 0x95, 0x5f, 0x76, 0xf9, 0x0a, 0x70, 0x76, 0x76, 0x06, 0x30, + 0xfa, 0xe5, 0x82, 0x43, 0x42, 0x08, 0x0e, 0x09, 0xd1, 0xb5, 0xaf, 0x97, 0x5e, 0x6f, 0x05, 0xa6, + 0x4c, 0x9d, 0x42, 0x70, 0x48, 0x08, 0x15, 0x47, 0x8f, 0x8a, 0xd2, 0xbb, 0x9e, 0x05, 0xbc, 0x86, + 0x7b, 0xf1, 0xd3, 0xf9, 0x9f, 0xf4, 0xae, 0x59, 0x52, 0xf8, 0x31, 0xc4, 0x2e, 0x03, 0xa0, 0x20, + 0x3f, 0x9f, 0xd2, 0xd2, 0x03, 0x26, 0xe5, 0x04, 0x41, 0xe8, 0x07, 0x6f, 0xac, 0x87, 0x4a, 0x9d, + 0xc9, 0x23, 0x0f, 0xcf, 0x13, 0xad, 0xf7, 0x56, 0x5e, 0x1e, 0xc3, 0xef, 0x18, 0x6e, 0xd4, 0x6f, + 0x49, 0xe1, 0xc7, 0x10, 0xbb, 0x0c, 0x80, 0xea, 0xea, 0xea, 0x9b, 0x7e, 0x4d, 0xf3, 0xf0, 0xf0, + 0x20, 0x2a, 0x3a, 0x9a, 0xdb, 0x86, 0xdc, 0x46, 0xd5, 0x67, 0x55, 0xa2, 0x4b, 0xd1, 0xb6, 0x66, + 0x6a, 0x50, 0x10, 0xa1, 0x61, 0x61, 0x1c, 0x29, 0x2f, 0x17, 0xa5, 0x57, 0x5f, 0x5f, 0xaf, 0xb7, + 0xb4, 0x04, 0xcb, 0x0b, 0x3f, 0x86, 0xd8, 0xe5, 0x1c, 0xe0, 0x99, 0xac, 0x2c, 0x6a, 0xbf, 0x3d, + 0x47, 0x4c, 0x6c, 0xac, 0xae, 0x2f, 0x6e, 0xd6, 0x2c, 0x0e, 0x68, 0xca, 0x58, 0xbf, 0xe1, 0x35, + 0x5e, 0xca, 0xce, 0xe6, 0xc3, 0x92, 0xbd, 0x24, 0xce, 0x13, 0xff, 0x34, 0xd9, 0x9a, 0xcc, 0x65, + 0x6a, 0xab, 0xd8, 0xb1, 0xb4, 0xf0, 0x63, 0x88, 0x5d, 0x06, 0xc0, 0xdd, 0xf7, 0x5c, 0x9d, 0xdc, + 0xd5, 0xd6, 0xd4, 0x20, 0x93, 0xc9, 0xc8, 0x50, 0xa9, 0xd8, 0xf8, 0xe6, 0x26, 0x14, 0x9e, 0x9e, + 0x1c, 0xd4, 0x68, 0x78, 0x79, 0xed, 0x5a, 0xae, 0x5c, 0xb9, 0xc2, 0x5f, 0x9e, 0x7e, 0xda, 0xc6, + 0x9e, 0x8a, 0x67, 0xd2, 0xe4, 0xc9, 0xbd, 0xfe, 0x1e, 0xd2, 0x9b, 0xc2, 0x8f, 0x21, 0x76, 0xf9, + 0x0a, 0xa8, 0xad, 0xa9, 0x61, 0x66, 0x5c, 0x1c, 0x1b, 0x73, 0xde, 0xa4, 0x53, 0xab, 0xc5, 0x3f, + 0x20, 0x00, 0xad, 0x56, 0x4b, 0xf6, 0x4b, 0x2f, 0xb1, 0x7d, 0xeb, 0x36, 0x00, 0x66, 0xc4, 0xc4, + 0x30, 0x61, 0xe2, 0x44, 0x9c, 0x9c, 0x9c, 0x6c, 0xec, 0xad, 0x78, 0x54, 0x6a, 0x75, 0xaf, 0x76, + 0x36, 0xa7, 0x2e, 0x4e, 0xb7, 0xda, 0x96, 0x34, 0xbb, 0xcc, 0x00, 0x9b, 0x73, 0xf3, 0xa8, 0xac, + 0xa8, 0x60, 0xec, 0xd8, 0xb1, 0xf8, 0x07, 0x04, 0xf0, 0x7d, 0x5d, 0x1d, 0x49, 0xf3, 0xe6, 0xeb, + 0x6e, 0xbe, 0xe7, 0x30, 0x4f, 0xc6, 0x8d, 0x1b, 0x47, 0xcd, 0xd7, 0x35, 0x74, 0x74, 0x74, 0xd8, + 0xd8, 0x5b, 0xf1, 0x4c, 0x9c, 0x34, 0x89, 0x88, 0x69, 0xd3, 0x2c, 0xd2, 0x55, 0x28, 0x14, 0x24, + 0x24, 0x24, 0x58, 0xcd, 0x17, 0x8b, 0x33, 0x80, 0x25, 0x25, 0x52, 0x73, 0x69, 0x69, 0x69, 0x21, + 0x69, 0xde, 0x7c, 0x7c, 0x7c, 0x7c, 0x70, 0x75, 0x73, 0xe3, 0xdc, 0x37, 0xdf, 0xd0, 0xd9, 0xf9, + 0x5b, 0x75, 0xad, 0xa5, 0xb9, 0x85, 0x88, 0xd0, 0x30, 0xae, 0xdc, 0x82, 0x37, 0xff, 0x3a, 0x2a, + 0x75, 0x26, 0x9f, 0x1e, 0x3a, 0x24, 0x5a, 0xaf, 0xb7, 0x85, 0x1f, 0x43, 0x2c, 0x0e, 0x80, 0x61, + 0x5e, 0xc3, 0x4c, 0x0b, 0x19, 0xf0, 0xeb, 0xaf, 0xbf, 0x9a, 0x25, 0xf7, 0xc7, 0xd9, 0x0f, 0xe2, + 0xef, 0x1f, 0xd0, 0xed, 0xf5, 0x96, 0xe6, 0x66, 0xaa, 0xaa, 0xaa, 0xa8, 0xac, 0xa8, 0x10, 0xed, + 0x83, 0xbd, 0x30, 0x61, 0xe2, 0x44, 0xa6, 0x47, 0x46, 0xf2, 0xc9, 0xc1, 0x83, 0x66, 0xeb, 0x38, + 0x3a, 0x3a, 0x92, 0x94, 0x2c, 0xbe, 0xa4, 0xdc, 0xa3, 0x4d, 0x4b, 0x15, 0xc7, 0x8c, 0xf9, 0xbd, + 0x68, 0x1d, 0xc3, 0x42, 0x46, 0x77, 0x44, 0x47, 0x3f, 0x40, 0xc2, 0xdc, 0x87, 0x4c, 0xca, 0xe5, + 0xe5, 0xe4, 0xb2, 0x2e, 0x3b, 0x5b, 0xb4, 0x1f, 0xf6, 0x82, 0x7a, 0xf9, 0x32, 0x0e, 0x7d, 0xf2, + 0x89, 0xd9, 0xf5, 0x8c, 0x39, 0xf1, 0xf1, 0x66, 0x9d, 0xae, 0x16, 0x83, 0xc5, 0x01, 0x10, 0x33, + 0x33, 0x96, 0xb5, 0x6b, 0xd6, 0x88, 0xd2, 0x29, 0x7e, 0xe7, 0x1d, 0x62, 0x66, 0xc6, 0xe2, 0xe7, + 0xe7, 0xd7, 0xa3, 0x5c, 0x5e, 0x6e, 0x2e, 0x1f, 0xbc, 0xbf, 0xbb, 0xdb, 0xeb, 0x1e, 0x0a, 0x05, + 0xea, 0x65, 0xcb, 0x49, 0x5f, 0x9c, 0xce, 0xeb, 0xaf, 0xbd, 0x26, 0xca, 0x07, 0x7b, 0x22, 0x60, + 0xfc, 0x78, 0x22, 0xa3, 0xa2, 0xd0, 0x94, 0x95, 0x99, 0x16, 0xc6, 0x7a, 0x4b, 0xbf, 0x1b, 0xb1, + 0x38, 0x00, 0xfc, 0xfc, 0xfc, 0x88, 0x9b, 0x35, 0x8b, 0x7d, 0x25, 0x25, 0x66, 0xeb, 0x9c, 0x3b, + 0x77, 0x8e, 0xb8, 0x19, 0x31, 0x78, 0x8f, 0xf0, 0xc6, 0xd5, 0xc5, 0x95, 0xfa, 0x86, 0x06, 0x94, + 0x09, 0x4a, 0xbd, 0x6d, 0xe1, 0x00, 0x0d, 0xf5, 0xf5, 0x46, 0x7b, 0xf1, 0x6f, 0x64, 0xc8, 0x85, + 0x5f, 0x68, 0x6e, 0x6e, 0xc6, 0x41, 0x2e, 0xc7, 0xb1, 0x9b, 0xd3, 0xc3, 0xb7, 0x0a, 0x2a, 0x75, + 0x26, 0x07, 0x35, 0x1a, 0x93, 0x59, 0xc0, 0x5a, 0x85, 0x1f, 0x43, 0x7a, 0xf5, 0xd7, 0x7b, 0x7e, + 0xe5, 0x0b, 0x9c, 0xfa, 0xf2, 0x4b, 0x7e, 0xf8, 0xe1, 0x07, 0xb3, 0x75, 0xba, 0xba, 0xba, 0x4c, + 0xbe, 0x0a, 0x9e, 0xc9, 0xca, 0x32, 0xeb, 0x15, 0xb0, 0xf7, 0xc3, 0xff, 0xd0, 0xda, 0xda, 0x6a, + 0xf6, 0xd8, 0xf6, 0x48, 0xc0, 0xf8, 0xf1, 0x44, 0x45, 0x47, 0x53, 0x56, 0x5a, 0xda, 0xa3, 0x5c, + 0x5f, 0x3c, 0xfd, 0xd0, 0xcb, 0x00, 0xf0, 0x1e, 0x31, 0x82, 0xe2, 0xdd, 0xbb, 0x58, 0x9e, 0xa9, + 0xa6, 0xfc, 0xb0, 0xf5, 0x4e, 0xec, 0xd6, 0xd4, 0x7c, 0xdd, 0xe3, 0x3a, 0xf9, 0xe2, 0xc5, 0x5f, + 0xf8, 0xec, 0xd8, 0x71, 0xde, 0x2b, 0x2e, 0xb6, 0xda, 0x98, 0xb6, 0x44, 0xa5, 0xce, 0x44, 0x53, + 0x56, 0xd6, 0x6d, 0x16, 0xb0, 0x66, 0xe1, 0xc7, 0x90, 0x5e, 0xe7, 0x4f, 0x6f, 0x6f, 0x6f, 0xf2, + 0x77, 0x16, 0x70, 0xac, 0xb2, 0x92, 0x3d, 0xff, 0xfa, 0x37, 0xe5, 0x87, 0x0f, 0x53, 0xf7, 0x7d, + 0x5d, 0x8f, 0x87, 0x2b, 0xe4, 0x72, 0x39, 0xc3, 0xef, 0xb8, 0x03, 0x1f, 0x1f, 0x1f, 0x46, 0x8f, + 0x1e, 0x63, 0x74, 0x3d, 0x2f, 0x27, 0x97, 0xbc, 0x9c, 0xdc, 0xde, 0xba, 0x76, 0xcb, 0xe0, 0x1f, + 0x10, 0x40, 0xf4, 0x8c, 0x07, 0x28, 0xfd, 0xf8, 0xe6, 0x1f, 0xc0, 0xac, 0x59, 0xf8, 0x31, 0xc4, + 0xe2, 0x7f, 0x11, 0xd3, 0x13, 0x9d, 0x5a, 0x2d, 0x3f, 0x5f, 0xb8, 0x40, 0x73, 0x73, 0x33, 0xda, + 0x6b, 0x6b, 0xf5, 0xa1, 0x2e, 0x2e, 0x38, 0x3b, 0x39, 0x31, 0xd4, 0xc5, 0x05, 0x4f, 0x4f, 0x4f, + 0xd1, 0x9b, 0x2d, 0x07, 0x3a, 0xff, 0x3d, 0x75, 0x8a, 0xf8, 0xd9, 0x7f, 0x32, 0xca, 0x02, 0x62, + 0x8f, 0x7a, 0x89, 0xa5, 0x4f, 0x66, 0x50, 0x72, 0x47, 0x47, 0x7c, 0x7d, 0x7d, 0xf1, 0xf5, 0xf5, + 0xed, 0x0b, 0xf3, 0x03, 0x92, 0xfb, 0xfc, 0xfd, 0x09, 0x09, 0x0d, 0x35, 0xfa, 0x52, 0xf8, 0x48, + 0x72, 0x72, 0x9f, 0xdd, 0x7c, 0xb0, 0xd3, 0x52, 0xf0, 0x60, 0xa4, 0xad, 0xad, 0x8d, 0xd3, 0xa7, + 0x4f, 0xeb, 0xf5, 0x39, 0x3a, 0x3a, 0x92, 0x2c, 0xf2, 0xa8, 0x97, 0x58, 0xa4, 0x00, 0xb0, 0x13, + 0x76, 0xbd, 0xf7, 0x9e, 0xd1, 0xf1, 0xb4, 0x39, 0xf1, 0xf1, 0xdc, 0xe9, 0x63, 0xdd, 0xc2, 0x8f, + 0x21, 0x52, 0x00, 0xd8, 0x01, 0x82, 0x20, 0xf0, 0xf6, 0x0e, 0xe3, 0xdd, 0xcd, 0x7d, 0xb5, 0xf4, + 0xbb, 0x11, 0x29, 0x00, 0xec, 0x80, 0xb2, 0x03, 0xa5, 0x9c, 0x3d, 0x7b, 0x56, 0xaf, 0x2f, 0x34, + 0x2c, 0xac, 0x4f, 0x0a, 0x3f, 0x86, 0x48, 0x01, 0x60, 0x07, 0x6c, 0xdd, 0xb2, 0xc5, 0xa8, 0x2f, + 0x35, 0x3d, 0xbd, 0x5f, 0xc6, 0x96, 0x02, 0xc0, 0xc6, 0x7c, 0x75, 0xe6, 0x2b, 0xa3, 0x9d, 0xc2, + 0x7e, 0x7e, 0x7e, 0x44, 0x45, 0x47, 0xf5, 0xcb, 0xf8, 0x52, 0x00, 0xd8, 0x98, 0x2d, 0x9b, 0x37, + 0x1b, 0xad, 0xfd, 0xfb, 0xb2, 0xf0, 0x63, 0x48, 0x9f, 0x14, 0x82, 0x24, 0xcc, 0xa3, 0xbe, 0xbe, + 0x9e, 0xe9, 0x61, 0xe1, 0x7a, 0xfb, 0x24, 0xfa, 0xba, 0xf0, 0x63, 0x88, 0x94, 0x01, 0x6c, 0xc8, + 0xdb, 0x3b, 0x76, 0x18, 0x6d, 0x92, 0x49, 0x4a, 0xb1, 0xee, 0x8e, 0x1f, 0x53, 0x48, 0x19, 0xc0, + 0x86, 0xb4, 0xb6, 0xb6, 0xa2, 0x35, 0x38, 0xdd, 0xe4, 0xea, 0xea, 0xda, 0xaf, 0x9f, 0xb8, 0xa5, + 0x00, 0x18, 0xe4, 0x38, 0x00, 0xb7, 0xd6, 0xf9, 0x2a, 0x09, 0xab, 0xe2, 0x00, 0xf4, 0xfe, 0xdf, + 0x63, 0x49, 0xdc, 0xaa, 0x34, 0x3a, 0x00, 0xe2, 0x0e, 0xaa, 0x49, 0x0c, 0x24, 0xca, 0x65, 0x82, + 0x20, 0x44, 0x03, 0x07, 0x90, 0xe6, 0x01, 0x83, 0x0d, 0x01, 0x88, 0x75, 0x90, 0xc9, 0x64, 0x65, + 0xc0, 0x8b, 0xb6, 0xf6, 0x46, 0xa2, 0xdf, 0xf9, 0x9b, 0x4c, 0x26, 0x3b, 0xa0, 0x7b, 0xea, 0x05, + 0x41, 0x98, 0x0b, 0xa8, 0x81, 0x89, 0xc0, 0x50, 0x9b, 0xb9, 0x25, 0xd1, 0x97, 0xb4, 0x03, 0x9f, + 0x03, 0xeb, 0x65, 0x32, 0xd9, 0x2e, 0x80, 0xff, 0x03, 0xff, 0x08, 0x81, 0xdd, 0xa8, 0xcb, 0xf5, + 0x99, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82, +}; diff --git a/examples/device/mtp/src/tusb_config.h b/examples/device/mtp/src/tusb_config.h index 251a99fd3..7e2c5b670 100644 --- a/examples/device/mtp/src/tusb_config.h +++ b/examples/device/mtp/src/tusb_config.h @@ -92,18 +92,52 @@ //------------- CLASS -------------// #define CFG_TUD_MTP 1 - -#define CFG_TUD_MANUFACTURER "TinyUsb Manufacturer" -#define CFG_TUD_MODEL "TinyUsb Device" - #define CFG_MTP_EP_SIZE 64 #define CFG_MTP_EVT_EP_SIZE 64 #define CFG_MTP_EVT_INTERVAL 100 -#define CFG_MTP_DEVICE_VERSION "1.0" -#define CFG_MTP_SERIAL_NUMBER "0" +//------------- MTP device info -------------// +#define CFG_TUD_MTP_DEVICEINFO_EXTENSIONS "microsoft.com: 1.0; " +#define CFG_TUD_MTP_DEVICEINFO_SUPPORTED_OPERATIONS \ + MTP_OP_GET_DEVICE_INFO, \ + MTP_OP_OPEN_SESSION, \ + MTP_OP_CLOSE_SESSION, \ + MTP_OP_GET_STORAGE_IDS, \ + MTP_OP_GET_STORAGE_INFO, \ + MTP_OP_GET_NUM_OBJECTS, \ + MTP_OP_GET_OBJECT_HANDLES, \ + MTP_OP_GET_OBJECT_INFO, \ + MTP_OP_GET_OBJECT, \ + MTP_OP_DELETE_OBJECT, \ + MTP_OP_SEND_OBJECT_INFO, \ + MTP_OP_SEND_OBJECT, \ + MTP_OP_FORMAT_STORE, \ + MTP_OP_RESET_DEVICE, \ + MTP_OP_GET_DEVICE_PROP_DESC, \ + MTP_OP_GET_DEVICE_PROP_VALUE, \ + MTP_OP_SET_DEVICE_PROP_VALUE + +#define CFG_TUD_MTP_DEVICEINFO_SUPPORTED_EVENTS \ + MTP_EVENT_OBJECT_ADDED + +#define CFG_TUD_MTP_DEVICEINFO_SUPPORTED_DEVICE_PROPERTIES \ + MTP_DEV_PROP_DEVICE_FRIENDLY_NAME + +#define CFG_TUD_MTP_DEVICEINFO_CAPTURE_FORMATS \ + MTP_OBJ_FORMAT_UNDEFINED, \ + MTP_OBJ_FORMAT_ASSOCIATION, \ + MTP_OBJ_FORMAT_TEXT, \ + MTP_OBJ_FORMAT_PNG + +#define CFG_TUD_MTP_DEVICEINFO_PLAYBACK_FORMATS \ + MTP_OBJ_FORMAT_UNDEFINED, \ + MTP_OBJ_FORMAT_ASSOCIATION, \ + MTP_OBJ_FORMAT_TEXT, \ + MTP_OBJ_FORMAT_PNG + +#define CFG_TUD_MANUFACTURER "TinyUsb Manufacturer" +#define CFG_TUD_MODEL "TinyUsb Device" #define CFG_MTP_INTERFACE (CFG_TUD_MODEL " MTP") -#define CFG_MTP_STORAGE_ID_COUNT 1 #ifdef __cplusplus } diff --git a/src/class/mtp/mtp.h b/src/class/mtp/mtp.h index c844b08ab..c44b99fda 100644 --- a/src/class/mtp/mtp.h +++ b/src/class/mtp/mtp.h @@ -470,7 +470,7 @@ typedef enum { MTP_OP_DELETE_OBJECT = 0x100Bu, MTP_OP_SEND_OBJECT_INFO = 0x100Cu, MTP_OP_SEND_OBJECT = 0x100Du, - MTP_OP_INITIAL_CAPTURE = 0x100Eu, + MTP_OP_INITIATE_CAPTURE = 0x100Eu, MTP_OP_FORMAT_STORE = 0x100Fu, MTP_OP_RESET_DEVICE = 0x1010u, MTP_OP_SELF_TEST = 0x1011u, @@ -734,6 +734,10 @@ typedef struct TU_ATTR_PACKED { uint16_t association_type; uint32_t association_desc; uint32_t sequence_number; + // mtp_string_t() filename + // mtp_string_t() date_created + // mtp_string_t() date_modified + // mtp_string_t() keywords } mtp_object_info_header_t; // Device property desc up to get/set @@ -775,12 +779,15 @@ typedef struct TU_ATTR_PACKED { //--------------------------------------------------------------------+ TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_raw(mtp_generic_container_t* p_container, const void* data, uint32_t len) { + TU_ASSERT(p_container->len + len < sizeof(mtp_generic_container_t), 0); memcpy((uint8_t*) p_container + p_container->len, data, len); p_container->len += len; return len; } TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_array(mtp_generic_container_t* p_container, uint8_t scalar_size, uint32_t count, const void* data) { + const uint32_t added_len = 4 + count * scalar_size; + TU_ASSERT(p_container->len + added_len < sizeof(mtp_generic_container_t), 0); uint8_t* container8 = (uint8_t*)p_container; tu_unaligned_write32(container8 + p_container->len, count); @@ -789,32 +796,43 @@ TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_array(mtp_generic memcpy(container8 + p_container->len, data, count * scalar_size); p_container->len += count * scalar_size; - return 4 + count * scalar_size; + return added_len; } TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_string(mtp_generic_container_t* p_container, uint8_t count, uint16_t* utf16) { + const uint32_t added_len = 1 + 2 * count; + TU_ASSERT(p_container->len + added_len < sizeof(mtp_generic_container_t), 0); uint8_t* container8 = (uint8_t*) p_container; + container8[p_container->len] = count; p_container->len++; memcpy(container8 + p_container->len, utf16, 2 * count); p_container->len += 2 * count; - return 1 + 2 * count; + return added_len; } TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_cstring(mtp_generic_container_t* p_container, const char* str) { - uint8_t* container8 = (uint8_t*) p_container; const uint8_t len = (uint8_t) (strlen(str) + 1); // include null + TU_ASSERT(p_container->len + 1 + 2 * len < sizeof(mtp_generic_container_t), 0); + + uint8_t* container8 = (uint8_t*) p_container; container8[p_container->len] = len; p_container->len++; - for (uint8_t i = 0; i < len; i++) { - container8[p_container->len] = str[i]; - container8[p_container->len + 1] = 0; - p_container->len += 2; + if (len == 1) { + // empty string (null only) + container8[p_container->len] = 0; + return 1; + } else { + for (uint8_t i = 0; i < len; i++) { + container8[p_container->len] = str[i]; + container8[p_container->len + 1] = 0; + p_container->len += 2; + } + return 1 + 2 * len; } - return 1 + 2 * len; } TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_uint8(mtp_generic_container_t* p_container, uint8_t data) { diff --git a/src/class/mtp/mtp_device.c b/src/class/mtp/mtp_device.c index ca594158e..91df13111 100644 --- a/src/class/mtp/mtp_device.c +++ b/src/class/mtp/mtp_device.c @@ -102,6 +102,65 @@ CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN static mtp_basic_object_info_t _mtpd_soi; CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN static char _mtp_datestr[20]; +//--------------------------------------------------------------------+ +// Debug +//--------------------------------------------------------------------+ +#if CFG_TUSB_DEBUG >= CFG_TUD_MTP_LOG_LEVEL + +TU_ATTR_UNUSED static tu_lookup_entry_t const _mpt_op_lookup[] = { +{.key = MTP_OP_UNDEFINED , .data = "Undefined" } , +{.key = MTP_OP_GET_DEVICE_INFO , .data = "GetDeviceInfo" } , +{.key = MTP_OP_OPEN_SESSION , .data = "OpenSession" } , +{.key = MTP_OP_CLOSE_SESSION , .data = "CloseSession" } , +{.key = MTP_OP_GET_STORAGE_IDS , .data = "GetStorageIDs" } , +{.key = MTP_OP_GET_STORAGE_INFO , .data = "GetStorageInfo" } , +{.key = MTP_OP_GET_NUM_OBJECTS , .data = "GetNumObjects" } , +{.key = MTP_OP_GET_OBJECT_HANDLES , .data = "GetObjectHandles" } , +{.key = MTP_OP_GET_OBJECT_INFO , .data = "GetObjectInfo" } , +{.key = MTP_OP_GET_OBJECT , .data = "GetObject" } , +{.key = MTP_OP_GET_THUMB , .data = "GetThumb" } , +{.key = MTP_OP_DELETE_OBJECT , .data = "DeleteObject" } , +{.key = MTP_OP_SEND_OBJECT_INFO , .data = "SendObjectInfo" } , +{.key = MTP_OP_SEND_OBJECT , .data = "SendObject" } , +{.key = MTP_OP_INITIATE_CAPTURE , .data = "InitiateCapture" } , +{.key = MTP_OP_FORMAT_STORE , .data = "FormatStore" } , +{.key = MTP_OP_RESET_DEVICE , .data = "ResetDevice" } , +{.key = MTP_OP_SELF_TEST , .data = "SelfTest" } , +{.key = MTP_OP_SET_OBJECT_PROTECTION , .data = "SetObjectProtection" } , +{.key = MTP_OP_POWER_DOWN , .data = "PowerDown" } , +{.key = MTP_OP_GET_DEVICE_PROP_DESC , .data = "GetDevicePropDesc" } , +{.key = MTP_OP_GET_DEVICE_PROP_VALUE , .data = "GetDevicePropValue" } , +{.key = MTP_OP_SET_DEVICE_PROP_VALUE , .data = "SetDevicePropValue" } , +{.key = MTP_OP_RESET_DEVICE_PROP_VALUE , .data = "ResetDevicePropValue" } , +{.key = MTP_OP_TERMINATE_OPEN_CAPTURE , .data = "TerminateOpenCapture" } , +{.key = MTP_OP_MOVE_OBJECT , .data = "MoveObject" } , +{.key = MTP_OP_COPY_OBJECT , .data = "CopyObject" } , +{.key = MTP_OP_GET_PARTIAL_OBJECT , .data = "GetPartialObject" } , +{.key = MTP_OP_INITIATE_OPEN_CAPTURE , .data = "InitiateOpenCapture" } , +{.key = MTP_OP_GET_OBJECT_PROPS_SUPPORTED , .data = "GetObjectPropsSupported" } , +{.key = MTP_OP_GET_OBJECT_PROP_DESC , .data = "GetObjectPropDesc" } , +{.key = MTP_OP_GET_OBJECT_PROP_VALUE , .data = "GetObjectPropValue" } , +{.key = MTP_OP_SET_OBJECT_PROP_VALUE , .data = "SetObjectPropValue" } , +{.key = MTP_OP_GET_OBJECT_PROPLIST , .data = "GetObjectPropList" } , +{.key = MTP_OP_GET_OBJECT_PROP_REFERENCES , .data = "GetObjectPropReferences" } , +{.key = MTP_OP_GET_SERVICE_IDS , .data = "GetServiceIDs" } , +{.key = MTP_OP_GET_SERVICE_INFO , .data = "GetServiceInfo" } , +{.key = MTP_OP_GET_SERVICE_CAPABILITIES , .data = "GetServiceCapabilities" } , +{.key = MTP_OP_GET_SERVICE_PROP_DESC , .data = "GetServicePropDesc" } , +{.key = MTP_OP_GET_OBJECT_PROP_LIST , .data = "GetObjectPropList" } , +{.key = MTP_OP_SET_OBJECT_PROP_LIST , .data = "SetObjectPropList" } , +{.key = MTP_OP_GET_INTERDEPENDENT_PROP_DESC , .data = "GetInterdependentPropDesc" } , +{.key = MTP_OP_SEND_OBJECT_PROP_LIST , .data = "SendObjectPropList" } +}; + +TU_ATTR_UNUSED static tu_lookup_table_t const _mtp_op_table = { + .count = TU_ARRAY_SIZE(_mpt_op_lookup), + .items = _mpt_op_lookup +}; + +#endif + + //--------------------------------------------------------------------+ // Helper //--------------------------------------------------------------------+ @@ -382,11 +441,12 @@ mtp_phase_type_t mtpd_handle_cmd(mtpd_interface_t* p_mtp) { _mtpd_soi.object_handle = 0; } - mtp_phase_type_t ret = MTP_PHASE_RESPONSE; + tu_lookup_find(&_mtp_op_table, cmd_block.code); + TU_LOG_DRV(" MTP command: %s\r\n", (char const*) tu_lookup_find(&_mtp_op_table, cmd_block.code)); - switch (p_container->code) { + mtp_phase_type_t ret = MTP_PHASE_RESPONSE; + switch (cmd_block.code) { case MTP_OP_GET_DEVICE_INFO: { - TU_LOG_DRV(" MTP command: MTP_OP_GET_DEVICE_INFO\n"); tud_mtp_device_info_t dev_info = { .standard_version = 100, .mtp_vendor_extension_id = 6, // MTP specs say 0xFFFFFFFF but libMTP check for value 6 @@ -435,63 +495,14 @@ mtp_phase_type_t mtpd_handle_cmd(mtpd_interface_t* p_mtp) { break; } - case MTP_OP_OPEN_SESSION: - TU_LOG_DRV(" MTP command: MTP_OP_OPEN_SESSION\n"); - break; - - case MTP_OP_CLOSE_SESSION: - TU_LOG_DRV(" MTP command: MTP_OP_CLOSE_SESSION\n"); - break; - - case MTP_OP_GET_STORAGE_IDS: - TU_LOG_DRV(" MTP command: MTP_OP_GET_STORAGE_IDS\n"); - break; - - case MTP_OP_GET_STORAGE_INFO: - TU_LOG_DRV(" MTP command: MTP_OP_GET_STORAGE_INFO for ID=%lu\n", p_container->data[0]); - break; - - case MTP_OP_GET_OBJECT_HANDLES: - TU_LOG_DRV(" MTP command: MTP_OP_GET_OBJECT_HANDLES\n"); - break; - - case MTP_OP_GET_OBJECT_INFO: - TU_LOG_DRV(" MTP command: MTP_OP_GET_OBJECT_INFO\n"); - break; - - case MTP_OP_GET_OBJECT: - TU_LOG_DRV(" MTP command: MTP_OP_GET_OBJECT\n"); - break; - - case MTP_OP_DELETE_OBJECT: - TU_LOG_DRV(" MTP command: MTP_OP_DELETE_OBJECT\n"); - return mtpd_handle_cmd_delete_object(); - - case MTP_OP_GET_DEVICE_PROP_DESC: - TU_LOG_DRV(" MTP command: MTP_OP_GET_DEVICE_PROP_DESC\n"); - break; - - case MTP_OP_GET_DEVICE_PROP_VALUE: - TU_LOG_DRV(" MTP command: MTP_OP_GET_DEVICE_PROP_VALUE\n"); - break; - - case MTP_OP_SEND_OBJECT_INFO: - TU_LOG_DRV(" MTP command: MTP_OP_SEND_OBJECT_INFO\n"); - return mtpd_handle_cmd_send_object_info(); - case MTP_OP_SEND_OBJECT: - TU_LOG_DRV(" MTP command: MTP_OP_SEND_OBJECT\n"); - return mtpd_handle_cmd_send_object(); - case MTP_OP_FORMAT_STORE: - TU_LOG_DRV(" MTP command: MTP_OP_FORMAT_STORE\n"); - return mtpd_handle_cmd_format_store(); default: - TU_LOG_DRV(" MTP command: MTP_OP_UNKNOWN_COMMAND %x!!!!\n", p_container->code); - return false; + break; } tud_mtp_command_received_cb(0, &cmd_block, p_container); return ret; } +#if 0 mtp_phase_type_t mtpd_handle_data(void) { @@ -614,6 +625,7 @@ mtp_phase_type_t mtpd_handle_cmd_format_store(void) p_container->len = MTP_CONTAINER_HEADER_LENGTH; return MTP_PHASE_RESPONSE; } +#endif //--------------------------------------------------------------------+ // Checker From 3c39f60f63109f7828009ebda135fc675288d610 Mon Sep 17 00:00:00 2001 From: hathach Date: Tue, 23 Sep 2025 15:23:16 +0700 Subject: [PATCH 17/43] refactor API --- examples/device/mtp/src/mtp_fs_example.c | 107 ++++++++++----------- examples/device/mtp/src/tusb_config.h | 4 +- examples/device/mtp/src/usb_descriptors.c | 10 +- src/class/mtp/mtp.h | 111 +++++++++++++--------- src/class/mtp/mtp_device.c | 67 ++++++------- src/class/mtp/mtp_device.h | 33 +++++-- 6 files changed, 179 insertions(+), 153 deletions(-) diff --git a/examples/device/mtp/src/mtp_fs_example.c b/examples/device/mtp/src/mtp_fs_example.c index 104260373..5b9b5b2eb 100644 --- a/examples/device/mtp/src/mtp_fs_example.c +++ b/examples/device/mtp/src/mtp_fs_example.c @@ -164,90 +164,87 @@ uint32_t fs_get_object_count(void) { return count; } -int32_t tud_mtp_data_complete_cb(uint8_t idx, mtp_container_header_t* cmd_header, mtp_generic_container_t* resp_block, tusb_xfer_result_t xfer_result, uint32_t xferred_bytes) { - (void) idx; - (void) cmd_header; - resp_block->len = MTP_CONTAINER_HEADER_LENGTH; - resp_block->code = (xfer_result == XFER_RESULT_SUCCESS) ? MTP_RESP_OK : MTP_RESP_GENERAL_ERROR; - tud_mtp_response_send(resp_block); +int32_t tud_mtp_data_complete_cb(tud_mtp_cb_data_t* cb_data) { + mtp_container_info_t* reply = &cb_data->reply; + reply->header->len = sizeof(mtp_container_header_t); + reply->header->code = (cb_data->xfer_result == XFER_RESULT_SUCCESS) ? MTP_RESP_OK : MTP_RESP_GENERAL_ERROR; + tud_mtp_response_send(reply); return 0; } -int32_t tud_mtp_response_complete_cb(uint8_t idx, mtp_container_header_t* cmd_header, mtp_generic_container_t* resp_block, tusb_xfer_result_t xfer_result, uint32_t xferred_bytes) { - (void) idx; - (void) cmd_header; - (void) resp_block; - (void) xfer_result; - (void) xferred_bytes; +int32_t tud_mtp_response_complete_cb(tud_mtp_cb_data_t* cb_data) { + (void) cb_data; return 0; // nothing to do } -int32_t tud_mtp_command_received_cb(uint8_t idx, mtp_generic_container_t* cmd_block, mtp_generic_container_t* out_block) { - (void)idx; - switch (cmd_block->code) { +int32_t tud_mtp_command_received_cb(tud_mtp_cb_data_t* cb_data) { + const mtp_container_command_t* command = cb_data->command; + mtp_container_info_t* reply = &cb_data->reply; + switch (command->header.code) { case MTP_OP_GET_DEVICE_INFO: { // Device info is already prepared up to playback formats. Application only need to add string fields - mtp_container_add_cstring(out_block, DEV_INFO_MANUFACTURER); - mtp_container_add_cstring(out_block, DEV_INFO_MODEL); - mtp_container_add_cstring(out_block, DEV_INFO_VERSION); - mtp_container_add_cstring(out_block, DEV_INFO_SERIAL); + mtp_container_add_cstring(&cb_data->reply, DEV_INFO_MANUFACTURER); + mtp_container_add_cstring(&cb_data->reply, DEV_INFO_MODEL); + mtp_container_add_cstring(&cb_data->reply, DEV_INFO_VERSION); + mtp_container_add_cstring(&cb_data->reply, DEV_INFO_SERIAL); - tud_mtp_data_send(out_block); + tud_mtp_data_send(&cb_data->reply); break; } case MTP_OP_OPEN_SESSION: if (is_session_opened) { //return MTP_RESP_SESSION_ALREADY_OPEN; - out_block->code = MTP_RESP_SESSION_ALREADY_OPEN; + reply->header->code = MTP_RESP_SESSION_ALREADY_OPEN; }else { - out_block->code = MTP_RESP_OK; + reply->header->code = MTP_RESP_OK; } is_session_opened = true; - tud_mtp_response_send(out_block); + tud_mtp_response_send(&cb_data->reply); break; case MTP_OP_CLOSE_SESSION: if (!is_session_opened) { // return MTP_RESP_SESSION_NOT_OPEN; - out_block->code = MTP_RESP_SESSION_NOT_OPEN; + reply->header->code = MTP_RESP_SESSION_NOT_OPEN; } else { - out_block->code = MTP_RESP_OK; + reply->header->code = MTP_RESP_OK; } is_session_opened = false; - tud_mtp_response_send(out_block); + tud_mtp_response_send(&cb_data->reply); break; case MTP_OP_GET_STORAGE_IDS: { uint32_t storage_ids [] = { SUPPORTED_STORAGE_ID }; // physical = 1, logical = 1 - mtp_container_add_auint32(out_block, 1, storage_ids); - tud_mtp_data_send(out_block); + mtp_container_add_auint32(&cb_data->reply, 1, storage_ids); + tud_mtp_data_send(&cb_data->reply); break; } case MTP_OP_GET_STORAGE_INFO: { - TU_VERIFY(SUPPORTED_STORAGE_ID == cmd_block->data[0], -1); + uint32_t storage_id = command->params[0]; + TU_VERIFY(SUPPORTED_STORAGE_ID == storage_id, -1); // update storage info with current free space storage_info.free_space_in_objects = FS_MAX_FILE_COUNT - fs_get_object_count(); storage_info.free_space_in_bytes = FS_MAX_CAPACITY_BYTES-fs_buf_head; - mtp_container_add_raw(out_block, &storage_info, sizeof(storage_info)); - tud_mtp_data_send(out_block); + mtp_container_add_raw(&cb_data->reply, &storage_info, sizeof(storage_info)); + tud_mtp_data_send(&cb_data->reply); break; } case MTP_OP_GET_DEVICE_PROP_DESC: { - const uint16_t dev_prop_code = (uint16_t) cmd_block->data[0]; + const uint16_t dev_prop_code = (uint16_t) command->params[0]; mtp_device_prop_desc_header_t device_prop_header; device_prop_header.device_property_code = dev_prop_code; switch (dev_prop_code) { case MTP_DEV_PROP_DEVICE_FRIENDLY_NAME: device_prop_header.datatype = MTP_DATA_TYPE_STR; device_prop_header.get_set = MTP_MODE_GET; - mtp_container_add_raw(out_block, &device_prop_header, sizeof(device_prop_header)); - mtp_container_add_cstring(out_block, DEV_PROP_FRIENDLY_NAME); // factory - mtp_container_add_cstring(out_block, DEV_PROP_FRIENDLY_NAME); // current - mtp_container_add_uint8(out_block, 0); // no form - tud_mtp_data_send(out_block); + mtp_container_add_raw(&cb_data->reply, &device_prop_header, sizeof(device_prop_header)); + mtp_container_add_cstring(&cb_data->reply, DEV_PROP_FRIENDLY_NAME); // factory + mtp_container_add_cstring(&cb_data->reply, DEV_PROP_FRIENDLY_NAME); // current + mtp_container_add_uint8(&cb_data->reply, 0); // no form + tud_mtp_data_send(&cb_data->reply); break; default: return MTP_RESP_PARAMETER_NOT_SUPPORTED; @@ -256,11 +253,11 @@ int32_t tud_mtp_command_received_cb(uint8_t idx, mtp_generic_container_t* cmd_bl } case MTP_OP_GET_DEVICE_PROP_VALUE: { - const uint16_t dev_prop_code = (uint16_t) cmd_block->data[0]; + const uint16_t dev_prop_code = (uint16_t) command->params[0]; switch (dev_prop_code) { case MTP_DEV_PROP_DEVICE_FRIENDLY_NAME: - mtp_container_add_cstring(out_block, DEV_PROP_FRIENDLY_NAME); - tud_mtp_data_send(out_block); + mtp_container_add_cstring(&cb_data->reply, DEV_PROP_FRIENDLY_NAME); + tud_mtp_data_send(&cb_data->reply); break; default: return MTP_RESP_PARAMETER_NOT_SUPPORTED; @@ -269,10 +266,10 @@ int32_t tud_mtp_command_received_cb(uint8_t idx, mtp_generic_container_t* cmd_bl } case MTP_OP_GET_OBJECT_HANDLES: { - const uint32_t storage_id = cmd_block->data[0]; - const uint32_t obj_format = cmd_block->data[1]; // optional + const uint32_t storage_id = command->params[0]; + const uint32_t obj_format = command->params[1]; // optional (void) obj_format; - const uint32_t parent_handle = cmd_block->data[2]; // folder handle, 0xFFFFFFFF is root + const uint32_t parent_handle = command->params[2]; // folder handle, 0xFFFFFFFF is root if (storage_id != 0xFFFFFFFF && storage_id != SUPPORTED_STORAGE_ID) { return MTP_RESP_INVALID_STORAGE_ID; } @@ -286,13 +283,13 @@ int32_t tud_mtp_command_received_cb(uint8_t idx, mtp_generic_container_t* cmd_bl handles[count++] = i + 1; // handle is index + 1 } } - mtp_container_add_auint32(out_block, count, handles); - tud_mtp_data_send(out_block); + mtp_container_add_auint32(&cb_data->reply, count, handles); + tud_mtp_data_send(&cb_data->reply); break; } case MTP_OP_GET_OBJECT_INFO: { - const uint32_t obj_handle = cmd_block->data[0]; + const uint32_t obj_handle = command->params[0]; fs_object_info_t* obj = fs_get_object(obj_handle); if (obj == NULL) { return MTP_RESP_INVALID_OBJECT_HANDLE; @@ -314,25 +311,25 @@ int32_t tud_mtp_command_received_cb(uint8_t idx, mtp_generic_container_t* cmd_bl .association_desc = 0, .sequence_number = 0 }; - mtp_container_add_raw(out_block, &obj_info_header, sizeof(obj_info_header)); - mtp_container_add_cstring(out_block, obj->name); - mtp_container_add_cstring(out_block, FS_FIXED_DATETIME); - mtp_container_add_cstring(out_block, FS_FIXED_DATETIME); - mtp_container_add_cstring(out_block, ""); // keywords, not used + mtp_container_add_raw(&cb_data->reply, &obj_info_header, sizeof(obj_info_header)); + mtp_container_add_cstring(&cb_data->reply, obj->name); + mtp_container_add_cstring(&cb_data->reply, FS_FIXED_DATETIME); + mtp_container_add_cstring(&cb_data->reply, FS_FIXED_DATETIME); + mtp_container_add_cstring(&cb_data->reply, ""); // keywords, not used - tud_mtp_data_send(out_block); + tud_mtp_data_send(&cb_data->reply); break; } case MTP_OP_GET_OBJECT: { - const uint32_t obj_handle = cmd_block->data[0]; + const uint32_t obj_handle = command->params[0]; fs_object_info_t* obj = fs_get_object(obj_handle); if (obj == NULL) { return MTP_RESP_INVALID_OBJECT_HANDLE; } - mtp_container_add_raw(out_block, obj->data, obj->size); - tud_mtp_data_send(out_block); + mtp_container_add_raw(&cb_data->reply, obj->data, obj->size); + tud_mtp_data_send(&cb_data->reply); break; } diff --git a/examples/device/mtp/src/tusb_config.h b/examples/device/mtp/src/tusb_config.h index 7e2c5b670..ef03e0480 100644 --- a/examples/device/mtp/src/tusb_config.h +++ b/examples/device/mtp/src/tusb_config.h @@ -92,9 +92,7 @@ //------------- CLASS -------------// #define CFG_TUD_MTP 1 -#define CFG_MTP_EP_SIZE 64 -#define CFG_MTP_EVT_EP_SIZE 64 -#define CFG_MTP_EVT_INTERVAL 100 +#define CFG_TUD_MTP_EP_BUFSIZE 512 //------------- MTP device info -------------// #define CFG_TUD_MTP_DEVICEINFO_EXTENSIONS "microsoft.com: 1.0; " diff --git a/examples/device/mtp/src/usb_descriptors.c b/examples/device/mtp/src/usb_descriptors.c index 73685c3fd..4a43a0dcc 100644 --- a/examples/device/mtp/src/usb_descriptors.c +++ b/examples/device/mtp/src/usb_descriptors.c @@ -109,18 +109,16 @@ enum #define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_MTP_DESC_LEN) -uint8_t const desc_fs_configuration[] = -{ +uint8_t const desc_fs_configuration[] = { // Config number, interface count, string index, total length, attribute, power in mA - TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0x00, 100), - TUD_MTP_DESCRIPTOR(ITF_NUM_MTP, 4, EPNUM_MTP_EVT, CFG_MTP_EVT_EP_SIZE, CFG_MTP_EVT_INTERVAL, EPNUM_MTP_OUT, EPNUM_MTP_IN, CFG_MTP_EP_SIZE), + TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0x00, 100), + TUD_MTP_DESCRIPTOR(ITF_NUM_MTP, 4, EPNUM_MTP_EVT, 64, 1, EPNUM_MTP_OUT, EPNUM_MTP_IN, 64), }; // Invoked when received GET CONFIGURATION DESCRIPTOR // Application return pointer to descriptor // Descriptor contents must exist long enough for transfer to complete -uint8_t const *tud_descriptor_configuration_cb(uint8_t index) -{ +uint8_t const *tud_descriptor_configuration_cb(uint8_t index) { (void) index; // for multiple configurations return desc_fs_configuration; } diff --git a/src/class/mtp/mtp.h b/src/class/mtp/mtp.h index c44b99fda..8e519bdc0 100644 --- a/src/class/mtp/mtp.h +++ b/src/class/mtp/mtp.h @@ -658,7 +658,6 @@ typedef enum { // Data structures //--------------------------------------------------------------------+ -#define MTP_CONTAINER_HEADER_LENGTH 12 #define MTP_MAX_PACKET_SIZE 512 typedef struct TU_ATTR_PACKED { @@ -669,14 +668,33 @@ typedef struct TU_ATTR_PACKED { } mtp_container_header_t; TU_VERIFY_STATIC(sizeof(mtp_container_header_t) == 12, "size is not correct"); +typedef struct TU_ATTR_PACKED { + mtp_container_header_t header; + uint32_t params[5]; +} mtp_container_command_t; +TU_VERIFY_STATIC(sizeof(mtp_container_command_t) == 32, "size is not correct"); + // PTP/MTP Generic container typedef struct TU_ATTR_PACKED { uint32_t len; uint16_t type; uint16_t code; uint32_t transaction_id; - uint32_t data[MTP_MAX_PACKET_SIZE / sizeof(uint32_t)]; + // union { + uint32_t data[(CFG_TUD_MTP_EP_BUFSIZE - sizeof(mtp_container_header_t)) / sizeof(uint32_t)]; + // uint8_t data[CFG_TUD_MTP_EP_BUFSIZE - sizeof(mtp_container_header_t)]; + // }; } mtp_generic_container_t; +TU_VERIFY_STATIC(sizeof(mtp_generic_container_t) == CFG_TUD_MTP_EP_BUFSIZE, "size is not correct"); + +typedef struct { + mtp_container_header_t* header; + union { + uint8_t* payload; + uint16_t* payload16; + uint32_t* payload32; + }; +} mtp_container_info_t; #define mtp_string_t(_nchars) \ struct TU_ATTR_PACKED { \ @@ -690,12 +708,10 @@ typedef struct TU_ATTR_PACKED { _type arr[_count];\ } +#define mtp_aint8_t(_count) mtp_array_t(int8_t, _count) #define mtp_auint16_t(_count) mtp_array_t(uint16_t, _count) - -typedef struct TU_ATTR_PACKED { - uint8_t count; - uint16_t utf16[]; -} mtp_flexible_string_t; +#define mtp_auint32_t(_count) mtp_array_t(uint32_t, _count) +#define mtp_auint64_t(_count) mtp_array_t(uint64_t, _count) typedef union TU_ATTR_PACKED { struct { @@ -775,95 +791,100 @@ typedef struct TU_ATTR_PACKED { } mtp_basic_object_info_t; //--------------------------------------------------------------------+ -// Generic Container function +// Container helper function +// return number of bytes added //--------------------------------------------------------------------+ -TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_raw(mtp_generic_container_t* p_container, const void* data, uint32_t len) { - TU_ASSERT(p_container->len + len < sizeof(mtp_generic_container_t), 0); - memcpy((uint8_t*) p_container + p_container->len, data, len); - p_container->len += len; +TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_raw(mtp_container_info_t* p_container, const void* data, uint32_t len) { + TU_ASSERT(p_container->header->len + len < sizeof(mtp_generic_container_t), 0); + uint8_t* buf = p_container->payload + p_container->header->len - sizeof(mtp_container_header_t); + memcpy(buf, data, len); + p_container->header->len += len; return len; } -TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_array(mtp_generic_container_t* p_container, uint8_t scalar_size, uint32_t count, const void* data) { +TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_array(mtp_container_info_t* p_container, uint8_t scalar_size, uint32_t count, const void* data) { const uint32_t added_len = 4 + count * scalar_size; - TU_ASSERT(p_container->len + added_len < sizeof(mtp_generic_container_t), 0); - uint8_t* container8 = (uint8_t*)p_container; + TU_ASSERT(p_container->header->len + added_len < sizeof(mtp_generic_container_t), 0); + uint8_t* buf = p_container->payload + p_container->header->len - sizeof(mtp_container_header_t); - tu_unaligned_write32(container8 + p_container->len, count); - p_container->len += 4; + tu_unaligned_write32(buf, count); + p_container->header->len += 4; + buf += 4; - memcpy(container8 + p_container->len, data, count * scalar_size); - p_container->len += count * scalar_size; + memcpy(buf, data, count * scalar_size); + p_container->header->len += count * scalar_size; return added_len; } -TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_string(mtp_generic_container_t* p_container, uint8_t count, uint16_t* utf16) { +TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_string(mtp_container_info_t* p_container, uint8_t count, uint16_t* utf16) { const uint32_t added_len = 1 + 2 * count; - TU_ASSERT(p_container->len + added_len < sizeof(mtp_generic_container_t), 0); - uint8_t* container8 = (uint8_t*) p_container; + TU_ASSERT(p_container->header->len + added_len < sizeof(mtp_generic_container_t), 0); + uint8_t* buf = p_container->payload + p_container->header->len - sizeof(mtp_container_header_t); - container8[p_container->len] = count; - p_container->len++; + *buf++ = count; + p_container->header->len++; - memcpy(container8 + p_container->len, utf16, 2 * count); - p_container->len += 2 * count; + memcpy(buf, utf16, 2 * count); + p_container->header->len += 2 * count; return added_len; } -TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_cstring(mtp_generic_container_t* p_container, const char* str) { +TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_cstring(mtp_container_info_t* p_container, const char* str) { const uint8_t len = (uint8_t) (strlen(str) + 1); // include null - TU_ASSERT(p_container->len + 1 + 2 * len < sizeof(mtp_generic_container_t), 0); - - uint8_t* container8 = (uint8_t*) p_container; - container8[p_container->len] = len; - p_container->len++; + TU_ASSERT(p_container->header->len + 1 + 2 * len < sizeof(mtp_generic_container_t), 0); + uint8_t* buf = p_container->payload + p_container->header->len - sizeof(mtp_container_header_t); if (len == 1) { - // empty string (null only) - container8[p_container->len] = 0; + // empty string (null only): single zero byte + *buf = 0; + p_container->header->len++; return 1; } else { + *buf++ = len; + p_container->header->len++; + for (uint8_t i = 0; i < len; i++) { - container8[p_container->len] = str[i]; - container8[p_container->len + 1] = 0; - p_container->len += 2; + buf[0] = str[i]; + buf[1] = 0; + buf += 2; + p_container->header->len += 2; } return 1 + 2 * len; } } -TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_uint8(mtp_generic_container_t* p_container, uint8_t data) { +TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_uint8(mtp_container_info_t* p_container, uint8_t data) { return mtp_container_add_raw(p_container, &data, 1); } -TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_uint16(mtp_generic_container_t* p_container, uint16_t data) { +TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_uint16(mtp_container_info_t* p_container, uint16_t data) { return mtp_container_add_raw(p_container, &data, 2); } -TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_uint32(mtp_generic_container_t* p_container, uint32_t data) { +TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_uint32(mtp_container_info_t* p_container, uint32_t data) { return mtp_container_add_raw(p_container, &data, 4); } -TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_uint64(mtp_generic_container_t* p_container, uint64_t data) { +TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_uint64(mtp_container_info_t* p_container, uint64_t data) { return mtp_container_add_raw(p_container, &data, 8); } -TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_uint128(mtp_generic_container_t* p_container, const void* data) { +TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_uint128(mtp_container_info_t* p_container, const void* data) { return mtp_container_add_raw(p_container, data, 16); } -TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_auint8(mtp_generic_container_t* p_container, uint32_t count, const uint8_t* data) { +TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_auint8(mtp_container_info_t* p_container, uint32_t count, const uint8_t* data) { return mtp_container_add_array(p_container, sizeof(uint8_t), count, data); } -TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_auint16(mtp_generic_container_t* p_container, uint32_t count, const uint16_t* data) { +TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_auint16(mtp_container_info_t* p_container, uint32_t count, const uint16_t* data) { return mtp_container_add_array(p_container, sizeof(uint16_t), count, data); } -TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_auint32(mtp_generic_container_t* p_container, uint32_t count, const uint32_t* data) { +TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_auint32(mtp_container_info_t* p_container, uint32_t count, const uint32_t* data) { return mtp_container_add_array(p_container, sizeof(uint32_t), count, data); } diff --git a/src/class/mtp/mtp_device.c b/src/class/mtp/mtp_device.c index 91df13111..71ead732d 100644 --- a/src/class/mtp/mtp_device.c +++ b/src/class/mtp/mtp_device.c @@ -67,7 +67,8 @@ typedef struct { bool xfer_completed; // true when DATA-IN/DATA-OUT transfer is completed uint32_t session_id; - mtp_container_header_t cmd_header; + mtp_container_command_t command; + // mtp_container_header_t reply_header; } mtpd_interface_t; typedef struct { @@ -81,7 +82,7 @@ typedef struct { static mtp_phase_type_t mtpd_chk_generic(const char *func_name, const bool err_cd, const uint16_t ret_code, const char *message); // MTP commands -static mtp_phase_type_t mtpd_handle_cmd(mtpd_interface_t* p_mtp); +static mtp_phase_type_t mtpd_handle_cmd(mtpd_interface_t* p_mtp, tud_mtp_cb_data_t* cb_data); static mtp_phase_type_t mtpd_handle_data(void); static mtp_phase_type_t mtpd_handle_cmd_delete_object(void); static mtp_phase_type_t mtpd_handle_cmd_send_object_info(void); @@ -265,26 +266,24 @@ bool mtpd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t return true; } -bool tud_mtp_data_send(mtp_generic_container_t* data_block) { +bool tud_mtp_data_send(mtp_container_info_t* p_container) { mtpd_interface_t* p_mtp = &_mtpd_itf; p_mtp->phase = MTP_PHASE_DATA; - p_mtp->total_len = data_block->len; + p_mtp->total_len = p_container->header->len; p_mtp->xferred_len = 0; p_mtp->handled_len = 0; p_mtp->xfer_completed = false; - data_block->type = MTP_CONTAINER_TYPE_DATA_BLOCK; - data_block->transaction_id = p_mtp->cmd_header.transaction_id; - TU_ASSERT(usbd_edpt_xfer(p_mtp->rhport, p_mtp->ep_in, (uint8_t*) data_block, (uint16_t)data_block->len)); + p_container->header->type = MTP_CONTAINER_TYPE_DATA_BLOCK; + TU_ASSERT(usbd_edpt_xfer(p_mtp->rhport, p_mtp->ep_in, (uint8_t *)(&_mtpd_epbuf.container), (uint16_t)p_container->header->len)); return true; } -bool tud_mtp_response_send(mtp_generic_container_t* resp_block) { +bool tud_mtp_response_send(mtp_container_info_t* p_container) { mtpd_interface_t* p_mtp = &_mtpd_itf; p_mtp->phase = MTP_PHASE_RESPONSE_QUEUED; - resp_block->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; - resp_block->transaction_id = p_mtp->cmd_header.transaction_id; - TU_ASSERT(usbd_edpt_xfer(p_mtp->rhport, p_mtp->ep_in, (uint8_t*) resp_block, (uint16_t)resp_block->len)); + p_container->header->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; + TU_ASSERT(usbd_edpt_xfer(p_mtp->rhport, p_mtp->ep_in, (uint8_t *)(&_mtpd_epbuf.container), (uint16_t)p_container->header->len)); return true; } @@ -300,6 +299,13 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t mtpd_interface_t* p_mtp = &_mtpd_itf; mtp_generic_container_t* p_container = &_mtpd_epbuf.container; + tud_mtp_cb_data_t cb_data; + cb_data.idx = 0; + cb_data.command = &p_mtp->command; + cb_data.reply.header = (mtp_container_header_t*) p_container; + cb_data.reply.payload32 = p_container->data; + cb_data.offset = 0; + switch (p_mtp->phase) { case MTP_PHASE_IDLE: // received new command @@ -308,7 +314,8 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t TU_ATTR_FALLTHROUGH; // handle in the next case case MTP_PHASE_COMMAND: { - mtpd_handle_cmd(p_mtp); + memcpy(&p_mtp->command, p_container, sizeof(mtp_container_command_t)); // copy new command + mtpd_handle_cmd(p_mtp, &cb_data); break; } @@ -320,7 +327,7 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t if (xferred_bytes == 0 || // ZLP (xferred_bytes & (bulk_mps - 1)) || // short packet p_mtp->xferred_len > p_mtp->total_len) { - tud_mtp_data_complete_cb(0, &p_mtp->cmd_header, p_container, event, p_mtp->xferred_len); + tud_mtp_data_complete_cb(&cb_data); } else { TU_ASSERT(false); } @@ -404,7 +411,7 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t case MTP_PHASE_RESPONSE_QUEUED: // response phase is complete -> prepare for new command TU_ASSERT(ep_addr == p_mtp->ep_in); - tud_mtp_response_complete_cb(0, &p_mtp->cmd_header, p_container, event, xferred_bytes); + tud_mtp_response_complete_cb(&cb_data); prepare_new_command(p_mtp); break; @@ -429,23 +436,15 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t //--------------------------------------------------------------------+ // Decode command and prepare response -mtp_phase_type_t mtpd_handle_cmd(mtpd_interface_t* p_mtp) { +mtp_phase_type_t mtpd_handle_cmd(mtpd_interface_t* p_mtp, tud_mtp_cb_data_t* cb_data) { mtp_generic_container_t* p_container = &_mtpd_epbuf.container; + p_container->len = sizeof(mtp_container_header_t); // default data/response length - mtp_generic_container_t cmd_block; // copy command block for callback - memcpy(&cmd_block, p_container, p_container->len); - memcpy(&p_mtp->cmd_header, p_container, sizeof(mtp_container_header_t)); - p_container->len = MTP_CONTAINER_HEADER_LENGTH; // default data/response length + tu_lookup_find(&_mtp_op_table, p_mtp->command.header.code); + TU_LOG_DRV(" MTP command: %s\r\n", (char const*) tu_lookup_find(&_mtp_op_table, p_mtp->command.header.code)); - if (p_container->code != MTP_OP_SEND_OBJECT) { - _mtpd_soi.object_handle = 0; - } - - tu_lookup_find(&_mtp_op_table, cmd_block.code); - TU_LOG_DRV(" MTP command: %s\r\n", (char const*) tu_lookup_find(&_mtp_op_table, cmd_block.code)); - - mtp_phase_type_t ret = MTP_PHASE_RESPONSE; - switch (cmd_block.code) { + // pre-processed commands + switch (p_mtp->command.header.code) { case MTP_OP_GET_DEVICE_INFO: { tud_mtp_device_info_t dev_info = { .standard_version = 100, @@ -486,12 +485,8 @@ mtp_phase_type_t mtpd_handle_cmd(mtpd_interface_t* p_mtp) { dev_info.mtp_extensions.utf16[i] = (uint16_t)CFG_TUD_MTP_DEVICEINFO_EXTENSIONS[i]; } #endif - p_container->len = MTP_CONTAINER_HEADER_LENGTH + sizeof(tud_mtp_device_info_t); + mtp_container_add_raw(&cb_data->reply, &dev_info, sizeof(tud_mtp_device_info_t)); p_container->type = MTP_CONTAINER_TYPE_DATA_BLOCK; - p_container->code = MTP_OP_GET_DEVICE_INFO; - memcpy(p_container->data, &dev_info, sizeof(tud_mtp_device_info_t)); - - ret = MTP_PHASE_RESPONSE; break; } @@ -499,8 +494,8 @@ mtp_phase_type_t mtpd_handle_cmd(mtpd_interface_t* p_mtp) { break; } - tud_mtp_command_received_cb(0, &cmd_block, p_container); - return ret; + tud_mtp_command_received_cb(cb_data); + return MTP_PHASE_RESPONSE; } #if 0 @@ -640,7 +635,7 @@ mtp_phase_type_t mtpd_chk_generic(const char *func_name, const bool err_cd, cons TU_LOG_DRV(" MTP error in %s: (%x) %s\n", func_name, ret_code, message); p_container->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; p_container->code = ret_code; - p_container->len = MTP_CONTAINER_HEADER_LENGTH; + p_container->len = sizeof(mtp_container_header_t); return MTP_PHASE_RESPONSE; } return MTP_PHASE_NONE; diff --git a/src/class/mtp/mtp_device.h b/src/class/mtp/mtp_device.h index b85b1706e..0524edcf6 100644 --- a/src/class/mtp/mtp_device.h +++ b/src/class/mtp/mtp_device.h @@ -37,10 +37,22 @@ #endif typedef struct { - const mtp_container_header_t* cmd_header; + uint8_t idx; // mtp instance + const mtp_container_command_t* command; + mtp_container_info_t reply; + + union { + uint8_t* buffer; + uint16_t* buffer16; + uint32_t* buffer32; + }; + uint32_t bufsize; + uint32_t offset; // offset from start of header, since data can span multiple xfers + + tusb_xfer_result_t xfer_result; uint32_t xferred_bytes; -} tud_mtp_cb_complete_data_t; +} tud_mtp_cb_data_t; // Number of supported operations, events, device properties, capture formats, playback formats // and max number of characters for strings manufacturer, model, device_version, serial_number @@ -91,23 +103,28 @@ typedef struct { //--------------------------------------------------------------------+ // Application API //--------------------------------------------------------------------+ -bool tud_mtp_data_send(mtp_generic_container_t* data_block); +bool tud_mtp_data_send(mtp_container_info_t* p_container); // bool tud_mtp_block_data_receive(); -bool tud_mtp_response_send(mtp_generic_container_t* resp_block); +bool tud_mtp_response_send(mtp_container_info_t* p_container); //--------------------------------------------------------------------+ -// Application Callbacks +// Control request Callbacks +//--------------------------------------------------------------------+ +// bool tud_mtp_control_xfer_cb(uint8_t idx, uint8_t stage, tusb_control_request_t const *p_request); + +//--------------------------------------------------------------------+ +// Bulk only protocol Callbacks //--------------------------------------------------------------------+ // Invoked when new command is received. Application fill the out_block with either DATA or RESPONSE container // return MTP response code -int32_t tud_mtp_command_received_cb(uint8_t idx, mtp_generic_container_t* cmd_block, mtp_generic_container_t* out_block); +int32_t tud_mtp_command_received_cb(tud_mtp_cb_data_t * cb_data); // Invoked when data phase is complete -int32_t tud_mtp_data_complete_cb(uint8_t idx, mtp_container_header_t* cmd_header, mtp_generic_container_t* resp_block, tusb_xfer_result_t xfer_result, uint32_t xferred_bytes); +int32_t tud_mtp_data_complete_cb(tud_mtp_cb_data_t* cb_data); // Invoked when response phase is complete -int32_t tud_mtp_response_complete_cb(uint8_t idx, mtp_container_header_t* cmd_header, mtp_generic_container_t* resp_block, tusb_xfer_result_t xfer_result, uint32_t xferred_bytes); +int32_t tud_mtp_response_complete_cb(tud_mtp_cb_data_t* cb_data); //--------------------------------------------------------------------+ // Helper functions From 6317730be6d7a97fc12376d6f80c90a6dbfc20ea Mon Sep 17 00:00:00 2001 From: hathach Date: Wed, 24 Sep 2025 12:49:52 +0700 Subject: [PATCH 18/43] unify callback argument. support multiple packet get object --- examples/device/mtp/src/mtp_fs_example.c | 26 ++++++++++++++ examples/device/mtp/src/tusb_config.h | 2 +- src/class/mtp/mtp.h | 35 +++++++++--------- src/class/mtp/mtp_device.c | 44 +++++++++++++++-------- src/class/mtp/mtp_device.h | 13 +++---- tools/file2carray.py | 45 ++++++++++++++++++++++++ 6 files changed, 124 insertions(+), 41 deletions(-) create mode 100644 tools/file2carray.py diff --git a/examples/device/mtp/src/mtp_fs_example.c b/examples/device/mtp/src/mtp_fs_example.c index 5b9b5b2eb..19ca71d62 100644 --- a/examples/device/mtp/src/mtp_fs_example.c +++ b/examples/device/mtp/src/mtp_fs_example.c @@ -177,6 +177,30 @@ int32_t tud_mtp_response_complete_cb(tud_mtp_cb_data_t* cb_data) { return 0; // nothing to do } +int32_t tud_mtp_data_more_cb(tud_mtp_cb_data_t* cb_data) { + // only a few command that need more data e.g GetObject and SendObject + const mtp_container_command_t* command = cb_data->command; + mtp_container_info_t* reply = &cb_data->reply; + switch (command->header.code) { + case MTP_OP_GET_OBJECT: { + const uint32_t obj_handle = command->params[0]; + fs_object_info_t* obj = fs_get_object(obj_handle); + if (obj == NULL) { + return MTP_RESP_INVALID_OBJECT_HANDLE; + } + // file contents offset is xferred byte minus header size + const uint32_t offset = cb_data->xferred_bytes - sizeof(mtp_container_header_t); + const uint32_t xact_len = tu_min32(obj->size - offset, reply->payload_size); + memcpy(reply->payload, obj->data + offset, xact_len); + tud_mtp_data_send(&cb_data->reply); + } + + default: return MTP_RESP_OPERATION_NOT_SUPPORTED; + } + + return MTP_RESP_OK; +} + int32_t tud_mtp_command_received_cb(tud_mtp_cb_data_t* cb_data) { const mtp_container_command_t* command = cb_data->command; mtp_container_info_t* reply = &cb_data->reply; @@ -328,6 +352,8 @@ int32_t tud_mtp_command_received_cb(tud_mtp_cb_data_t* cb_data) { return MTP_RESP_INVALID_OBJECT_HANDLE; } + // If file contents is larger than CFG_TUD_MTP_EP_BUFSIZE, only partial data is added here + // the rest will be sent in tud_mtp_data_more_cb mtp_container_add_raw(&cb_data->reply, obj->data, obj->size); tud_mtp_data_send(&cb_data->reply); break; diff --git a/examples/device/mtp/src/tusb_config.h b/examples/device/mtp/src/tusb_config.h index ef03e0480..7db5235d0 100644 --- a/examples/device/mtp/src/tusb_config.h +++ b/examples/device/mtp/src/tusb_config.h @@ -92,7 +92,7 @@ //------------- CLASS -------------// #define CFG_TUD_MTP 1 -#define CFG_TUD_MTP_EP_BUFSIZE 512 +#define CFG_TUD_MTP_EP_BUFSIZE 512 //------------- MTP device info -------------// #define CFG_TUD_MTP_DEVICEINFO_EXTENSIONS "microsoft.com: 1.0; " diff --git a/src/class/mtp/mtp.h b/src/class/mtp/mtp.h index 8e519bdc0..d4d81492e 100644 --- a/src/class/mtp/mtp.h +++ b/src/class/mtp/mtp.h @@ -657,9 +657,6 @@ typedef enum { //--------------------------------------------------------------------+ // Data structures //--------------------------------------------------------------------+ - -#define MTP_MAX_PACKET_SIZE 512 - typedef struct TU_ATTR_PACKED { uint32_t len; uint16_t type; @@ -694,6 +691,7 @@ typedef struct { uint16_t* payload16; uint32_t* payload32; }; + uint32_t payload_size; } mtp_container_info_t; #define mtp_string_t(_nchars) \ @@ -713,14 +711,6 @@ typedef struct { #define mtp_auint32_t(_count) mtp_array_t(uint32_t, _count) #define mtp_auint64_t(_count) mtp_array_t(uint64_t, _count) -typedef union TU_ATTR_PACKED { - struct { - uint16_t physical; // physical location - uint16_t logical; // logical within physical - }; - uint32_t id; -} mtp_storage_id_t; - #define MTP_STORAGE_INFO_STRUCT(_storage_desc_chars, _volume_id_chars) \ struct TU_ATTR_PACKED { \ uint16_t storage_type; \ @@ -795,12 +785,25 @@ typedef struct TU_ATTR_PACKED { // return number of bytes added //--------------------------------------------------------------------+ +// return payload buffer for next write +TU_ATTR_ALWAYS_INLINE static inline uint8_t* mtp_container_payload_next(mtp_container_info_t* p_container) { + // only 1st packet include header + uint32_t pos = p_container->header->len - sizeof(mtp_container_header_t); + while (pos > CFG_TUD_MTP_EP_BUFSIZE) { + pos -= CFG_TUD_MTP_EP_BUFSIZE; + } + return p_container->payload + pos; +} + +// only add_raw does partial copy TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_raw(mtp_container_info_t* p_container, const void* data, uint32_t len) { - TU_ASSERT(p_container->header->len + len < sizeof(mtp_generic_container_t), 0); - uint8_t* buf = p_container->payload + p_container->header->len - sizeof(mtp_container_header_t); - memcpy(buf, data, len); - p_container->header->len += len; - return len; + uint8_t* buf = mtp_container_payload_next(p_container); + const uint32_t added_len = tu_min32(len, sizeof(mtp_generic_container_t) - p_container->header->len); + if (added_len > 0) { + memcpy(buf, data, added_len); + } + p_container->header->len += len; // always increase len, even partial copy + return added_len; } TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_array(mtp_container_info_t* p_container, uint8_t scalar_size, uint32_t count, const void* data) { diff --git a/src/class/mtp/mtp_device.c b/src/class/mtp/mtp_device.c index 71ead732d..92801a8ad 100644 --- a/src/class/mtp/mtp_device.c +++ b/src/class/mtp/mtp_device.c @@ -60,15 +60,12 @@ typedef struct { // Bulk Only Transfer (BOT) Protocol uint8_t phase; - uint32_t queued_len; // number of bytes queued from the DataIN Stage - uint32_t total_len; // byte to be transferred, can be smaller than total_bytes in cbw - uint32_t xferred_len; // number of bytes transferred so far in the Data Stage - uint32_t handled_len; // number of bytes already handled in the Data Stage - bool xfer_completed; // true when DATA-IN/DATA-OUT transfer is completed + uint32_t total_len; + uint32_t xferred_len; uint32_t session_id; mtp_container_command_t command; - // mtp_container_header_t reply_header; + mtp_container_header_t reply_header; } mtpd_interface_t; typedef struct { @@ -268,14 +265,23 @@ bool mtpd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t bool tud_mtp_data_send(mtp_container_info_t* p_container) { mtpd_interface_t* p_mtp = &_mtpd_itf; - p_mtp->phase = MTP_PHASE_DATA; - p_mtp->total_len = p_container->header->len; - p_mtp->xferred_len = 0; - p_mtp->handled_len = 0; - p_mtp->xfer_completed = false; + if (p_mtp->phase == MTP_PHASE_COMMAND) { + // 1st data block: header + payload + p_mtp->phase = MTP_PHASE_DATA; + p_mtp->total_len = p_container->header->len; + p_mtp->xferred_len = 0; + + p_container->header->type = MTP_CONTAINER_TYPE_DATA_BLOCK; + p_container->header->transaction_id = p_mtp->command.header.transaction_id; + p_mtp->reply_header = *p_container->header; // save header for subsequent data + } else { + // subsequent data block: payload only + TU_ASSERT(p_mtp->phase == MTP_PHASE_DATA); + } + + const uint16_t xact_len = tu_min32(p_mtp->total_len - p_mtp->xferred_len, CFG_TUD_MTP_EP_BUFSIZE); + TU_ASSERT(usbd_edpt_xfer(p_mtp->rhport, p_mtp->ep_in, (uint8_t *)(&_mtpd_epbuf.container), xact_len)); - p_container->header->type = MTP_CONTAINER_TYPE_DATA_BLOCK; - TU_ASSERT(usbd_edpt_xfer(p_mtp->rhport, p_mtp->ep_in, (uint8_t *)(&_mtpd_epbuf.container), (uint16_t)p_container->header->len)); return true; } @@ -283,6 +289,7 @@ bool tud_mtp_response_send(mtp_container_info_t* p_container) { mtpd_interface_t* p_mtp = &_mtpd_itf; p_mtp->phase = MTP_PHASE_RESPONSE_QUEUED; p_container->header->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; + p_container->header->transaction_id = p_mtp->command.header.transaction_id; TU_ASSERT(usbd_edpt_xfer(p_mtp->rhport, p_mtp->ep_in, (uint8_t *)(&_mtpd_epbuf.container), (uint16_t)p_container->header->len)); return true; } @@ -304,7 +311,9 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t cb_data.command = &p_mtp->command; cb_data.reply.header = (mtp_container_header_t*) p_container; cb_data.reply.payload32 = p_container->data; - cb_data.offset = 0; + cb_data.reply.payload_size = CFG_TUD_MTP_EP_BUFSIZE - sizeof(mtp_container_header_t); + cb_data.xferred_bytes = 0; + cb_data.xfer_result = event; switch (p_mtp->phase) { case MTP_PHASE_IDLE: @@ -322,6 +331,7 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t case MTP_PHASE_DATA: { const uint16_t bulk_mps = (tud_speed_get() == TUSB_SPEED_HIGH) ? 512 : 64; p_mtp->xferred_len += xferred_bytes; + cb_data.xferred_bytes = p_mtp->xferred_len; // transfer complete if ZLP or short packet or overflow if (xferred_bytes == 0 || // ZLP @@ -329,7 +339,11 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t p_mtp->xferred_len > p_mtp->total_len) { tud_mtp_data_complete_cb(&cb_data); } else { - TU_ASSERT(false); + // payload only packet + cb_data.reply.header = &p_mtp->reply_header; + cb_data.reply.payload = (uint8_t*) p_container; + cb_data.reply.payload_size = CFG_TUD_MTP_EP_BUFSIZE; + tud_mtp_data_more_cb(&cb_data); } break; } diff --git a/src/class/mtp/mtp_device.h b/src/class/mtp/mtp_device.h index 0524edcf6..88960b1aa 100644 --- a/src/class/mtp/mtp_device.h +++ b/src/class/mtp/mtp_device.h @@ -41,15 +41,6 @@ typedef struct { const mtp_container_command_t* command; mtp_container_info_t reply; - union { - uint8_t* buffer; - uint16_t* buffer16; - uint32_t* buffer32; - }; - uint32_t bufsize; - uint32_t offset; // offset from start of header, since data can span multiple xfers - - tusb_xfer_result_t xfer_result; uint32_t xferred_bytes; } tud_mtp_cb_data_t; @@ -117,9 +108,13 @@ bool tud_mtp_response_send(mtp_container_info_t* p_container); //--------------------------------------------------------------------+ // Invoked when new command is received. Application fill the out_block with either DATA or RESPONSE container +// and call tud_mtp_data_send() or tud_mtp_response_send(). // return MTP response code int32_t tud_mtp_command_received_cb(tud_mtp_cb_data_t * cb_data); +// Invoked when a data packet is received/sent, and more data is expected +int32_t tud_mtp_data_more_cb(tud_mtp_cb_data_t* cb_data); + // Invoked when data phase is complete int32_t tud_mtp_data_complete_cb(tud_mtp_cb_data_t* cb_data); diff --git a/tools/file2carray.py b/tools/file2carray.py new file mode 100644 index 000000000..abfb4e21b --- /dev/null +++ b/tools/file2carray.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +import argparse +import random +import os +import sys +import time +import subprocess +from pathlib import Path +from multiprocessing import Pool +from weakref import finalize + + +def print_carray(f, payload): + while len(payload) > 0: + f.write('\n ') + f.write(', '.join('0x{:02x}'.format(x) for x in payload[0:16])) + f.write(',') + payload = payload[16:] + f.write('\n') + + +def main(): + parser = argparse.ArgumentParser(description='Convert binary files to C array format') + parser.add_argument('files', nargs='+', help='Binary files to convert') + args = parser.parse_args() + + files = args.files + for fin_name in files: + if not os.path.isfile(fin_name): + print(f"File {fin_name} does not exist") + continue + + with open(fin_name, 'rb') as fin: + contents = fin.read() + fout_name = fin_name + '.h' + with open(fout_name, 'w') as fout: + print(f"Converting {fin_name} to {fout_name}") + fout.write(f'const size_t bindata_len = {len(contents)};\n') + fout.write(f'const uint8_t bindata[] __attribute__((aligned(16))) = {{') + print_carray(fout, contents) + fout.write('};\n') + + +if __name__ == '__main__': + sys.exit(main()) From b8126d9c4e9f1fe55988e1789d1bc286a3c0cbb2 Mon Sep 17 00:00:00 2001 From: hathach Date: Wed, 24 Sep 2025 14:36:25 +0700 Subject: [PATCH 19/43] clean up --- examples/device/mtp/src/mtp_fs_example.c | 196 ++++++++++------------- src/class/mtp/mtp.h | 5 +- src/class/mtp/mtp_device.c | 190 ++++------------------ src/class/mtp/mtp_device.h | 26 +-- 4 files changed, 124 insertions(+), 293 deletions(-) diff --git a/examples/device/mtp/src/mtp_fs_example.c b/examples/device/mtp/src/mtp_fs_example.c index 19ca71d62..7e19fa75c 100644 --- a/examples/device/mtp/src/mtp_fs_example.c +++ b/examples/device/mtp/src/mtp_fs_example.c @@ -27,9 +27,6 @@ #include "tusb.h" #include "tinyusb_logo_png.h" -#define MTPD_STORAGE_DESCRIPTION "storage" -#define MTPD_VOLUME_IDENTIFIER "volume" - //--------------------------------------------------------------------+ // Dataset //--------------------------------------------------------------------+ @@ -118,29 +115,6 @@ enum { static bool is_session_opened = false; -//--------------------------------------------------------------------+ -// OPERATING STATUS -//--------------------------------------------------------------------+ -typedef struct { - // Session - uint32_t session_id; - // Association traversal - uint32_t traversal_parent; - uint32_t traversal_index; - // Object open for reading - uint32_t read_handle; - uint32_t read_pos; - // Object open for writing - uint32_t write_handle; - uint32_t write_pos; - // Unique identifier - uint32_t last_handle; -} fs_operation_t; - -static fs_operation_t _fs_operation = { - .last_handle = 1 -}; - //--------------------------------------------------------------------+ // INTERNAL FUNCTIONS //--------------------------------------------------------------------+ @@ -166,7 +140,6 @@ uint32_t fs_get_object_count(void) { int32_t tud_mtp_data_complete_cb(tud_mtp_cb_data_t* cb_data) { mtp_container_info_t* reply = &cb_data->reply; - reply->header->len = sizeof(mtp_container_header_t); reply->header->code = (cb_data->xfer_result == XFER_RESULT_SUCCESS) ? MTP_RESP_OK : MTP_RESP_GENERAL_ERROR; tud_mtp_response_send(reply); return 0; @@ -181,30 +154,42 @@ int32_t tud_mtp_data_more_cb(tud_mtp_cb_data_t* cb_data) { // only a few command that need more data e.g GetObject and SendObject const mtp_container_command_t* command = cb_data->command; mtp_container_info_t* reply = &cb_data->reply; - switch (command->header.code) { + uint32_t resp_code = 0; + switch (command->code) { case MTP_OP_GET_OBJECT: { const uint32_t obj_handle = command->params[0]; fs_object_info_t* obj = fs_get_object(obj_handle); if (obj == NULL) { - return MTP_RESP_INVALID_OBJECT_HANDLE; + resp_code = MTP_RESP_INVALID_OBJECT_HANDLE; + } else { + // file contents offset is xferred byte minus header size + const uint32_t offset = cb_data->xferred_bytes - sizeof(mtp_container_header_t); + const uint32_t xact_len = tu_min32(obj->size - offset, reply->payload_size); + memcpy(reply->payload, obj->data + offset, xact_len); + tud_mtp_data_send(&cb_data->reply); } - // file contents offset is xferred byte minus header size - const uint32_t offset = cb_data->xferred_bytes - sizeof(mtp_container_header_t); - const uint32_t xact_len = tu_min32(obj->size - offset, reply->payload_size); - memcpy(reply->payload, obj->data + offset, xact_len); - tud_mtp_data_send(&cb_data->reply); + break; } - default: return MTP_RESP_OPERATION_NOT_SUPPORTED; + default: + resp_code = MTP_RESP_OPERATION_NOT_SUPPORTED; + break; } - return MTP_RESP_OK; + // send response if needed + if (resp_code != 0) { + reply->header->code = resp_code; + tud_mtp_response_send(reply); + } + + return 0; // 0 mean data/response is sent already } int32_t tud_mtp_command_received_cb(tud_mtp_cb_data_t* cb_data) { const mtp_container_command_t* command = cb_data->command; mtp_container_info_t* reply = &cb_data->reply; - switch (command->header.code) { + uint32_t resp_code = 0; + switch (command->code) { case MTP_OP_GET_DEVICE_INFO: { // Device info is already prepared up to playback formats. Application only need to add string fields mtp_container_add_cstring(&cb_data->reply, DEV_INFO_MANUFACTURER); @@ -218,24 +203,20 @@ int32_t tud_mtp_command_received_cb(tud_mtp_cb_data_t* cb_data) { case MTP_OP_OPEN_SESSION: if (is_session_opened) { - //return MTP_RESP_SESSION_ALREADY_OPEN; - reply->header->code = MTP_RESP_SESSION_ALREADY_OPEN; + resp_code = MTP_RESP_SESSION_ALREADY_OPEN; }else { - reply->header->code = MTP_RESP_OK; + resp_code = MTP_RESP_OK; } is_session_opened = true; - tud_mtp_response_send(&cb_data->reply); break; case MTP_OP_CLOSE_SESSION: if (!is_session_opened) { - // return MTP_RESP_SESSION_NOT_OPEN; - reply->header->code = MTP_RESP_SESSION_NOT_OPEN; + resp_code = MTP_RESP_SESSION_NOT_OPEN; } else { - reply->header->code = MTP_RESP_OK; + resp_code = MTP_RESP_OK; } is_session_opened = false; - tud_mtp_response_send(&cb_data->reply); break; case MTP_OP_GET_STORAGE_IDS: { @@ -246,7 +227,7 @@ int32_t tud_mtp_command_received_cb(tud_mtp_cb_data_t* cb_data) { } case MTP_OP_GET_STORAGE_INFO: { - uint32_t storage_id = command->params[0]; + const uint32_t storage_id = command->params[0]; TU_VERIFY(SUPPORTED_STORAGE_ID == storage_id, -1); // update storage info with current free space storage_info.free_space_in_objects = FS_MAX_FILE_COUNT - fs_get_object_count(); @@ -271,7 +252,9 @@ int32_t tud_mtp_command_received_cb(tud_mtp_cb_data_t* cb_data) { tud_mtp_data_send(&cb_data->reply); break; - default: return MTP_RESP_PARAMETER_NOT_SUPPORTED; + default: + resp_code = MTP_RESP_PARAMETER_NOT_SUPPORTED; + break; } break; } @@ -284,7 +267,9 @@ int32_t tud_mtp_command_received_cb(tud_mtp_cb_data_t* cb_data) { tud_mtp_data_send(&cb_data->reply); break; - default: return MTP_RESP_PARAMETER_NOT_SUPPORTED; + default: + resp_code = MTP_RESP_PARAMETER_NOT_SUPPORTED; + break; } break; } @@ -295,20 +280,20 @@ int32_t tud_mtp_command_received_cb(tud_mtp_cb_data_t* cb_data) { (void) obj_format; const uint32_t parent_handle = command->params[2]; // folder handle, 0xFFFFFFFF is root if (storage_id != 0xFFFFFFFF && storage_id != SUPPORTED_STORAGE_ID) { - return MTP_RESP_INVALID_STORAGE_ID; - } - - uint32_t handles[FS_MAX_FILE_COUNT] = { 0 }; - uint32_t count = 0; - for (uint8_t i = 0; i < FS_MAX_FILE_COUNT; i++) { - fs_object_info_t* obj = &fs_objects[i]; - if (obj->name[0] != 0 && - (parent_handle == obj->parent || (parent_handle == 0xFFFFFFFF && obj->parent == 0))) { - handles[count++] = i + 1; // handle is index + 1 + resp_code = MTP_RESP_INVALID_STORAGE_ID; + } else { + uint32_t handles[FS_MAX_FILE_COUNT] = { 0 }; + uint32_t count = 0; + for (uint8_t i = 0; i < FS_MAX_FILE_COUNT; i++) { + fs_object_info_t* obj = &fs_objects[i]; + if (obj->name[0] != 0 && + (parent_handle == obj->parent || (parent_handle == 0xFFFFFFFF && obj->parent == 0))) { + handles[count++] = i + 1; // handle is index + 1 + } } + mtp_container_add_auint32(&cb_data->reply, count, handles); + tud_mtp_data_send(&cb_data->reply); } - mtp_container_add_auint32(&cb_data->reply, count, handles); - tud_mtp_data_send(&cb_data->reply); break; } @@ -316,32 +301,33 @@ int32_t tud_mtp_command_received_cb(tud_mtp_cb_data_t* cb_data) { const uint32_t obj_handle = command->params[0]; fs_object_info_t* obj = fs_get_object(obj_handle); if (obj == NULL) { - return MTP_RESP_INVALID_OBJECT_HANDLE; - } - mtp_object_info_header_t obj_info_header = { - .storage_id = SUPPORTED_STORAGE_ID, - .object_format = obj->format, - .protection_status = MTP_PROTECTION_STATUS_NO_PROTECTION, - .object_compressed_size = obj->size, - .thumb_format = MTP_OBJ_FORMAT_UNDEFINED, - .thumb_compressed_size = 0, - .thumb_pix_width = 0, - .thumb_pix_height = 0, - .image_pix_width = 128, - .image_pix_height = 64, - .image_bit_depth = 32, - .parent_object = obj->parent, - .association_type = MTP_ASSOCIATION_UNDEFINED, - .association_desc = 0, - .sequence_number = 0 - }; - mtp_container_add_raw(&cb_data->reply, &obj_info_header, sizeof(obj_info_header)); - mtp_container_add_cstring(&cb_data->reply, obj->name); - mtp_container_add_cstring(&cb_data->reply, FS_FIXED_DATETIME); - mtp_container_add_cstring(&cb_data->reply, FS_FIXED_DATETIME); - mtp_container_add_cstring(&cb_data->reply, ""); // keywords, not used + resp_code = MTP_RESP_INVALID_OBJECT_HANDLE; + } else { + mtp_object_info_header_t obj_info_header = { + .storage_id = SUPPORTED_STORAGE_ID, + .object_format = obj->format, + .protection_status = MTP_PROTECTION_STATUS_NO_PROTECTION, + .object_compressed_size = obj->size, + .thumb_format = MTP_OBJ_FORMAT_UNDEFINED, + .thumb_compressed_size = 0, + .thumb_pix_width = 0, + .thumb_pix_height = 0, + .image_pix_width = 128, + .image_pix_height = 64, + .image_bit_depth = 32, + .parent_object = obj->parent, + .association_type = MTP_ASSOCIATION_UNDEFINED, + .association_desc = 0, + .sequence_number = 0 + }; + mtp_container_add_raw(&cb_data->reply, &obj_info_header, sizeof(obj_info_header)); + mtp_container_add_cstring(&cb_data->reply, obj->name); + mtp_container_add_cstring(&cb_data->reply, FS_FIXED_DATETIME); + mtp_container_add_cstring(&cb_data->reply, FS_FIXED_DATETIME); + mtp_container_add_cstring(&cb_data->reply, ""); // keywords, not used - tud_mtp_data_send(&cb_data->reply); + tud_mtp_data_send(&cb_data->reply); + } break; } @@ -349,20 +335,28 @@ int32_t tud_mtp_command_received_cb(tud_mtp_cb_data_t* cb_data) { const uint32_t obj_handle = command->params[0]; fs_object_info_t* obj = fs_get_object(obj_handle); if (obj == NULL) { - return MTP_RESP_INVALID_OBJECT_HANDLE; + resp_code = MTP_RESP_INVALID_OBJECT_HANDLE; + } else { + // If file contents is larger than CFG_TUD_MTP_EP_BUFSIZE, only partial data is added here + // the rest will be sent in tud_mtp_data_more_cb + mtp_container_add_raw(&cb_data->reply, obj->data, obj->size); + tud_mtp_data_send(&cb_data->reply); } - - // If file contents is larger than CFG_TUD_MTP_EP_BUFSIZE, only partial data is added here - // the rest will be sent in tud_mtp_data_more_cb - mtp_container_add_raw(&cb_data->reply, obj->data, obj->size); - tud_mtp_data_send(&cb_data->reply); break; } - default: return MTP_RESP_OPERATION_NOT_SUPPORTED; + default: + resp_code = MTP_RESP_OPERATION_NOT_SUPPORTED; + break; } - return MTP_RESP_OK; + // send response if needed + if (resp_code != 0) { + reply->header->code = resp_code; + tud_mtp_response_send(reply); + } + + return 0; } //--------------------------------------------------------------------+ @@ -556,23 +550,7 @@ void tud_mtp_storage_object_done(void) { #endif void tud_mtp_storage_cancel(void) { - fs_object_info_t* obj; - - _fs_operation.traversal_parent = 0; - _fs_operation.traversal_index = 0; - _fs_operation.read_handle = 0; - _fs_operation.read_pos = 0; - // If write operation is canceled, discard object - if (_fs_operation.write_handle) { - obj = fs_get_object(_fs_operation.write_handle); - // if (obj) - // obj->allocated = false; - } - _fs_operation.write_handle = 0; - _fs_operation.write_pos = 0; } void tud_mtp_storage_reset(void) { - tud_mtp_storage_cancel(); - _fs_operation.session_id = 0; } diff --git a/src/class/mtp/mtp.h b/src/class/mtp/mtp.h index d4d81492e..f1b1eaf23 100644 --- a/src/class/mtp/mtp.h +++ b/src/class/mtp/mtp.h @@ -666,7 +666,10 @@ typedef struct TU_ATTR_PACKED { TU_VERIFY_STATIC(sizeof(mtp_container_header_t) == 12, "size is not correct"); typedef struct TU_ATTR_PACKED { - mtp_container_header_t header; + uint32_t len; + uint16_t type; + uint16_t code; + uint32_t transaction_id; uint32_t params[5]; } mtp_container_command_t; TU_VERIFY_STATIC(sizeof(mtp_container_command_t) == 32, "size is not correct"); diff --git a/src/class/mtp/mtp_device.c b/src/class/mtp/mtp_device.c index 92801a8ad..358114655 100644 --- a/src/class/mtp/mtp_device.c +++ b/src/class/mtp/mtp_device.c @@ -69,17 +69,13 @@ typedef struct { } mtpd_interface_t; typedef struct { - TUD_EPBUF_TYPE_DEF(mtp_generic_container_t, container); + TUD_EPBUF_DEF(buf, CFG_TUD_MTP_EP_BUFSIZE); } mtpd_epbuf_t; //--------------------------------------------------------------------+ // INTERNAL FUNCTION DECLARATION //--------------------------------------------------------------------+ -// Checker -static mtp_phase_type_t mtpd_chk_generic(const char *func_name, const bool err_cd, const uint16_t ret_code, const char *message); - -// MTP commands -static mtp_phase_type_t mtpd_handle_cmd(mtpd_interface_t* p_mtp, tud_mtp_cb_data_t* cb_data); +static int32_t mtpd_handle_cmd(mtpd_interface_t* p_mtp, tud_mtp_cb_data_t* cb_data); static mtp_phase_type_t mtpd_handle_data(void); static mtp_phase_type_t mtpd_handle_cmd_delete_object(void); static mtp_phase_type_t mtpd_handle_cmd_send_object_info(void); @@ -95,10 +91,7 @@ static mtpd_interface_t _mtpd_itf; CFG_TUD_MEM_SECTION static mtpd_epbuf_t _mtpd_epbuf; CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN static mtp_device_status_res_t _mtpd_device_status_res; -CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN static uint32_t _mtpd_get_object_handle; CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN static mtp_basic_object_info_t _mtpd_soi; -CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN static char _mtp_datestr[20]; - //--------------------------------------------------------------------+ // Debug @@ -165,17 +158,15 @@ TU_ATTR_UNUSED static tu_lookup_table_t const _mtp_op_table = { static bool prepare_new_command(mtpd_interface_t* p_mtp) { p_mtp->phase = MTP_PHASE_IDLE; - return usbd_edpt_xfer(p_mtp->rhport, p_mtp->ep_out, (uint8_t *)(&_mtpd_epbuf.container), sizeof(mtp_generic_container_t)); + return usbd_edpt_xfer(p_mtp->rhport, p_mtp->ep_out, _mtpd_epbuf.buf, CFG_TUD_MTP_EP_BUFSIZE); } - //--------------------------------------------------------------------+ // USBD Driver API //--------------------------------------------------------------------+ void mtpd_init(void) { tu_memclr(&_mtpd_itf, sizeof(mtpd_interface_t)); tu_memclr(&_mtpd_soi, sizeof(mtp_basic_object_info_t)); - _mtpd_get_object_handle = 0; } bool mtpd_deinit(void) { @@ -187,7 +178,6 @@ void mtpd_reset(uint8_t rhport) { tu_memclr(&_mtpd_itf, sizeof(mtpd_interface_t)); tu_memclr(&_mtpd_epbuf, sizeof(mtpd_epbuf_t)); tu_memclr(&_mtpd_soi, sizeof(mtp_basic_object_info_t)); - _mtpd_get_object_handle = 0; } uint16_t mtpd_open(uint8_t rhport, tusb_desc_interface_t const* itf_desc, uint16_t max_len) { @@ -242,7 +232,7 @@ bool mtpd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t TU_LOG_DRV(" MTP request: MTP_REQ_RESET\n"); tud_mtp_storage_reset(); // Prepare for a new command - TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_out, (uint8_t *)(&_mtpd_epbuf.container), sizeof(mtp_generic_container_t))); + TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_out, _mtpd_epbuf.buf, CFG_TUD_MTP_EP_BUFSIZE)); break; case MTP_REQ_GET_DEVICE_STATUS: { @@ -272,7 +262,7 @@ bool tud_mtp_data_send(mtp_container_info_t* p_container) { p_mtp->xferred_len = 0; p_container->header->type = MTP_CONTAINER_TYPE_DATA_BLOCK; - p_container->header->transaction_id = p_mtp->command.header.transaction_id; + p_container->header->transaction_id = p_mtp->command.transaction_id; p_mtp->reply_header = *p_container->header; // save header for subsequent data } else { // subsequent data block: payload only @@ -280,7 +270,7 @@ bool tud_mtp_data_send(mtp_container_info_t* p_container) { } const uint16_t xact_len = tu_min32(p_mtp->total_len - p_mtp->xferred_len, CFG_TUD_MTP_EP_BUFSIZE); - TU_ASSERT(usbd_edpt_xfer(p_mtp->rhport, p_mtp->ep_in, (uint8_t *)(&_mtpd_epbuf.container), xact_len)); + TU_ASSERT(usbd_edpt_xfer(p_mtp->rhport, p_mtp->ep_in, _mtpd_epbuf.buf, xact_len)); return true; } @@ -289,22 +279,25 @@ bool tud_mtp_response_send(mtp_container_info_t* p_container) { mtpd_interface_t* p_mtp = &_mtpd_itf; p_mtp->phase = MTP_PHASE_RESPONSE_QUEUED; p_container->header->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; - p_container->header->transaction_id = p_mtp->command.header.transaction_id; - TU_ASSERT(usbd_edpt_xfer(p_mtp->rhport, p_mtp->ep_in, (uint8_t *)(&_mtpd_epbuf.container), (uint16_t)p_container->header->len)); + p_container->header->transaction_id = p_mtp->command.transaction_id; + TU_ASSERT(usbd_edpt_xfer(p_mtp->rhport, p_mtp->ep_in, _mtpd_epbuf.buf, (uint16_t)p_container->header->len)); return true; } // Transfer on bulk endpoints bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes) { - TU_ASSERT(event == XFER_RESULT_SUCCESS); - if (ep_addr == _mtpd_itf.ep_event) { // nothing to do return true; } mtpd_interface_t* p_mtp = &_mtpd_itf; - mtp_generic_container_t* p_container = &_mtpd_epbuf.container; + mtp_generic_container_t* p_container = (mtp_generic_container_t*) _mtpd_epbuf.buf; + +#if CFG_TUSB_DEBUG >= CFG_TUD_MTP_LOG_LEVEL + tu_lookup_find(&_mtp_op_table, p_mtp->command.code); + TU_LOG_DRV(" MTP %s phase = %u\r\n", (const char *) tu_lookup_find(&_mtp_op_table, p_mtp->command.code), p_mtp->phase); +#endif tud_mtp_cb_data_t cb_data; cb_data.idx = 0; @@ -323,8 +316,10 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t TU_ATTR_FALLTHROUGH; // handle in the next case case MTP_PHASE_COMMAND: { - memcpy(&p_mtp->command, p_container, sizeof(mtp_container_command_t)); // copy new command - mtpd_handle_cmd(p_mtp, &cb_data); + memcpy(&p_mtp->command, p_container, sizeof(mtp_container_command_t)); // save new command + if (mtpd_handle_cmd(p_mtp, &cb_data) < 0) { + p_mtp->phase = MTP_PHASE_ERROR; + } break; } @@ -337,6 +332,7 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t if (xferred_bytes == 0 || // ZLP (xferred_bytes & (bulk_mps - 1)) || // short packet p_mtp->xferred_len > p_mtp->total_len) { + cb_data.reply.header->len = sizeof(mtp_container_header_t); tud_mtp_data_complete_cb(&cb_data); } else { // payload only packet @@ -450,15 +446,11 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t //--------------------------------------------------------------------+ // Decode command and prepare response -mtp_phase_type_t mtpd_handle_cmd(mtpd_interface_t* p_mtp, tud_mtp_cb_data_t* cb_data) { - mtp_generic_container_t* p_container = &_mtpd_epbuf.container; - p_container->len = sizeof(mtp_container_header_t); // default data/response length - - tu_lookup_find(&_mtp_op_table, p_mtp->command.header.code); - TU_LOG_DRV(" MTP command: %s\r\n", (char const*) tu_lookup_find(&_mtp_op_table, p_mtp->command.header.code)); +int32_t mtpd_handle_cmd(mtpd_interface_t* p_mtp, tud_mtp_cb_data_t* cb_data) { + cb_data->reply.header->len = sizeof(mtp_container_header_t); // pre-processed commands - switch (p_mtp->command.header.code) { + switch (p_mtp->command.code) { case MTP_OP_GET_DEVICE_INFO: { tud_mtp_device_info_t dev_info = { .standard_version = 100, @@ -500,7 +492,6 @@ mtp_phase_type_t mtpd_handle_cmd(mtpd_interface_t* p_mtp, tud_mtp_cb_data_t* cb_ } #endif mtp_container_add_raw(&cb_data->reply, &dev_info, sizeof(tud_mtp_device_info_t)); - p_container->type = MTP_CONTAINER_TYPE_DATA_BLOCK; break; } @@ -508,14 +499,13 @@ mtp_phase_type_t mtpd_handle_cmd(mtpd_interface_t* p_mtp, tud_mtp_cb_data_t* cb_ break; } - tud_mtp_command_received_cb(cb_data); - return MTP_PHASE_RESPONSE; + return tud_mtp_command_received_cb(cb_data); } #if 0 mtp_phase_type_t mtpd_handle_data(void) { - mtp_generic_container_t* p_container = &_mtpd_epbuf.container; + mtp_generic_container_t* p_container = &_mtpd_epbuf.buf; TU_ASSERT(p_container->type == MTP_CONTAINER_TYPE_DATA_BLOCK); switch(p_container->code) @@ -535,7 +525,7 @@ mtp_phase_type_t mtpd_handle_data(void) mtp_phase_type_t mtpd_handle_cmd_delete_object(void) { - mtp_generic_container_t* p_container = &_mtpd_epbuf.container; + mtp_generic_container_t* p_container = &_mtpd_epbuf.buf; uint32_t object_handle = p_container->data[0]; uint32_t object_code_format = p_container->data[1]; // not used (void) object_code_format; @@ -552,7 +542,7 @@ mtp_phase_type_t mtpd_handle_cmd_delete_object(void) mtp_phase_type_t mtpd_handle_cmd_send_object_info(void) { - mtp_generic_container_t* p_container = &_mtpd_epbuf.container; + mtp_generic_container_t* p_container = &_mtpd_epbuf.buf; _mtpd_soi.storage_id = p_container->data[0]; _mtpd_soi.parent_object_handle = (p_container->data[1] == 0xFFFFFFFF ? 0 : p_container->data[1]); @@ -562,7 +552,7 @@ mtp_phase_type_t mtpd_handle_cmd_send_object_info(void) mtp_phase_type_t mtpd_handle_dto_send_object_info(void) { - mtp_generic_container_t* p_container = &_mtpd_epbuf.container; + mtp_generic_container_t* p_container = &_mtpd_epbuf.buf; uint32_t new_object_handle = 0; mtp_response_t res = tud_mtp_storage_object_write_info(_mtpd_soi.storage_id, _mtpd_soi.parent_object_handle, &new_object_handle, (mtp_object_info_header_t *)p_container->data); mtp_phase_type_t phase; @@ -589,7 +579,7 @@ mtp_phase_type_t mtpd_handle_cmd_send_object(void) mtp_phase_type_t mtpd_handle_dto_send_object(void) { - mtp_generic_container_t* p_container = &_mtpd_epbuf.container; + mtp_generic_container_t* p_container = &_mtpd_epbuf.buf; uint8_t *buffer = (uint8_t *)&p_container->data; uint32_t buffer_size = _mtpd_itf.xferred_len - _mtpd_itf.handled_len; // First block of DATA @@ -622,7 +612,7 @@ mtp_phase_type_t mtpd_handle_dto_send_object(void) mtp_phase_type_t mtpd_handle_cmd_format_store(void) { - mtp_generic_container_t* p_container = &_mtpd_epbuf.container; + mtp_generic_container_t* p_container = &_mtpd_epbuf.buf; uint32_t storage_id = p_container->data[0]; uint32_t file_system_format = p_container->data[1]; // not used (void) file_system_format; @@ -636,124 +626,4 @@ mtp_phase_type_t mtpd_handle_cmd_format_store(void) } #endif -//--------------------------------------------------------------------+ -// Checker -//--------------------------------------------------------------------+ -mtp_phase_type_t mtpd_chk_generic(const char *func_name, const bool err_cd, const uint16_t ret_code, const char *message) -{ - (void)func_name; - (void)message; - mtp_generic_container_t* p_container = &_mtpd_epbuf.container; - if (err_cd) - { - TU_LOG_DRV(" MTP error in %s: (%x) %s\n", func_name, ret_code, message); - p_container->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; - p_container->code = ret_code; - p_container->len = sizeof(mtp_container_header_t); - return MTP_PHASE_RESPONSE; - } - return MTP_PHASE_NONE; -} - -//--------------------------------------------------------------------+ -// Generic container data -//--------------------------------------------------------------------+ -void mtpd_wc16cpy(uint8_t *dest, const char *src) -{ - wchar16_t s; - while(true) - { - s = *src; - memcpy(dest, &s, sizeof(wchar16_t)); - if (*src == 0) break; - ++src; - dest += sizeof(wchar16_t); - } -} - -//--------------------------------------------------------------------+ -// Generic container function -//--------------------------------------------------------------------+ -bool mtpd_gct_append_uint8(const uint8_t value) -{ - mtp_generic_container_t* p_container = &_mtpd_epbuf.container; - uint8_t *p_value = ((uint8_t *)p_container) + p_container->len; - p_container->len += sizeof(uint8_t); - // Verify space requirement (8 bit string length, number of wide characters including terminator) - TU_ASSERT(p_container->len < sizeof(mtp_generic_container_t)); - *p_value = value; - return true; -} - -bool mtpd_gct_append_object_handle(const uint32_t object_handle) -{ - mtp_generic_container_t* p_container = &_mtpd_epbuf.container; - p_container->len += sizeof(uint32_t); - TU_ASSERT(p_container->len < sizeof(mtp_generic_container_t)); - p_container->data[0]++; - p_container->data[p_container->data[0]] = object_handle; - return true; -} - -bool mtpd_gct_append_wstring(const char *s) -{ - mtp_generic_container_t* p_container = &_mtpd_epbuf.container; - size_t len = strlen(s) + 1; - TU_ASSERT(len <= UINT8_MAX); - uint8_t *p_len = ((uint8_t *)p_container)+p_container->len; - p_container->len += sizeof(uint8_t) + sizeof(wchar16_t) * len; - // Verify space requirement (8 bit string length, number of wide characters including terminator) - TU_ASSERT(p_container->len < sizeof(mtp_generic_container_t)); - *p_len = (uint8_t)len; - uint8_t *p_str = p_len + sizeof(uint8_t); - mtpd_wc16cpy(p_str, s); - return true; -} - -bool mtpd_gct_get_string(uint16_t *offset_data, char *string, const uint16_t max_size) -{ - mtp_generic_container_t* p_container = &_mtpd_epbuf.container; - uint16_t size = *(((uint8_t *)&p_container->data) + *offset_data); - if (size > max_size) - size = max_size; - TU_ASSERT(*offset_data + size < sizeof(p_container->data)); - - uint8_t *s = ((uint8_t *)&p_container->data) + *offset_data + sizeof(uint8_t); - for(uint16_t i = 0; i < size; i++) - { - string[i] = *s; - s += sizeof(wchar16_t); - } - *offset_data += (uint16_t)(sizeof(uint8_t) + size * sizeof(wchar16_t)); - return true; -} - -bool mtpd_gct_append_array(uint32_t array_size, const void *data, size_t type_size) -{ - mtp_generic_container_t* p_container = &_mtpd_epbuf.container; - TU_ASSERT(p_container->len + sizeof(uint32_t) + array_size * type_size < sizeof(p_container->data)); - uint8_t *p = ((uint8_t *)p_container) + p_container->len; - memcpy(p, &array_size, sizeof(uint32_t)); - p += sizeof(uint32_t); - memcpy(p, data, array_size * type_size); - p_container->len += sizeof(uint32_t) + array_size * type_size; - return true; -} - -bool mtpd_gct_append_date(struct tm *timeinfo) -{ - mtp_generic_container_t* p_container = &_mtpd_epbuf.container; - // strftime is not supported by all platform, this implementation is just for reference - int len = snprintf(_mtp_datestr, sizeof(p_container->data) - p_container->len, "%04d%02d%02dT%02d%02d%02dZ", - timeinfo->tm_year + 1900, - timeinfo->tm_mon + 1, - timeinfo->tm_mday, - timeinfo->tm_hour, - timeinfo->tm_min, - timeinfo->tm_sec); - if (len == 0) - return false; - return mtpd_gct_append_wstring(_mtp_datestr); -} - -#endif // (CFG_TUD_ENABLED && CFG_TUD_MTP) +#endif diff --git a/src/class/mtp/mtp_device.h b/src/class/mtp/mtp_device.h index 88960b1aa..28c01ddc6 100644 --- a/src/class/mtp/mtp_device.h +++ b/src/class/mtp/mtp_device.h @@ -107,9 +107,9 @@ bool tud_mtp_response_send(mtp_container_info_t* p_container); // Bulk only protocol Callbacks //--------------------------------------------------------------------+ -// Invoked when new command is received. Application fill the out_block with either DATA or RESPONSE container -// and call tud_mtp_data_send() or tud_mtp_response_send(). -// return MTP response code +/* Invoked when new command is received. Application fill the cb_data->reply with either DATA or RESPONSE and call + * tud_mtp_data_send() or tud_mtp_response_send(). Return negative to stall the endpoints + */ int32_t tud_mtp_command_received_cb(tud_mtp_cb_data_t * cb_data); // Invoked when a data packet is received/sent, and more data is expected @@ -121,26 +121,6 @@ int32_t tud_mtp_data_complete_cb(tud_mtp_cb_data_t* cb_data); // Invoked when response phase is complete int32_t tud_mtp_response_complete_cb(tud_mtp_cb_data_t* cb_data); -//--------------------------------------------------------------------+ -// Helper functions -//--------------------------------------------------------------------+ -// Generic container function -void mtpd_wc16cpy(uint8_t *dest, const char *src); -bool mtpd_gct_append_uint8(const uint8_t value); -bool mtpd_gct_append_object_handle(const uint32_t object_handle); -bool mtpd_gct_append_wstring(const char *s); -bool mtpd_gct_get_string(uint16_t *offset_data, char *string, const uint16_t max_size); - -// Append the given array to the global context buffer -// The function returns true if the data fits in the available buffer space. -bool mtpd_gct_append_array(uint32_t array_size, const void *data, size_t type_size); - -// Append an UTC date string to the global context buffer -// Required format is 'YYYYMMDDThhmmss.s' optionally added 'Z' for UTC or +/-hhmm for time zone -// This function is provided for reference and only supports UTC format without partial seconds -// The function returns true if the data fits in the available buffer space. -bool mtpd_gct_append_date(struct tm *timeinfo); - //--------------------------------------------------------------------+ // Internal Class Driver API //--------------------------------------------------------------------+ From f8397717ea3f476c810ae3ce27ec4c866b305506 Mon Sep 17 00:00:00 2001 From: hathach Date: Thu, 25 Sep 2025 15:30:37 +0700 Subject: [PATCH 20/43] implement MTP_OP_SEND_OBJECT_INFO, refactor fs example --- examples/device/mtp/src/mtp_fs_example.c | 300 +++++++++++++++-------- src/class/mtp/mtp.h | 29 ++- src/class/mtp/mtp_device.c | 96 +++++--- src/class/mtp/mtp_device.h | 20 +- 4 files changed, 301 insertions(+), 144 deletions(-) diff --git a/examples/device/mtp/src/mtp_fs_example.c b/examples/device/mtp/src/mtp_fs_example.c index 7e19fa75c..d6d7f1907 100644 --- a/examples/device/mtp/src/mtp_fs_example.c +++ b/examples/device/mtp/src/mtp_fs_example.c @@ -51,41 +51,55 @@ typedef MTP_STORAGE_INFO_STRUCT(TU_ARRAY_SIZE((uint16_t[]) STORAGE_DESCRIPTRION) // RAM FILESYSTEM //--------------------------------------------------------------------+ #define FS_MAX_FILE_COUNT 5UL -#define FS_MAX_CAPACITY_BYTES (2 * 1024UL) -#define FS_MAX_FILENAME_LEN 16UL +#define FS_MAX_CAPACITY_BYTES (4 * 1024UL) +#define FS_MAX_FILENAME_LEN 16 #define FS_FIXED_DATETIME "20250808T173500.0" // "YYYYMMDDTHHMMSS.s" #define README_TXT_CONTENT "TinyUSB MTP on RAM Filesystem example" typedef struct { - // uint32_t handle; - char name[FS_MAX_FILENAME_LEN]; - mtp_object_formats_t format; + uint16_t name[FS_MAX_FILENAME_LEN]; + mtp_object_formats_t object_format; + uint16_t protection_status; + uint32_t image_pix_width; + uint32_t image_pix_height; + uint32_t image_bit_depth; + uint32_t parent; - bool association; + uint8_t association_type; + uint32_t size; uint8_t* data; -} fs_object_info_t; + +} fs_file_t; // object data buffer (excluding 2 predefined files) uint8_t fs_buf[FS_MAX_CAPACITY_BYTES]; uint8_t fs_buf_head = 0; // simple allocation pointer // Files system, handle is index + 1 -static fs_object_info_t fs_objects[FS_MAX_FILE_COUNT] = { +static fs_file_t fs_objects[FS_MAX_FILE_COUNT] = { { - .name = "readme.txt", - .format = MTP_OBJ_FORMAT_TEXT, + .name = { 'r', 'e', 'a', 'd', 'm', 'e', '.', 't', 'x', 't', 0 }, // readme.txt + .object_format = MTP_OBJ_FORMAT_TEXT, + .protection_status = MTP_PROTECTION_STATUS_READ_ONLY, + .image_pix_width = 0, + .image_pix_height = 0, + .image_bit_depth = 0, .parent = 0, - .association = false, + .association_type = MTP_ASSOCIATION_UNDEFINED, .data = (uint8_t*) README_TXT_CONTENT, .size = sizeof(README_TXT_CONTENT) }, { - .name = "tinyusb.png", - .format = MTP_OBJ_FORMAT_PNG, + .name = { 't', 'i', 'n', 'y', 'u', 's', 'b', '.', 'p', 'n', 'g', 0 }, // "tinyusb.png" + .object_format = MTP_OBJ_FORMAT_PNG, + .protection_status = MTP_PROTECTION_STATUS_READ_ONLY, + .image_pix_width = 128, + .image_pix_height = 64, + .image_bit_depth = 32, .parent = 0, - .association = false, + .association_type = MTP_ASSOCIATION_UNDEFINED, .data = logo_bin, .size = logo_len, } @@ -114,34 +128,77 @@ enum { }; static bool is_session_opened = false; - -//--------------------------------------------------------------------+ -// INTERNAL FUNCTIONS -//--------------------------------------------------------------------+ +static uint32_t send_obj_handle = 0; // Get pointer to object info from handle -static inline fs_object_info_t* fs_get_object(uint32_t handle) { +static inline fs_file_t* fs_get_file(uint32_t handle) { if (handle == 0 || handle > FS_MAX_FILE_COUNT) { return NULL; } return &fs_objects[handle-1]; } +static inline bool fs_file_exist(fs_file_t* f) { + return f->name[0] != 0; +} + // Get the number of allocated nodes in filesystem -uint32_t fs_get_object_count(void) { +static uint32_t fs_get_file_count(void) { uint32_t count = 0; - for (unsigned int i = 0; i < FS_MAX_FILE_COUNT; i++) { - if (fs_objects[i].name[0] != 0) { + for (size_t i = 0; i < FS_MAX_FILE_COUNT; i++) { + if (fs_file_exist(&fs_objects[i])) { count++; } } return count; } +static inline fs_file_t* fs_create_file(void) { + for (size_t i = 0; i < FS_MAX_FILE_COUNT; i++) { + fs_file_t* f = &fs_objects[i]; + if (!fs_file_exist(f)) { + send_obj_handle = i + 1; + return f; + } + } +} + +// simple malloc +static inline uint8_t* fs_malloc(size_t size) { + if (fs_buf_head + size > FS_MAX_CAPACITY_BYTES) { + return NULL; + } + uint8_t* ptr = &fs_buf[fs_buf_head]; + fs_buf_head += size; + return ptr; +} + +//--------------------------------------------------------------------+ +// +//--------------------------------------------------------------------+ int32_t tud_mtp_data_complete_cb(tud_mtp_cb_data_t* cb_data) { - mtp_container_info_t* reply = &cb_data->reply; - reply->header->code = (cb_data->xfer_result == XFER_RESULT_SUCCESS) ? MTP_RESP_OK : MTP_RESP_GENERAL_ERROR; - tud_mtp_response_send(reply); + const mtp_container_command_t* command = cb_data->command_container; + mtp_container_info_t* resp = &cb_data->io_container; + switch (command->code) { + case MTP_OP_SEND_OBJECT_INFO: { + fs_file_t* f = fs_get_file(send_obj_handle); + if (f == NULL) { + resp->header->code = MTP_RESP_GENERAL_ERROR; + break; + } + // parameter is: storage id, parent handle, new handle + mtp_container_add_uint32(resp, SUPPORTED_STORAGE_ID); + mtp_container_add_uint32(resp, f->parent); + mtp_container_add_uint32(resp, send_obj_handle); + resp->header->code = MTP_RESP_OK; + break; + } + + default: + resp->header->code = (cb_data->xfer_result == XFER_RESULT_SUCCESS) ? MTP_RESP_OK : MTP_RESP_GENERAL_ERROR; + break; + } + tud_mtp_response_send(resp); return 0; } @@ -150,27 +207,62 @@ int32_t tud_mtp_response_complete_cb(tud_mtp_cb_data_t* cb_data) { return 0; // nothing to do } -int32_t tud_mtp_data_more_cb(tud_mtp_cb_data_t* cb_data) { - // only a few command that need more data e.g GetObject and SendObject - const mtp_container_command_t* command = cb_data->command; - mtp_container_info_t* reply = &cb_data->reply; +int32_t tud_mtp_data_xfer_cb(tud_mtp_cb_data_t* cb_data) { + const mtp_container_command_t* command = cb_data->command_container; + mtp_container_info_t* io_container = &cb_data->io_container; uint32_t resp_code = 0; switch (command->code) { case MTP_OP_GET_OBJECT: { + // File contents span over multiple xfers const uint32_t obj_handle = command->params[0]; - fs_object_info_t* obj = fs_get_object(obj_handle); - if (obj == NULL) { + fs_file_t* f = fs_get_file(obj_handle); + if (f == NULL) { resp_code = MTP_RESP_INVALID_OBJECT_HANDLE; } else { // file contents offset is xferred byte minus header size const uint32_t offset = cb_data->xferred_bytes - sizeof(mtp_container_header_t); - const uint32_t xact_len = tu_min32(obj->size - offset, reply->payload_size); - memcpy(reply->payload, obj->data + offset, xact_len); - tud_mtp_data_send(&cb_data->reply); + const uint32_t xact_len = tu_min32(f->size - offset, io_container->payload_size); + memcpy(io_container->payload, f->data + offset, xact_len); + tud_mtp_data_send(&cb_data->io_container); } break; } + case MTP_OP_SEND_OBJECT_INFO: { + mtp_object_info_header_t* obj_info = (mtp_object_info_header_t*) io_container->payload; + if (obj_info->storage_id != 0 && obj_info->storage_id != SUPPORTED_STORAGE_ID) { + resp_code = MTP_RESP_INVALID_STORAGE_ID; + break; + } + + if (obj_info->parent_object) { + fs_file_t* parent = fs_get_file(obj_info->parent_object); + if (parent == NULL || !parent->association_type) { + resp_code = MTP_RESP_INVALID_PARENT_OBJECT; + break; + } + } + + fs_file_t* f = fs_create_file(); + f->object_format = obj_info->object_format; + f->protection_status = obj_info->protection_status; + f->image_pix_width = obj_info->image_pix_width; + f->image_pix_height = obj_info->image_pix_height; + f->image_bit_depth = obj_info->image_bit_depth; + f->parent = obj_info->parent_object; + f->association_type = obj_info->association_type; + f->size = obj_info->object_compressed_size; + f->data = fs_malloc(f->size); + if (f->data == NULL) { + resp_code = MTP_RESP_STORE_FULL; + break; + } + uint8_t* buf = io_container->payload + sizeof(mtp_object_info_header_t); + mtp_container_get_string(buf,f->name); + // ignore date created/modified/keywords + break; + } + default: resp_code = MTP_RESP_OPERATION_NOT_SUPPORTED; break; @@ -178,26 +270,26 @@ int32_t tud_mtp_data_more_cb(tud_mtp_cb_data_t* cb_data) { // send response if needed if (resp_code != 0) { - reply->header->code = resp_code; - tud_mtp_response_send(reply); + io_container->header->code = resp_code; + tud_mtp_response_send(io_container); } return 0; // 0 mean data/response is sent already } int32_t tud_mtp_command_received_cb(tud_mtp_cb_data_t* cb_data) { - const mtp_container_command_t* command = cb_data->command; - mtp_container_info_t* reply = &cb_data->reply; + const mtp_container_command_t* command = cb_data->command_container; + mtp_container_info_t* io_container = &cb_data->io_container; uint32_t resp_code = 0; switch (command->code) { case MTP_OP_GET_DEVICE_INFO: { // Device info is already prepared up to playback formats. Application only need to add string fields - mtp_container_add_cstring(&cb_data->reply, DEV_INFO_MANUFACTURER); - mtp_container_add_cstring(&cb_data->reply, DEV_INFO_MODEL); - mtp_container_add_cstring(&cb_data->reply, DEV_INFO_VERSION); - mtp_container_add_cstring(&cb_data->reply, DEV_INFO_SERIAL); + mtp_container_add_cstring(io_container, DEV_INFO_MANUFACTURER); + mtp_container_add_cstring(io_container, DEV_INFO_MODEL); + mtp_container_add_cstring(io_container, DEV_INFO_VERSION); + mtp_container_add_cstring(io_container, DEV_INFO_SERIAL); - tud_mtp_data_send(&cb_data->reply); + tud_mtp_data_send(io_container); break; } @@ -220,9 +312,9 @@ int32_t tud_mtp_command_received_cb(tud_mtp_cb_data_t* cb_data) { break; case MTP_OP_GET_STORAGE_IDS: { - uint32_t storage_ids [] = { SUPPORTED_STORAGE_ID }; // physical = 1, logical = 1 - mtp_container_add_auint32(&cb_data->reply, 1, storage_ids); - tud_mtp_data_send(&cb_data->reply); + uint32_t storage_ids [] = { SUPPORTED_STORAGE_ID }; + mtp_container_add_auint32(io_container, 1, storage_ids); + tud_mtp_data_send(io_container); break; } @@ -230,10 +322,10 @@ int32_t tud_mtp_command_received_cb(tud_mtp_cb_data_t* cb_data) { const uint32_t storage_id = command->params[0]; TU_VERIFY(SUPPORTED_STORAGE_ID == storage_id, -1); // update storage info with current free space - storage_info.free_space_in_objects = FS_MAX_FILE_COUNT - fs_get_object_count(); + storage_info.free_space_in_objects = FS_MAX_FILE_COUNT - fs_get_file_count(); storage_info.free_space_in_bytes = FS_MAX_CAPACITY_BYTES-fs_buf_head; - mtp_container_add_raw(&cb_data->reply, &storage_info, sizeof(storage_info)); - tud_mtp_data_send(&cb_data->reply); + mtp_container_add_raw(io_container, &storage_info, sizeof(storage_info)); + tud_mtp_data_send(io_container); break; } @@ -245,11 +337,11 @@ int32_t tud_mtp_command_received_cb(tud_mtp_cb_data_t* cb_data) { case MTP_DEV_PROP_DEVICE_FRIENDLY_NAME: device_prop_header.datatype = MTP_DATA_TYPE_STR; device_prop_header.get_set = MTP_MODE_GET; - mtp_container_add_raw(&cb_data->reply, &device_prop_header, sizeof(device_prop_header)); - mtp_container_add_cstring(&cb_data->reply, DEV_PROP_FRIENDLY_NAME); // factory - mtp_container_add_cstring(&cb_data->reply, DEV_PROP_FRIENDLY_NAME); // current - mtp_container_add_uint8(&cb_data->reply, 0); // no form - tud_mtp_data_send(&cb_data->reply); + mtp_container_add_raw(io_container, &device_prop_header, sizeof(device_prop_header)); + mtp_container_add_cstring(io_container, DEV_PROP_FRIENDLY_NAME); // factory + mtp_container_add_cstring(io_container, DEV_PROP_FRIENDLY_NAME); // current + mtp_container_add_uint8(io_container, 0); // no form + tud_mtp_data_send(io_container); break; default: @@ -263,8 +355,8 @@ int32_t tud_mtp_command_received_cb(tud_mtp_cb_data_t* cb_data) { const uint16_t dev_prop_code = (uint16_t) command->params[0]; switch (dev_prop_code) { case MTP_DEV_PROP_DEVICE_FRIENDLY_NAME: - mtp_container_add_cstring(&cb_data->reply, DEV_PROP_FRIENDLY_NAME); - tud_mtp_data_send(&cb_data->reply); + mtp_container_add_cstring(io_container, DEV_PROP_FRIENDLY_NAME); + tud_mtp_data_send(io_container); break; default: @@ -285,66 +377,82 @@ int32_t tud_mtp_command_received_cb(tud_mtp_cb_data_t* cb_data) { uint32_t handles[FS_MAX_FILE_COUNT] = { 0 }; uint32_t count = 0; for (uint8_t i = 0; i < FS_MAX_FILE_COUNT; i++) { - fs_object_info_t* obj = &fs_objects[i]; - if (obj->name[0] != 0 && - (parent_handle == obj->parent || (parent_handle == 0xFFFFFFFF && obj->parent == 0))) { - handles[count++] = i + 1; // handle is index + 1 + fs_file_t* f = &fs_objects[i]; + if (fs_file_exist(f) && + (parent_handle == f->parent || (parent_handle == 0xFFFFFFFF && f->parent == 0))) { + handles[count++] = i + 1; // handle is index + 1 } } - mtp_container_add_auint32(&cb_data->reply, count, handles); - tud_mtp_data_send(&cb_data->reply); + mtp_container_add_auint32(io_container, count, handles); + tud_mtp_data_send(io_container); } break; } case MTP_OP_GET_OBJECT_INFO: { const uint32_t obj_handle = command->params[0]; - fs_object_info_t* obj = fs_get_object(obj_handle); - if (obj == NULL) { + fs_file_t* f = fs_get_file(obj_handle); + if (f == NULL) { resp_code = MTP_RESP_INVALID_OBJECT_HANDLE; } else { mtp_object_info_header_t obj_info_header = { .storage_id = SUPPORTED_STORAGE_ID, - .object_format = obj->format, + .object_format = f->object_format, .protection_status = MTP_PROTECTION_STATUS_NO_PROTECTION, - .object_compressed_size = obj->size, + .object_compressed_size = f->size, .thumb_format = MTP_OBJ_FORMAT_UNDEFINED, .thumb_compressed_size = 0, .thumb_pix_width = 0, .thumb_pix_height = 0, - .image_pix_width = 128, - .image_pix_height = 64, - .image_bit_depth = 32, - .parent_object = obj->parent, - .association_type = MTP_ASSOCIATION_UNDEFINED, + .image_pix_width = f->image_pix_width, + .image_pix_height = f->image_pix_height, + .image_bit_depth = f->image_bit_depth, + .parent_object = f->parent, + .association_type = f->association_type, .association_desc = 0, .sequence_number = 0 }; - mtp_container_add_raw(&cb_data->reply, &obj_info_header, sizeof(obj_info_header)); - mtp_container_add_cstring(&cb_data->reply, obj->name); - mtp_container_add_cstring(&cb_data->reply, FS_FIXED_DATETIME); - mtp_container_add_cstring(&cb_data->reply, FS_FIXED_DATETIME); - mtp_container_add_cstring(&cb_data->reply, ""); // keywords, not used + mtp_container_add_raw(io_container, &obj_info_header, sizeof(obj_info_header)); + mtp_container_add_string(io_container, f->name); + mtp_container_add_cstring(io_container, FS_FIXED_DATETIME); + mtp_container_add_cstring(io_container, FS_FIXED_DATETIME); + mtp_container_add_cstring(io_container, ""); // keywords, not used - tud_mtp_data_send(&cb_data->reply); + tud_mtp_data_send(io_container); } break; } case MTP_OP_GET_OBJECT: { const uint32_t obj_handle = command->params[0]; - fs_object_info_t* obj = fs_get_object(obj_handle); + fs_file_t* obj = fs_get_file(obj_handle); if (obj == NULL) { resp_code = MTP_RESP_INVALID_OBJECT_HANDLE; } else { // If file contents is larger than CFG_TUD_MTP_EP_BUFSIZE, only partial data is added here // the rest will be sent in tud_mtp_data_more_cb - mtp_container_add_raw(&cb_data->reply, obj->data, obj->size); - tud_mtp_data_send(&cb_data->reply); + mtp_container_add_raw(io_container, obj->data, obj->size); + tud_mtp_data_send(io_container); } break; } + case MTP_OP_SEND_OBJECT_INFO: { + const uint32_t storage_id = command->params[0]; + const uint32_t parent_handle = command->params[1]; // folder handle, 0xFFFFFFFF is root + if (!is_session_opened) { + resp_code = MTP_RESP_SESSION_NOT_OPEN; + } else if (storage_id != 0xFFFFFFFF && storage_id != SUPPORTED_STORAGE_ID) { + resp_code = MTP_RESP_INVALID_STORAGE_ID; + } else { + tud_mtp_data_receive(io_container); + } + break; + } + + case MTP_OP_SEND_OBJECT: + break; + default: resp_code = MTP_RESP_OPERATION_NOT_SUPPORTED; break; @@ -352,8 +460,8 @@ int32_t tud_mtp_command_received_cb(tud_mtp_cb_data_t* cb_data) { // send response if needed if (resp_code != 0) { - reply->header->code = resp_code; - tud_mtp_response_send(reply); + io_container->header->code = resp_code; + tud_mtp_response_send(io_container); } return 0; @@ -382,7 +490,7 @@ mtp_response_t tud_mtp_storage_format(uint32_t storage_id) { mtp_response_t tud_mtp_storage_object_write_info(uint32_t storage_id, uint32_t parent_object, uint32_t* new_object_handle, const mtp_object_info_header_t* info) { - fs_object_info_t* obj = NULL; + fs_file_t* obj = NULL; if (_fs_operation.session_id == 0) { TU_LOG1("ERR: Session not open\r\n"); @@ -405,12 +513,12 @@ mtp_response_t tud_mtp_storage_object_write_info(uint32_t storage_id, uint32_t p // Ensure we are not creating an orphaned object outside root if (parent_object != 0) { - obj = fs_get_object(parent_object); + obj = fs_get_file(parent_object); if (obj == NULL) { TU_LOG1("Parent %ld does not exist\r\n", parent_object); return MTP_RESP_INVALID_PARENT_OBJECT; } - if (!obj->association) { + if (!obj->association_type) { TU_LOG1("Parent %ld is not an association\r\n", parent_object); return MTP_RESP_INVALID_PARENT_OBJECT; } @@ -434,7 +542,7 @@ mtp_response_t tud_mtp_storage_object_write_info(uint32_t storage_id, uint32_t p obj->handle = ++_fs_operation.last_handle; obj->parent = parent_object; obj->size = info->object_compressed_size; - obj->association = info->object_format == MTP_OBJ_FORMAT_ASSOCIATION; + obj->association_type = info->object_format == MTP_OBJ_FORMAT_ASSOCIATION; // Extract variable data uint16_t offset_data = sizeof(mtp_object_info_header_t); @@ -443,7 +551,7 @@ mtp_response_t tud_mtp_storage_object_write_info(uint32_t storage_id, uint32_t p mtpd_gct_get_string(&offset_data, obj->modified, FS_ISODATETIME_LEN); TU_LOG1("Create %s %s with handle %ld, parent %ld and size %ld\r\n", - obj->association ? "association" : "object", + obj->association_type ? "association" : "object", obj->name, obj->handle, obj->parent, obj->size); *new_object_handle = obj->handle; // Initialize operation @@ -453,9 +561,9 @@ mtp_response_t tud_mtp_storage_object_write_info(uint32_t storage_id, uint32_t p } mtp_response_t tud_mtp_storage_object_write(uint32_t object_handle, const uint8_t* buffer, uint32_t size) { - fs_object_info_t* obj; + fs_file_t* obj; - obj = fs_get_object(object_handle); + obj = fs_get_file(object_handle); if (obj == NULL) { TU_LOG1("ERR: Object with handle %ld does not exist\r\n", object_handle); return MTP_RESP_INVALID_OBJECT_HANDLE; @@ -481,25 +589,25 @@ mtp_response_t tud_mtp_storage_object_write(uint32_t object_handle, const uint8_ } mtp_response_t tud_mtp_storage_object_move(uint32_t object_handle, uint32_t new_parent_object_handle) { - fs_object_info_t* obj; + fs_file_t* obj; if (new_parent_object_handle == 0xFFFFFFFF) new_parent_object_handle = 0; // Ensure we are not moving to an nonexisting parent if (new_parent_object_handle != 0) { - obj = fs_get_object(new_parent_object_handle); + obj = fs_get_file(new_parent_object_handle); if (obj == NULL) { TU_LOG1("Parent %ld does not exist\r\n", new_parent_object_handle); return MTP_RESP_INVALID_PARENT_OBJECT; } - if (!obj->association) { + if (!obj->association_type) { TU_LOG1("Parent %ld is not an association\r\n", new_parent_object_handle); return MTP_RESP_INVALID_PARENT_OBJECT; } } - obj = fs_get_object(object_handle); + obj = fs_get_file(object_handle); if (obj == NULL) { TU_LOG1("ERR: Object with handle %ld does not exist\r\n", object_handle); @@ -511,7 +619,7 @@ mtp_response_t tud_mtp_storage_object_move(uint32_t object_handle, uint32_t new_ } mtp_response_t tud_mtp_storage_object_delete(uint32_t object_handle) { - fs_object_info_t* obj; + fs_file_t* obj; if (_fs_operation.session_id == 0) { TU_LOG1("ERR: Session not open\r\n"); @@ -522,7 +630,7 @@ mtp_response_t tud_mtp_storage_object_delete(uint32_t object_handle) { object_handle = 0; if (object_handle != 0) { - obj = fs_get_object(object_handle); + obj = fs_get_file(object_handle); if (obj == NULL) { TU_LOG1("ERR: Object with handle %ld does not exist\r\n", object_handle); @@ -532,7 +640,7 @@ mtp_response_t tud_mtp_storage_object_delete(uint32_t object_handle) { TU_LOG1("Delete object with handle %ld\r\n", object_handle); } - if (object_handle == 0 || obj->association) { + if (object_handle == 0 || obj->association_type) { // Delete also children for (unsigned int i = 0; i < FS_MAX_NODES; i++) { obj = &fs_objects[i]; diff --git a/src/class/mtp/mtp.h b/src/class/mtp/mtp.h index f1b1eaf23..e713d5c23 100644 --- a/src/class/mtp/mtp.h +++ b/src/class/mtp/mtp.h @@ -676,16 +676,12 @@ TU_VERIFY_STATIC(sizeof(mtp_container_command_t) == 32, "size is not correct"); // PTP/MTP Generic container typedef struct TU_ATTR_PACKED { - uint32_t len; - uint16_t type; - uint16_t code; - uint32_t transaction_id; + mtp_container_header_t header; // union { uint32_t data[(CFG_TUD_MTP_EP_BUFSIZE - sizeof(mtp_container_header_t)) / sizeof(uint32_t)]; // uint8_t data[CFG_TUD_MTP_EP_BUFSIZE - sizeof(mtp_container_header_t)]; // }; } mtp_generic_container_t; -TU_VERIFY_STATIC(sizeof(mtp_generic_container_t) == CFG_TUD_MTP_EP_BUFSIZE, "size is not correct"); typedef struct { mtp_container_header_t* header; @@ -801,7 +797,7 @@ TU_ATTR_ALWAYS_INLINE static inline uint8_t* mtp_container_payload_next(mtp_cont // only add_raw does partial copy TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_raw(mtp_container_info_t* p_container, const void* data, uint32_t len) { uint8_t* buf = mtp_container_payload_next(p_container); - const uint32_t added_len = tu_min32(len, sizeof(mtp_generic_container_t) - p_container->header->len); + const uint32_t added_len = tu_min32(len, CFG_TUD_MTP_EP_BUFSIZE - p_container->header->len); if (added_len > 0) { memcpy(buf, data, added_len); } @@ -811,7 +807,7 @@ TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_raw(mtp_container TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_array(mtp_container_info_t* p_container, uint8_t scalar_size, uint32_t count, const void* data) { const uint32_t added_len = 4 + count * scalar_size; - TU_ASSERT(p_container->header->len + added_len < sizeof(mtp_generic_container_t), 0); + TU_ASSERT(p_container->header->len + added_len < CFG_TUD_MTP_EP_BUFSIZE, 0); uint8_t* buf = p_container->payload + p_container->header->len - sizeof(mtp_container_header_t); tu_unaligned_write32(buf, count); @@ -824,9 +820,13 @@ TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_array(mtp_contain return added_len; } -TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_string(mtp_container_info_t* p_container, uint8_t count, uint16_t* utf16) { +TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_string(mtp_container_info_t* p_container, uint16_t* utf16) { + uint8_t count = 0; + while (utf16[count]) { + count++; + } const uint32_t added_len = 1 + 2 * count; - TU_ASSERT(p_container->header->len + added_len < sizeof(mtp_generic_container_t), 0); + TU_ASSERT(p_container->header->len + added_len < CFG_TUD_MTP_EP_BUFSIZE, 0); uint8_t* buf = p_container->payload + p_container->header->len - sizeof(mtp_container_header_t); *buf++ = count; @@ -840,7 +840,7 @@ TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_string(mtp_contai TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_cstring(mtp_container_info_t* p_container, const char* str) { const uint8_t len = (uint8_t) (strlen(str) + 1); // include null - TU_ASSERT(p_container->header->len + 1 + 2 * len < sizeof(mtp_generic_container_t), 0); + TU_ASSERT(p_container->header->len + 1 + 2 * len < CFG_TUD_MTP_EP_BUFSIZE, 0); uint8_t* buf = p_container->payload + p_container->header->len - sizeof(mtp_container_header_t); if (len == 1) { @@ -894,6 +894,15 @@ TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_auint32(mtp_conta return mtp_container_add_array(p_container, sizeof(uint32_t), count, data); } +//--------------------------------------------------------------------+ +// +//--------------------------------------------------------------------+ +TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_get_string(uint8_t* buf, uint16_t utf16[]) { + uint8_t nchars = *buf++; + memcpy(buf, utf16, nchars * 2); + return 1 + nchars * 2; +} + #ifdef __cplusplus } #endif diff --git a/src/class/mtp/mtp_device.c b/src/class/mtp/mtp_device.c index 358114655..db562d0c7 100644 --- a/src/class/mtp/mtp_device.c +++ b/src/class/mtp/mtp_device.c @@ -65,7 +65,7 @@ typedef struct { uint32_t session_id; mtp_container_command_t command; - mtp_container_header_t reply_header; + mtp_container_header_t io_header; } mtpd_interface_t; typedef struct { @@ -75,7 +75,7 @@ typedef struct { //--------------------------------------------------------------------+ // INTERNAL FUNCTION DECLARATION //--------------------------------------------------------------------+ -static int32_t mtpd_handle_cmd(mtpd_interface_t* p_mtp, tud_mtp_cb_data_t* cb_data); +static void process_cmd(mtpd_interface_t* p_mtp, tud_mtp_cb_data_t* cb_data); static mtp_phase_type_t mtpd_handle_data(void); static mtp_phase_type_t mtpd_handle_cmd_delete_object(void); static mtp_phase_type_t mtpd_handle_cmd_send_object_info(void); @@ -253,28 +253,39 @@ bool mtpd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t return true; } -bool tud_mtp_data_send(mtp_container_info_t* p_container) { +static bool mtpd_data_xfer(mtp_container_info_t* p_container, uint8_t ep_addr) { mtpd_interface_t* p_mtp = &_mtpd_itf; if (p_mtp->phase == MTP_PHASE_COMMAND) { // 1st data block: header + payload p_mtp->phase = MTP_PHASE_DATA; - p_mtp->total_len = p_container->header->len; p_mtp->xferred_len = 0; - p_container->header->type = MTP_CONTAINER_TYPE_DATA_BLOCK; - p_container->header->transaction_id = p_mtp->command.transaction_id; - p_mtp->reply_header = *p_container->header; // save header for subsequent data + if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN) { + p_mtp->total_len = p_container->header->len; + p_container->header->type = MTP_CONTAINER_TYPE_DATA_BLOCK; + p_container->header->transaction_id = p_mtp->command.transaction_id; + p_mtp->io_header = *p_container->header; // save header for subsequent data + } else { + p_mtp->total_len = CFG_TUD_MTP_EP_BUFSIZE; + } } else { // subsequent data block: payload only TU_ASSERT(p_mtp->phase == MTP_PHASE_DATA); } const uint16_t xact_len = tu_min32(p_mtp->total_len - p_mtp->xferred_len, CFG_TUD_MTP_EP_BUFSIZE); - TU_ASSERT(usbd_edpt_xfer(p_mtp->rhport, p_mtp->ep_in, _mtpd_epbuf.buf, xact_len)); - + TU_ASSERT(usbd_edpt_xfer(p_mtp->rhport, ep_addr, _mtpd_epbuf.buf, xact_len)); return true; } +bool tud_mtp_data_send(mtp_container_info_t* p_container) { + return mtpd_data_xfer(p_container, _mtpd_itf.ep_in); +} + +bool tud_mtp_data_receive(mtp_container_info_t* p_container) { + return mtpd_data_xfer(p_container, _mtpd_itf.ep_out); +} + bool tud_mtp_response_send(mtp_container_info_t* p_container) { mtpd_interface_t* p_mtp = &_mtpd_itf; p_mtp->phase = MTP_PHASE_RESPONSE_QUEUED; @@ -301,23 +312,25 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t tud_mtp_cb_data_t cb_data; cb_data.idx = 0; - cb_data.command = &p_mtp->command; - cb_data.reply.header = (mtp_container_header_t*) p_container; - cb_data.reply.payload32 = p_container->data; - cb_data.reply.payload_size = CFG_TUD_MTP_EP_BUFSIZE - sizeof(mtp_container_header_t); + cb_data.command_container = &p_mtp->command; + cb_data.io_container.header = &p_container->header; + cb_data.io_container.payload32 = p_container->data; + cb_data.io_container.payload_size = CFG_TUD_MTP_EP_BUFSIZE - sizeof(mtp_container_header_t); cb_data.xferred_bytes = 0; cb_data.xfer_result = event; switch (p_mtp->phase) { case MTP_PHASE_IDLE: // received new command - TU_VERIFY(ep_addr == p_mtp->ep_out && p_container->type == MTP_CONTAINER_TYPE_COMMAND_BLOCK); + TU_VERIFY(ep_addr == p_mtp->ep_out && p_container->header.type == MTP_CONTAINER_TYPE_COMMAND_BLOCK); p_mtp->phase = MTP_PHASE_COMMAND; TU_ATTR_FALLTHROUGH; // handle in the next case case MTP_PHASE_COMMAND: { memcpy(&p_mtp->command, p_container, sizeof(mtp_container_command_t)); // save new command - if (mtpd_handle_cmd(p_mtp, &cb_data) < 0) { + p_container->header.len = sizeof(mtp_container_header_t); // default container to header only + process_cmd(p_mtp, &cb_data); + if (tud_mtp_command_received_cb(&cb_data) < 0) { p_mtp->phase = MTP_PHASE_ERROR; } break; @@ -328,18 +341,44 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t p_mtp->xferred_len += xferred_bytes; cb_data.xferred_bytes = p_mtp->xferred_len; - // transfer complete if ZLP or short packet or overflow + bool is_complete = false; + // complete if ZLP or short packet or overflow if (xferred_bytes == 0 || // ZLP (xferred_bytes & (bulk_mps - 1)) || // short packet p_mtp->xferred_len > p_mtp->total_len) { - cb_data.reply.header->len = sizeof(mtp_container_header_t); - tud_mtp_data_complete_cb(&cb_data); + is_complete = true; + } + + const mtp_container_info_t headerless_packet = { + .header = &p_mtp->io_header, + .payload = _mtpd_epbuf.buf, + .payload_size = CFG_TUD_MTP_EP_BUFSIZE + }; + + if (ep_addr == p_mtp->ep_in) { + // Data In + if (is_complete) { + cb_data.io_container.header->len = sizeof(mtp_container_header_t); + tud_mtp_data_complete_cb(&cb_data); + } else { + // 2nd+ packet: payload only + cb_data.io_container = headerless_packet; + tud_mtp_data_xfer_cb(&cb_data); + } } else { - // payload only packet - cb_data.reply.header = &p_mtp->reply_header; - cb_data.reply.payload = (uint8_t*) p_container; - cb_data.reply.payload_size = CFG_TUD_MTP_EP_BUFSIZE; - tud_mtp_data_more_cb(&cb_data); + // Data Out + if (p_mtp->xferred_len == xferred_bytes) { + // 1st OUT packet: header + payload + p_mtp->io_header = p_container->header; // save header for subsequent transaction + } else { + // 2nd+ packet: payload only + cb_data.io_container = headerless_packet; + } + tud_mtp_data_xfer_cb(&cb_data); + if (is_complete) { + cb_data.io_container.header->len = sizeof(mtp_container_header_t); + tud_mtp_data_complete_cb(&cb_data); + } } break; } @@ -445,11 +484,8 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t // MTPD Internal functionality //--------------------------------------------------------------------+ -// Decode command and prepare response -int32_t mtpd_handle_cmd(mtpd_interface_t* p_mtp, tud_mtp_cb_data_t* cb_data) { - cb_data->reply.header->len = sizeof(mtp_container_header_t); - - // pre-processed commands +// pre-processed commands +void process_cmd(mtpd_interface_t* p_mtp, tud_mtp_cb_data_t* cb_data) { switch (p_mtp->command.code) { case MTP_OP_GET_DEVICE_INFO: { tud_mtp_device_info_t dev_info = { @@ -491,15 +527,13 @@ int32_t mtpd_handle_cmd(mtpd_interface_t* p_mtp, tud_mtp_cb_data_t* cb_data) { dev_info.mtp_extensions.utf16[i] = (uint16_t)CFG_TUD_MTP_DEVICEINFO_EXTENSIONS[i]; } #endif - mtp_container_add_raw(&cb_data->reply, &dev_info, sizeof(tud_mtp_device_info_t)); + mtp_container_add_raw(&cb_data->io_container, &dev_info, sizeof(tud_mtp_device_info_t)); break; } default: break; } - - return tud_mtp_command_received_cb(cb_data); } #if 0 diff --git a/src/class/mtp/mtp_device.h b/src/class/mtp/mtp_device.h index 28c01ddc6..7b6ae9e0f 100644 --- a/src/class/mtp/mtp_device.h +++ b/src/class/mtp/mtp_device.h @@ -38,11 +38,11 @@ typedef struct { uint8_t idx; // mtp instance - const mtp_container_command_t* command; - mtp_container_info_t reply; + const mtp_container_command_t* command_container; + mtp_container_info_t io_container; tusb_xfer_result_t xfer_result; - uint32_t xferred_bytes; + uint32_t xferred_bytes; // number of bytes transferred so far in this phase } tud_mtp_cb_data_t; // Number of supported operations, events, device properties, capture formats, playback formats @@ -94,8 +94,14 @@ typedef struct { //--------------------------------------------------------------------+ // Application API //--------------------------------------------------------------------+ + +// send data phase bool tud_mtp_data_send(mtp_container_info_t* p_container); -// bool tud_mtp_block_data_receive(); + +// receive data phase +bool tud_mtp_data_receive(mtp_container_info_t* p_container); + +// send response bool tud_mtp_response_send(mtp_container_info_t* p_container); //--------------------------------------------------------------------+ @@ -112,10 +118,10 @@ bool tud_mtp_response_send(mtp_container_info_t* p_container); */ int32_t tud_mtp_command_received_cb(tud_mtp_cb_data_t * cb_data); -// Invoked when a data packet is received/sent, and more data is expected -int32_t tud_mtp_data_more_cb(tud_mtp_cb_data_t* cb_data); +// Invoked when a data packet is transferred, and more data is expected +int32_t tud_mtp_data_xfer_cb(tud_mtp_cb_data_t* cb_data); -// Invoked when data phase is complete +// Invoked when all bytes in DATA phase is complete. A response packet is expected int32_t tud_mtp_data_complete_cb(tud_mtp_cb_data_t* cb_data); // Invoked when response phase is complete From c9a8330081b59c05b901889d405ed7b9648c0947 Mon Sep 17 00:00:00 2001 From: hathach Date: Thu, 25 Sep 2025 17:43:25 +0700 Subject: [PATCH 21/43] implement send object command, able to create new file --- examples/device/mtp/src/mtp_fs_example.c | 336 +++++++++-------------- src/class/mtp/mtp.h | 6 +- src/class/mtp/mtp_device.c | 183 ++---------- src/class/mtp/mtp_device.h | 2 +- 4 files changed, 160 insertions(+), 367 deletions(-) diff --git a/examples/device/mtp/src/mtp_fs_example.c b/examples/device/mtp/src/mtp_fs_example.c index d6d7f1907..468fc9146 100644 --- a/examples/device/mtp/src/mtp_fs_example.c +++ b/examples/device/mtp/src/mtp_fs_example.c @@ -176,107 +176,6 @@ static inline uint8_t* fs_malloc(size_t size) { //--------------------------------------------------------------------+ // //--------------------------------------------------------------------+ -int32_t tud_mtp_data_complete_cb(tud_mtp_cb_data_t* cb_data) { - const mtp_container_command_t* command = cb_data->command_container; - mtp_container_info_t* resp = &cb_data->io_container; - switch (command->code) { - case MTP_OP_SEND_OBJECT_INFO: { - fs_file_t* f = fs_get_file(send_obj_handle); - if (f == NULL) { - resp->header->code = MTP_RESP_GENERAL_ERROR; - break; - } - // parameter is: storage id, parent handle, new handle - mtp_container_add_uint32(resp, SUPPORTED_STORAGE_ID); - mtp_container_add_uint32(resp, f->parent); - mtp_container_add_uint32(resp, send_obj_handle); - resp->header->code = MTP_RESP_OK; - break; - } - - default: - resp->header->code = (cb_data->xfer_result == XFER_RESULT_SUCCESS) ? MTP_RESP_OK : MTP_RESP_GENERAL_ERROR; - break; - } - tud_mtp_response_send(resp); - return 0; -} - -int32_t tud_mtp_response_complete_cb(tud_mtp_cb_data_t* cb_data) { - (void) cb_data; - return 0; // nothing to do -} - -int32_t tud_mtp_data_xfer_cb(tud_mtp_cb_data_t* cb_data) { - const mtp_container_command_t* command = cb_data->command_container; - mtp_container_info_t* io_container = &cb_data->io_container; - uint32_t resp_code = 0; - switch (command->code) { - case MTP_OP_GET_OBJECT: { - // File contents span over multiple xfers - const uint32_t obj_handle = command->params[0]; - fs_file_t* f = fs_get_file(obj_handle); - if (f == NULL) { - resp_code = MTP_RESP_INVALID_OBJECT_HANDLE; - } else { - // file contents offset is xferred byte minus header size - const uint32_t offset = cb_data->xferred_bytes - sizeof(mtp_container_header_t); - const uint32_t xact_len = tu_min32(f->size - offset, io_container->payload_size); - memcpy(io_container->payload, f->data + offset, xact_len); - tud_mtp_data_send(&cb_data->io_container); - } - break; - } - - case MTP_OP_SEND_OBJECT_INFO: { - mtp_object_info_header_t* obj_info = (mtp_object_info_header_t*) io_container->payload; - if (obj_info->storage_id != 0 && obj_info->storage_id != SUPPORTED_STORAGE_ID) { - resp_code = MTP_RESP_INVALID_STORAGE_ID; - break; - } - - if (obj_info->parent_object) { - fs_file_t* parent = fs_get_file(obj_info->parent_object); - if (parent == NULL || !parent->association_type) { - resp_code = MTP_RESP_INVALID_PARENT_OBJECT; - break; - } - } - - fs_file_t* f = fs_create_file(); - f->object_format = obj_info->object_format; - f->protection_status = obj_info->protection_status; - f->image_pix_width = obj_info->image_pix_width; - f->image_pix_height = obj_info->image_pix_height; - f->image_bit_depth = obj_info->image_bit_depth; - f->parent = obj_info->parent_object; - f->association_type = obj_info->association_type; - f->size = obj_info->object_compressed_size; - f->data = fs_malloc(f->size); - if (f->data == NULL) { - resp_code = MTP_RESP_STORE_FULL; - break; - } - uint8_t* buf = io_container->payload + sizeof(mtp_object_info_header_t); - mtp_container_get_string(buf,f->name); - // ignore date created/modified/keywords - break; - } - - default: - resp_code = MTP_RESP_OPERATION_NOT_SUPPORTED; - break; - } - - // send response if needed - if (resp_code != 0) { - io_container->header->code = resp_code; - tud_mtp_response_send(io_container); - } - - return 0; // 0 mean data/response is sent already -} - int32_t tud_mtp_command_received_cb(tud_mtp_cb_data_t* cb_data) { const mtp_container_command_t* command = cb_data->command_container; mtp_container_info_t* io_container = &cb_data->io_container; @@ -425,13 +324,13 @@ int32_t tud_mtp_command_received_cb(tud_mtp_cb_data_t* cb_data) { case MTP_OP_GET_OBJECT: { const uint32_t obj_handle = command->params[0]; - fs_file_t* obj = fs_get_file(obj_handle); - if (obj == NULL) { + fs_file_t* f = fs_get_file(obj_handle); + if (f == NULL) { resp_code = MTP_RESP_INVALID_OBJECT_HANDLE; } else { // If file contents is larger than CFG_TUD_MTP_EP_BUFSIZE, only partial data is added here // the rest will be sent in tud_mtp_data_more_cb - mtp_container_add_raw(io_container, obj->data, obj->size); + mtp_container_add_raw(io_container, f->data, f->size); tud_mtp_data_send(io_container); } break; @@ -450,8 +349,16 @@ int32_t tud_mtp_command_received_cb(tud_mtp_cb_data_t* cb_data) { break; } - case MTP_OP_SEND_OBJECT: + case MTP_OP_SEND_OBJECT: { + fs_file_t* f = fs_get_file(send_obj_handle); + if (f == NULL) { + resp_code = MTP_RESP_INVALID_OBJECT_HANDLE; + } else { + io_container->header->len += f->size; + tud_mtp_data_receive(io_container); + } break; + } default: resp_code = MTP_RESP_OPERATION_NOT_SUPPORTED; @@ -467,6 +374,125 @@ int32_t tud_mtp_command_received_cb(tud_mtp_cb_data_t* cb_data) { return 0; } +int32_t tud_mtp_data_xfer_cb(tud_mtp_cb_data_t* cb_data) { + const mtp_container_command_t* command = cb_data->command_container; + mtp_container_info_t* io_container = &cb_data->io_container; + uint32_t resp_code = 0; + switch (command->code) { + case MTP_OP_GET_OBJECT: { + // File contents span over multiple xfers + const uint32_t obj_handle = command->params[0]; + fs_file_t* f = fs_get_file(obj_handle); + if (f == NULL) { + resp_code = MTP_RESP_INVALID_OBJECT_HANDLE; + } else { + // file contents offset is xferred byte minus header size + const uint32_t offset = cb_data->total_xferred_bytes - sizeof(mtp_container_header_t); + const uint32_t xact_len = tu_min32(f->size - offset, io_container->payload_bytes); + memcpy(io_container->payload, f->data + offset, xact_len); + tud_mtp_data_send(io_container); + } + break; + } + + case MTP_OP_SEND_OBJECT_INFO: { + mtp_object_info_header_t* obj_info = (mtp_object_info_header_t*) io_container->payload; + if (obj_info->storage_id != 0 && obj_info->storage_id != SUPPORTED_STORAGE_ID) { + resp_code = MTP_RESP_INVALID_STORAGE_ID; + break; + } + + if (obj_info->parent_object) { + fs_file_t* parent = fs_get_file(obj_info->parent_object); + if (parent == NULL || !parent->association_type) { + resp_code = MTP_RESP_INVALID_PARENT_OBJECT; + break; + } + } + + fs_file_t* f = fs_create_file(); + f->object_format = obj_info->object_format; + f->protection_status = obj_info->protection_status; + f->image_pix_width = obj_info->image_pix_width; + f->image_pix_height = obj_info->image_pix_height; + f->image_bit_depth = obj_info->image_bit_depth; + f->parent = obj_info->parent_object; + f->association_type = obj_info->association_type; + f->size = obj_info->object_compressed_size; + f->data = fs_malloc(f->size); + if (f->data == NULL) { + resp_code = MTP_RESP_STORE_FULL; + break; + } + uint8_t* buf = io_container->payload + sizeof(mtp_object_info_header_t); + mtp_container_get_string(buf, f->name); + // ignore date created/modified/keywords + break; + } + + case MTP_OP_SEND_OBJECT: { + fs_file_t* f = fs_get_file(send_obj_handle); + if (f == NULL) { + resp_code = MTP_RESP_INVALID_OBJECT_HANDLE; + } else { + // file contents offset is total xferred minus header size minus last received chunk + const uint32_t offset = cb_data->total_xferred_bytes - sizeof(mtp_container_header_t) - io_container->payload_bytes; + memcpy(f->data + offset, io_container->payload, io_container->payload_bytes); + tud_mtp_data_receive(io_container); // receive more data if needed + } + break; + } + + default: + resp_code = MTP_RESP_OPERATION_NOT_SUPPORTED; + break; + } + + // send response if needed + if (resp_code != 0) { + io_container->header->code = resp_code; + tud_mtp_response_send(io_container); + } + + return 0; // 0 mean data/response is sent already +} + +int32_t tud_mtp_data_complete_cb(tud_mtp_cb_data_t* cb_data) { + const mtp_container_command_t* command = cb_data->command_container; + mtp_container_info_t* resp = &cb_data->io_container; + switch (command->code) { + case MTP_OP_SEND_OBJECT_INFO: { + fs_file_t* f = fs_get_file(send_obj_handle); + if (f == NULL) { + resp->header->code = MTP_RESP_GENERAL_ERROR; + break; + } + // parameter is: storage id, parent handle, new handle + mtp_container_add_uint32(resp, SUPPORTED_STORAGE_ID); + mtp_container_add_uint32(resp, f->parent); + mtp_container_add_uint32(resp, send_obj_handle); + resp->header->code = MTP_RESP_OK; + break; + } + + case MTP_OP_SEND_OBJECT: + resp->header->code = (cb_data->xfer_result == XFER_RESULT_SUCCESS) ? MTP_RESP_OK : MTP_RESP_GENERAL_ERROR; + break; + + default: + resp->header->code = (cb_data->xfer_result == XFER_RESULT_SUCCESS) ? MTP_RESP_OK : MTP_RESP_GENERAL_ERROR; + break; + } + + tud_mtp_response_send(resp); + return 0; +} + +int32_t tud_mtp_response_complete_cb(tud_mtp_cb_data_t* cb_data) { + (void) cb_data; + return 0; // nothing to do +} + //--------------------------------------------------------------------+ // API //--------------------------------------------------------------------+ @@ -488,106 +514,6 @@ mtp_response_t tud_mtp_storage_format(uint32_t storage_id) { return MTP_RESP_OK; } -mtp_response_t tud_mtp_storage_object_write_info(uint32_t storage_id, uint32_t parent_object, - uint32_t* new_object_handle, const mtp_object_info_header_t* info) { - fs_file_t* obj = NULL; - - if (_fs_operation.session_id == 0) { - TU_LOG1("ERR: Session not open\r\n"); - return MTP_RESP_SESSION_NOT_OPEN; - } - // Accept command on default storage - if (storage_id != 0xFFFFFFFF && storage_id != SUPPORTED_STORAGE_ID) { - TU_LOG1("ERR: Unexpected storage id %ld\r\n", storage_id); - return MTP_RESP_INVALID_STORAGE_ID; - } - - if (info->object_compressed_size > FS_MAX_NODE_BYTES) { - TU_LOG1("Object size %ld is more than maximum %ld\r\n", info->object_compressed_size, FS_MAX_NODE_BYTES); - return MTP_RESP_STORE_FULL; - } - - // Request for objects with no parent (0xFFFFFFFF) are considered root objects - if (parent_object == 0xFFFFFFFF) - parent_object = 0; - - // Ensure we are not creating an orphaned object outside root - if (parent_object != 0) { - obj = fs_get_file(parent_object); - if (obj == NULL) { - TU_LOG1("Parent %ld does not exist\r\n", parent_object); - return MTP_RESP_INVALID_PARENT_OBJECT; - } - if (!obj->association_type) { - TU_LOG1("Parent %ld is not an association\r\n", parent_object); - return MTP_RESP_INVALID_PARENT_OBJECT; - } - } - - // Search for first free object - for (unsigned int i = 0; i < FS_MAX_NODES; i++) { - if (!fs_objects[i].allocated) { - obj = &fs_objects[i]; - break; - } - } - - if (obj == NULL) { - TU_LOG1("No space left on device\r\n"); - return MTP_RESP_STORE_FULL; - } - - // Fill-in structure - obj->allocated = true; - obj->handle = ++_fs_operation.last_handle; - obj->parent = parent_object; - obj->size = info->object_compressed_size; - obj->association_type = info->object_format == MTP_OBJ_FORMAT_ASSOCIATION; - - // Extract variable data - uint16_t offset_data = sizeof(mtp_object_info_header_t); - mtpd_gct_get_string(&offset_data, obj->name, FS_MAX_NODE_NAME_LEN); - mtpd_gct_get_string(&offset_data, obj->created, FS_ISODATETIME_LEN); - mtpd_gct_get_string(&offset_data, obj->modified, FS_ISODATETIME_LEN); - - TU_LOG1("Create %s %s with handle %ld, parent %ld and size %ld\r\n", - obj->association_type ? "association" : "object", - obj->name, obj->handle, obj->parent, obj->size); - *new_object_handle = obj->handle; - // Initialize operation - _fs_operation.write_handle = obj->handle; - _fs_operation.write_pos = 0; - return MTP_RESP_OK; -} - -mtp_response_t tud_mtp_storage_object_write(uint32_t object_handle, const uint8_t* buffer, uint32_t size) { - fs_file_t* obj; - - obj = fs_get_file(object_handle); - if (obj == NULL) { - TU_LOG1("ERR: Object with handle %ld does not exist\r\n", object_handle); - return MTP_RESP_INVALID_OBJECT_HANDLE; - } - // It's a requirement that this command is preceded by a write info - if (object_handle != _fs_operation.write_handle) { - TU_LOG1("ERR: Object %ld not open for write\r\n", object_handle); - return MTP_RESP_NO_VALID_OBJECTINFO; - } - - TU_LOG1("Write object %ld: data chunk at %ld/%ld bytes at offset %ld\r\n", object_handle, _fs_operation.write_pos, - obj->size, size); - TU_ASSERT(obj->size >= _fs_operation.write_pos + size, MTP_RESP_INCOMPLETE_TRANSFER); - if (_fs_operation.write_pos + size < FS_MAX_NODE_BYTES) - memcpy(&obj->data[_fs_operation.write_pos], buffer, size); - _fs_operation.write_pos += size; - // Write operation completed - if (_fs_operation.write_pos == obj->size) { - _fs_operation.write_handle = 0; - _fs_operation.write_pos = 0; - } - return MTP_RESP_OK; -} - mtp_response_t tud_mtp_storage_object_move(uint32_t object_handle, uint32_t new_parent_object_handle) { fs_file_t* obj; diff --git a/src/class/mtp/mtp.h b/src/class/mtp/mtp.h index e713d5c23..15cf1bfc6 100644 --- a/src/class/mtp/mtp.h +++ b/src/class/mtp/mtp.h @@ -59,8 +59,6 @@ typedef enum { MTP_PHASE_IDLE = 0, MTP_PHASE_COMMAND, MTP_PHASE_DATA, - MTP_PHASE_DATA_IN, - MTP_PHASE_DATA_OUT, MTP_PHASE_RESPONSE, MTP_PHASE_RESPONSE_QUEUED, MTP_PHASE_ERROR, @@ -690,7 +688,7 @@ typedef struct { uint16_t* payload16; uint32_t* payload32; }; - uint32_t payload_size; + uint32_t payload_bytes; // available bytes for read/write } mtp_container_info_t; #define mtp_string_t(_nchars) \ @@ -899,7 +897,7 @@ TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_auint32(mtp_conta //--------------------------------------------------------------------+ TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_get_string(uint8_t* buf, uint16_t utf16[]) { uint8_t nchars = *buf++; - memcpy(buf, utf16, nchars * 2); + memcpy(utf16, buf, 2 * nchars); return 1 + nchars * 2; } diff --git a/src/class/mtp/mtp_device.c b/src/class/mtp/mtp_device.c index db562d0c7..6d1c7b29b 100644 --- a/src/class/mtp/mtp_device.c +++ b/src/class/mtp/mtp_device.c @@ -266,7 +266,8 @@ static bool mtpd_data_xfer(mtp_container_info_t* p_container, uint8_t ep_addr) { p_container->header->transaction_id = p_mtp->command.transaction_id; p_mtp->io_header = *p_container->header; // save header for subsequent data } else { - p_mtp->total_len = CFG_TUD_MTP_EP_BUFSIZE; + // OUT transfer: total length is at least max packet size + p_mtp->total_len = tu_max32(p_container->header->len, CFG_TUD_MTP_EP_BUFSIZE); } } else { // subsequent data block: payload only @@ -274,7 +275,10 @@ static bool mtpd_data_xfer(mtp_container_info_t* p_container, uint8_t ep_addr) { } const uint16_t xact_len = tu_min32(p_mtp->total_len - p_mtp->xferred_len, CFG_TUD_MTP_EP_BUFSIZE); - TU_ASSERT(usbd_edpt_xfer(p_mtp->rhport, ep_addr, _mtpd_epbuf.buf, xact_len)); + if (xact_len) { + // already transferred all bytes in header's length. Application make an unnecessary extra call + TU_ASSERT(usbd_edpt_xfer(p_mtp->rhport, ep_addr, _mtpd_epbuf.buf, xact_len)); + } return true; } @@ -310,13 +314,23 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t TU_LOG_DRV(" MTP %s phase = %u\r\n", (const char *) tu_lookup_find(&_mtp_op_table, p_mtp->command.code), p_mtp->phase); #endif + const mtp_container_info_t headered_packet = { + .header = &p_container->header, + .payload32 = p_container->data, + .payload_bytes = CFG_TUD_MTP_EP_BUFSIZE - sizeof(mtp_container_header_t) + }; + + const mtp_container_info_t headerless_packet = { + .header = &p_mtp->io_header, + .payload = _mtpd_epbuf.buf, + .payload_bytes = CFG_TUD_MTP_EP_BUFSIZE + }; + tud_mtp_cb_data_t cb_data; cb_data.idx = 0; cb_data.command_container = &p_mtp->command; - cb_data.io_container.header = &p_container->header; - cb_data.io_container.payload32 = p_container->data; - cb_data.io_container.payload_size = CFG_TUD_MTP_EP_BUFSIZE - sizeof(mtp_container_header_t); - cb_data.xferred_bytes = 0; + cb_data.io_container = headered_packet; + cb_data.total_xferred_bytes = 0; cb_data.xfer_result = event; switch (p_mtp->phase) { @@ -339,7 +353,7 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t case MTP_PHASE_DATA: { const uint16_t bulk_mps = (tud_speed_get() == TUSB_SPEED_HIGH) ? 512 : 64; p_mtp->xferred_len += xferred_bytes; - cb_data.xferred_bytes = p_mtp->xferred_len; + cb_data.total_xferred_bytes = p_mtp->xferred_len; bool is_complete = false; // complete if ZLP or short packet or overflow @@ -349,12 +363,6 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t is_complete = true; } - const mtp_container_info_t headerless_packet = { - .header = &p_mtp->io_header, - .payload = _mtpd_epbuf.buf, - .payload_size = CFG_TUD_MTP_EP_BUFSIZE - }; - if (ep_addr == p_mtp->ep_in) { // Data In if (is_complete) { @@ -370,12 +378,17 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t if (p_mtp->xferred_len == xferred_bytes) { // 1st OUT packet: header + payload p_mtp->io_header = p_container->header; // save header for subsequent transaction + cb_data.io_container.payload_bytes = xferred_bytes - sizeof(mtp_container_header_t); } else { // 2nd+ packet: payload only cb_data.io_container = headerless_packet; + cb_data.io_container.payload_bytes = xferred_bytes; } tud_mtp_data_xfer_cb(&cb_data); + 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); tud_mtp_data_complete_cb(&cb_data); } @@ -383,80 +396,6 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t break; } -#if 0 - case MTP_PHASE_DATA_IN: - p_mtp->xferred_len += xferred_bytes; - p_mtp->handled_len = p_mtp->xferred_len; - - // Check if transfer completed TODO check ZLP with FS/HS bulk size - if (p_mtp->xferred_len >= p_mtp->total_len && (xferred_bytes == 0 || (xferred_bytes % CFG_MTP_EP_SIZE) != 0)) { - p_mtp->phase = MTP_PHASE_RESPONSE; - p_container->code = MTP_RESP_OK; - p_container->len = MTP_CONTAINER_HEADER_LENGTH; - if (p_mtp->session_id != 0) { // is this needed ? - p_container->data[0] = p_mtp->session_id; - p_container->len += sizeof(uint32_t); - } - } else { - // Send next block of DATA - if (p_mtp->xferred_len == p_mtp->total_len) { - // send Zero-Length Packet - TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_in, NULL, 0 )); - } else { - p_mtp->phase = mtpd_handle_data(); - if (p_mtp->phase == MTP_PHASE_DATA_IN) { - TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_in, ((uint8_t *)(&p_container->data)), (uint16_t)p_mtp->queued_len)); - } - } - } - break; - - case MTP_PHASE_DATA_OUT: - // First block of data - if (p_mtp->xferred_len == 0) { - p_mtp->total_len = p_container->len; - p_mtp->handled_len = 0; - p_mtp->xfer_completed = false; - TU_ASSERT(p_container->type == MTP_CONTAINER_TYPE_DATA_BLOCK); - } - p_mtp->xferred_len += xferred_bytes; - - // A zero-length or a short packet termination - if (xferred_bytes < CFG_MTP_EP_SIZE) { - p_mtp->xfer_completed = true; - // Handle data block - p_mtp->phase = mtpd_handle_data(); - if (p_mtp->phase == MTP_PHASE_DATA_OUT) { - TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_out, (uint8_t*) p_container, sizeof(mtp_generic_container_t)), 0); - p_mtp->xferred_len = 0; - p_mtp->xfer_completed = false; - } - } else { - // Handle data block when container is full - if (p_mtp->xferred_len - p_mtp->handled_len >= MTP_MAX_PACKET_SIZE - CFG_MTP_EP_SIZE) { - p_mtp->phase = mtpd_handle_data(); - p_mtp->handled_len = p_mtp->xferred_len; - } - // Transfer completed: wait for zero-length packet - // Some platforms may not respect EP size and xferred_bytes may be more than CFG_MTP_EP_SIZE if - // the OUT EP is waiting for more data. Ensure we are not waiting for more than CFG_MTP_EP_SIZE. - if (p_mtp->total_len == p_mtp->xferred_len) { - TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_out, ((uint8_t *)(&p_container->data)), CFG_MTP_EP_SIZE), 0); - } else if (p_mtp->handled_len == 0) { - // First data block includes container header + container data - TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_out, - (uint8_t*) p_container + p_mtp->xferred_len, - (uint16_t)TU_MIN(p_mtp->total_len - p_mtp->xferred_len, CFG_MTP_EP_SIZE))); - } else { - // Successive data block includes only container data - TU_ASSERT(usbd_edpt_xfer(rhport, p_mtp->ep_out, - ((uint8_t *)(&p_container->data)) + p_mtp->xferred_len - p_mtp->handled_len, - (uint16_t)TU_MIN(p_mtp->total_len - p_mtp->xferred_len, CFG_MTP_EP_SIZE))); - } - } - break; -#endif - case MTP_PHASE_RESPONSE_QUEUED: // response phase is complete -> prepare for new command TU_ASSERT(ep_addr == p_mtp->ep_in); @@ -574,76 +513,6 @@ mtp_phase_type_t mtpd_handle_cmd_delete_object(void) return MTP_PHASE_RESPONSE; } -mtp_phase_type_t mtpd_handle_cmd_send_object_info(void) -{ - mtp_generic_container_t* p_container = &_mtpd_epbuf.buf; - _mtpd_soi.storage_id = p_container->data[0]; - _mtpd_soi.parent_object_handle = (p_container->data[1] == 0xFFFFFFFF ? 0 : p_container->data[1]); - - // Enter OUT phase and wait for DATA BLOCK - return MTP_PHASE_DATA_OUT; -} - -mtp_phase_type_t mtpd_handle_dto_send_object_info(void) -{ - mtp_generic_container_t* p_container = &_mtpd_epbuf.buf; - uint32_t new_object_handle = 0; - mtp_response_t res = tud_mtp_storage_object_write_info(_mtpd_soi.storage_id, _mtpd_soi.parent_object_handle, &new_object_handle, (mtp_object_info_header_t *)p_container->data); - mtp_phase_type_t phase; - if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESP_OK), res, "")) != MTP_PHASE_NONE) return phase; - - // Save send_object_info - _mtpd_soi.object_handle = new_object_handle; - - // Response - p_container->len = MTP_CONTAINER_HEADER_LENGTH + 3 * sizeof(uint32_t); - p_container->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; - p_container->code = MTP_RESP_OK; - p_container->data[0] = _mtpd_soi.storage_id; - p_container->data[1] = _mtpd_soi.parent_object_handle; - p_container->data[2] = _mtpd_soi.object_handle; - return MTP_PHASE_RESPONSE; -} - -mtp_phase_type_t mtpd_handle_cmd_send_object(void) -{ - // Enter OUT phase and wait for DATA BLOCK - return MTP_PHASE_DATA_OUT; -} - -mtp_phase_type_t mtpd_handle_dto_send_object(void) -{ - mtp_generic_container_t* p_container = &_mtpd_epbuf.buf; - uint8_t *buffer = (uint8_t *)&p_container->data; - uint32_t buffer_size = _mtpd_itf.xferred_len - _mtpd_itf.handled_len; - // First block of DATA - if (_mtpd_itf.handled_len == 0) - { - buffer_size -= MTP_CONTAINER_HEADER_LENGTH; - } - - if (buffer_size > 0) - { - mtp_response_t res = tud_mtp_storage_object_write(_mtpd_soi.object_handle, buffer, buffer_size); - mtp_phase_type_t phase; - if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESP_OK), res, "")) != MTP_PHASE_NONE) return phase; - } - - if (!_mtpd_itf.xfer_completed) - { - // Continue with next DATA BLOCK - return MTP_PHASE_DATA_OUT; - } - - // Send completed - tud_mtp_storage_object_done(); - - p_container->len = MTP_CONTAINER_HEADER_LENGTH; - p_container->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; - p_container->code = MTP_RESP_OK; - return MTP_PHASE_RESPONSE; -} - mtp_phase_type_t mtpd_handle_cmd_format_store(void) { mtp_generic_container_t* p_container = &_mtpd_epbuf.buf; diff --git a/src/class/mtp/mtp_device.h b/src/class/mtp/mtp_device.h index 7b6ae9e0f..d0c894742 100644 --- a/src/class/mtp/mtp_device.h +++ b/src/class/mtp/mtp_device.h @@ -42,7 +42,7 @@ typedef struct { mtp_container_info_t io_container; tusb_xfer_result_t xfer_result; - uint32_t xferred_bytes; // number of bytes transferred so far in this phase + uint32_t total_xferred_bytes; // number of bytes transferred so far in this phase } tud_mtp_cb_data_t; // Number of supported operations, events, device properties, capture formats, playback formats From 879f02f69c700d18ce89ec15eee7ab3ee83be92c Mon Sep 17 00:00:00 2001 From: hathach Date: Thu, 25 Sep 2025 18:47:43 +0700 Subject: [PATCH 22/43] implement delete object --- examples/device/mtp/src/mtp_fs_example.c | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/examples/device/mtp/src/mtp_fs_example.c b/examples/device/mtp/src/mtp_fs_example.c index 468fc9146..4ccc122c7 100644 --- a/examples/device/mtp/src/mtp_fs_example.c +++ b/examples/device/mtp/src/mtp_fs_example.c @@ -82,7 +82,7 @@ static fs_file_t fs_objects[FS_MAX_FILE_COUNT] = { { .name = { 'r', 'e', 'a', 'd', 'm', 'e', '.', 't', 'x', 't', 0 }, // readme.txt .object_format = MTP_OBJ_FORMAT_TEXT, - .protection_status = MTP_PROTECTION_STATUS_READ_ONLY, + .protection_status = MTP_PROTECTION_STATUS_NO_PROTECTION, .image_pix_width = 0, .image_pix_height = 0, .image_bit_depth = 0, @@ -94,7 +94,7 @@ static fs_file_t fs_objects[FS_MAX_FILE_COUNT] = { { .name = { 't', 'i', 'n', 'y', 'u', 's', 'b', '.', 'p', 'n', 'g', 0 }, // "tinyusb.png" .object_format = MTP_OBJ_FORMAT_PNG, - .protection_status = MTP_PROTECTION_STATUS_READ_ONLY, + .protection_status = MTP_PROTECTION_STATUS_NO_PROTECTION, .image_pix_width = 128, .image_pix_height = 64, .image_bit_depth = 32, @@ -360,6 +360,26 @@ int32_t tud_mtp_command_received_cb(tud_mtp_cb_data_t* cb_data) { break; } + case MTP_OP_DELETE_OBJECT: { + if (!is_session_opened) { + resp_code = MTP_RESP_SESSION_NOT_OPEN; + } else { + const uint32_t obj_handle = command->params[0]; + const uint32_t obj_format = command->params[1]; // optional + (void) obj_format; + fs_file_t* f = fs_get_file(obj_handle); + if (f == NULL) { + resp_code = MTP_RESP_INVALID_OBJECT_HANDLE; + break; + } + + // delete object by clear the name + f->name[0] = 0; + resp_code = MTP_RESP_OK; + } + break; + } + default: resp_code = MTP_RESP_OPERATION_NOT_SUPPORTED; break; From 34be38db190c9fb8ca5f6c1db8bab33566e397cb Mon Sep 17 00:00:00 2001 From: hathach Date: Thu, 25 Sep 2025 18:58:25 +0700 Subject: [PATCH 23/43] clean up, wrap up bulk command supported --- examples/device/mtp/src/mtp_fs_example.c | 90 ------------------------ examples/device/mtp/src/tusb_config.h | 2 - src/class/mtp/mtp_device.c | 61 ---------------- 3 files changed, 153 deletions(-) diff --git a/examples/device/mtp/src/mtp_fs_example.c b/examples/device/mtp/src/mtp_fs_example.c index 4ccc122c7..a06fcb8db 100644 --- a/examples/device/mtp/src/mtp_fs_example.c +++ b/examples/device/mtp/src/mtp_fs_example.c @@ -513,96 +513,6 @@ int32_t tud_mtp_response_complete_cb(tud_mtp_cb_data_t* cb_data) { return 0; // nothing to do } -//--------------------------------------------------------------------+ -// API -//--------------------------------------------------------------------+ -#if 0 -mtp_response_t tud_mtp_storage_format(uint32_t storage_id) { - if (_fs_operation.session_id == 0) { - TU_LOG1("ERR: Session not open\r\n"); - return MTP_RESP_SESSION_NOT_OPEN; - } - if (storage_id != SUPPORTED_STORAGE_ID) { - TU_LOG1("ERR: Unexpected storage id %ld\r\n", storage_id); - return MTP_RESP_INVALID_STORAGE_ID; - } - - // Simply deallocate all entries - for (unsigned int i = 0; i < FS_MAX_NODES; i++) - fs_objects[i].allocated = false; - TU_LOG1("Format completed\r\n"); - return MTP_RESP_OK; -} - -mtp_response_t tud_mtp_storage_object_move(uint32_t object_handle, uint32_t new_parent_object_handle) { - fs_file_t* obj; - - if (new_parent_object_handle == 0xFFFFFFFF) - new_parent_object_handle = 0; - - // Ensure we are not moving to an nonexisting parent - if (new_parent_object_handle != 0) { - obj = fs_get_file(new_parent_object_handle); - if (obj == NULL) { - TU_LOG1("Parent %ld does not exist\r\n", new_parent_object_handle); - return MTP_RESP_INVALID_PARENT_OBJECT; - } - if (!obj->association_type) { - TU_LOG1("Parent %ld is not an association\r\n", new_parent_object_handle); - return MTP_RESP_INVALID_PARENT_OBJECT; - } - } - - obj = fs_get_file(object_handle); - - if (obj == NULL) { - TU_LOG1("ERR: Object with handle %ld does not exist\r\n", object_handle); - return MTP_RESP_INVALID_OBJECT_HANDLE; - } - TU_LOG1("Move object %ld to new parent %ld\r\n", object_handle, new_parent_object_handle); - obj->parent = new_parent_object_handle; - return MTP_RESP_OK; -} - -mtp_response_t tud_mtp_storage_object_delete(uint32_t object_handle) { - fs_file_t* obj; - - if (_fs_operation.session_id == 0) { - TU_LOG1("ERR: Session not open\r\n"); - return MTP_RESP_SESSION_NOT_OPEN; - } - - if (object_handle == 0xFFFFFFFF) - object_handle = 0; - - if (object_handle != 0) { - obj = fs_get_file(object_handle); - - if (obj == NULL) { - TU_LOG1("ERR: Object with handle %ld does not exist\r\n", object_handle); - return MTP_RESP_INVALID_OBJECT_HANDLE; - } - obj->allocated = false; - TU_LOG1("Delete object with handle %ld\r\n", object_handle); - } - - if (object_handle == 0 || obj->association_type) { - // Delete also children - for (unsigned int i = 0; i < FS_MAX_NODES; i++) { - obj = &fs_objects[i]; - if (obj->allocated && obj->parent == object_handle) { - tud_mtp_storage_object_delete(obj->handle); - } - } - } - - return MTP_RESP_OK; -} - -void tud_mtp_storage_object_done(void) { -} -#endif - void tud_mtp_storage_cancel(void) { } diff --git a/examples/device/mtp/src/tusb_config.h b/examples/device/mtp/src/tusb_config.h index 7db5235d0..62a5729bf 100644 --- a/examples/device/mtp/src/tusb_config.h +++ b/examples/device/mtp/src/tusb_config.h @@ -102,14 +102,12 @@ MTP_OP_CLOSE_SESSION, \ MTP_OP_GET_STORAGE_IDS, \ MTP_OP_GET_STORAGE_INFO, \ - MTP_OP_GET_NUM_OBJECTS, \ MTP_OP_GET_OBJECT_HANDLES, \ MTP_OP_GET_OBJECT_INFO, \ MTP_OP_GET_OBJECT, \ MTP_OP_DELETE_OBJECT, \ MTP_OP_SEND_OBJECT_INFO, \ MTP_OP_SEND_OBJECT, \ - MTP_OP_FORMAT_STORE, \ MTP_OP_RESET_DEVICE, \ MTP_OP_GET_DEVICE_PROP_DESC, \ MTP_OP_GET_DEVICE_PROP_VALUE, \ diff --git a/src/class/mtp/mtp_device.c b/src/class/mtp/mtp_device.c index 6d1c7b29b..6438fd2d6 100644 --- a/src/class/mtp/mtp_device.c +++ b/src/class/mtp/mtp_device.c @@ -76,13 +76,6 @@ typedef struct { // INTERNAL FUNCTION DECLARATION //--------------------------------------------------------------------+ static void process_cmd(mtpd_interface_t* p_mtp, tud_mtp_cb_data_t* cb_data); -static mtp_phase_type_t mtpd_handle_data(void); -static mtp_phase_type_t mtpd_handle_cmd_delete_object(void); -static mtp_phase_type_t mtpd_handle_cmd_send_object_info(void); -static mtp_phase_type_t mtpd_handle_dto_send_object_info(void); -static mtp_phase_type_t mtpd_handle_cmd_send_object(void); -static mtp_phase_type_t mtpd_handle_dto_send_object(void); -static mtp_phase_type_t mtpd_handle_cmd_format_store(void); //--------------------------------------------------------------------+ // MTP variable declaration @@ -474,59 +467,5 @@ void process_cmd(mtpd_interface_t* p_mtp, tud_mtp_cb_data_t* cb_data) { break; } } -#if 0 - -mtp_phase_type_t mtpd_handle_data(void) -{ - mtp_generic_container_t* p_container = &_mtpd_epbuf.buf; - TU_ASSERT(p_container->type == MTP_CONTAINER_TYPE_DATA_BLOCK); - - switch(p_container->code) - { - case MTP_OP_SEND_OBJECT_INFO: - TU_LOG_DRV(" MTP command: MTP_OP_SEND_OBJECT_INFO-DATA_OUT\n"); - return mtpd_handle_dto_send_object_info(); - case MTP_OP_SEND_OBJECT: - TU_LOG_DRV(" MTP command: MTP_OP_SEND_OBJECT-DATA_OUT\n"); - return mtpd_handle_dto_send_object(); - default: - TU_LOG_DRV(" MTP command: MTP_OP_UNKNOWN_COMMAND %x!!!!\n", p_container->code); - return false; - } - return true; -} - -mtp_phase_type_t mtpd_handle_cmd_delete_object(void) -{ - mtp_generic_container_t* p_container = &_mtpd_epbuf.buf; - uint32_t object_handle = p_container->data[0]; - uint32_t object_code_format = p_container->data[1]; // not used - (void) object_code_format; - - mtp_response_t res = tud_mtp_storage_object_delete(object_handle); - mtp_phase_type_t phase; - if ((phase = mtpd_chk_generic(__func__, (res != MTP_RESP_OK), res, "")) != MTP_PHASE_NONE) return phase; - - p_container->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; - p_container->code = MTP_RESP_OK; - p_container->len = MTP_CONTAINER_HEADER_LENGTH; - return MTP_PHASE_RESPONSE; -} - -mtp_phase_type_t mtpd_handle_cmd_format_store(void) -{ - mtp_generic_container_t* p_container = &_mtpd_epbuf.buf; - uint32_t storage_id = p_container->data[0]; - uint32_t file_system_format = p_container->data[1]; // not used - (void) file_system_format; - - mtp_response_t res = tud_mtp_storage_format(storage_id); - - p_container->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; - p_container->code = res; - p_container->len = MTP_CONTAINER_HEADER_LENGTH; - return MTP_PHASE_RESPONSE; -} -#endif #endif From e4f7fcf7ec4239aff6193a8d71cd0e8ce94024dd Mon Sep 17 00:00:00 2001 From: hathach Date: Fri, 26 Sep 2025 10:06:32 +0700 Subject: [PATCH 24/43] fix compile warnings --- examples/device/mtp/src/mtp_fs_example.c | 15 +++++++++------ src/class/mtp/mtp.h | 11 ++++------- src/class/mtp/mtp_device.c | 4 ++-- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/examples/device/mtp/src/mtp_fs_example.c b/examples/device/mtp/src/mtp_fs_example.c index a06fcb8db..2feb69758 100644 --- a/examples/device/mtp/src/mtp_fs_example.c +++ b/examples/device/mtp/src/mtp_fs_example.c @@ -66,7 +66,7 @@ typedef struct { uint32_t image_bit_depth; uint32_t parent; - uint8_t association_type; + uint16_t association_type; uint32_t size; uint8_t* data; @@ -74,8 +74,9 @@ typedef struct { } fs_file_t; // object data buffer (excluding 2 predefined files) +// with simple allocation pointer uint8_t fs_buf[FS_MAX_CAPACITY_BYTES]; -uint8_t fs_buf_head = 0; // simple allocation pointer +size_t fs_buf_head = 0; // Files system, handle is index + 1 static fs_file_t fs_objects[FS_MAX_FILE_COUNT] = { @@ -88,7 +89,7 @@ static fs_file_t fs_objects[FS_MAX_FILE_COUNT] = { .image_bit_depth = 0, .parent = 0, .association_type = MTP_ASSOCIATION_UNDEFINED, - .data = (uint8_t*) README_TXT_CONTENT, + .data = (uint8_t*) (uintptr_t) README_TXT_CONTENT, .size = sizeof(README_TXT_CONTENT) }, { @@ -100,7 +101,7 @@ static fs_file_t fs_objects[FS_MAX_FILE_COUNT] = { .image_bit_depth = 32, .parent = 0, .association_type = MTP_ASSOCIATION_UNDEFINED, - .data = logo_bin, + .data = (uint8_t*) (uintptr_t) logo_bin, .size = logo_len, } }; @@ -161,6 +162,7 @@ static inline fs_file_t* fs_create_file(void) { return f; } } + return NULL; } // simple malloc @@ -179,7 +181,7 @@ static inline uint8_t* fs_malloc(size_t size) { int32_t tud_mtp_command_received_cb(tud_mtp_cb_data_t* cb_data) { const mtp_container_command_t* command = cb_data->command_container; mtp_container_info_t* io_container = &cb_data->io_container; - uint32_t resp_code = 0; + uint16_t resp_code = 0; switch (command->code) { case MTP_OP_GET_DEVICE_INFO: { // Device info is already prepared up to playback formats. Application only need to add string fields @@ -339,6 +341,7 @@ int32_t tud_mtp_command_received_cb(tud_mtp_cb_data_t* cb_data) { case MTP_OP_SEND_OBJECT_INFO: { const uint32_t storage_id = command->params[0]; const uint32_t parent_handle = command->params[1]; // folder handle, 0xFFFFFFFF is root + (void) parent_handle; if (!is_session_opened) { resp_code = MTP_RESP_SESSION_NOT_OPEN; } else if (storage_id != 0xFFFFFFFF && storage_id != SUPPORTED_STORAGE_ID) { @@ -397,7 +400,7 @@ int32_t tud_mtp_command_received_cb(tud_mtp_cb_data_t* cb_data) { int32_t tud_mtp_data_xfer_cb(tud_mtp_cb_data_t* cb_data) { const mtp_container_command_t* command = cb_data->command_container; mtp_container_info_t* io_container = &cb_data->io_container; - uint32_t resp_code = 0; + uint16_t resp_code = 0; switch (command->code) { case MTP_OP_GET_OBJECT: { // File contents span over multiple xfers diff --git a/src/class/mtp/mtp.h b/src/class/mtp/mtp.h index 15cf1bfc6..073e6c470 100644 --- a/src/class/mtp/mtp.h +++ b/src/class/mtp/mtp.h @@ -675,10 +675,7 @@ TU_VERIFY_STATIC(sizeof(mtp_container_command_t) == 32, "size is not correct"); // PTP/MTP Generic container typedef struct TU_ATTR_PACKED { mtp_container_header_t header; - // union { - uint32_t data[(CFG_TUD_MTP_EP_BUFSIZE - sizeof(mtp_container_header_t)) / sizeof(uint32_t)]; - // uint8_t data[CFG_TUD_MTP_EP_BUFSIZE - sizeof(mtp_container_header_t)]; - // }; + uint8_t payload[(CFG_TUD_MTP_EP_BUFSIZE - sizeof(mtp_container_header_t))]; } mtp_generic_container_t; typedef struct { @@ -823,7 +820,7 @@ TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_string(mtp_contai while (utf16[count]) { count++; } - const uint32_t added_len = 1 + 2 * count; + const uint32_t added_len = 1u + 2u * count; TU_ASSERT(p_container->header->len + added_len < CFG_TUD_MTP_EP_BUFSIZE, 0); uint8_t* buf = p_container->payload + p_container->header->len - sizeof(mtp_container_header_t); @@ -856,7 +853,7 @@ TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_cstring(mtp_conta buf += 2; p_container->header->len += 2; } - return 1 + 2 * len; + return 1u + 2u * len; } } @@ -898,7 +895,7 @@ TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_auint32(mtp_conta TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_get_string(uint8_t* buf, uint16_t utf16[]) { uint8_t nchars = *buf++; memcpy(utf16, buf, 2 * nchars); - return 1 + nchars * 2; + return 1u + 2u * nchars; } #ifdef __cplusplus diff --git a/src/class/mtp/mtp_device.c b/src/class/mtp/mtp_device.c index 6438fd2d6..61b1c9613 100644 --- a/src/class/mtp/mtp_device.c +++ b/src/class/mtp/mtp_device.c @@ -267,7 +267,7 @@ static bool mtpd_data_xfer(mtp_container_info_t* p_container, uint8_t ep_addr) { TU_ASSERT(p_mtp->phase == MTP_PHASE_DATA); } - const uint16_t xact_len = tu_min32(p_mtp->total_len - p_mtp->xferred_len, CFG_TUD_MTP_EP_BUFSIZE); + const uint16_t xact_len = tu_min16((uint16_t) (p_mtp->total_len - p_mtp->xferred_len), CFG_TUD_MTP_EP_BUFSIZE); if (xact_len) { // already transferred all bytes in header's length. Application make an unnecessary extra call TU_ASSERT(usbd_edpt_xfer(p_mtp->rhport, ep_addr, _mtpd_epbuf.buf, xact_len)); @@ -309,7 +309,7 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t const mtp_container_info_t headered_packet = { .header = &p_container->header, - .payload32 = p_container->data, + .payload = p_container->payload, .payload_bytes = CFG_TUD_MTP_EP_BUFSIZE - sizeof(mtp_container_header_t) }; From e92262eb5cdc35dfc6019c97d1b9166be0f5e62f Mon Sep 17 00:00:00 2001 From: hathach Date: Fri, 26 Sep 2025 11:51:09 +0700 Subject: [PATCH 25/43] fix more ci build --- examples/device/mtp/src/mtp_fs_example.c | 160 +++++++++++++---------- hw/bsp/samd11/family.cmake | 1 + src/class/mtp/mtp_device.c | 8 +- src/class/mtp/mtp_device.h | 33 +---- 4 files changed, 96 insertions(+), 106 deletions(-) diff --git a/examples/device/mtp/src/mtp_fs_example.c b/examples/device/mtp/src/mtp_fs_example.c index 2feb69758..a13acfcdd 100644 --- a/examples/device/mtp/src/mtp_fs_example.c +++ b/examples/device/mtp/src/mtp_fs_example.c @@ -47,71 +47,16 @@ typedef MTP_STORAGE_INFO_STRUCT(TU_ARRAY_SIZE((uint16_t[]) STORAGE_DESCRIPTRION) TU_ARRAY_SIZE(((uint16_t[])VOLUME_IDENTIFIER)) ) storage_info_t; -//--------------------------------------------------------------------+ -// RAM FILESYSTEM -//--------------------------------------------------------------------+ -#define FS_MAX_FILE_COUNT 5UL -#define FS_MAX_CAPACITY_BYTES (4 * 1024UL) -#define FS_MAX_FILENAME_LEN 16 -#define FS_FIXED_DATETIME "20250808T173500.0" // "YYYYMMDDTHHMMSS.s" - -#define README_TXT_CONTENT "TinyUSB MTP on RAM Filesystem example" - -typedef struct { - uint16_t name[FS_MAX_FILENAME_LEN]; - mtp_object_formats_t object_format; - uint16_t protection_status; - uint32_t image_pix_width; - uint32_t image_pix_height; - uint32_t image_bit_depth; - - uint32_t parent; - uint16_t association_type; - - uint32_t size; - uint8_t* data; - -} fs_file_t; - -// object data buffer (excluding 2 predefined files) -// with simple allocation pointer -uint8_t fs_buf[FS_MAX_CAPACITY_BYTES]; -size_t fs_buf_head = 0; - -// Files system, handle is index + 1 -static fs_file_t fs_objects[FS_MAX_FILE_COUNT] = { - { - .name = { 'r', 'e', 'a', 'd', 'm', 'e', '.', 't', 'x', 't', 0 }, // readme.txt - .object_format = MTP_OBJ_FORMAT_TEXT, - .protection_status = MTP_PROTECTION_STATUS_NO_PROTECTION, - .image_pix_width = 0, - .image_pix_height = 0, - .image_bit_depth = 0, - .parent = 0, - .association_type = MTP_ASSOCIATION_UNDEFINED, - .data = (uint8_t*) (uintptr_t) README_TXT_CONTENT, - .size = sizeof(README_TXT_CONTENT) - }, - { - .name = { 't', 'i', 'n', 'y', 'u', 's', 'b', '.', 'p', 'n', 'g', 0 }, // "tinyusb.png" - .object_format = MTP_OBJ_FORMAT_PNG, - .protection_status = MTP_PROTECTION_STATUS_NO_PROTECTION, - .image_pix_width = 128, - .image_pix_height = 64, - .image_bit_depth = 32, - .parent = 0, - .association_type = MTP_ASSOCIATION_UNDEFINED, - .data = (uint8_t*) (uintptr_t) logo_bin, - .size = logo_len, - } -}; - -//------------- Storage Info -------------// storage_info_t storage_info = { + #ifdef CFG_EXAMPLE_MTP_READONLY + .storage_type = MTP_STORAGE_TYPE_FIXED_ROM, + #else .storage_type = MTP_STORAGE_TYPE_FIXED_RAM, + #endif + .filesystem_type = MTP_FILESYSTEM_TYPE_GENERIC_HIERARCHICAL, .access_capability = MTP_ACCESS_CAPABILITY_READ_WRITE, - .max_capacity_in_bytes = sizeof(README_TXT_CONTENT) + logo_len + FS_MAX_CAPACITY_BYTES, + .max_capacity_in_bytes = 0, // calculated at runtime .free_space_in_bytes = 0, // calculated at runtime .free_space_in_objects = 0, // calculated at runtime .storage_description = { @@ -124,6 +69,69 @@ storage_info_t storage_info = { } }; + +//--------------------------------------------------------------------+ +// MTP FILESYSTEM +//--------------------------------------------------------------------+ +#define FS_MAX_FILE_COUNT 5UL +#define FS_MAX_FILENAME_LEN 16 + +#ifdef CFG_EXAMPLE_MTP_READONLY + #define FS_MAX_CAPACITY_BYTES 0 +#else + #define FS_MAX_CAPACITY_BYTES (4 * 1024UL) + + // object data buffer (excluding 2 predefined files) + // with simple allocation pointer + uint8_t fs_buf[FS_MAX_CAPACITY_BYTES]; +#endif +size_t fs_buf_head = 0; + +#define FS_FIXED_DATETIME "20250808T173500.0" // "YYYYMMDDTHHMMSS.s" +#define README_TXT_CONTENT "TinyUSB MTP Filesystem example" + +typedef struct { + uint16_t name[FS_MAX_FILENAME_LEN]; + mtp_object_formats_t object_format; + uint16_t protection_status; + uint32_t image_pix_width; + uint32_t image_pix_height; + uint32_t image_bit_depth; + uint32_t parent; + uint16_t association_type; + uint32_t size; + uint8_t* data; +} fs_file_t; + +// Files system, handle is index + 1 +static fs_file_t fs_objects[FS_MAX_FILE_COUNT] = { + { + .name = { 'r', 'e', 'a', 'd', 'm', 'e', '.', 't', 'x', 't', 0 }, // readme.txt + .object_format = MTP_OBJ_FORMAT_TEXT, + .protection_status = MTP_PROTECTION_STATUS_READ_ONLY, + .image_pix_width = 0, + .image_pix_height = 0, + .image_bit_depth = 0, + .parent = 0, + .association_type = MTP_ASSOCIATION_UNDEFINED, + .data = (uint8_t*) (uintptr_t) README_TXT_CONTENT, + .size = sizeof(README_TXT_CONTENT) + }, + { + .name = { 't', 'i', 'n', 'y', 'u', 's', 'b', '.', 'p', 'n', 'g', 0 }, // "tinyusb.png" + .object_format = MTP_OBJ_FORMAT_PNG, + .protection_status = MTP_PROTECTION_STATUS_READ_ONLY, + .image_pix_width = 128, + .image_pix_height = 64, + .image_bit_depth = 32, + .parent = 0, + .association_type = MTP_ASSOCIATION_UNDEFINED, + .data = (uint8_t*) (uintptr_t) logo_bin, + .size = logo_len, + } +}; + + enum { SUPPORTED_STORAGE_ID = 0x00010001u // physical = 1, logical = 1 }; @@ -167,16 +175,21 @@ static inline fs_file_t* fs_create_file(void) { // simple malloc static inline uint8_t* fs_malloc(size_t size) { +#ifdef CFG_EXAMPLE_MTP_READONLY + (void) size; + return NULL; +#else if (fs_buf_head + size > FS_MAX_CAPACITY_BYTES) { return NULL; } uint8_t* ptr = &fs_buf[fs_buf_head]; fs_buf_head += size; return ptr; +#endif } //--------------------------------------------------------------------+ -// +// Bulk Only Protocol //--------------------------------------------------------------------+ int32_t tud_mtp_command_received_cb(tud_mtp_cb_data_t* cb_data) { const mtp_container_command_t* command = cb_data->command_container; @@ -223,6 +236,7 @@ int32_t tud_mtp_command_received_cb(tud_mtp_cb_data_t* cb_data) { const uint32_t storage_id = command->params[0]; TU_VERIFY(SUPPORTED_STORAGE_ID == storage_id, -1); // update storage info with current free space + storage_info.max_capacity_in_bytes = sizeof(README_TXT_CONTENT) + logo_len + FS_MAX_CAPACITY_BYTES; storage_info.free_space_in_objects = FS_MAX_FILE_COUNT - fs_get_file_count(); storage_info.free_space_in_bytes = FS_MAX_CAPACITY_BYTES-fs_buf_head; mtp_container_add_raw(io_container, &storage_info, sizeof(storage_info)); @@ -299,7 +313,7 @@ int32_t tud_mtp_command_received_cb(tud_mtp_cb_data_t* cb_data) { mtp_object_info_header_t obj_info_header = { .storage_id = SUPPORTED_STORAGE_ID, .object_format = f->object_format, - .protection_status = MTP_PROTECTION_STATUS_NO_PROTECTION, + .protection_status = f->protection_status, .object_compressed_size = f->size, .thumb_format = MTP_OBJ_FORMAT_UNDEFINED, .thumb_compressed_size = 0, @@ -433,7 +447,17 @@ int32_t tud_mtp_data_xfer_cb(tud_mtp_cb_data_t* cb_data) { } } + uint8_t* f_buf = fs_malloc(obj_info->object_compressed_size); + if (f_buf == NULL) { + resp_code = MTP_RESP_STORE_FULL; + break; + } fs_file_t* f = fs_create_file(); + if (f == NULL) { + resp_code = MTP_RESP_STORE_FULL; + break; + } + f->object_format = obj_info->object_format; f->protection_status = obj_info->protection_status; f->image_pix_width = obj_info->image_pix_width; @@ -442,11 +466,7 @@ int32_t tud_mtp_data_xfer_cb(tud_mtp_cb_data_t* cb_data) { f->parent = obj_info->parent_object; f->association_type = obj_info->association_type; f->size = obj_info->object_compressed_size; - f->data = fs_malloc(f->size); - if (f->data == NULL) { - resp_code = MTP_RESP_STORE_FULL; - break; - } + f->data = f_buf; uint8_t* buf = io_container->payload + sizeof(mtp_object_info_header_t); mtp_container_get_string(buf, f->name); // ignore date created/modified/keywords @@ -498,10 +518,6 @@ int32_t tud_mtp_data_complete_cb(tud_mtp_cb_data_t* cb_data) { break; } - case MTP_OP_SEND_OBJECT: - resp->header->code = (cb_data->xfer_result == XFER_RESULT_SUCCESS) ? MTP_RESP_OK : MTP_RESP_GENERAL_ERROR; - break; - default: resp->header->code = (cb_data->xfer_result == XFER_RESULT_SUCCESS) ? MTP_RESP_OK : MTP_RESP_GENERAL_ERROR; break; diff --git a/hw/bsp/samd11/family.cmake b/hw/bsp/samd11/family.cmake index 965b1cfb5..e3dc23c35 100644 --- a/hw/bsp/samd11/family.cmake +++ b/hw/bsp/samd11/family.cmake @@ -54,6 +54,7 @@ function(add_board_target BOARD_TARGET) OSC32K_OVERWRITE_CALIBRATION=0 CFG_EXAMPLE_MSC_READONLY CFG_EXAMPLE_VIDEO_READONLY + CFG_EXAMPLE_MTP_READONLY ) update_board(${BOARD_TARGET}) diff --git a/src/class/mtp/mtp_device.c b/src/class/mtp/mtp_device.c index 61b1c9613..b3d982323 100644 --- a/src/class/mtp/mtp_device.c +++ b/src/class/mtp/mtp_device.c @@ -424,14 +424,10 @@ void process_cmd(mtpd_interface_t* p_mtp, tud_mtp_cb_data_t* cb_data) { .standard_version = 100, .mtp_vendor_extension_id = 6, // MTP specs say 0xFFFFFFFF but libMTP check for value 6 .mtp_version = 100, -#ifdef CFG_TUD_MTP_DEVICEINFO_EXTENSIONS .mtp_extensions = { .count = sizeof(CFG_TUD_MTP_DEVICEINFO_EXTENSIONS), .utf16 = { 0 } }, -#else - .mtp_extensions = 0, -#endif .functional_mode = 0x0000, .supported_operations = { .count = TU_ARGS_NUM(CFG_TUD_MTP_DEVICEINFO_SUPPORTED_OPERATIONS), @@ -454,11 +450,11 @@ void process_cmd(mtpd_interface_t* p_mtp, tud_mtp_cb_data_t* cb_data) { .arr = { CFG_TUD_MTP_DEVICEINFO_PLAYBACK_FORMATS } } }; -#ifdef CFG_TUD_MTP_DEVICEINFO_EXTENSIONS + for (uint8_t i=0; i < dev_info.mtp_extensions.count; i++) { dev_info.mtp_extensions.utf16[i] = (uint16_t)CFG_TUD_MTP_DEVICEINFO_EXTENSIONS[i]; } -#endif + mtp_container_add_raw(&cb_data->io_container, &dev_info, sizeof(tud_mtp_device_info_t)); break; } diff --git a/src/class/mtp/mtp_device.h b/src/class/mtp/mtp_device.h index d0c894742..b10c3abda 100644 --- a/src/class/mtp/mtp_device.h +++ b/src/class/mtp/mtp_device.h @@ -62,34 +62,11 @@ typedef struct { /* string fields will be added using append function */ \ } -#define MTP_DEVICE_INFO_NO_EXTENSION_STRUCT(_op_count, _event_count, _devprop_count, _capture_count, _playback_count) \ - struct TU_ATTR_PACKED { \ - uint16_t standard_version; \ - uint32_t mtp_vendor_extension_id; \ - uint16_t mtp_version; \ - uint8_t mtp_extensions; \ - uint16_t functional_mode; \ - mtp_auint16_t(_op_count) supported_operations; \ - mtp_auint16_t(_event_count) supported_events; \ - mtp_auint16_t(_devprop_count) supported_device_properties; \ - mtp_auint16_t(_capture_count) capture_formats; \ - mtp_auint16_t(_playback_count) playback_formats; \ - /* string fields will be added using append function */ \ - } - -#ifdef CFG_TUD_MTP_DEVICEINFO_EXTENSIONS - typedef MTP_DEVICE_INFO_STRUCT( - sizeof(CFG_TUD_MTP_DEVICEINFO_EXTENSIONS), TU_ARGS_NUM(CFG_TUD_MTP_DEVICEINFO_SUPPORTED_OPERATIONS), - TU_ARGS_NUM(CFG_TUD_MTP_DEVICEINFO_SUPPORTED_EVENTS), TU_ARGS_NUM(CFG_TUD_MTP_DEVICEINFO_SUPPORTED_DEVICE_PROPERTIES), - TU_ARGS_NUM(CFG_TUD_MTP_DEVICEINFO_CAPTURE_FORMATS), TU_ARGS_NUM(CFG_TUD_MTP_DEVICEINFO_PLAYBACK_FORMATS) - ) tud_mtp_device_info_t; -#else - typedef MTP_DEVICE_INFO_NO_EXTENSION_STRUCT( - TU_ARGS_NUM(CFG_TUD_MTP_DEVICEINFO_SUPPORTED_OPERATIONS), - TU_ARGS_NUM(CFG_TUD_MTP_DEVICEINFO_SUPPORTED_EVENTS), TU_ARGS_NUM(CFG_TUD_MTP_DEVICEINFO_SUPPORTED_DEVICE_PROPERTIES), - TU_ARGS_NUM(CFG_TUD_MTP_DEVICEINFO_CAPTURE_FORMATS), TU_ARGS_NUM(CFG_TUD_MTP_DEVICEINFO_PLAYBACK_FORMATS) - ) tud_mtp_device_info_t; -#endif +typedef MTP_DEVICE_INFO_STRUCT( + sizeof(CFG_TUD_MTP_DEVICEINFO_EXTENSIONS), TU_ARGS_NUM(CFG_TUD_MTP_DEVICEINFO_SUPPORTED_OPERATIONS), + TU_ARGS_NUM(CFG_TUD_MTP_DEVICEINFO_SUPPORTED_EVENTS), TU_ARGS_NUM(CFG_TUD_MTP_DEVICEINFO_SUPPORTED_DEVICE_PROPERTIES), + TU_ARGS_NUM(CFG_TUD_MTP_DEVICEINFO_CAPTURE_FORMATS), TU_ARGS_NUM(CFG_TUD_MTP_DEVICEINFO_PLAYBACK_FORMATS) +) tud_mtp_device_info_t; //--------------------------------------------------------------------+ // Application API From 5c630ee0d0d6ba53a7329da0361dfb33a902a290 Mon Sep 17 00:00:00 2001 From: hathach Date: Fri, 26 Sep 2025 12:50:30 +0700 Subject: [PATCH 26/43] skip mtp for cynthion_d11 --- examples/device/mtp/skip.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/device/mtp/skip.txt b/examples/device/mtp/skip.txt index e69de29bb..37a4485d7 100644 --- a/examples/device/mtp/skip.txt +++ b/examples/device/mtp/skip.txt @@ -0,0 +1 @@ +board:cynthion_d11 From f9d4bc798177e63532cd5a2ef058400d6f0ef5e4 Mon Sep 17 00:00:00 2001 From: hathach Date: Fri, 26 Sep 2025 15:52:37 +0700 Subject: [PATCH 27/43] implement tud_mtp_mounted() and tud_mtp_event_send() refactor phase state --- src/class/mtp/mtp.h | 33 ++++------ src/class/mtp/mtp_device.c | 132 +++++++++++++++++++++---------------- src/class/mtp/mtp_device.h | 5 ++ 3 files changed, 94 insertions(+), 76 deletions(-) diff --git a/src/class/mtp/mtp.h b/src/class/mtp/mtp.h index 073e6c470..b73bd200a 100644 --- a/src/class/mtp/mtp.h +++ b/src/class/mtp/mtp.h @@ -56,18 +56,14 @@ typedef enum { // PTP/MTP protocol phases typedef enum { - MTP_PHASE_IDLE = 0, - MTP_PHASE_COMMAND, + MTP_PHASE_COMMAND = 0, MTP_PHASE_DATA, MTP_PHASE_RESPONSE, - MTP_PHASE_RESPONSE_QUEUED, - MTP_PHASE_ERROR, - MTP_PHASE_NONE, + MTP_PHASE_ERROR } mtp_phase_type_t; // PTP/MTP Class requests, PIMA 15740-2000: D.5.2 -typedef enum -{ +typedef enum { MTP_REQ_CANCEL = 0x64, MTP_REQ_GET_EXT_EVENT_DATA = 0x65, MTP_REQ_RESET = 0x66, @@ -75,8 +71,7 @@ typedef enum } mtp_class_request_t; // PTP/MTP Container type -typedef enum -{ +typedef enum { MTP_CONTAINER_TYPE_UNDEFINED = 0, MTP_CONTAINER_TYPE_COMMAND_BLOCK = 1, MTP_CONTAINER_TYPE_DATA_BLOCK = 2, @@ -85,8 +80,7 @@ typedef enum } mtp_container_type_t; // MTP 1.1 Appendix A: Object formats -typedef enum -{ +typedef enum { // ---- Base formats ---- MTP_OBJ_FORMAT_UNDEFINED = 0x3000u, // Undefined object MTP_OBJ_FORMAT_ASSOCIATION = 0x3001u, // Association (for example, a folder) @@ -572,12 +566,6 @@ typedef enum { MTP_EVENT_OBJECT_REFERENCES_CHANGED = 0xC803u, } mtp_event_code_t; -// Predefined Object handles -typedef enum -{ - MTP_OBJH_ROOT = 0x0000, -} mtp_object_handles_t; - // Datatypes typedef enum { MTP_DATA_TYPE_UNDEFINED = 0x0000u, @@ -607,8 +595,7 @@ typedef enum { } mtp_data_type_t; // Get/Set -typedef enum -{ +typedef enum { MTP_MODE_GET = 0x00u, MTP_MODE_GET_SET = 0x01u, } mtp_mode_get_set_t; @@ -688,6 +675,14 @@ typedef struct { uint32_t payload_bytes; // available bytes for read/write } mtp_container_info_t; +typedef struct TU_ATTR_PACKED { + uint16_t code; + uint32_t session_id; + uint32_t transaction_id; + uint32_t params[3]; +} mtp_event_t; +TU_VERIFY_STATIC(sizeof(mtp_event_t) == 22, "size is not correct"); + #define mtp_string_t(_nchars) \ struct TU_ATTR_PACKED { \ uint8_t count; /* in characters including null */ \ diff --git a/src/class/mtp/mtp_device.c b/src/class/mtp/mtp_device.c index b3d982323..4e468458d 100644 --- a/src/class/mtp/mtp_device.c +++ b/src/class/mtp/mtp_device.c @@ -70,6 +70,7 @@ typedef struct { typedef struct { TUD_EPBUF_DEF(buf, CFG_TUD_MTP_EP_BUFSIZE); + TUD_EPBUF_TYPE_DEF(mtp_event_t, buf_event); } mtpd_epbuf_t; //--------------------------------------------------------------------+ @@ -142,18 +143,84 @@ TU_ATTR_UNUSED static tu_lookup_table_t const _mtp_op_table = { .items = _mpt_op_lookup }; +TU_ATTR_UNUSED static const char* _mtp_phase_str[] = { + "Command", + "Data", + "Response", + "Error" +}; + #endif //--------------------------------------------------------------------+ // Helper //--------------------------------------------------------------------+ - static bool prepare_new_command(mtpd_interface_t* p_mtp) { - p_mtp->phase = MTP_PHASE_IDLE; + p_mtp->phase = MTP_PHASE_COMMAND; return usbd_edpt_xfer(p_mtp->rhport, p_mtp->ep_out, _mtpd_epbuf.buf, CFG_TUD_MTP_EP_BUFSIZE); } +static bool mtpd_data_xfer(mtp_container_info_t* p_container, uint8_t ep_addr) { + mtpd_interface_t* p_mtp = &_mtpd_itf; + if (p_mtp->phase == MTP_PHASE_COMMAND) { + // 1st data block: header + payload + p_mtp->phase = MTP_PHASE_DATA; + p_mtp->xferred_len = 0; + + if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN) { + p_mtp->total_len = p_container->header->len; + p_container->header->type = MTP_CONTAINER_TYPE_DATA_BLOCK; + p_container->header->transaction_id = p_mtp->command.transaction_id; + p_mtp->io_header = *p_container->header; // save header for subsequent data + } else { + // OUT transfer: total length is at least max packet size + p_mtp->total_len = tu_max32(p_container->header->len, CFG_TUD_MTP_EP_BUFSIZE); + } + } else { + // subsequent data block: payload only + TU_ASSERT(p_mtp->phase == MTP_PHASE_DATA); + } + + const uint16_t xact_len = tu_min16((uint16_t) (p_mtp->total_len - p_mtp->xferred_len), CFG_TUD_MTP_EP_BUFSIZE); + if (xact_len) { + // already transferred all bytes in header's length. Application make an unnecessary extra call + TU_ASSERT(usbd_edpt_xfer(p_mtp->rhport, ep_addr, _mtpd_epbuf.buf, xact_len)); + } + return true; +} + +bool tud_mtp_data_send(mtp_container_info_t* p_container) { + return mtpd_data_xfer(p_container, _mtpd_itf.ep_in); +} + +bool tud_mtp_data_receive(mtp_container_info_t* p_container) { + return mtpd_data_xfer(p_container, _mtpd_itf.ep_out); +} + +bool tud_mtp_response_send(mtp_container_info_t* p_container) { + mtpd_interface_t* p_mtp = &_mtpd_itf; + p_mtp->phase = MTP_PHASE_RESPONSE; + p_container->header->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; + p_container->header->transaction_id = p_mtp->command.transaction_id; + TU_ASSERT(usbd_edpt_xfer(p_mtp->rhport, p_mtp->ep_in, _mtpd_epbuf.buf, (uint16_t)p_container->header->len)); + return true; +} + +bool tud_mtp_mounted(void) { + mtpd_interface_t* p_mtp = &_mtpd_itf; + return p_mtp->ep_out != 0 && p_mtp->ep_in != 0; +} + +bool tud_mtp_event_send(mtp_event_t* event) { + mtpd_interface_t* p_mtp = &_mtpd_itf; + TU_VERIFY(p_mtp->ep_event != 0); + _mtpd_epbuf.buf_event = *event; + TU_VERIFY(usbd_edpt_claim(p_mtp->rhport, p_mtp->ep_event)); // Claim the endpoint + + return usbd_edpt_xfer(p_mtp->rhport, p_mtp->ep_event, (uint8_t*) &_mtpd_epbuf.buf_event, sizeof(mtp_event_t)); +} + //--------------------------------------------------------------------+ // USBD Driver API //--------------------------------------------------------------------+ @@ -246,52 +313,6 @@ bool mtpd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t return true; } -static bool mtpd_data_xfer(mtp_container_info_t* p_container, uint8_t ep_addr) { - mtpd_interface_t* p_mtp = &_mtpd_itf; - if (p_mtp->phase == MTP_PHASE_COMMAND) { - // 1st data block: header + payload - p_mtp->phase = MTP_PHASE_DATA; - p_mtp->xferred_len = 0; - - if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN) { - p_mtp->total_len = p_container->header->len; - p_container->header->type = MTP_CONTAINER_TYPE_DATA_BLOCK; - p_container->header->transaction_id = p_mtp->command.transaction_id; - p_mtp->io_header = *p_container->header; // save header for subsequent data - } else { - // OUT transfer: total length is at least max packet size - p_mtp->total_len = tu_max32(p_container->header->len, CFG_TUD_MTP_EP_BUFSIZE); - } - } else { - // subsequent data block: payload only - TU_ASSERT(p_mtp->phase == MTP_PHASE_DATA); - } - - const uint16_t xact_len = tu_min16((uint16_t) (p_mtp->total_len - p_mtp->xferred_len), CFG_TUD_MTP_EP_BUFSIZE); - if (xact_len) { - // already transferred all bytes in header's length. Application make an unnecessary extra call - TU_ASSERT(usbd_edpt_xfer(p_mtp->rhport, ep_addr, _mtpd_epbuf.buf, xact_len)); - } - return true; -} - -bool tud_mtp_data_send(mtp_container_info_t* p_container) { - return mtpd_data_xfer(p_container, _mtpd_itf.ep_in); -} - -bool tud_mtp_data_receive(mtp_container_info_t* p_container) { - return mtpd_data_xfer(p_container, _mtpd_itf.ep_out); -} - -bool tud_mtp_response_send(mtp_container_info_t* p_container) { - mtpd_interface_t* p_mtp = &_mtpd_itf; - p_mtp->phase = MTP_PHASE_RESPONSE_QUEUED; - p_container->header->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; - p_container->header->transaction_id = p_mtp->command.transaction_id; - TU_ASSERT(usbd_edpt_xfer(p_mtp->rhport, p_mtp->ep_in, _mtpd_epbuf.buf, (uint16_t)p_container->header->len)); - return true; -} - // Transfer on bulk endpoints bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes) { if (ep_addr == _mtpd_itf.ep_event) { @@ -304,7 +325,8 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t #if CFG_TUSB_DEBUG >= CFG_TUD_MTP_LOG_LEVEL tu_lookup_find(&_mtp_op_table, p_mtp->command.code); - TU_LOG_DRV(" MTP %s phase = %u\r\n", (const char *) tu_lookup_find(&_mtp_op_table, p_mtp->command.code), p_mtp->phase); + TU_LOG_DRV(" MTP %s: %s phase\r\n", (const char *) tu_lookup_find(&_mtp_op_table, p_mtp->command.code), + _mtp_phase_str[p_mtp->phase]); #endif const mtp_container_info_t headered_packet = { @@ -327,13 +349,9 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t cb_data.xfer_result = event; switch (p_mtp->phase) { - case MTP_PHASE_IDLE: + case MTP_PHASE_COMMAND: { // received new command TU_VERIFY(ep_addr == p_mtp->ep_out && p_container->header.type == MTP_CONTAINER_TYPE_COMMAND_BLOCK); - p_mtp->phase = MTP_PHASE_COMMAND; - TU_ATTR_FALLTHROUGH; // handle in the next case - - case MTP_PHASE_COMMAND: { memcpy(&p_mtp->command, p_container, sizeof(mtp_container_command_t)); // save new command p_container->header.len = sizeof(mtp_container_header_t); // default container to header only process_cmd(p_mtp, &cb_data); @@ -389,16 +407,15 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t break; } - case MTP_PHASE_RESPONSE_QUEUED: + case MTP_PHASE_RESPONSE: // response phase is complete -> prepare for new command TU_ASSERT(ep_addr == p_mtp->ep_in); tud_mtp_response_complete_cb(&cb_data); prepare_new_command(p_mtp); break; - case MTP_PHASE_RESPONSE: case MTP_PHASE_ERROR: - // supposedly to be empty + // handled after switch, supposedly to be empty break; default: return false; } @@ -412,6 +429,7 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t return true; } + //--------------------------------------------------------------------+ // MTPD Internal functionality //--------------------------------------------------------------------+ diff --git a/src/class/mtp/mtp_device.h b/src/class/mtp/mtp_device.h index b10c3abda..4956628d3 100644 --- a/src/class/mtp/mtp_device.h +++ b/src/class/mtp/mtp_device.h @@ -72,6 +72,9 @@ typedef MTP_DEVICE_INFO_STRUCT( // Application API //--------------------------------------------------------------------+ +// check if mtp interface is mounted +bool tud_mtp_mounted(void); + // send data phase bool tud_mtp_data_send(mtp_container_info_t* p_container); @@ -81,6 +84,8 @@ bool tud_mtp_data_receive(mtp_container_info_t* p_container); // send response bool tud_mtp_response_send(mtp_container_info_t* p_container); +bool tud_mtp_event_send(mtp_event_t* event); + //--------------------------------------------------------------------+ // Control request Callbacks //--------------------------------------------------------------------+ From cb21ca1b0cb3221341dca2559109c0bb4dc18e6f Mon Sep 17 00:00:00 2001 From: hathach Date: Sat, 27 Sep 2025 16:15:04 +0700 Subject: [PATCH 28/43] implement control request --- examples/device/mtp/src/mtp_fs_example.c | 41 ++++++-- examples/device/mtp/src/tusb_config.h | 5 +- examples/device/mtp/src/usb_descriptors.c | 10 +- src/class/mtp/mtp.h | 13 +-- src/class/mtp/mtp_device.c | 120 ++++++++++++++++------ src/class/mtp/mtp_device.h | 44 +++++++- src/class/mtp/mtp_device_storage.h | 93 ----------------- 7 files changed, 169 insertions(+), 157 deletions(-) delete mode 100644 src/class/mtp/mtp_device_storage.h diff --git a/examples/device/mtp/src/mtp_fs_example.c b/examples/device/mtp/src/mtp_fs_example.c index a13acfcdd..9d90f7663 100644 --- a/examples/device/mtp/src/mtp_fs_example.c +++ b/examples/device/mtp/src/mtp_fs_example.c @@ -23,7 +23,6 @@ * */ -#include "class/mtp/mtp_device_storage.h" #include "tusb.h" #include "tinyusb_logo_png.h" @@ -188,6 +187,40 @@ static inline uint8_t* fs_malloc(size_t size) { #endif } +//--------------------------------------------------------------------+ +// Control Request callback +//--------------------------------------------------------------------+ +bool tud_mtp_request_cancel_cb(tud_mtp_request_cb_data_t* cb_data) { + mtp_request_reset_cancel_data_t cancel_data; + memcpy(&cancel_data, cb_data->buf, sizeof(cancel_data)); + (void) cancel_data.code; + (void ) cancel_data.transaction_id; + return true; +} + +// Invoked when received Device Reset request +// return false to stall the request +bool tud_mtp_request_device_reset_cb(tud_mtp_request_cb_data_t* cb_data) { + (void) cb_data; + return true; +} + +// Invoked when received Get Extended Event request. Application fill callback data's buffer for response +// return negative to stall the request +int32_t tud_mtp_request_get_extended_event_cb(tud_mtp_request_cb_data_t* cb_data) { + (void) cb_data; + return false; // not implemented yet +} + +// Invoked when received Get DeviceStatus request. Application fill callback data's buffer for response +// return negative to stall the request +int32_t tud_mtp_request_get_device_status_cb(tud_mtp_request_cb_data_t* cb_data) { + uint16_t* buf16 = (uint16_t*)(uintptr_t) cb_data->buf; + buf16[0] = 4; // length + buf16[1] = MTP_RESP_OK; // status + return 4; +} + //--------------------------------------------------------------------+ // Bulk Only Protocol //--------------------------------------------------------------------+ @@ -531,9 +564,3 @@ int32_t tud_mtp_response_complete_cb(tud_mtp_cb_data_t* cb_data) { (void) cb_data; return 0; // nothing to do } - -void tud_mtp_storage_cancel(void) { -} - -void tud_mtp_storage_reset(void) { -} diff --git a/examples/device/mtp/src/tusb_config.h b/examples/device/mtp/src/tusb_config.h index 62a5729bf..4d166aa63 100644 --- a/examples/device/mtp/src/tusb_config.h +++ b/examples/device/mtp/src/tusb_config.h @@ -93,6 +93,7 @@ //------------- CLASS -------------// #define CFG_TUD_MTP 1 #define CFG_TUD_MTP_EP_BUFSIZE 512 +#define CFG_TUD_MTP_EP_CONTROL_BUFSIZE 16 // should be enough to hold data in MTP control request //------------- MTP device info -------------// #define CFG_TUD_MTP_DEVICEINFO_EXTENSIONS "microsoft.com: 1.0; " @@ -131,10 +132,6 @@ MTP_OBJ_FORMAT_TEXT, \ MTP_OBJ_FORMAT_PNG -#define CFG_TUD_MANUFACTURER "TinyUsb Manufacturer" -#define CFG_TUD_MODEL "TinyUsb Device" -#define CFG_MTP_INTERFACE (CFG_TUD_MODEL " MTP") - #ifdef __cplusplus } #endif diff --git a/examples/device/mtp/src/usb_descriptors.c b/examples/device/mtp/src/usb_descriptors.c index 4a43a0dcc..810137c46 100644 --- a/examples/device/mtp/src/usb_descriptors.c +++ b/examples/device/mtp/src/usb_descriptors.c @@ -140,10 +140,10 @@ enum { char const *string_desc_arr[] = { (const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409) - CFG_TUD_MANUFACTURER, // 1: Manufacturer - CFG_TUD_MODEL, // 2: Product + "TinyUsb", // 1: Manufacturer + "TinyUsb Device", // 2: Product NULL, // 3: Serials will use unique ID if possible - CFG_MTP_INTERFACE, // 4: MTP Interface + "TinyUSBB MTP", // 4: MTP Interface }; static uint16_t _desc_str[32 + 1]; @@ -168,7 +168,9 @@ uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid) { // Note: the 0xEE index string is a Microsoft OS 1.0 Descriptors. // https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors - if ( !(index < sizeof(string_desc_arr) / sizeof(string_desc_arr[0])) ) return NULL; + if ( !(index < sizeof(string_desc_arr) / sizeof(string_desc_arr[0])) ) { + return NULL; + } const char *str = string_desc_arr[index]; diff --git a/src/class/mtp/mtp.h b/src/class/mtp/mtp.h index b73bd200a..0450c7e36 100644 --- a/src/class/mtp/mtp.h +++ b/src/class/mtp/mtp.h @@ -38,8 +38,6 @@ extern "C" { #endif -typedef uint16_t wchar16_t; - //--------------------------------------------------------------------+ // Media Transfer Protocol Class Constant //--------------------------------------------------------------------+ @@ -759,15 +757,10 @@ typedef struct TU_ATTR_PACKED { }; typedef struct TU_ATTR_PACKED { - uint16_t wLength; uint16_t code; -} mtp_device_status_res_t; - -typedef struct TU_ATTR_PACKED { - uint32_t object_handle; - uint32_t storage_id; - uint32_t parent_object_handle; -} mtp_basic_object_info_t; + uint32_t transaction_id; +} mtp_request_reset_cancel_data_t; +TU_VERIFY_STATIC(sizeof(mtp_request_reset_cancel_data_t) == 6, "size is not correct"); //--------------------------------------------------------------------+ // Container helper function diff --git a/src/class/mtp/mtp_device.c b/src/class/mtp/mtp_device.c index 4e468458d..a72712795 100644 --- a/src/class/mtp/mtp_device.c +++ b/src/class/mtp/mtp_device.c @@ -31,12 +31,11 @@ //--------------------------------------------------------------------+ // INCLUDE //--------------------------------------------------------------------+ -#include "device/dcd.h" // for faking dcd_event_xfer_complete +#include "device/dcd.h" #include "device/usbd.h" #include "device/usbd_pvt.h" #include "mtp_device.h" -#include "mtp_device_storage.h" // Level where CFG_TUSB_DEBUG must be at least for this driver is logged #ifndef CFG_TUD_MTP_LOG_LEVEL @@ -45,7 +44,45 @@ #define TU_LOG_DRV(...) TU_LOG(CFG_TUD_MTP_LOG_LEVEL, __VA_ARGS__) -#define BULK_PACKET_SIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) +//--------------------------------------------------------------------+ +// Weak stubs: invoked if no strong implementation is available +//--------------------------------------------------------------------+ +TU_ATTR_WEAK bool tud_mtp_request_cancel_cb(tud_mtp_request_cb_data_t* cb_data) { + (void) cb_data; + return false; +} +TU_ATTR_WEAK bool tud_mtp_request_device_reset_cb(tud_mtp_request_cb_data_t* cb_data) { + (void) cb_data; + return false; +} +TU_ATTR_WEAK int32_t tud_mtp_request_get_extended_event_cb(tud_mtp_request_cb_data_t* cb_data) { + (void) cb_data; + return -1; +} +TU_ATTR_WEAK int32_t tud_mtp_request_get_device_status_cb(tud_mtp_request_cb_data_t* cb_data) { + (void) cb_data; + return -1; +} +TU_ATTR_WEAK bool tud_mtp_request_vendor_cb(tud_mtp_request_cb_data_t* cb_data) { + (void) cb_data; + return false; +} +TU_ATTR_WEAK int32_t tud_mtp_command_received_cb(tud_mtp_cb_data_t * cb_data) { + (void) cb_data; + return -1; +} +TU_ATTR_WEAK int32_t tud_mtp_data_xfer_cb(tud_mtp_cb_data_t* cb_data) { + (void) cb_data; + return -1; +} +TU_ATTR_WEAK int32_t tud_mtp_data_complete_cb(tud_mtp_cb_data_t* cb_data) { + (void) cb_data; + return -1; +} +TU_ATTR_WEAK int32_t tud_mtp_response_complete_cb(tud_mtp_cb_data_t* cb_data) { + (void) cb_data; + return -1; +} //--------------------------------------------------------------------+ // STRUCT @@ -66,6 +103,8 @@ typedef struct { uint32_t session_id; mtp_container_command_t command; mtp_container_header_t io_header; + + TU_ATTR_ALIGNED(4) uint8_t control_buf[CFG_TUD_MTP_EP_CONTROL_BUFSIZE]; } mtpd_interface_t; typedef struct { @@ -76,16 +115,10 @@ typedef struct { //--------------------------------------------------------------------+ // INTERNAL FUNCTION DECLARATION //--------------------------------------------------------------------+ -static void process_cmd(mtpd_interface_t* p_mtp, tud_mtp_cb_data_t* cb_data); - -//--------------------------------------------------------------------+ -// MTP variable declaration -//--------------------------------------------------------------------+ static mtpd_interface_t _mtpd_itf; CFG_TUD_MEM_SECTION static mtpd_epbuf_t _mtpd_epbuf; -CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN static mtp_device_status_res_t _mtpd_device_status_res; -CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN static mtp_basic_object_info_t _mtpd_soi; +static void preprocess_cmd(mtpd_interface_t* p_mtp, tud_mtp_cb_data_t* cb_data); //--------------------------------------------------------------------+ // Debug @@ -226,7 +259,6 @@ bool tud_mtp_event_send(mtp_event_t* event) { //--------------------------------------------------------------------+ void mtpd_init(void) { tu_memclr(&_mtpd_itf, sizeof(mtpd_interface_t)); - tu_memclr(&_mtpd_soi, sizeof(mtp_basic_object_info_t)); } bool mtpd_deinit(void) { @@ -236,8 +268,6 @@ bool mtpd_deinit(void) { void mtpd_reset(uint8_t rhport) { (void) rhport; tu_memclr(&_mtpd_itf, sizeof(mtpd_interface_t)); - tu_memclr(&_mtpd_epbuf, sizeof(mtpd_epbuf_t)); - tu_memclr(&_mtpd_soi, sizeof(mtp_basic_object_info_t)); } uint16_t mtpd_open(uint8_t rhport, tusb_desc_interface_t const* itf_desc, uint16_t max_len) { @@ -264,7 +294,6 @@ uint16_t mtpd_open(uint8_t rhport, tusb_desc_interface_t const* itf_desc, uint16 // Open endpoint pair TU_ASSERT(usbd_open_edpt_pair(rhport, tu_desc_next(ep_desc), 2, TUSB_XFER_BULK, &p_mtp->ep_out, &p_mtp->ep_in), 0); - TU_ASSERT(prepare_new_command(p_mtp), 0); return mtpd_itf_size; @@ -274,40 +303,63 @@ uint16_t mtpd_open(uint8_t rhport, tusb_desc_interface_t const* itf_desc, uint16 // Driver response accordingly to the request and the transfer stage (setup/data/ack) // return false to stall control endpoint (e.g unsupported request) bool mtpd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const* request) { - if (stage != CONTROL_STAGE_SETUP) { - return true; // nothing to do with DATA & ACK stage - } + mtpd_interface_t* p_mtp = &_mtpd_itf; + tud_mtp_request_cb_data_t cb_data = { + .idx = 0, + .stage = stage, + .request = request, + .buf = p_mtp->control_buf, + .bufsize = tu_le16toh(request->wLength), + }; switch (request->bRequest) { case MTP_REQ_CANCEL: - TU_LOG_DRV(" MTP request: MTP_REQ_CANCEL\n"); - tud_mtp_storage_cancel(); + TU_LOG_DRV(" MTP request: Cancel\n"); + if (stage == CONTROL_STAGE_SETUP) { + return tud_control_xfer(rhport, request, p_mtp->control_buf, CFG_TUD_MTP_EP_CONTROL_BUFSIZE); + } else if (stage == CONTROL_STAGE_ACK) { + return tud_mtp_request_cancel_cb(&cb_data); + } break; case MTP_REQ_GET_EXT_EVENT_DATA: - TU_LOG_DRV(" MTP request: MTP_REQ_GET_EXT_EVENT_DATA\n"); + TU_LOG_DRV(" MTP request: Get Extended Event Data\n"); + if (stage == CONTROL_STAGE_SETUP) { + const int32_t len = tud_mtp_request_get_extended_event_cb(&cb_data); + TU_VERIFY(len > 0); + return tud_control_xfer(rhport,request, p_mtp->control_buf, (uint16_t) len); + } break; case MTP_REQ_RESET: - TU_LOG_DRV(" MTP request: MTP_REQ_RESET\n"); - tud_mtp_storage_reset(); - // Prepare for a new command - TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_out, _mtpd_epbuf.buf, CFG_TUD_MTP_EP_BUFSIZE)); + TU_LOG_DRV(" MTP request: Device Reset\n"); + // used by the host to return the Still Image Capture Device to the Idle state after the Bulk-pipe has stalled + if (stage == CONTROL_STAGE_SETUP) { + // clear stalled + if (usbd_edpt_stalled(rhport, p_mtp->ep_out)) { + usbd_edpt_clear_stall(rhport, p_mtp->ep_out); + } + if (usbd_edpt_stalled(rhport, p_mtp->ep_in)) { + usbd_edpt_clear_stall(rhport, p_mtp->ep_in); + } + } else if (stage == CONTROL_STAGE_ACK) { + prepare_new_command(p_mtp); + return tud_mtp_request_device_reset_cb(&cb_data); + } break; case MTP_REQ_GET_DEVICE_STATUS: { - TU_LOG_DRV(" MTP request: MTP_REQ_GET_DEVICE_STATUS\n"); - uint16_t len = 4; - _mtpd_device_status_res.wLength = len; - // Cancel is synchronous, always answer OK - _mtpd_device_status_res.code = MTP_RESP_OK; - TU_ASSERT(tud_control_xfer(rhport, request, (uint8_t *)&_mtpd_device_status_res , len)); + TU_LOG_DRV(" MTP request: Get Device Status\n"); + if (stage == CONTROL_STAGE_SETUP) { + const int32_t len = tud_mtp_request_get_device_status_cb(&cb_data); + TU_VERIFY(len > 0); + return tud_control_xfer(rhport, request, p_mtp->control_buf, (uint16_t) len); + } break; } default: - TU_LOG_DRV(" MTP request: invalid request\r\n"); - return false; // stall unsupported request + return tud_mtp_request_vendor_cb(&cb_data); } return true; @@ -354,7 +406,7 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t TU_VERIFY(ep_addr == p_mtp->ep_out && p_container->header.type == MTP_CONTAINER_TYPE_COMMAND_BLOCK); memcpy(&p_mtp->command, p_container, sizeof(mtp_container_command_t)); // save new command p_container->header.len = sizeof(mtp_container_header_t); // default container to header only - process_cmd(p_mtp, &cb_data); + preprocess_cmd(p_mtp, &cb_data); if (tud_mtp_command_received_cb(&cb_data) < 0) { p_mtp->phase = MTP_PHASE_ERROR; } @@ -435,7 +487,7 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t //--------------------------------------------------------------------+ // pre-processed commands -void process_cmd(mtpd_interface_t* p_mtp, tud_mtp_cb_data_t* cb_data) { +void preprocess_cmd(mtpd_interface_t* p_mtp, tud_mtp_cb_data_t* cb_data) { switch (p_mtp->command.code) { case MTP_OP_GET_DEVICE_INFO: { tud_mtp_device_info_t dev_info = { diff --git a/src/class/mtp/mtp_device.h b/src/class/mtp/mtp_device.h index 4956628d3..c69f6f12f 100644 --- a/src/class/mtp/mtp_device.h +++ b/src/class/mtp/mtp_device.h @@ -36,6 +36,7 @@ extern "C" { #endif +// callback data for Bulk Only Transfer (BOT) protocol typedef struct { uint8_t idx; // mtp instance const mtp_container_command_t* command_container; @@ -45,6 +46,16 @@ typedef struct { uint32_t total_xferred_bytes; // number of bytes transferred so far in this phase } tud_mtp_cb_data_t; +// callback data for Control requests +typedef struct { + uint8_t idx; + uint8_t stage; // control stage + const tusb_control_request_t* request; + // buffer for data stage + uint8_t* buf; + uint16_t bufsize; +} tud_mtp_request_cb_data_t; + // Number of supported operations, events, device properties, capture formats, playback formats // and max number of characters for strings manufacturer, model, device_version, serial_number #define MTP_DEVICE_INFO_STRUCT(_extension_nchars, _op_count, _event_count, _devprop_count, _capture_count, _playback_count) \ @@ -89,24 +100,47 @@ bool tud_mtp_event_send(mtp_event_t* event); //--------------------------------------------------------------------+ // Control request Callbacks //--------------------------------------------------------------------+ -// bool tud_mtp_control_xfer_cb(uint8_t idx, uint8_t stage, tusb_control_request_t const *p_request); + +// Invoked when received Cancel request. Data is available in callback data's buffer +// return false to stall the request +bool tud_mtp_request_cancel_cb(tud_mtp_request_cb_data_t* cb_data); + +// Invoked when received Device Reset request +// return false to stall the request +bool tud_mtp_request_device_reset_cb(tud_mtp_request_cb_data_t* cb_data); + +// Invoked when received Get Extended Event request. Application fill callback data's buffer for response +// return negative to stall the request +int32_t tud_mtp_request_get_extended_event_cb(tud_mtp_request_cb_data_t* cb_data); + +// Invoked when received Get DeviceStatus request. Application fill callback data's buffer for response +// return negative to stall the request +int32_t tud_mtp_request_get_device_status_cb(tud_mtp_request_cb_data_t* cb_data); + +// Invoked when received vendor-specific request not in the above standard MTP requests +// return false to stall the request +bool tud_mtp_request_vendor_cb(tud_mtp_request_cb_data_t* cb_data); //--------------------------------------------------------------------+ // Bulk only protocol Callbacks //--------------------------------------------------------------------+ -/* Invoked when new command is received. Application fill the cb_data->reply with either DATA or RESPONSE and call - * tud_mtp_data_send() or tud_mtp_response_send(). Return negative to stall the endpoints - */ +// Invoked when new command is received. Application fill the cb_data->io_container and call tud_mtp_data_send() or +// tud_mtp_response_send() for Data or Response phase. +// Return negative to stall the endpoints int32_t tud_mtp_command_received_cb(tud_mtp_cb_data_t * cb_data); -// Invoked when a data packet is transferred, and more data is expected +// Invoked when a data packet is transferred. If data spans over multiple packets, application can use +// total_xferred_bytes and io_container's payload_bytes to determine the offset and remaining bytes to be transferred. +// Return negative to stall the endpoints int32_t tud_mtp_data_xfer_cb(tud_mtp_cb_data_t* cb_data); // Invoked when all bytes in DATA phase is complete. A response packet is expected +// Return negative to stall the endpoints int32_t tud_mtp_data_complete_cb(tud_mtp_cb_data_t* cb_data); // Invoked when response phase is complete +// Return negative to stall the endpoints int32_t tud_mtp_response_complete_cb(tud_mtp_cb_data_t* cb_data); //--------------------------------------------------------------------+ diff --git a/src/class/mtp/mtp_device_storage.h b/src/class/mtp/mtp_device_storage.h deleted file mode 100644 index 30038978f..000000000 --- a/src/class/mtp/mtp_device_storage.h +++ /dev/null @@ -1,93 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2025 Ennebi Elettronica (https://ennebielettronica.com) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * This file is part of the TinyUSB stack. - */ - -#ifndef _TUSB_MTP_DEVICE_STORAGE_H_ -#define _TUSB_MTP_DEVICE_STORAGE_H_ - -#include "common/tusb_common.h" -#include "mtp.h" - -#if (CFG_TUD_ENABLED && CFG_TUD_MTP) - -#ifdef __cplusplus - extern "C" { -#endif - -//--------------------------------------------------------------------+ -// Storage Application Callbacks -//--------------------------------------------------------------------+ - -/* - * The entire MTP functionality is based on object handles, as described in MTP Specs v. 1.1 under 3.4.1. - * The major weakness of the protocol is that those handles are supposed to be unique within a session - * and for every enumerated object. There is no specified lifetime limit or way to control the expiration: - * once given, they have to persist for an indefinite time and number of iterations. - * If the filesystem does not provide unique persistent object handle for every entry, the best approach - * would be to keep a full association between generated handles and full file paths. The suggested - * approach with memory constrained devices is to keep a hard ID associated with each file or a volatile - * ID generated on the fly and invalidated on each operation that may rearrange the order. - * In order to invalidate existing IDS, it might be necessary to invalidate the whole session from - * the device side. - * Depending on the application, the handle could be also be the file name or a tag (i.e. host-only file access) - */ - -// Format the specified storage -mtp_response_t tud_mtp_storage_format(uint32_t storage_id); - -// Called with the creation of a new object is requested. -// The handle of the new object shall be returned in new_object_handle. -// The structure info contains the information to be used for file creation, as passted by the host. -// Note that the variable information (e.g. wstring file name, dates and tags shall be retrieved by using the library functions) -mtp_response_t tud_mtp_storage_object_write_info(uint32_t storage_id, uint32_t parent_object, uint32_t *new_object_handle, const mtp_object_info_header_t *info); - -// Write object data -// -// The function shall open the object for writing if not already open. -// The binary data shall be written to the file in full before this function is returned. -mtp_response_t tud_mtp_storage_object_write(uint32_t object_handle, const uint8_t *buffer, uint32_t buffer_size); - -// Move an object to a new parent -mtp_response_t tud_mtp_storage_object_move(uint32_t object_handle, uint32_t new_parent_object_handle); - -// Delete the specified object -mtp_response_t tud_mtp_storage_object_delete(uint32_t object_handle); - -// Issued when IO operation has been terminated (e.g. read, traverse), close open file handles -void tud_mtp_storage_object_done(void); - -// Cancel any pending operation. Current operation shall be discarded. -void tud_mtp_storage_cancel(void); - -// Restore the operation out of reset. Cancel any pending operation and close the session. -void tud_mtp_storage_reset(void); - -#ifdef __cplusplus - } -#endif - -#endif /* CFG_TUD_ENABLED && CFG_TUD_MTP */ - -#endif /* _TUSB_MTP_DEVICE_STORAGE_H_ */ From adce3bbac02f81c0e7e17f4fcbd1deb83bef7cb4 Mon Sep 17 00:00:00 2001 From: hathach Date: Sat, 27 Sep 2025 17:30:27 +0700 Subject: [PATCH 29/43] implement serial for MTP --- README.rst | 1 + examples/device/mtp/src/mtp_fs_example.c | 12 ++++++++---- src/class/mtp/mtp.h | 4 ++-- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/README.rst b/README.rst index d6830e707..2c127bfdc 100644 --- a/README.rst +++ b/README.rst @@ -60,6 +60,7 @@ Supports multiple device configurations by dynamically changing USB descriptors, - Human Interface Device (HID): Generic (In & Out), Keyboard, Mouse, Gamepad etc ... - Mass Storage Class (MSC): with multiple LUNs - Musical Instrument Digital Interface (MIDI) +- Media Transfer Protocol (MTP/PTP) - Network with RNDIS, Ethernet Control Model (ECM), Network Control Model (NCM) - Test and Measurement Class (USBTMC) - Video class 1.5 (UVC): work in progress diff --git a/examples/device/mtp/src/mtp_fs_example.c b/examples/device/mtp/src/mtp_fs_example.c index 9d90f7663..72fff5402 100644 --- a/examples/device/mtp/src/mtp_fs_example.c +++ b/examples/device/mtp/src/mtp_fs_example.c @@ -23,7 +23,9 @@ * */ +#include "bsp/board_api.h" #include "tusb.h" + #include "tinyusb_logo_png.h" //--------------------------------------------------------------------+ @@ -68,7 +70,6 @@ storage_info_t storage_info = { } }; - //--------------------------------------------------------------------+ // MTP FILESYSTEM //--------------------------------------------------------------------+ @@ -80,8 +81,7 @@ storage_info_t storage_info = { #else #define FS_MAX_CAPACITY_BYTES (4 * 1024UL) - // object data buffer (excluding 2 predefined files) - // with simple allocation pointer + // object data buffer (excluding 2 predefined files) with simple allocation pointer uint8_t fs_buf[FS_MAX_CAPACITY_BYTES]; #endif size_t fs_buf_head = 0; @@ -234,7 +234,11 @@ int32_t tud_mtp_command_received_cb(tud_mtp_cb_data_t* cb_data) { mtp_container_add_cstring(io_container, DEV_INFO_MANUFACTURER); mtp_container_add_cstring(io_container, DEV_INFO_MODEL); mtp_container_add_cstring(io_container, DEV_INFO_VERSION); - mtp_container_add_cstring(io_container, DEV_INFO_SERIAL); + + uint16_t serial_utf16[32]; + board_usb_get_serial(serial_utf16, 32); + serial_utf16[31] = 0; // ensure null termination + mtp_container_add_string(io_container, serial_utf16); tud_mtp_data_send(io_container); break; diff --git a/src/class/mtp/mtp.h b/src/class/mtp/mtp.h index 0450c7e36..a8123b380 100644 --- a/src/class/mtp/mtp.h +++ b/src/class/mtp/mtp.h @@ -768,7 +768,7 @@ TU_VERIFY_STATIC(sizeof(mtp_request_reset_cancel_data_t) == 6, "size is not corr //--------------------------------------------------------------------+ // return payload buffer for next write -TU_ATTR_ALWAYS_INLINE static inline uint8_t* mtp_container_payload_next(mtp_container_info_t* p_container) { +TU_ATTR_ALWAYS_INLINE static inline uint8_t* mtp_container_payload_ptr(mtp_container_info_t* p_container) { // only 1st packet include header uint32_t pos = p_container->header->len - sizeof(mtp_container_header_t); while (pos > CFG_TUD_MTP_EP_BUFSIZE) { @@ -779,7 +779,7 @@ TU_ATTR_ALWAYS_INLINE static inline uint8_t* mtp_container_payload_next(mtp_cont // only add_raw does partial copy TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_add_raw(mtp_container_info_t* p_container, const void* data, uint32_t len) { - uint8_t* buf = mtp_container_payload_next(p_container); + uint8_t* buf = mtp_container_payload_ptr(p_container); const uint32_t added_len = tu_min32(len, CFG_TUD_MTP_EP_BUFSIZE - p_container->header->len); if (added_len > 0) { memcpy(buf, data, added_len); From bc688ccbad78053ee47306307a91e8f2d9d51e38 Mon Sep 17 00:00:00 2001 From: hathach Date: Sat, 27 Sep 2025 18:28:11 +0700 Subject: [PATCH 30/43] add edpt claim for public API --- src/class/mtp/mtp_device.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/class/mtp/mtp_device.c b/src/class/mtp/mtp_device.c index a72712795..86d18a942 100644 --- a/src/class/mtp/mtp_device.c +++ b/src/class/mtp/mtp_device.c @@ -218,6 +218,7 @@ static bool mtpd_data_xfer(mtp_container_info_t* p_container, uint8_t ep_addr) { const uint16_t xact_len = tu_min16((uint16_t) (p_mtp->total_len - p_mtp->xferred_len), CFG_TUD_MTP_EP_BUFSIZE); if (xact_len) { // already transferred all bytes in header's length. Application make an unnecessary extra call + TU_VERIFY(usbd_edpt_claim(p_mtp->rhport, ep_addr)); TU_ASSERT(usbd_edpt_xfer(p_mtp->rhport, ep_addr, _mtpd_epbuf.buf, xact_len)); } return true; @@ -236,8 +237,8 @@ bool tud_mtp_response_send(mtp_container_info_t* p_container) { p_mtp->phase = MTP_PHASE_RESPONSE; p_container->header->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; p_container->header->transaction_id = p_mtp->command.transaction_id; - TU_ASSERT(usbd_edpt_xfer(p_mtp->rhport, p_mtp->ep_in, _mtpd_epbuf.buf, (uint16_t)p_container->header->len)); - return true; + TU_VERIFY(usbd_edpt_claim(p_mtp->rhport, p_mtp->ep_in)); + return usbd_edpt_xfer(p_mtp->rhport, p_mtp->ep_in, _mtpd_epbuf.buf, (uint16_t)p_container->header->len); } bool tud_mtp_mounted(void) { @@ -250,7 +251,6 @@ bool tud_mtp_event_send(mtp_event_t* event) { TU_VERIFY(p_mtp->ep_event != 0); _mtpd_epbuf.buf_event = *event; TU_VERIFY(usbd_edpt_claim(p_mtp->rhport, p_mtp->ep_event)); // Claim the endpoint - return usbd_edpt_xfer(p_mtp->rhport, p_mtp->ep_event, (uint8_t*) &_mtpd_epbuf.buf_event, sizeof(mtp_event_t)); } From a09c65c4e4860d215648a3fc2d5f768f292e0e01 Mon Sep 17 00:00:00 2001 From: hathach Date: Sat, 27 Sep 2025 18:43:46 +0700 Subject: [PATCH 31/43] make command container more consistent --- examples/device/mtp/src/mtp_fs_example.c | 6 +++--- src/class/mtp/mtp.h | 5 +---- src/class/mtp/mtp_device.c | 10 +++++----- tools/iar_template.ipcf | 1 - 4 files changed, 9 insertions(+), 13 deletions(-) diff --git a/examples/device/mtp/src/mtp_fs_example.c b/examples/device/mtp/src/mtp_fs_example.c index 72fff5402..5b582e7db 100644 --- a/examples/device/mtp/src/mtp_fs_example.c +++ b/examples/device/mtp/src/mtp_fs_example.c @@ -228,7 +228,7 @@ int32_t tud_mtp_command_received_cb(tud_mtp_cb_data_t* cb_data) { const mtp_container_command_t* command = cb_data->command_container; mtp_container_info_t* io_container = &cb_data->io_container; uint16_t resp_code = 0; - switch (command->code) { + switch (command->header.code) { case MTP_OP_GET_DEVICE_INFO: { // Device info is already prepared up to playback formats. Application only need to add string fields mtp_container_add_cstring(io_container, DEV_INFO_MANUFACTURER); @@ -452,7 +452,7 @@ int32_t tud_mtp_data_xfer_cb(tud_mtp_cb_data_t* cb_data) { const mtp_container_command_t* command = cb_data->command_container; mtp_container_info_t* io_container = &cb_data->io_container; uint16_t resp_code = 0; - switch (command->code) { + switch (command->header.code) { case MTP_OP_GET_OBJECT: { // File contents span over multiple xfers const uint32_t obj_handle = command->params[0]; @@ -540,7 +540,7 @@ int32_t tud_mtp_data_xfer_cb(tud_mtp_cb_data_t* cb_data) { int32_t tud_mtp_data_complete_cb(tud_mtp_cb_data_t* cb_data) { const mtp_container_command_t* command = cb_data->command_container; mtp_container_info_t* resp = &cb_data->io_container; - switch (command->code) { + switch (command->header.code) { case MTP_OP_SEND_OBJECT_INFO: { fs_file_t* f = fs_get_file(send_obj_handle); if (f == NULL) { diff --git a/src/class/mtp/mtp.h b/src/class/mtp/mtp.h index a8123b380..84fd1b429 100644 --- a/src/class/mtp/mtp.h +++ b/src/class/mtp/mtp.h @@ -649,10 +649,7 @@ typedef struct TU_ATTR_PACKED { TU_VERIFY_STATIC(sizeof(mtp_container_header_t) == 12, "size is not correct"); typedef struct TU_ATTR_PACKED { - uint32_t len; - uint16_t type; - uint16_t code; - uint32_t transaction_id; + mtp_container_header_t header; uint32_t params[5]; } mtp_container_command_t; TU_VERIFY_STATIC(sizeof(mtp_container_command_t) == 32, "size is not correct"); diff --git a/src/class/mtp/mtp_device.c b/src/class/mtp/mtp_device.c index 86d18a942..f3f594dcf 100644 --- a/src/class/mtp/mtp_device.c +++ b/src/class/mtp/mtp_device.c @@ -204,7 +204,7 @@ static bool mtpd_data_xfer(mtp_container_info_t* p_container, uint8_t ep_addr) { if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN) { p_mtp->total_len = p_container->header->len; p_container->header->type = MTP_CONTAINER_TYPE_DATA_BLOCK; - p_container->header->transaction_id = p_mtp->command.transaction_id; + p_container->header->transaction_id = p_mtp->command.header.transaction_id; p_mtp->io_header = *p_container->header; // save header for subsequent data } else { // OUT transfer: total length is at least max packet size @@ -236,7 +236,7 @@ bool tud_mtp_response_send(mtp_container_info_t* p_container) { mtpd_interface_t* p_mtp = &_mtpd_itf; p_mtp->phase = MTP_PHASE_RESPONSE; p_container->header->type = MTP_CONTAINER_TYPE_RESPONSE_BLOCK; - p_container->header->transaction_id = p_mtp->command.transaction_id; + p_container->header->transaction_id = p_mtp->command.header.transaction_id; TU_VERIFY(usbd_edpt_claim(p_mtp->rhport, p_mtp->ep_in)); return usbd_edpt_xfer(p_mtp->rhport, p_mtp->ep_in, _mtpd_epbuf.buf, (uint16_t)p_container->header->len); } @@ -376,8 +376,8 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t mtp_generic_container_t* p_container = (mtp_generic_container_t*) _mtpd_epbuf.buf; #if CFG_TUSB_DEBUG >= CFG_TUD_MTP_LOG_LEVEL - tu_lookup_find(&_mtp_op_table, p_mtp->command.code); - TU_LOG_DRV(" MTP %s: %s phase\r\n", (const char *) tu_lookup_find(&_mtp_op_table, p_mtp->command.code), + tu_lookup_find(&_mtp_op_table, p_mtp->command.header.code); + TU_LOG_DRV(" MTP %s: %s phase\r\n", (const char *) tu_lookup_find(&_mtp_op_table, p_mtp->command.header.code), _mtp_phase_str[p_mtp->phase]); #endif @@ -488,7 +488,7 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t // pre-processed commands void preprocess_cmd(mtpd_interface_t* p_mtp, tud_mtp_cb_data_t* cb_data) { - switch (p_mtp->command.code) { + switch (p_mtp->command.header.code) { case MTP_OP_GET_DEVICE_INFO: { tud_mtp_device_info_t dev_info = { .standard_version = 100, diff --git a/tools/iar_template.ipcf b/tools/iar_template.ipcf index c93795b9c..2581a4702 100644 --- a/tools/iar_template.ipcf +++ b/tools/iar_template.ipcf @@ -62,7 +62,6 @@ $TUSB_DIR$/src/class/mtp/mtp_device.c $TUSB_DIR$/src/class/mtp/mtp.h $TUSB_DIR$/src/class/mtp/mtp_device.h - $TUSB_DIR$/src/class/mtp/mtp_device_storage.h $TUSB_DIR$/src/class/net/ecm_rndis_device.c From 4f3a5f92fa21307b1f1c9700cd041e945a627f63 Mon Sep 17 00:00:00 2001 From: hathach Date: Mon, 29 Sep 2025 15:02:51 +0700 Subject: [PATCH 32/43] refactor mtp examples, add phase and session id to callback data --- examples/device/mtp/src/mtp_fs_example.c | 641 ++++++++++++----------- src/class/mtp/mtp_device.c | 3 + src/class/mtp/mtp_device.h | 6 + 3 files changed, 358 insertions(+), 292 deletions(-) diff --git a/examples/device/mtp/src/mtp_fs_example.c b/examples/device/mtp/src/mtp_fs_example.c index 5b582e7db..09a5f4ffc 100644 --- a/examples/device/mtp/src/mtp_fs_example.c +++ b/examples/device/mtp/src/mtp_fs_example.c @@ -130,14 +130,50 @@ static fs_file_t fs_objects[FS_MAX_FILE_COUNT] = { } }; - enum { SUPPORTED_STORAGE_ID = 0x00010001u // physical = 1, logical = 1 }; +static int32_t fs_get_device_info(tud_mtp_cb_data_t* cb_data); +static int32_t fs_open_close_session(tud_mtp_cb_data_t* cb_data); +static int32_t fs_get_storage_ids(tud_mtp_cb_data_t* cb_data); +static int32_t fs_get_storage_info(tud_mtp_cb_data_t* cb_data); +static int32_t fs_get_device_properties(tud_mtp_cb_data_t* cb_data); +static int32_t fs_get_object_handles(tud_mtp_cb_data_t* cb_data); +static int32_t fs_get_object_info(tud_mtp_cb_data_t* cb_data); +static int32_t fs_get_object(tud_mtp_cb_data_t* cb_data); +static int32_t fs_delete_object(tud_mtp_cb_data_t* cb_data); +static int32_t fs_send_object_info(tud_mtp_cb_data_t* cb_data); +static int32_t fs_send_object(tud_mtp_cb_data_t* cb_data); + +typedef int32_t (*fs_op_handler_t)(tud_mtp_cb_data_t* cb_data); +typedef struct { + uint32_t op_code; + fs_op_handler_t handler; +}fs_op_handler_dict_t; + +fs_op_handler_dict_t fs_op_handler_dict[] = { + { MTP_OP_GET_DEVICE_INFO, fs_get_device_info }, + { MTP_OP_OPEN_SESSION, fs_open_close_session }, + { MTP_OP_CLOSE_SESSION, fs_open_close_session }, + { MTP_OP_GET_STORAGE_IDS, fs_get_storage_ids }, + { MTP_OP_GET_STORAGE_INFO, fs_get_storage_info }, + { MTP_OP_GET_DEVICE_PROP_DESC, fs_get_device_properties }, + { MTP_OP_GET_DEVICE_PROP_VALUE, fs_get_device_properties }, + { MTP_OP_GET_OBJECT_HANDLES, fs_get_object_handles }, + { MTP_OP_GET_OBJECT_INFO, fs_get_object_info }, + { MTP_OP_GET_OBJECT, fs_get_object }, + { MTP_OP_DELETE_OBJECT, fs_delete_object }, + { MTP_OP_SEND_OBJECT_INFO, fs_send_object_info }, + { MTP_OP_SEND_OBJECT, fs_send_object }, +}; + static bool is_session_opened = false; static uint32_t send_obj_handle = 0; +//--------------------------------------------------------------------+ +// +//--------------------------------------------------------------------+ // Get pointer to object info from handle static inline fs_file_t* fs_get_file(uint32_t handle) { if (handle == 0 || handle > FS_MAX_FILE_COUNT) { @@ -227,314 +263,54 @@ int32_t tud_mtp_request_get_device_status_cb(tud_mtp_request_cb_data_t* cb_data) int32_t tud_mtp_command_received_cb(tud_mtp_cb_data_t* cb_data) { const mtp_container_command_t* command = cb_data->command_container; mtp_container_info_t* io_container = &cb_data->io_container; - uint16_t resp_code = 0; - switch (command->header.code) { - case MTP_OP_GET_DEVICE_INFO: { - // Device info is already prepared up to playback formats. Application only need to add string fields - mtp_container_add_cstring(io_container, DEV_INFO_MANUFACTURER); - mtp_container_add_cstring(io_container, DEV_INFO_MODEL); - mtp_container_add_cstring(io_container, DEV_INFO_VERSION); - - uint16_t serial_utf16[32]; - board_usb_get_serial(serial_utf16, 32); - serial_utf16[31] = 0; // ensure null termination - mtp_container_add_string(io_container, serial_utf16); - - tud_mtp_data_send(io_container); + fs_op_handler_t handler = NULL; + for (size_t i = 0; i < TU_ARRAY_SIZE(fs_op_handler_dict); i++) { + if (fs_op_handler_dict[i].op_code == command->header.code) { + handler = fs_op_handler_dict[i].handler; break; } - - case MTP_OP_OPEN_SESSION: - if (is_session_opened) { - resp_code = MTP_RESP_SESSION_ALREADY_OPEN; - }else { - resp_code = MTP_RESP_OK; - } - is_session_opened = true; - break; - - case MTP_OP_CLOSE_SESSION: - if (!is_session_opened) { - resp_code = MTP_RESP_SESSION_NOT_OPEN; - } else { - resp_code = MTP_RESP_OK; - } - is_session_opened = false; - break; - - case MTP_OP_GET_STORAGE_IDS: { - uint32_t storage_ids [] = { SUPPORTED_STORAGE_ID }; - mtp_container_add_auint32(io_container, 1, storage_ids); - tud_mtp_data_send(io_container); - break; - } - - case MTP_OP_GET_STORAGE_INFO: { - const uint32_t storage_id = command->params[0]; - TU_VERIFY(SUPPORTED_STORAGE_ID == storage_id, -1); - // update storage info with current free space - storage_info.max_capacity_in_bytes = sizeof(README_TXT_CONTENT) + logo_len + FS_MAX_CAPACITY_BYTES; - storage_info.free_space_in_objects = FS_MAX_FILE_COUNT - fs_get_file_count(); - storage_info.free_space_in_bytes = FS_MAX_CAPACITY_BYTES-fs_buf_head; - mtp_container_add_raw(io_container, &storage_info, sizeof(storage_info)); - tud_mtp_data_send(io_container); - break; - } - - case MTP_OP_GET_DEVICE_PROP_DESC: { - const uint16_t dev_prop_code = (uint16_t) command->params[0]; - mtp_device_prop_desc_header_t device_prop_header; - device_prop_header.device_property_code = dev_prop_code; - switch (dev_prop_code) { - case MTP_DEV_PROP_DEVICE_FRIENDLY_NAME: - device_prop_header.datatype = MTP_DATA_TYPE_STR; - device_prop_header.get_set = MTP_MODE_GET; - mtp_container_add_raw(io_container, &device_prop_header, sizeof(device_prop_header)); - mtp_container_add_cstring(io_container, DEV_PROP_FRIENDLY_NAME); // factory - mtp_container_add_cstring(io_container, DEV_PROP_FRIENDLY_NAME); // current - mtp_container_add_uint8(io_container, 0); // no form - tud_mtp_data_send(io_container); - break; - - default: - resp_code = MTP_RESP_PARAMETER_NOT_SUPPORTED; - break; - } - break; - } - - case MTP_OP_GET_DEVICE_PROP_VALUE: { - const uint16_t dev_prop_code = (uint16_t) command->params[0]; - switch (dev_prop_code) { - case MTP_DEV_PROP_DEVICE_FRIENDLY_NAME: - mtp_container_add_cstring(io_container, DEV_PROP_FRIENDLY_NAME); - tud_mtp_data_send(io_container); - break; - - default: - resp_code = MTP_RESP_PARAMETER_NOT_SUPPORTED; - break; - } - break; - } - - case MTP_OP_GET_OBJECT_HANDLES: { - const uint32_t storage_id = command->params[0]; - const uint32_t obj_format = command->params[1]; // optional - (void) obj_format; - const uint32_t parent_handle = command->params[2]; // folder handle, 0xFFFFFFFF is root - if (storage_id != 0xFFFFFFFF && storage_id != SUPPORTED_STORAGE_ID) { - resp_code = MTP_RESP_INVALID_STORAGE_ID; - } else { - uint32_t handles[FS_MAX_FILE_COUNT] = { 0 }; - uint32_t count = 0; - for (uint8_t i = 0; i < FS_MAX_FILE_COUNT; i++) { - fs_file_t* f = &fs_objects[i]; - if (fs_file_exist(f) && - (parent_handle == f->parent || (parent_handle == 0xFFFFFFFF && f->parent == 0))) { - handles[count++] = i + 1; // handle is index + 1 - } - } - mtp_container_add_auint32(io_container, count, handles); - tud_mtp_data_send(io_container); - } - break; - } - - case MTP_OP_GET_OBJECT_INFO: { - const uint32_t obj_handle = command->params[0]; - fs_file_t* f = fs_get_file(obj_handle); - if (f == NULL) { - resp_code = MTP_RESP_INVALID_OBJECT_HANDLE; - } else { - mtp_object_info_header_t obj_info_header = { - .storage_id = SUPPORTED_STORAGE_ID, - .object_format = f->object_format, - .protection_status = f->protection_status, - .object_compressed_size = f->size, - .thumb_format = MTP_OBJ_FORMAT_UNDEFINED, - .thumb_compressed_size = 0, - .thumb_pix_width = 0, - .thumb_pix_height = 0, - .image_pix_width = f->image_pix_width, - .image_pix_height = f->image_pix_height, - .image_bit_depth = f->image_bit_depth, - .parent_object = f->parent, - .association_type = f->association_type, - .association_desc = 0, - .sequence_number = 0 - }; - mtp_container_add_raw(io_container, &obj_info_header, sizeof(obj_info_header)); - mtp_container_add_string(io_container, f->name); - mtp_container_add_cstring(io_container, FS_FIXED_DATETIME); - mtp_container_add_cstring(io_container, FS_FIXED_DATETIME); - mtp_container_add_cstring(io_container, ""); // keywords, not used - - tud_mtp_data_send(io_container); - } - break; - } - - case MTP_OP_GET_OBJECT: { - const uint32_t obj_handle = command->params[0]; - fs_file_t* f = fs_get_file(obj_handle); - if (f == NULL) { - resp_code = MTP_RESP_INVALID_OBJECT_HANDLE; - } else { - // If file contents is larger than CFG_TUD_MTP_EP_BUFSIZE, only partial data is added here - // the rest will be sent in tud_mtp_data_more_cb - mtp_container_add_raw(io_container, f->data, f->size); - tud_mtp_data_send(io_container); - } - break; - } - - case MTP_OP_SEND_OBJECT_INFO: { - const uint32_t storage_id = command->params[0]; - const uint32_t parent_handle = command->params[1]; // folder handle, 0xFFFFFFFF is root - (void) parent_handle; - if (!is_session_opened) { - resp_code = MTP_RESP_SESSION_NOT_OPEN; - } else if (storage_id != 0xFFFFFFFF && storage_id != SUPPORTED_STORAGE_ID) { - resp_code = MTP_RESP_INVALID_STORAGE_ID; - } else { - tud_mtp_data_receive(io_container); - } - break; - } - - case MTP_OP_SEND_OBJECT: { - fs_file_t* f = fs_get_file(send_obj_handle); - if (f == NULL) { - resp_code = MTP_RESP_INVALID_OBJECT_HANDLE; - } else { - io_container->header->len += f->size; - tud_mtp_data_receive(io_container); - } - break; - } - - case MTP_OP_DELETE_OBJECT: { - if (!is_session_opened) { - resp_code = MTP_RESP_SESSION_NOT_OPEN; - } else { - const uint32_t obj_handle = command->params[0]; - const uint32_t obj_format = command->params[1]; // optional - (void) obj_format; - fs_file_t* f = fs_get_file(obj_handle); - if (f == NULL) { - resp_code = MTP_RESP_INVALID_OBJECT_HANDLE; - break; - } - - // delete object by clear the name - f->name[0] = 0; - resp_code = MTP_RESP_OK; - } - break; - } - - default: - resp_code = MTP_RESP_OPERATION_NOT_SUPPORTED; - break; } - // send response if needed - if (resp_code != 0) { - io_container->header->code = resp_code; - tud_mtp_response_send(io_container); + int32_t resp_code; + if (handler == NULL) { + resp_code = MTP_RESP_OPERATION_NOT_SUPPORTED; + } else { + resp_code = handler(cb_data); + if (resp_code > MTP_RESP_UNDEFINED) { + // send response if needed + io_container->header->code = (uint16_t)resp_code; + tud_mtp_response_send(io_container); + } } - return 0; + return resp_code; } int32_t tud_mtp_data_xfer_cb(tud_mtp_cb_data_t* cb_data) { const mtp_container_command_t* command = cb_data->command_container; mtp_container_info_t* io_container = &cb_data->io_container; - uint16_t resp_code = 0; - switch (command->header.code) { - case MTP_OP_GET_OBJECT: { - // File contents span over multiple xfers - const uint32_t obj_handle = command->params[0]; - fs_file_t* f = fs_get_file(obj_handle); - if (f == NULL) { - resp_code = MTP_RESP_INVALID_OBJECT_HANDLE; - } else { - // file contents offset is xferred byte minus header size - const uint32_t offset = cb_data->total_xferred_bytes - sizeof(mtp_container_header_t); - const uint32_t xact_len = tu_min32(f->size - offset, io_container->payload_bytes); - memcpy(io_container->payload, f->data + offset, xact_len); - tud_mtp_data_send(io_container); - } + + fs_op_handler_t handler = NULL; + for (size_t i = 0; i < TU_ARRAY_SIZE(fs_op_handler_dict); i++) { + if (fs_op_handler_dict[i].op_code == command->header.code) { + handler = fs_op_handler_dict[i].handler; break; } - - case MTP_OP_SEND_OBJECT_INFO: { - mtp_object_info_header_t* obj_info = (mtp_object_info_header_t*) io_container->payload; - if (obj_info->storage_id != 0 && obj_info->storage_id != SUPPORTED_STORAGE_ID) { - resp_code = MTP_RESP_INVALID_STORAGE_ID; - break; - } - - if (obj_info->parent_object) { - fs_file_t* parent = fs_get_file(obj_info->parent_object); - if (parent == NULL || !parent->association_type) { - resp_code = MTP_RESP_INVALID_PARENT_OBJECT; - break; - } - } - - uint8_t* f_buf = fs_malloc(obj_info->object_compressed_size); - if (f_buf == NULL) { - resp_code = MTP_RESP_STORE_FULL; - break; - } - fs_file_t* f = fs_create_file(); - if (f == NULL) { - resp_code = MTP_RESP_STORE_FULL; - break; - } - - f->object_format = obj_info->object_format; - f->protection_status = obj_info->protection_status; - f->image_pix_width = obj_info->image_pix_width; - f->image_pix_height = obj_info->image_pix_height; - f->image_bit_depth = obj_info->image_bit_depth; - f->parent = obj_info->parent_object; - f->association_type = obj_info->association_type; - f->size = obj_info->object_compressed_size; - f->data = f_buf; - uint8_t* buf = io_container->payload + sizeof(mtp_object_info_header_t); - mtp_container_get_string(buf, f->name); - // ignore date created/modified/keywords - break; - } - - case MTP_OP_SEND_OBJECT: { - fs_file_t* f = fs_get_file(send_obj_handle); - if (f == NULL) { - resp_code = MTP_RESP_INVALID_OBJECT_HANDLE; - } else { - // file contents offset is total xferred minus header size minus last received chunk - const uint32_t offset = cb_data->total_xferred_bytes - sizeof(mtp_container_header_t) - io_container->payload_bytes; - memcpy(f->data + offset, io_container->payload, io_container->payload_bytes); - tud_mtp_data_receive(io_container); // receive more data if needed - } - break; - } - - default: - resp_code = MTP_RESP_OPERATION_NOT_SUPPORTED; - break; } - // send response if needed - if (resp_code != 0) { - io_container->header->code = resp_code; - tud_mtp_response_send(io_container); + int32_t resp_code; + if (handler == NULL) { + resp_code = MTP_RESP_OPERATION_NOT_SUPPORTED; + } else { + resp_code = handler(cb_data); + if (resp_code > MTP_RESP_UNDEFINED) { + // send response if needed + io_container->header->code = (uint16_t)resp_code; + tud_mtp_response_send(io_container); + } } - return 0; // 0 mean data/response is sent already + return 0; } int32_t tud_mtp_data_complete_cb(tud_mtp_cb_data_t* cb_data) { @@ -568,3 +344,284 @@ int32_t tud_mtp_response_complete_cb(tud_mtp_cb_data_t* cb_data) { (void) cb_data; return 0; // nothing to do } + +//--------------------------------------------------------------------+ +// File System Handlers +//--------------------------------------------------------------------+ +static int32_t fs_get_device_info(tud_mtp_cb_data_t* cb_data) { + // Device info is already prepared up to playback formats. Application only need to add string fields + mtp_container_info_t* io_container = &cb_data->io_container; + mtp_container_add_cstring(io_container, DEV_INFO_MANUFACTURER); + mtp_container_add_cstring(io_container, DEV_INFO_MODEL); + mtp_container_add_cstring(io_container, DEV_INFO_VERSION); + + uint16_t serial_utf16[32]; + board_usb_get_serial(serial_utf16, 32); + serial_utf16[31] = 0; // ensure null termination + mtp_container_add_string(io_container, serial_utf16); + + tud_mtp_data_send(io_container); + return 0; +} + +static int32_t fs_open_close_session(tud_mtp_cb_data_t* cb_data) { + const mtp_container_command_t* command = cb_data->command_container; + if (command->header.code == MTP_OP_OPEN_SESSION) { + if (is_session_opened) { + return MTP_RESP_SESSION_ALREADY_OPEN; + } + is_session_opened = true; + } else { // close session + if (!is_session_opened) { + return MTP_RESP_SESSION_NOT_OPEN; + } + is_session_opened = false; + } + return MTP_RESP_OK; +} + +static int32_t fs_get_storage_ids(tud_mtp_cb_data_t* cb_data) { + mtp_container_info_t* io_container = &cb_data->io_container; + uint32_t storage_ids [] = { SUPPORTED_STORAGE_ID }; + mtp_container_add_auint32(io_container, 1, storage_ids); + tud_mtp_data_send(io_container); + return 0; +} + +static int32_t fs_get_storage_info(tud_mtp_cb_data_t* cb_data) { + const mtp_container_command_t* command = cb_data->command_container; + mtp_container_info_t* io_container = &cb_data->io_container; + const uint32_t storage_id = command->params[0]; + TU_VERIFY(SUPPORTED_STORAGE_ID == storage_id, -1); + // update storage info with current free space + storage_info.max_capacity_in_bytes = sizeof(README_TXT_CONTENT) + logo_len + FS_MAX_CAPACITY_BYTES; + storage_info.free_space_in_objects = FS_MAX_FILE_COUNT - fs_get_file_count(); + storage_info.free_space_in_bytes = FS_MAX_CAPACITY_BYTES-fs_buf_head; + mtp_container_add_raw(io_container, &storage_info, sizeof(storage_info)); + tud_mtp_data_send(io_container); + return 0; +} + +static int32_t fs_get_device_properties(tud_mtp_cb_data_t* cb_data) { + const mtp_container_command_t* command = cb_data->command_container; + mtp_container_info_t* io_container = &cb_data->io_container; + const uint16_t dev_prop_code = (uint16_t) command->params[0]; + + if (command->header.code == MTP_OP_GET_DEVICE_PROP_DESC) { + // get describing dataset + mtp_device_prop_desc_header_t device_prop_header; + device_prop_header.device_property_code = dev_prop_code; + switch (dev_prop_code) { + case MTP_DEV_PROP_DEVICE_FRIENDLY_NAME: + device_prop_header.datatype = MTP_DATA_TYPE_STR; + device_prop_header.get_set = MTP_MODE_GET; + mtp_container_add_raw(io_container, &device_prop_header, sizeof(device_prop_header)); + mtp_container_add_cstring(io_container, DEV_PROP_FRIENDLY_NAME); // factory + mtp_container_add_cstring(io_container, DEV_PROP_FRIENDLY_NAME); // current + mtp_container_add_uint8(io_container, 0); // no form + tud_mtp_data_send(io_container); + break; + + default: + return MTP_RESP_PARAMETER_NOT_SUPPORTED; + } + } else { + // get value + switch (dev_prop_code) { + case MTP_DEV_PROP_DEVICE_FRIENDLY_NAME: + mtp_container_add_cstring(io_container, DEV_PROP_FRIENDLY_NAME); + tud_mtp_data_send(io_container); + break; + + default: + return MTP_RESP_PARAMETER_NOT_SUPPORTED; + } + } + return 0; +} + +static int32_t fs_get_object_handles(tud_mtp_cb_data_t* cb_data) { + const mtp_container_command_t* command = cb_data->command_container; + mtp_container_info_t* io_container = &cb_data->io_container; + + const uint32_t storage_id = command->params[0]; + const uint32_t obj_format = command->params[1]; // optional + const uint32_t parent_handle = command->params[2]; // folder handle, 0xFFFFFFFF is root + (void)obj_format; + + if (storage_id != 0xFFFFFFFF && storage_id != SUPPORTED_STORAGE_ID) { + return MTP_RESP_INVALID_STORAGE_ID; + } + + uint32_t handles[FS_MAX_FILE_COUNT] = { 0 }; + uint32_t count = 0; + for (uint8_t i = 0; i < FS_MAX_FILE_COUNT; i++) { + fs_file_t* f = &fs_objects[i]; + if (fs_file_exist(f) && + (parent_handle == f->parent || (parent_handle == 0xFFFFFFFF && f->parent == 0))) { + handles[count++] = i + 1; // handle is index + 1 + } + } + mtp_container_add_auint32(io_container, count, handles); + tud_mtp_data_send(io_container); + + return 0; +} + +static int32_t fs_get_object_info(tud_mtp_cb_data_t* cb_data) { + const mtp_container_command_t* command = cb_data->command_container; + mtp_container_info_t* io_container = &cb_data->io_container; + const uint32_t obj_handle = command->params[0]; + fs_file_t* f = fs_get_file(obj_handle); + if (f == NULL) { + return MTP_RESP_INVALID_OBJECT_HANDLE; + } + mtp_object_info_header_t obj_info_header = { + .storage_id = SUPPORTED_STORAGE_ID, + .object_format = f->object_format, + .protection_status = f->protection_status, + .object_compressed_size = f->size, + .thumb_format = MTP_OBJ_FORMAT_UNDEFINED, + .thumb_compressed_size = 0, + .thumb_pix_width = 0, + .thumb_pix_height = 0, + .image_pix_width = f->image_pix_width, + .image_pix_height = f->image_pix_height, + .image_bit_depth = f->image_bit_depth, + .parent_object = f->parent, + .association_type = f->association_type, + .association_desc = 0, + .sequence_number = 0 + }; + mtp_container_add_raw(io_container, &obj_info_header, sizeof(obj_info_header)); + mtp_container_add_string(io_container, f->name); + mtp_container_add_cstring(io_container, FS_FIXED_DATETIME); + mtp_container_add_cstring(io_container, FS_FIXED_DATETIME); + mtp_container_add_cstring(io_container, ""); // keywords, not used + tud_mtp_data_send(io_container); + + return 0; +} + +static int32_t fs_get_object(tud_mtp_cb_data_t* cb_data) { + const mtp_container_command_t* command = cb_data->command_container; + mtp_container_info_t* io_container = &cb_data->io_container; + const uint32_t obj_handle = command->params[0]; + const fs_file_t* f = fs_get_file(obj_handle); + if (f == NULL) { + return MTP_RESP_INVALID_OBJECT_HANDLE; + } + + if (cb_data->phase == MTP_PHASE_COMMAND) { + // If file contents is larger than CFG_TUD_MTP_EP_BUFSIZE, data may only partially is added here + // the rest will be sent in tud_mtp_data_more_cb + mtp_container_add_raw(io_container, f->data, f->size); + tud_mtp_data_send(io_container); + } else if (cb_data->phase == MTP_PHASE_DATA) { + // continue sending remaining data: file contents offset is xferred byte minus header size + const uint32_t offset = cb_data->total_xferred_bytes - sizeof(mtp_container_header_t); + const uint32_t xact_len = tu_min32(f->size - offset, io_container->payload_bytes); + if (xact_len > 0) { + memcpy(io_container->payload, f->data + offset, xact_len); + tud_mtp_data_send(io_container); + } + } + + return 0; +} + +static int32_t fs_send_object_info(tud_mtp_cb_data_t* cb_data) { + const mtp_container_command_t* command = cb_data->command_container; + mtp_container_info_t* io_container = &cb_data->io_container; + const uint32_t storage_id = command->params[0]; + const uint32_t parent_handle = command->params[1]; // folder handle, 0xFFFFFFFF is root + (void) parent_handle; + + if (!is_session_opened) { + return MTP_RESP_SESSION_NOT_OPEN; + } + if (storage_id != 0xFFFFFFFF && storage_id != SUPPORTED_STORAGE_ID) { + return MTP_RESP_INVALID_STORAGE_ID; + } + + if (cb_data->phase == MTP_PHASE_COMMAND) { + tud_mtp_data_receive(io_container); + } else if (cb_data->phase == MTP_PHASE_DATA) { + mtp_object_info_header_t* obj_info = (mtp_object_info_header_t*) io_container->payload; + if (obj_info->storage_id != 0 && obj_info->storage_id != SUPPORTED_STORAGE_ID) { + return MTP_RESP_INVALID_STORAGE_ID; + } + + if (obj_info->parent_object) { + fs_file_t* parent = fs_get_file(obj_info->parent_object); + if (parent == NULL || !parent->association_type) { + return MTP_RESP_INVALID_PARENT_OBJECT; + } + } + + uint8_t* f_buf = fs_malloc(obj_info->object_compressed_size); + if (f_buf == NULL) { + return MTP_RESP_STORE_FULL; + } + fs_file_t* f = fs_create_file(); + if (f == NULL) { + return MTP_RESP_STORE_FULL; + } + + f->object_format = obj_info->object_format; + f->protection_status = obj_info->protection_status; + f->image_pix_width = obj_info->image_pix_width; + f->image_pix_height = obj_info->image_pix_height; + f->image_bit_depth = obj_info->image_bit_depth; + f->parent = obj_info->parent_object; + f->association_type = obj_info->association_type; + f->size = obj_info->object_compressed_size; + f->data = f_buf; + uint8_t* buf = io_container->payload + sizeof(mtp_object_info_header_t); + mtp_container_get_string(buf, f->name); + // ignore date created/modified/keywords + } + + return 0; +} + +static int32_t fs_send_object(tud_mtp_cb_data_t* cb_data) { + mtp_container_info_t* io_container = &cb_data->io_container; + fs_file_t* f = fs_get_file(send_obj_handle); + if (f == NULL) { + return MTP_RESP_INVALID_OBJECT_HANDLE; + } + + if (cb_data->phase == MTP_PHASE_COMMAND) { + io_container->header->len += f->size; + tud_mtp_data_receive(io_container); + } else { + // file contents offset is total xferred minus header size minus last received chunk + const uint32_t offset = cb_data->total_xferred_bytes - sizeof(mtp_container_header_t) - io_container->payload_bytes; + memcpy(f->data + offset, io_container->payload, io_container->payload_bytes); + if (cb_data->total_xferred_bytes - sizeof(mtp_container_header_t) < f->size) { + tud_mtp_data_receive(io_container); + } + } + + return 0; +} + +static int32_t fs_delete_object(tud_mtp_cb_data_t* cb_data) { + const mtp_container_command_t* command = cb_data->command_container; + const uint32_t obj_handle = command->params[0]; + const uint32_t obj_format = command->params[1]; // optional + (void) obj_format; + + if (!is_session_opened) { + return MTP_RESP_SESSION_NOT_OPEN; + } + fs_file_t* f = fs_get_file(obj_handle); + if (f == NULL) { + return MTP_RESP_INVALID_OBJECT_HANDLE; + } + + // delete object by clear the name + f->name[0] = 0; + return MTP_RESP_OK; +} diff --git a/src/class/mtp/mtp_device.c b/src/class/mtp/mtp_device.c index f3f594dcf..798a965eb 100644 --- a/src/class/mtp/mtp_device.c +++ b/src/class/mtp/mtp_device.c @@ -307,6 +307,7 @@ bool mtpd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t tud_mtp_request_cb_data_t cb_data = { .idx = 0, .stage = stage, + .session_id = p_mtp->session_id, .request = request, .buf = p_mtp->control_buf, .bufsize = tu_le16toh(request->wLength), @@ -395,6 +396,8 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t tud_mtp_cb_data_t cb_data; cb_data.idx = 0; + cb_data.phase = p_mtp->phase; + cb_data.session_id = p_mtp->session_id; cb_data.command_container = &p_mtp->command; cb_data.io_container = headered_packet; cb_data.total_xferred_bytes = 0; diff --git a/src/class/mtp/mtp_device.h b/src/class/mtp/mtp_device.h index c69f6f12f..397fbbbce 100644 --- a/src/class/mtp/mtp_device.h +++ b/src/class/mtp/mtp_device.h @@ -39,6 +39,9 @@ // callback data for Bulk Only Transfer (BOT) protocol typedef struct { uint8_t idx; // mtp instance + uint8_t phase; // current phase + uint32_t session_id; + const mtp_container_command_t* command_container; mtp_container_info_t io_container; @@ -50,6 +53,8 @@ typedef struct { typedef struct { uint8_t idx; uint8_t stage; // control stage + uint32_t session_id; + const tusb_control_request_t* request; // buffer for data stage uint8_t* buf; @@ -95,6 +100,7 @@ bool tud_mtp_data_receive(mtp_container_info_t* p_container); // send response bool tud_mtp_response_send(mtp_container_info_t* p_container); +// send event notification on event endpoint bool tud_mtp_event_send(mtp_event_t* event); //--------------------------------------------------------------------+ From ff492dc2b6c9da4fc6ab7f080435a5cb3e7be1c4 Mon Sep 17 00:00:00 2001 From: hathach Date: Mon, 29 Sep 2025 15:32:53 +0700 Subject: [PATCH 33/43] fix ci --- examples/device/mtp/src/main.c | 5 +---- examples/device/mtp/src/mtp_fs_example.c | 2 -- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/examples/device/mtp/src/main.c b/examples/device/mtp/src/main.c index 709aeb16f..57d1535b2 100644 --- a/examples/device/mtp/src/main.c +++ b/examples/device/mtp/src/main.c @@ -59,10 +59,7 @@ int main(void) { .speed = TUSB_SPEED_AUTO }; tusb_init(BOARD_TUD_RHPORT, &dev_init); - - if (board_init_after_tusb) { - board_init_after_tusb(); - } + board_init_after_tusb(); while (1) { tud_task(); // tinyusb device task diff --git a/examples/device/mtp/src/mtp_fs_example.c b/examples/device/mtp/src/mtp_fs_example.c index 09a5f4ffc..d47ec41b8 100644 --- a/examples/device/mtp/src/mtp_fs_example.c +++ b/examples/device/mtp/src/mtp_fs_example.c @@ -36,8 +36,6 @@ #define DEV_INFO_MANUFACTURER "TinyUSB" #define DEV_INFO_MODEL "MTP Example" #define DEV_INFO_VERSION "1.0" -#define DEV_INFO_SERIAL "123456" - #define DEV_PROP_FRIENDLY_NAME "TinyUSB MTP" //------------- storage info -------------// From 732d4a8c97a667dd3784f3b8a664850fa0d767d8 Mon Sep 17 00:00:00 2001 From: hathach Date: Tue, 30 Sep 2025 22:44:58 +0700 Subject: [PATCH 34/43] fix serial, try to test mtp with hil --- examples/device/mtp/src/mtp_fs_example.c | 4 ++-- test/hil/hil_test.py | 25 +++++++++++++++++++++++- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/examples/device/mtp/src/mtp_fs_example.c b/examples/device/mtp/src/mtp_fs_example.c index d47ec41b8..4914661bb 100644 --- a/examples/device/mtp/src/mtp_fs_example.c +++ b/examples/device/mtp/src/mtp_fs_example.c @@ -354,8 +354,8 @@ static int32_t fs_get_device_info(tud_mtp_cb_data_t* cb_data) { mtp_container_add_cstring(io_container, DEV_INFO_VERSION); uint16_t serial_utf16[32]; - board_usb_get_serial(serial_utf16, 32); - serial_utf16[31] = 0; // ensure null termination + size_t nchars = board_usb_get_serial(serial_utf16, 32); + serial_utf16[tu_min32(nchars, 31u)] = 0; // ensure null termination mtp_container_add_string(io_container, serial_utf16); tud_mtp_data_send(io_container); diff --git a/test/hil/hil_test.py b/test/hil/hil_test.py index 93cc0a750..fc7c43bbe 100755 --- a/test/hil/hil_test.py +++ b/test/hil/hil_test.py @@ -38,6 +38,16 @@ import glob from multiprocessing import Pool import fs +import ctypes +from pymtp import MTP, LIBMTP_MTPDevice, LIBMTP_RawDevice +mtp = MTP() +lib = mtp.mtp + +# # tell ctypes that Open_Raw_Device returns MTPDevice* +lib.LIBMTP_Open_Raw_Device.restype = ctypes.POINTER(LIBMTP_MTPDevice) +lib.LIBMTP_Open_Raw_Device.argtypes = [ctypes.POINTER(LIBMTP_RawDevice)] + + ENUM_TIMEOUT = 30 STATUS_OK = "\033[32mOK\033[0m" @@ -456,7 +466,6 @@ def test_device_dfu(board): def test_device_dfu_runtime(board): uid = board['uid'] - # Wait device enum timeout = ENUM_TIMEOUT while timeout > 0: @@ -491,6 +500,19 @@ def test_device_hid_composite_freertos(id): pass +def test_device_mtp(board): + uid = board['uid'] + for raw in mtp.detect_devices(): + mtp.device = lib.LIBMTP_Open_Raw_Device(ctypes.byref(raw)) + if mtp.device and mtp.get_serialnumber().decode('utf-8') == uid: + break + else: + mtp.device = None + if mtp.device is None: + assert False, 'MTP device not found' + + + # ------------------------------------------------------------- # Main # ------------------------------------------------------------- @@ -503,6 +525,7 @@ device_tests = [ 'device/dfu_runtime', 'device/cdc_msc_freertos', 'device/hid_boot_interface', + 'device/mtp' ] dual_tests = [ From 4e4e2be5662bf26fc3da6f79022db2f11134a64c Mon Sep 17 00:00:00 2001 From: hathach Date: Wed, 1 Oct 2025 15:26:04 +0700 Subject: [PATCH 35/43] test mtp with hil --- .github/workflows/build.yml | 2 - examples/device/mtp/src/mtp_fs_example.c | 16 +- examples/device/mtp/src/tinyusb_logo_png.h | 1 + test/hil/hil_test.py | 63 +- test/hil/pymtp.py | 1290 ++++++++++++++++++++ 5 files changed, 1352 insertions(+), 20 deletions(-) create mode 100644 test/hil/pymtp.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ff59421ce..676e2d428 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -208,8 +208,6 @@ jobs: - name: Checkout TinyUSB if: github.run_attempt == '1' uses: actions/checkout@v4 - with: - sparse-checkout: test/hil - name: Download Artifacts if: github.run_attempt == '1' diff --git a/examples/device/mtp/src/mtp_fs_example.c b/examples/device/mtp/src/mtp_fs_example.c index 4914661bb..3ae63173c 100644 --- a/examples/device/mtp/src/mtp_fs_example.c +++ b/examples/device/mtp/src/mtp_fs_example.c @@ -71,7 +71,8 @@ storage_info_t storage_info = { //--------------------------------------------------------------------+ // MTP FILESYSTEM //--------------------------------------------------------------------+ -#define FS_MAX_FILE_COUNT 5UL +// only allow to add 1 more object to make it simpler to manage memory +#define FS_MAX_FILE_COUNT 3UL #define FS_MAX_FILENAME_LEN 16 #ifdef CFG_EXAMPLE_MTP_READONLY @@ -82,14 +83,13 @@ storage_info_t storage_info = { // object data buffer (excluding 2 predefined files) with simple allocation pointer uint8_t fs_buf[FS_MAX_CAPACITY_BYTES]; #endif -size_t fs_buf_head = 0; #define FS_FIXED_DATETIME "20250808T173500.0" // "YYYYMMDDTHHMMSS.s" #define README_TXT_CONTENT "TinyUSB MTP Filesystem example" typedef struct { uint16_t name[FS_MAX_FILENAME_LEN]; - mtp_object_formats_t object_format; + uint16_t object_format; uint16_t protection_status; uint32_t image_pix_width; uint32_t image_pix_height; @@ -112,7 +112,7 @@ static fs_file_t fs_objects[FS_MAX_FILE_COUNT] = { .parent = 0, .association_type = MTP_ASSOCIATION_UNDEFINED, .data = (uint8_t*) (uintptr_t) README_TXT_CONTENT, - .size = sizeof(README_TXT_CONTENT) + .size = sizeof(README_TXT_CONTENT)-1 }, { .name = { 't', 'i', 'n', 'y', 'u', 's', 'b', '.', 'p', 'n', 'g', 0 }, // "tinyusb.png" @@ -212,12 +212,10 @@ static inline uint8_t* fs_malloc(size_t size) { (void) size; return NULL; #else - if (fs_buf_head + size > FS_MAX_CAPACITY_BYTES) { + if (size > FS_MAX_CAPACITY_BYTES) { return NULL; } - uint8_t* ptr = &fs_buf[fs_buf_head]; - fs_buf_head += size; - return ptr; + return fs_buf; #endif } @@ -394,7 +392,7 @@ static int32_t fs_get_storage_info(tud_mtp_cb_data_t* cb_data) { // update storage info with current free space storage_info.max_capacity_in_bytes = sizeof(README_TXT_CONTENT) + logo_len + FS_MAX_CAPACITY_BYTES; storage_info.free_space_in_objects = FS_MAX_FILE_COUNT - fs_get_file_count(); - storage_info.free_space_in_bytes = FS_MAX_CAPACITY_BYTES-fs_buf_head; + storage_info.free_space_in_bytes = storage_info.free_space_in_objects ? FS_MAX_CAPACITY_BYTES : 0; mtp_container_add_raw(io_container, &storage_info, sizeof(storage_info)); tud_mtp_data_send(io_container); return 0; diff --git a/examples/device/mtp/src/tinyusb_logo_png.h b/examples/device/mtp/src/tinyusb_logo_png.h index d1071c6d3..061fbc85a 100644 --- a/examples/device/mtp/src/tinyusb_logo_png.h +++ b/examples/device/mtp/src/tinyusb_logo_png.h @@ -1,3 +1,4 @@ +// convert using tools/file2carray.py const size_t logo_len = 2733; const uint8_t logo_bin[] __attribute__((aligned(16))) = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, diff --git a/test/hil/hil_test.py b/test/hil/hil_test.py index fc7c43bbe..1a846dd4a 100755 --- a/test/hil/hil_test.py +++ b/test/hil/hil_test.py @@ -28,6 +28,7 @@ import argparse import os +import random import re import sys import time @@ -37,16 +38,11 @@ import json import glob from multiprocessing import Pool import fs - +import hashlib import ctypes -from pymtp import MTP, LIBMTP_MTPDevice, LIBMTP_RawDevice +from pymtp import MTP + mtp = MTP() -lib = mtp.mtp - -# # tell ctypes that Open_Raw_Device returns MTPDevice* -lib.LIBMTP_Open_Raw_Device.restype = ctypes.POINTER(LIBMTP_MTPDevice) -lib.LIBMTP_Open_Raw_Device.argtypes = [ctypes.POINTER(LIBMTP_RawDevice)] - ENUM_TIMEOUT = 30 @@ -502,15 +498,64 @@ def test_device_hid_composite_freertos(id): def test_device_mtp(board): uid = board['uid'] + + # --- BEFORE: mute C-level stderr for libmtp vid/pid warnings --- + fd = sys.stderr.fileno() + _saved = os.dup(fd) + _null = os.open(os.devnull, os.O_WRONLY) + os.dup2(_null, fd) + for raw in mtp.detect_devices(): - mtp.device = lib.LIBMTP_Open_Raw_Device(ctypes.byref(raw)) + mtp.device = mtp.mtp.LIBMTP_Open_Raw_Device(ctypes.byref(raw)) if mtp.device and mtp.get_serialnumber().decode('utf-8') == uid: break else: mtp.device = None + + # --- AFTER: restore stderr --- + os.dup2(_saved, fd) + os.close(_null) + os.close(_saved) + if mtp.device is None: assert False, 'MTP device not found' + assert b"TinyUSB" == mtp.get_manufacturer(), 'MTP wrong manufacturer' + assert b"MTP Example" == mtp.get_modelname(), 'MTP wrong model' + assert b'1.0' == mtp.get_deviceversion(), 'MTP wrong version' + assert b'TinyUSB MTP' == mtp.get_devicename(), 'MTP wrong device name' + + # read and compare readme.txt and logo.png + f1_expect = b'TinyUSB MTP Filesystem example' + f2_md5_expect = '40ef23fc2891018d41a05d4a0d5f822f' # md5sum of logo.png + f1 = uid.encode("utf-8") + b'_file1' + f2 = uid.encode("utf-8") + b'_file2' + f3 = uid.encode("utf-8") + b'_file3' + mtp.get_file_to_file(1, f1) + with open(f1, 'rb') as file: + f1_data = file.read() + os.remove(f1) + assert f1_data == f1_expect, 'MTP file1 wrong data' + mtp.get_file_to_file(2, f2) + with open(f2, 'rb') as file: + f2_data = file.read() + os.remove(f2) + 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)) + file.write(f3_data) + file.close() + fid = mtp.send_file_from_file(f3, b'file3') + f3_readback = f3 + b'_readback' + mtp.get_file_to_file(fid, f3_readback) + with open(f3_readback, 'rb') as f: + f3_rb_data = f.read() + os.remove(f3_readback) + assert f3_rb_data == f3_data, 'MTP file3 wrong data' + os.remove(f3) + mtp.delete_object(fid) + # ------------------------------------------------------------- diff --git a/test/hil/pymtp.py b/test/hil/pymtp.py new file mode 100644 index 000000000..8b694df94 --- /dev/null +++ b/test/hil/pymtp.py @@ -0,0 +1,1290 @@ +#!/usr/bin/env python +# +# A Ctypes wrapper to LibMTP +# Developed by: Nick Devito (nick@nick125.com) +# (c) 2008 Nick Devito +# Released under the GPLv3 or later. +# + +""" + PyMTP is a pythonic wrapper around libmtp, making it a bit more + friendly to use in python + + Example Usage (or see examples/): + >>> import pymtp + >>> mtp = pymtp.MTP() + >>> mtp.connect() + PTP: Opening session + >>> print mtp.get_devicename() + Device name + >>> mtp.disconnect() + PTP: Closing session + >>> +""" + +__VERSION__ = "0.0.5" +__VERSION_MACRO__ = 5 +__VERSION_MINOR__ = 0 +__VERSION_MAJOR__ = 0 +__VERSION_TUPLE__ = (__VERSION_MAJOR__, __VERSION_MINOR__, __VERSION_MACRO__) +__AUTHOR__ = "Nick Devito (nick@nick125.com)" +__LICENSE__ = "GPL-3" +__DEBUG__ = 1 + +import os +import ctypes +import ctypes.util + +# NOTE: This code *may* work on windows, I don't have a win32 system to test +# this on. +_module_path = ctypes.util.find_library("mtp") +_libmtp = ctypes.CDLL(_module_path) + +# ---------- +# Error Definitions +# ---------- +class NoDeviceConnected(Exception): + """ + Raised when there isn't a device connected to the USB bus + """ + + pass + +class AlreadyConnected(Exception): + """ + Raised when we're already connected to a device and there is + an attempt to connect + """ + + pass + +class UnsupportedCommand(Exception): + """ + Raised when the connected device does not support the command + issued + """ + + pass + +class CommandFailed(Exception): + """ + Raised when the connected device returned an error when trying + to execute a command + """ + + pass + +class NotConnected(Exception): + """ + Raised when a command is called and the device is not connected + """ + + pass + +class ObjectNotFound(Exception): + """ + Raised when a command tries to get an object that doesn't exist + """ + + pass + +# ---------- +# End Error Definitions +# ---------- + +# ---------- +# Data Model Definitions +# ---------- + +class LIBMTP_Error(ctypes.Structure): + """ + LIBMTP_Error + Contains the ctypes structure for LIBMTP_error_t + """ + + def __repr__(self): + return self.errornumber + +LIBMTP_Error._fields_ = [("errornumber", ctypes.c_int), + ("error_text", ctypes.c_char_p), + ("next", ctypes.POINTER(LIBMTP_Error))] + +class LIBMTP_DeviceStorage(ctypes.Structure): + """ + LIBMTP_DeviceStorage + Contains the ctypes structure for LIBMTP_devicestorage_t + """ + + def __repr__(self): + return self.id + +LIBMTP_DeviceStorage._fields_ = [("id", ctypes.c_uint32), + ("StorageType", ctypes.c_uint16), + ("FilesystemType", ctypes.c_uint16), + ("AccessCapability", ctypes.c_uint16), + ("MaxCapacity", ctypes.c_uint64), + ("FreeSpaceInBytes", ctypes.c_uint64), + ("FreeSpaceInObjects", ctypes.c_uint64), + ("StorageDescription", ctypes.c_char_p), + ("VolumeIdentifier", ctypes.c_char_p), + ("next", ctypes.POINTER(LIBMTP_DeviceStorage)), + ("prev", ctypes.POINTER(LIBMTP_DeviceStorage))] + +class LIBMTP_DeviceEntry(ctypes.Structure): + """ + LIBMTP_DeviceEntry + Contains the ctypes structure for LIBMTP_device_entry_t + """ + + def __repr__(self): + return self.vendor + +LIBMTP_DeviceEntry._fields_ = [("vendor", ctypes.c_char_p), + ("vendor_id", ctypes.c_uint16), + ("product", ctypes.c_char_p), + ("product_id", ctypes.c_uint16), + ("device_flags", ctypes.c_uint32)] + +class LIBMTP_RawDevice(ctypes.Structure): + """ + LIBMTP_RawDevice + Contains the ctypes structure for LIBMTP_raw_device_t + """ + + def __repr__(self): + return self.device_entry + +LIBMTP_RawDevice._fields_ = [("device_entry", LIBMTP_DeviceEntry), + ("bus_location", ctypes.c_uint32), + ("devnum", ctypes.c_uint8)] + +class LIBMTP_MTPDevice(ctypes.Structure): + """ + LIBMTP_MTPDevice + Contains the ctypes structure for LIBMTP_mtpdevice_t + """ + + def __repr__(self): + return self.interface_number + +LIBMTP_MTPDevice._fields_ = [("interface_number", ctypes.c_uint8), + ("params", ctypes.c_void_p), + ("usbinfo", ctypes.c_void_p), + ("storage", ctypes.POINTER(LIBMTP_DeviceStorage)), + ("errorstack", ctypes.POINTER(LIBMTP_Error)), + ("maximum_battery_level", ctypes.c_uint8), + ("default_music_folder", ctypes.c_uint32), + ("default_playlist_folder", ctypes.c_uint32), + ("default_picture_folder", ctypes.c_uint32), + ("default_video_folder", ctypes.c_uint32), + ("default_organizer_folder", ctypes.c_uint32), + ("default_zencast_folder", ctypes.c_uint32), + ("default_album_folder", ctypes.c_uint32), + ("default_text_folder", ctypes.c_uint32), + ("cd", ctypes.c_void_p), + ("next", ctypes.POINTER(LIBMTP_MTPDevice))] + +class LIBMTP_File(ctypes.Structure): + """ + LIBMTP_File + Contains the ctypes structure for LIBMTP_file_t + """ + + def __repr__(self): + return "%s (%s)" % (self.filename, self.item_id) + +LIBMTP_File._fields_ = [("item_id", ctypes.c_uint32), + ("parent_id", ctypes.c_uint32), + ("storage_id", ctypes.c_uint32), + ("filename", ctypes.c_char_p), + ("filesize", ctypes.c_uint64), + ("modificationdate", ctypes.c_uint64), + ("filetype", ctypes.c_int), # LIBMTP_filetype_t enum + ("next", ctypes.POINTER(LIBMTP_File))] + +class LIBMTP_Track(ctypes.Structure): + """ + LIBMTP_Track + Contains the ctypes structure for LIBMTP_track_t + """ + + def __repr__(self): + return "%s - %s (%s)" % (self.artist, self.title, self.item_id) + +LIBMTP_Track._fields_ = [("item_id", ctypes.c_uint32), + ("parent_id", ctypes.c_uint32), + ("storage_id", ctypes.c_uint32), + ("title", ctypes.c_char_p), + ("artist", ctypes.c_char_p), + ("composer", ctypes.c_char_p), + ("genre", ctypes.c_char_p), + ("album", ctypes.c_char_p), + ("date", ctypes.c_char_p), + ("filename", ctypes.c_char_p), + ("tracknumber", ctypes.c_uint16), + ("duration", ctypes.c_uint32), + ("samplerate", ctypes.c_uint32), + ("nochannels", ctypes.c_uint16), + ("wavecodec", ctypes.c_uint32), + ("bitrate", ctypes.c_uint32), + ("bitratetype", ctypes.c_uint16), + ("rating", ctypes.c_uint16), + ("usecount", ctypes.c_uint32), + ("filesize", ctypes.c_uint64), + ("modificationdate", ctypes.c_uint64), + ("filetype", ctypes.c_int), # LIBMTP_filetype_t enum + ("next", ctypes.POINTER(LIBMTP_Track))] + +class LIBMTP_Playlist(ctypes.Structure): + """ + LIBMTP_Playlist + Contains the ctypes structure for LIBMTP_playlist_t + """ + + def __init__(self): + self.tracks = ctypes.pointer(ctypes.c_uint32(0)) + self.no_tracks = ctypes.c_uint32(0) + def __repr__(self): + return "%s (%s)" % (self.name, self.playlist_id) + + def __iter__(self): + """ + This allows the playlist object to act like a list with + a generator. + """ + for track in xrange(self.no_tracks): + yield self.tracks[track] + + def __getitem__(self, key): + """ + This allows the playlist to return tracks like a list + """ + + if (key > (self.no_tracks - 1)): + raise IndexError + + return self.tracks[key] + + def __setitem__(self, key, value): + """ + This allows the user to manipulate the playlist like a + list. However, this will only modify existing objects, + you can't try to set a key outside of the current size. + """ + + if (key > (self.no_tracks - 1)): + raise IndexError + + self.tracks[key] = value + + def __delitem__(self, key): + """ + This allows the user to delete an object + from the playlist + """ + + if (key > (self.no_tracks - 1)): + raise IndexError + + for i in range(key, (self.no_tracks - 1)): + self.tracks[i] = self.tracks[i + 1] + + self.no_tracks -= 1 + + def append(self, value): + """ + This function appends a track to the end of the tracks + list. + """ + if (self.tracks == None): + self.tracks = ctypes.pointer(ctypes.c_uint32(0)) + + self.no_tracks += 1 + self.tracks[(self.no_tracks - 1)] = value + + def __len__(self): + """ + This returns the number of tracks in the playlist + """ + + return self.no_tracks + +LIBMTP_Playlist._fields_ = [("playlist_id", ctypes.c_uint32), + ("parent_id", ctypes.c_uint32), + ("storage_id", ctypes.c_uint32), + ("name", ctypes.c_char_p), + ("tracks", ctypes.POINTER(ctypes.c_uint32)), + ("no_tracks", ctypes.c_uint32), + ("next", ctypes.POINTER(LIBMTP_Playlist))] + +class LIBMTP_Folder(ctypes.Structure): + """ + LIBMTP_Folder + Contains the ctypes structure for LIBMTP_folder_t + """ + + def __repr__(self): + return "%s (%s)" % (self.name, self.folder_id) + +LIBMTP_Folder._fields_ = [("folder_id", ctypes.c_uint32), + ("parent_id", ctypes.c_uint32), + ("storage_id", ctypes.c_uint32), + ("name", ctypes.c_char_p), + ("sibling", ctypes.POINTER(LIBMTP_Folder)), + ("child", ctypes.POINTER(LIBMTP_Folder))] + +# Abstracted from libmtp's LIBMTP_filetype_t. This must be kept in sync. +# first checked in 0.2.6.1 +# last checked in version 1.1.6 +LIBMTP_Filetype = { + "WAV": ctypes.c_int(0), + "MP3": ctypes.c_int(1), + "WMA": ctypes.c_int(2), + "OGG": ctypes.c_int(3), + "AUDIBLE": ctypes.c_int(4), + "MP4": ctypes.c_int(5), + "UNDEF_AUDIO": ctypes.c_int(6), + "WMV": ctypes.c_int(7), + "AVI": ctypes.c_int(8), + "MPEG": ctypes.c_int(9), + "ASF": ctypes.c_int(10), + "QT": ctypes.c_int(11), + "UNDEF_VIDEO": ctypes.c_int(12), + "JPEG": ctypes.c_int(13), + "JFIF": ctypes.c_int(14), + "TIFF": ctypes.c_int(15), + "BMP": ctypes.c_int(16), + "GIF": ctypes.c_int(17), + "PICT": ctypes.c_int(18), + "PNG": ctypes.c_int(19), + "VCALENDAR1": ctypes.c_int(20), + "VCALENDAR2": ctypes.c_int(21), + "VCARD2": ctypes.c_int(22), + "VCARD3": ctypes.c_int(23), + "WINDOWSIMAGEFORMAT": ctypes.c_int(24), + "WINEXEC": ctypes.c_int(25), + "TEXT": ctypes.c_int(26), + "HTML": ctypes.c_int(27), + "FIRMWARE": ctypes.c_int(28), + "AAC": ctypes.c_int(29), + "MEDIACARD": ctypes.c_int(30), + "FLAC": ctypes.c_int(31), + "MP2": ctypes.c_int(32), + "M4A": ctypes.c_int(33), + "DOC": ctypes.c_int(34), + "XML": ctypes.c_int(35), + "XLS": ctypes.c_int(36), + "PPT": ctypes.c_int(37), + "MHT": ctypes.c_int(38), + "JP2": ctypes.c_int(39), + "JPX": ctypes.c_int(40), + "ALBUM": ctypes.c_int(41), + "PLAYLIST": ctypes.c_int(42), + "UNKNOWN": ctypes.c_int(43), +} + +# Synced from libmtp 0.2.6.1's libmtp.h. Must be kept in sync. +LIBMTP_Error_Number = { + "NONE": ctypes.c_int(0), + "GENERAL": ctypes.c_int(1), + "PTP_LAYER": ctypes.c_int(2), + "USB_LAYER": ctypes.c_int(3), + "MEMORY_ALLOCATION": ctypes.c_int(4), + "NO_DEVICE_ATTACHED": ctypes.c_int(5), + "STORAGE_FULL": ctypes.c_int(6), + "CONNECTING": ctypes.c_int(7), + "CANCELLED": ctypes.c_int(8), +} + +# ---------- +# End Data Model Definitions +# ---------- + +# ---------- +# Type Definitions +# ---------- +_libmtp.LIBMTP_Detect_Raw_Devices.restype = ctypes.c_int # actually LIBMTP_Error_Number enum +_libmtp.LIBMTP_Get_Friendlyname.restype = ctypes.c_char_p +_libmtp.LIBMTP_Get_Serialnumber.restype = ctypes.c_char_p +_libmtp.LIBMTP_Get_Modelname.restype = ctypes.c_char_p +_libmtp.LIBMTP_Get_Manufacturername.restype = ctypes.c_char_p +_libmtp.LIBMTP_Get_Deviceversion.restype = ctypes.c_char_p +_libmtp.LIBMTP_Get_Filelisting_With_Callback.restype = ctypes.POINTER(LIBMTP_File) +_libmtp.LIBMTP_Get_Tracklisting_With_Callback.restype = ctypes.POINTER(LIBMTP_Track) +_libmtp.LIBMTP_Get_Filetype_Description.restype = ctypes.c_char_p +_libmtp.LIBMTP_Get_Filemetadata.restype = ctypes.POINTER(LIBMTP_File) +_libmtp.LIBMTP_Get_Trackmetadata.restype = ctypes.POINTER(LIBMTP_Track) +_libmtp.LIBMTP_Get_First_Device.restype = ctypes.POINTER(LIBMTP_MTPDevice) +_libmtp.LIBMTP_Get_Playlist_List.restype = ctypes.POINTER(LIBMTP_Playlist) +_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_Open_Raw_Device.restype = ctypes.POINTER(LIBMTP_MTPDevice) +_libmtp.LIBMTP_Open_Raw_Device.argtypes = [ctypes.POINTER(LIBMTP_RawDevice)] + +# This is for callbacks with the type of LIBMTP_progressfunc_t +Progressfunc = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_uint64, ctypes.c_uint64) + +# ---------- +# End Type Definitions +# ---------- + +class MTP: + """ + The MTP object + This is the main wrapper around libmtp + """ + + def __init__(self): + """ + Initializes the MTP object + + @rtype: None + @return: None + """ + + self.mtp = _libmtp + self.mtp.LIBMTP_Init() + self.device = None + + def debug_stack(self): + """ + Checks if __DEBUG__ is set, if so, prints and clears the + errorstack. + + @rtype: None + @return: None + """ + + if __DEBUG__: + self.mtp.LIBMTP_Dump_Errorstack() + #self.mtp.LIBMTP_Clear_Errorstack() + + def detect_devices(self): + """ + Detect if any MTP devices are connected + + @rtype: None + @return: a list of LIBMTP_RawDevice instances for devices found + + """ + + devlist = [] + device = LIBMTP_RawDevice() + devices = ctypes.pointer(device) + numdevs = ctypes.c_int(0) + err = self.mtp.LIBMTP_Detect_Raw_Devices(ctypes.byref(devices), + ctypes.byref(numdevs)) + if err == LIBMTP_Error_Number['NO_DEVICE_ATTACHED']: + return devlist + elif err == LIBMTP_Error_Number['STORAGE_FULL']: + # ignore this, we're just trying to detect here, not do anything else + pass + elif err == LIBMTP_Error_Number['CONNECTING']: + raise AlreadyConnected('CONNECTING') + elif err == LIBMTP_Error_Number['GENERAL']: + raise CommandFailed('GENERAL') + elif err == LIBMTP_Error_Number['PTP_LAYER']: + raise CommandFailed('PTP_LAYER') + elif err == LIBMTP_Error_Number['USB_LAYER']: + raise CommandFailed('USB_LAYER') + elif err == LIBMTP_Error_Number['MEMORY_ALLOCATION']: + raise CommandFailed('MEMORY_ALLOCATION') + elif err == LIBMTP_Error_Number['CANCELLED']: + raise CommandFailed('CANCELLED') + if numdevs.value == 0: + return devlist + for i in range(numdevs.value): + devlist.append(devices[i]) + return devlist + + def connect(self): + """ + Initializes the MTP connection to the device + + @rtype: None + @return: None + + """ + + if (self.device != None): + raise AlreadyConnected + + self.device = self.mtp.LIBMTP_Get_First_Device() + + if not self.device: + self.device = None + raise NoDeviceConnected + + def disconnect(self): + """ + Disconnects the MTP device and deletes the self.device object + + @rtype: None + @return: None + """ + + if (self.device == None): + raise NotConnected + + self.mtp.LIBMTP_Release_Device(self.device) + del self.device + self.device = None + + def get_devicename(self): + """ + Returns the connected device's 'friendly name' (or + known as the owner name) + + @rtype: string + @return: The connected device's 'friendly name' + """ + + if (self.device == None): + raise NotConnected + + return self.mtp.LIBMTP_Get_Friendlyname(self.device) + + def set_devicename(self, name): + """ + Changes the connected device's 'friendly name' to name + + @type name: string + @param name: The name to change the connected device's + 'friendly name' to + @rtype: None + @return: None + """ + + if (self.device == None): + raise NotConnected + + ret = self.mtp.LIBMTP_Set_Friendlyname(self.device, name) + if (ret != 0): + self.debug_stack() + raise CommandFailed + + def get_serialnumber(self): + """ + Returns the connected device's serial number + + @rtype: string + @return: The connected device's serial number + """ + + if (self.device == None): + raise NotConnected + + return self.mtp.LIBMTP_Get_Serialnumber(self.device) + + def get_manufacturer(self): + """ + Return the connected device's manufacturer + + @rtype: string + @return: The connected device's manufacturer + """ + if (self.device == None): + raise NotConnected + + return self.mtp.LIBMTP_Get_Manufacturername(self.device) + + def get_batterylevel(self): + """ + Returns the connected device's maximum and current + battery levels + + @rtype: tuple + @return: The connected device's maximum and current + battery levels ([0] is maximum, [1] is current) + """ + + if (self.device == None): + raise NotConnected + + maximum_level = ctypes.c_uint8() + current_level = ctypes.c_uint8() + + ret = self.mtp.LIBMTP_Get_Batterylevel(self.device, \ + ctypes.byref(maximum_level), ctypes.byref(current_level)) + + if (ret != 0): + raise CommandFailed + + return (maximum_level.value, current_level.value) + + def get_modelname(self): + """ + Returns the connected device's model name (such + as "Zen V Plus") + + @rtype: string + @return: The connected device's model name + """ + + if (self.device == None): + raise NotConnected + + return self.mtp.LIBMTP_Get_Modelname(self.device) + + def get_deviceversion(self): + """ + Returns the connected device's version (such as + firmware/hardware version) + + @rtype: string + @return: Returns the connect device's version + information + """ + + if (self.device == None): + raise NotConnected + + return self.mtp.LIBMTP_Get_Deviceversion(self.device) + + def get_filelisting(self, callback=None): + """ + Returns the connected device's file listing as a tuple, + containing L{LIBMTP_File} objects. + + @type callback: function or None + @param callback: The function provided to libmtp to + receive callbacks from ptp. Callback must take two + arguments, total and sent (in bytes) + @rtype: tuple + @return: Returns the connect device file listing tuple + """ + + if (self.device == None): + raise NotConnected + + if (callback != None): + callback = Progressfunc(callback) + + files = self.mtp.LIBMTP_Get_Filelisting_With_Callback(self.device, callback, None) + ret = [] + next = files + + while next: + ret.append(next.contents) + if (next.contents.next == None): + break + next = next.contents.next + + return ret + + def get_filetype_description(self, filetype): + """ + Returns the description of the filetype + + @type filetype: int + @param filetype: The MTP filetype integer + @rtype: string + @return: The file type information + """ + + if (self.device == None): + raise NotConnected + + return self.mtp.LIBMTP_Get_Filetype_Description(filetype) + + def get_file_metadata(self, file_id): + """ + Returns the file metadata from the connected device + + As per the libmtp documentation, calling this function + repeatedly is not recommended, as it is slow and creates + a large amount of USB traffic. + + @type file_id: int + @param file_id: The unique numeric file id + @rtype: LIBMTP_File + @return: The file metadata + """ + + if (self.device == None): + raise NotConnected + + ret = self.mtp.LIBMTP_Get_Filemetadata(self.device, file_id) + + if (not hasattr(ret, 'contents')): + raise ObjectNotFound + + return ret.contents + + def get_tracklisting(self, callback=None): + """ + Returns tracks from the connected device + + @type callback: function or None + @param callback: The function provided to libmtp to + receive callbacks from ptp. Callback must take two + arguments, total and sent (in bytes) + @rtype: tuple + @return: Returns a tuple full of L{LIBMTP_Track} objects + """ + + if (self.device == None): + raise NotConnected + + if (callback != None): + callback = Progressfunc(callback) + + tracks = self.mtp.LIBMTP_Get_Tracklisting_With_Callback(self.device, callback, None) + ret = [] + next = tracks + + while next: + ret.append(next.contents) + if (next.contents.next == None): + break + next = next.contents.next + + return ret + + def get_track_metadata(self, track_id): + """ + Returns the track metadata + + As per the libmtp documentation, calling this function repeatedly is not + recommended, as it is slow and creates a large amount of USB traffic. + + @type track_id: int + @param track_id: The unique numeric track id + @rtype: L{LIBMTP_Track} + @return: The track metadata + """ + + if (self.device == None): + raise NotConnected + + ret = self.mtp.LIBMTP_Get_Trackmetadata(self.device, track_id) + + if (not hasattr(ret, 'contents')): + raise ObjectNotFound + + return ret.contents + + def get_file_to_file(self, file_id, target, callback=None): + """ + Downloads the file from the connected device and stores it at the + target location + + @type file_id: int + @param file_id: The unique numeric file id + @type target: str + @param target: The location to place the file + @type callback: function or None + @param callback: The function provided to libmtp to + receive callbacks from ptp. Callback must take two + arguments, total and sent (in bytes) + """ + + if (self.device == None): + raise NotConnected + + if (callback != None): + callback = Progressfunc(callback) + + ret = self.mtp.LIBMTP_Get_File_To_File(self.device, file_id, target, callback, None) + + if (ret != 0): + self.debug_stack() + raise CommandFailed + + def get_track_to_file(self, track_id, target, callback=None): + """ + Downloads the track from the connected device and stores it at + the target location + + @type track_id: int + @param track_id: The unique numeric track id + @type target: str + @param target: The location to place the track + @type callback: function or None + @param callback: The function provided to libmtp to + receive callbacks from ptp. Callback must take two + arguments, total and sent (in bytes) + """ + + if (self.device == None): + raise NotConnected + + if (callback != None): + callback = Progressfunc(callback) + + ret = self.mtp.LIBMTP_Get_Track_To_File(self.device, track_id, target, callback, None) + + if (ret != 0): + self.debug_stack() + raise CommandFailed + + def find_filetype(self, filename): + """ + Attempts to guess the filetype off the filename. Kind of + inaccurate and should be trusted with a grain of salt. It + works in most situations, though. + + @type filename: str + @param filename: The filename to attempt to guess from + @rtype: int + @return: The integer of the Filetype + """ + + fileext = filename.decode('utf-8').lower().split(".")[-1] + + if (fileext == "wav" or fileext == "wave"): + return LIBMTP_Filetype["WAV"] + elif (fileext == "mp3"): + return LIBMTP_Filetype["MP3"] + elif (fileext == "wma"): + return LIBMTP_Filetype["WMA"] + elif (fileext == "ogg"): + return LIBMTP_Filetype["OGG"] + elif (fileext == "mp4"): + return LIBMTP_Filetype["MP4"] + elif (fileext == "wmv"): + return LIBMTP_Filetype["WMV"] + elif (fileext == "avi"): + return LIBMTP_Filetype["AVI"] + elif (fileext == "mpeg" or fileext == "mpg"): + return LIBMTP_Filetype["MPEG"] + elif (fileext == "asf"): + return LIBMTP_Filetype["ASF"] + elif (fileext == "qt" or fileext == "mov"): + return LIBMTP_Filetype["QT"] + elif (fileext == "jpeg" or fileext == "jpg"): + return LIBMTP_Filetype["JPEG"] + elif (fileext == "jfif"): + return LIBMTP_Filetype["JFIF"] + elif (fileext == "tif" or fileext == "tiff"): + return LIBMTP_Filetype["TIFF"] + elif (fileext == "bmp"): + return LIBMTP_Filetype["BMP"] + elif (fileext == "gif"): + return LIBMTP_Filetype["GIF"] + elif (fileext == "pic" or fileext == "pict"): + return LIBMTP_Filetype["PICT"] + elif (fileext == "png"): + return LIBMTP_Filetype["PNG"] + elif (fileext == "wmf"): + return LIBMTP_Filetype["WINDOWSIMAGEFORMAT"] + elif (fileext == "ics"): + return LIBMTP_Filetype["VCALENDAR2"] + elif (fileext == "exe" or fileext == "com" or fileext == "bat"\ + or fileext == "dll" or fileext == "sys"): + return LIBMTP_Filetype["WINEXEC"] + elif (fileext == "aac"): + return LIBMTP_Filetype["AAC"] + elif (fileext == "mp2"): + return LIBMTP_Filetype["MP2"] + elif (fileext == "flac"): + return LIBMTP_Filetype["FLAC"] + elif (fileext == "m4a"): + return LIBMTP_Filetype["M4A"] + elif (fileext == "doc"): + return LIBMTP_Filetype["DOC"] + elif (fileext == "xml"): + return LIBMTP_Filetype["XML"] + elif (fileext == "xls"): + return LIBMTP_Filetype["XLS"] + elif (fileext == "ppt"): + return LIBMTP_Filetype["PPT"] + elif (fileext == "mht"): + return LIBMTP_Filetype["MHT"] + elif (fileext == "jp2"): + return LIBMTP_Filetype["JP2"] + elif (fileext == "jpx"): + return LIBMTP_Filetype["JPX"] + else: + return LIBMTP_Filetype["UNKNOWN"] + + def send_file_from_file(self, source, target, callback=None): + """ + Sends a file from the filesystem to the connected device + and stores it at the target filename inside the parent. + + This will attempt to "guess" the filetype with + find_filetype() + + @type source: str + @param source: The path on the filesystem where the file resides + @type target: str + @param target: The target filename on the device + @type callback: function or None + @param callback: The function provided to libmtp to + receive callbacks from ptp. Callback function must + take two arguments, sent and total (in bytes) + @rtype: int + @return: The object ID of the new file + """ + + if (self.device == None): + raise NotConnected + + if (os.path.isfile(source) == False): + raise IOError + + if (callback != None): + callback = Progressfunc(callback) + + metadata = LIBMTP_File(filename=target, \ + filetype=self.find_filetype(source), \ + filesize=os.stat(source).st_size) + + ret = self.mtp.LIBMTP_Send_File_From_File(self.device, source, \ + ctypes.pointer(metadata), callback, None) + + if (ret != 0): + self.debug_stack() + raise CommandFailed + + return metadata.item_id + + def send_track_from_file(self, source, target, metadata, callback=None): + """ + Sends a track from the filesystem to the connected + device + + @type source: str + @param source: The path where the track resides + @type target: str + @param target: The target filename on the device + @type metadata: LIBMTP_Track + @param metadata: The track metadata + @type callback: function or None + @param callback: The function provided to libmtp to + receive callbacks from ptp. Callback function must + take two arguments, sent and total (in bytes) + @rtype: int + @return: The object ID of the new track + """ + + if (self.device == None): + raise NotConnected + + if (os.path.exists(source) == None): + raise IOError + + if callback: + callback = Progressfunc(callback) + + metadata.filename = target + metadata.filetype = self.find_filetype(source) + metadata.filesize = os.stat(source).st_size + + ret = self.mtp.LIBMTP_Send_Track_From_File(self.device, source, \ + ctypes.pointer(metadata), callback, None) + + if (ret != 0): + self.debug_stack() + raise CommandFailed + + return metadata.item_id + + def get_freespace(self): + """ + Returns the amount of free space on the connected device + @rtype: long + @return: The amount of free storage in bytes + """ + + if (self.device == None): + raise NotConnected + + self.mtp.LIBMTP_Get_Storage(self.device, 0) + return self.device.contents.storage.contents.FreeSpaceInBytes + + def get_totalspace(self): + """ + Returns the total space on the connected device + @rtype: long + @return: The amount of total storage in bytes + """ + + if (self.device == None): + raise NotConnected + + self.mtp.LIBMTP_Get_Storage(self.device, 0) + return self.device.contents.storage.contents.MaxCapacity + + def get_usedspace(self): + """ + Returns the amount of used space on the connected device + + @rtype: long + @return: The amount of used storage in bytes + """ + + if (self.device == None): + raise NotConnected + + self.mtp.LIBMTP_Get_Storage(self.device, 0) + storage = self.device.contents.storage.contents + return (storage.MaxCapacity - storage.FreeSpaceInBytes) + + def get_usedspace_percent(self): + """ + Returns the amount of used space as a percentage + + @rtype: float + @return: The percentage of used storage + """ + + if (self.device == None): + raise NotConnected + + self.mtp.LIBMTP_Get_Storage(self.device, 0) + storage = self.device.contents.storage.contents + + # Why don't we call self.get_totalspace/self.get_usedspace + # here? That would require 3 *more* calls to + # LIBMTP_Get_Storage + usedspace = storage.MaxCapacity - storage.FreeSpaceInBytes + return ((float(usedspace) / float(storage.MaxCapacity)) * 100) + + def delete_object(self, object_id): + """ + Deletes the object off the connected device. + + @type object_id: int + @param object_id: The unique object identifier + """ + + if (self.device == None): + raise NotConnected + + ret = self.mtp.LIBMTP_Delete_Object(self.device, object_id) + + if (ret != 0): + self.debug_stack() + raise CommandFailed + + def get_playlists(self): + """ + Returns a tuple filled with L{LIBMTP_Playlist} objects + from the connected device. + + The main gotcha of this function is that the tracks + variable of LIBMTP_Playlist isn't iterable (without + segfaults), so, you have to iterate over the no_tracks + (through range or xrange) and access it that way (i.e. + tracks[track_id]). Kind of sucks. + + @rtype: tuple + @return: Tuple filled with LIBMTP_Playlist objects + """ + + if (self.device == None): + raise NotConnected + + playlists = self.mtp.LIBMTP_Get_Playlist_List(self.device) + ret = [] + next = playlists + + while next: + ret.append(next.contents) + if (next.contents.next == None): + break + next = next.contents.next + + return ret + + def get_playlist(self, playlist_id): + """ + Returns a L{LIBMTP_Playlist} object of the requested + playlist_id from the connected device + + @type playlist_id: int + @param playlist_id: The unique playlist identifier + @rtype: LIBMTP_Playlist + @return: The playlist object + """ + + if (self.device == None): + raise NotConnected + + try: + ret = self.mtp.LIBMTP_Get_Playlist(self.device, playlist_id).contents + except ValueError: + raise ObjectNotFound + + return ret + + def create_new_playlist(self, metadata): + """ + Creates a new playlist based on the metadata object + passed. + + @type metadata: LIBMTP_Playlist + @param metadata: A LIBMTP_Playlist object describing + the playlist + @rtype: int + @return: The object ID of the new playlist + """ + + if (self.device == None): + raise NotConnected + + ret = self.mtp.LIBMTP_Create_New_Playlist(self.device, ctypes.pointer(metadata)) + + if (ret != 0): + self.debug_stack() + raise CommandFailed + + return metadata.playlist_id + + def update_playlist(self, metadata): + """ + Updates a playlist based on the supplied metadata. + + When updating the tracks field in a playlist, this + function will replace the playlist's tracks with + the tracks supplied in the metadata object. This + means that the previous tracks in the playlist + will be overwritten. + + @type metadata: LIBMTP_Playlist + @param metadata: A LIBMTP_Playlist object describing + the updates to the playlist. + """ + + if (self.device == None): + raise NotConnected + + ret = self.mtp.LIBMTP_Update_Playlist(self.device, ctypes.pointer(metadata)) + + if (ret != 0): + self.debug_stack() + raise CommandFailed + + def get_folder_list(self): + """ + Returns a pythonic dict of the folders on the + device. + + @rtype: dict + @return: A dict of the folders on the device where + the folder ID is the key. + """ + + if (self.device == None): + raise NotConnected + + folders = self.mtp.LIBMTP_Get_Folder_List(self.device) + next = folders + # List of folders, key being the folder ID + ret = {} + # Iterate over the folders to grab the first-level parents + while True: + next = next.contents + scanned = True + + # Check if this ID exists, if not, add it + # and trigger a scan of the children + if not (ret.has_key(next.folder_id)): + ret[next.folder_id] = next + scanned = False + + if ((scanned == False) and (next.child)): + ## Scan the children + next = next.child + + elif (next.sibling): + ## Scan the siblings + next = next.sibling + + elif (next.parent_id != 0): + ## If we have no children/siblings to visit, + ## and we aren't at the parent, go back to + ## the parent. + next = self.mtp.LIBMTP_Find_Folder(folders, int(next.parent_id)) + + else: + ## We have scanned everything, let's go home. + break + + return ret + + def get_parent_folders(self): + """ + Returns a list of only the parent folders. + @rtype: list + @return: Returns a list of the parent folders + """ + + if (self.device == None): + raise NotConnected + folders = self.mtp.LIBMTP_Get_Folder_List(self.device) + next = folders + # A temporary holding space, this makes checking folder + # IDs easier + tmp = {} + + while True: + next = next.contents + ## Check if this folder is in the dict + if not (tmp.has_key(next.folder_id)): + tmp[next.folder_id] = next + + # Check for siblings + if (next.sibling): + ## Scan the sibling + next = next.sibling + else: + ## We're done here. + break + + ## convert the dict into a list + ret = [] + for key in tmp: + ret.append(tmp[key]) + + return ret + + def create_folder(self, name, parent=0, storage=0): + """ + This creates a new folder in the parent. If the parent + is 0, it will go in the main directory. + + @type name: str + @param name: The name for the folder + @type parent: int + @param parent: The parent ID or 0 for main directory + @type storage: int + @param storage: The storage id or 0 to create the new folder + on the primary storage + @rtype: int + @return: Returns the object ID of the new folder + """ + + if (self.device == None): + raise NotConnected + + ret = self.mtp.LIBMTP_Create_Folder(self.device, name, parent, storage) + + if (ret == 0): + self.debug_stack() + raise CommandFailed + + return ret + + def get_errorstack(self): + """ + Returns the connected device's errorstack from + LIBMTP. + @rtype: L{LIBMTP_Error} + @return: An array of LIBMTP_Errors. + """ + + if (self.device == None): + raise NotConnected + + ret = self.mtp.LIBMTP_Get_Errorstack(self.device) + + if (ret != 0): + raise CommandFailed + + return ret From 13c240134ac0ce0d830e0257ca97d955e032ab93 Mon Sep 17 00:00:00 2001 From: hathach Date: Wed, 1 Oct 2025 16:53:44 +0700 Subject: [PATCH 36/43] minor update --- src/class/mtp/mtp.h | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/class/mtp/mtp.h b/src/class/mtp/mtp.h index 84fd1b429..40b6dd8b0 100644 --- a/src/class/mtp/mtp.h +++ b/src/class/mtp/mtp.h @@ -24,13 +24,10 @@ * This file is part of the TinyUSB stack. */ -#ifndef _TUSB_MTP_H_ -#define _TUSB_MTP_H_ +#ifndef TUSB_MTP_H_ +#define TUSB_MTP_H_ -#include -#include -#include -#include "tusb_option.h" +#include "common/tusb_common.h" #if (CFG_TUD_ENABLED && CFG_TUD_MTP) @@ -887,6 +884,5 @@ TU_ATTR_ALWAYS_INLINE static inline uint32_t mtp_container_get_string(uint8_t* b } #endif -#endif /* CFG_TUD_ENABLED && CFG_TUD_MTP */ - -#endif /* _TUSB_MTP_H_ */ +#endif +#endif From 26939587b55dc3c6958a3b630664e6a0b7d9da9b Mon Sep 17 00:00:00 2001 From: hathach Date: Wed, 1 Oct 2025 17:22:44 +0700 Subject: [PATCH 37/43] hil tinyusb always checkoutt/download artifacts --- .github/workflows/build.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 676e2d428..a0daefab9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -199,18 +199,15 @@ jobs: runs-on: [self-hosted, X64, hathach, hardware-in-the-loop] steps: - name: Clean workspace - if: github.run_attempt == '1' run: | echo "Cleaning up for the first run" rm -rf "${{ github.workspace }}" mkdir -p "${{ github.workspace }}" - name: Checkout TinyUSB - if: github.run_attempt == '1' uses: actions/checkout@v4 - name: Download Artifacts - if: github.run_attempt == '1' uses: actions/download-artifact@v4 with: path: cmake-build From 6d32256188d578079b0d050509f2c7d7178897a0 Mon Sep 17 00:00:00 2001 From: hathach Date: Wed, 1 Oct 2025 20:56:20 +0700 Subject: [PATCH 38/43] HIL add timeout for opening mtp device --- .github/workflows/build.yml | 3 +++ docs/reference/getting_started.rst | 2 +- examples/device/mtp/src/usb_descriptors.c | 2 +- test/hil/hil_test.py | 24 ++++++++++++++--------- 4 files changed, 20 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a0daefab9..676e2d428 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -199,15 +199,18 @@ jobs: runs-on: [self-hosted, X64, hathach, hardware-in-the-loop] steps: - name: Clean workspace + if: github.run_attempt == '1' run: | echo "Cleaning up for the first run" rm -rf "${{ github.workspace }}" mkdir -p "${{ github.workspace }}" - name: Checkout TinyUSB + if: github.run_attempt == '1' uses: actions/checkout@v4 - name: Download Artifacts + if: github.run_attempt == '1' uses: actions/download-artifact@v4 with: path: cmake-build diff --git a/docs/reference/getting_started.rst b/docs/reference/getting_started.rst index bb9ff1cb4..f1a755804 100644 --- a/docs/reference/getting_started.rst +++ b/docs/reference/getting_started.rst @@ -178,7 +178,7 @@ By default log message is printed via on-board UART which is slow and take lots * Pros: work with most if not all MCUs * Software viewer is JLink RTT Viewer/Client/Logger which is bundled with JLink driver package. -* ``LOGGER=swo``\ : Use dedicated SWO pin of ARM Cortex SWD debug header. +* ``LOGGER=swo`` : Use dedicated SWO pin of ARM Cortex SWD debug header. * Cons: only work with ARM Cortex MCUs minus M0 * Pros: should be compatible with more debugger that support SWO. diff --git a/examples/device/mtp/src/usb_descriptors.c b/examples/device/mtp/src/usb_descriptors.c index 810137c46..ddda4d686 100644 --- a/examples/device/mtp/src/usb_descriptors.c +++ b/examples/device/mtp/src/usb_descriptors.c @@ -143,7 +143,7 @@ char const *string_desc_arr[] = "TinyUsb", // 1: Manufacturer "TinyUsb Device", // 2: Product NULL, // 3: Serials will use unique ID if possible - "TinyUSBB MTP", // 4: MTP Interface + "TinyUSB MTP", // 4: MTP Interface }; static uint16_t _desc_str[32 + 1]; diff --git a/test/hil/hil_test.py b/test/hil/hil_test.py index 1a846dd4a..c747ad763 100755 --- a/test/hil/hil_test.py +++ b/test/hil/hil_test.py @@ -42,8 +42,6 @@ import hashlib import ctypes from pymtp import MTP -mtp = MTP() - ENUM_TIMEOUT = 30 STATUS_OK = "\033[32mOK\033[0m" @@ -140,6 +138,19 @@ def read_disk_file(uid, lun, fname): return None +def open_mtp_dev(uid): + mtp = MTP() + timeout = ENUM_TIMEOUT + while timeout > 0: + for raw in mtp.detect_devices(): + mtp.device = mtp.mtp.LIBMTP_Open_Raw_Device(ctypes.byref(raw)) + if mtp.device and mtp.get_serialnumber().decode('utf-8') == uid: + return mtp + time.sleep(1) + timeout -= 1 + return None + + # ------------------------------------------------------------- # Flashing firmware # ------------------------------------------------------------- @@ -505,19 +516,14 @@ def test_device_mtp(board): _null = os.open(os.devnull, os.O_WRONLY) os.dup2(_null, fd) - for raw in mtp.detect_devices(): - mtp.device = mtp.mtp.LIBMTP_Open_Raw_Device(ctypes.byref(raw)) - if mtp.device and mtp.get_serialnumber().decode('utf-8') == uid: - break - else: - mtp.device = None + mtp = open_mtp_dev(uid) # --- AFTER: restore stderr --- os.dup2(_saved, fd) os.close(_null) os.close(_saved) - if mtp.device is None: + if mtp is None or mtp.device is None: assert False, 'MTP device not found' assert b"TinyUSB" == mtp.get_manufacturer(), 'MTP wrong manufacturer' From 4a0613cbafd1109a8d656d8d379040dfc38efa60 Mon Sep 17 00:00:00 2001 From: hathach Date: Thu, 2 Oct 2025 12:49:38 +0700 Subject: [PATCH 39/43] fix mtp serial --- examples/device/mtp/src/mtp_fs_example.c | 7 ++- test/hil/hil_test.py | 73 ++++++++++++------------ 2 files changed, 42 insertions(+), 38 deletions(-) diff --git a/examples/device/mtp/src/mtp_fs_example.c b/examples/device/mtp/src/mtp_fs_example.c index 3ae63173c..5f56ca24d 100644 --- a/examples/device/mtp/src/mtp_fs_example.c +++ b/examples/device/mtp/src/mtp_fs_example.c @@ -351,9 +351,10 @@ static int32_t fs_get_device_info(tud_mtp_cb_data_t* cb_data) { mtp_container_add_cstring(io_container, DEV_INFO_MODEL); mtp_container_add_cstring(io_container, DEV_INFO_VERSION); - uint16_t serial_utf16[32]; - size_t nchars = board_usb_get_serial(serial_utf16, 32); - serial_utf16[tu_min32(nchars, 31u)] = 0; // ensure null termination + enum { MAX_SERIAL_NCHARS = 32 }; + uint16_t serial_utf16[MAX_SERIAL_NCHARS+1]; + size_t nchars = board_usb_get_serial(serial_utf16, MAX_SERIAL_NCHARS); + serial_utf16[tu_min32(nchars, MAX_SERIAL_NCHARS)] = 0; // ensure null termination mtp_container_add_string(io_container, serial_utf16); tud_mtp_data_send(io_container); diff --git a/test/hil/hil_test.py b/test/hil/hil_test.py index c747ad763..eb36b156e 100755 --- a/test/hil/hil_test.py +++ b/test/hil/hil_test.py @@ -142,6 +142,7 @@ def open_mtp_dev(uid): mtp = MTP() timeout = ENUM_TIMEOUT while timeout > 0: + # run_cmd(f"gio mount -u mtp://TinyUsb_TinyUsb_Device_{uid}/") for raw in mtp.detect_devices(): mtp.device = mtp.mtp.LIBMTP_Open_Raw_Device(ctypes.byref(raw)) if mtp.device and mtp.get_serialnumber().decode('utf-8') == uid: @@ -526,42 +527,44 @@ def test_device_mtp(board): if mtp is None or mtp.device is None: assert False, 'MTP device not found' - assert b"TinyUSB" == mtp.get_manufacturer(), 'MTP wrong manufacturer' - assert b"MTP Example" == mtp.get_modelname(), 'MTP wrong model' - assert b'1.0' == mtp.get_deviceversion(), 'MTP wrong version' - assert b'TinyUSB MTP' == mtp.get_devicename(), 'MTP wrong device name' - - # read and compare readme.txt and logo.png - f1_expect = b'TinyUSB MTP Filesystem example' - f2_md5_expect = '40ef23fc2891018d41a05d4a0d5f822f' # md5sum of logo.png - f1 = uid.encode("utf-8") + b'_file1' - f2 = uid.encode("utf-8") + b'_file2' - f3 = uid.encode("utf-8") + b'_file3' - mtp.get_file_to_file(1, f1) - with open(f1, 'rb') as file: - f1_data = file.read() - os.remove(f1) - assert f1_data == f1_expect, 'MTP file1 wrong data' - mtp.get_file_to_file(2, f2) - with open(f2, 'rb') as file: - f2_data = file.read() - os.remove(f2) - 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)) - file.write(f3_data) - file.close() - fid = mtp.send_file_from_file(f3, b'file3') - f3_readback = f3 + b'_readback' - mtp.get_file_to_file(fid, f3_readback) - with open(f3_readback, 'rb') as f: - f3_rb_data = f.read() - os.remove(f3_readback) - assert f3_rb_data == f3_data, 'MTP file3 wrong data' - os.remove(f3) - mtp.delete_object(fid) + try: + assert b"TinyUSB" == mtp.get_manufacturer(), 'MTP wrong manufacturer' + assert b"MTP Example" == mtp.get_modelname(), 'MTP wrong model' + assert b'1.0' == mtp.get_deviceversion(), 'MTP wrong version' + assert b'TinyUSB MTP' == mtp.get_devicename(), 'MTP wrong device name' + # read and compare readme.txt and logo.png + f1_expect = b'TinyUSB MTP Filesystem example' + f2_md5_expect = '40ef23fc2891018d41a05d4a0d5f822f' # md5sum of logo.png + f1 = uid.encode("utf-8") + b'_file1' + f2 = uid.encode("utf-8") + b'_file2' + f3 = uid.encode("utf-8") + b'_file3' + mtp.get_file_to_file(1, f1) + with open(f1, 'rb') as file: + f1_data = file.read() + os.remove(f1) + assert f1_data == f1_expect, 'MTP file1 wrong data' + mtp.get_file_to_file(2, f2) + with open(f2, 'rb') as file: + f2_data = file.read() + os.remove(f2) + 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)) + file.write(f3_data) + file.close() + fid = mtp.send_file_from_file(f3, b'file3') + f3_readback = f3 + b'_readback' + mtp.get_file_to_file(fid, f3_readback) + with open(f3_readback, 'rb') as f: + f3_rb_data = f.read() + os.remove(f3_readback) + assert f3_rb_data == f3_data, 'MTP file3 wrong data' + os.remove(f3) + mtp.delete_object(fid) + finally: + mtp.disconnect() # ------------------------------------------------------------- From 981dc982ce2e8b37041e47d0c17f479d08f11bd6 Mon Sep 17 00:00:00 2001 From: hathach Date: Thu, 2 Oct 2025 15:57:59 +0700 Subject: [PATCH 40/43] mtp example work with highspeed device --- examples/device/mtp/src/usb_descriptors.c | 72 +++++++++++++++++++++-- src/device/usbd.h | 2 +- 2 files changed, 68 insertions(+), 6 deletions(-) diff --git a/examples/device/mtp/src/usb_descriptors.c b/examples/device/mtp/src/usb_descriptors.c index ddda4d686..cabb96439 100644 --- a/examples/device/mtp/src/usb_descriptors.c +++ b/examples/device/mtp/src/usb_descriptors.c @@ -109,18 +109,79 @@ enum #define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_MTP_DESC_LEN) -uint8_t const desc_fs_configuration[] = { +// full speed configuration +const uint8_t desc_fs_configuration[] = { // Config number, interface count, string index, total length, attribute, power in mA TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0x00, 100), + // Interface number, string index, EP event, EP event size, EP event polling, EP Out & EP In address, EP size TUD_MTP_DESCRIPTOR(ITF_NUM_MTP, 4, EPNUM_MTP_EVT, 64, 1, EPNUM_MTP_OUT, EPNUM_MTP_IN, 64), }; +#if TUD_OPT_HIGH_SPEED +// Per USB specs: high speed capable device must report device_qualifier and other_speed_configuration + +// high speed configuration +uint8_t const desc_hs_configuration[] = { + // Config number, interface count, string index, total length, attribute, power in mA + TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0x00, 100), + // Interface number, string index, EP event, EP event size, EP event polling, EP Out & EP In address, EP size + TUD_MTP_DESCRIPTOR(ITF_NUM_MTP, 4, EPNUM_MTP_EVT, 64, 1, EPNUM_MTP_OUT, EPNUM_MTP_IN, 512), +}; + +// other speed configuration +uint8_t desc_other_speed_config[CONFIG_TOTAL_LEN]; + +// device qualifier is mostly similar to device descriptor since we don't change configuration based on speed +tusb_desc_device_qualifier_t const desc_device_qualifier = { + .bLength = sizeof(tusb_desc_device_qualifier_t), + .bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER, + .bcdUSB = USB_BCD, + + .bDeviceClass = TUSB_CLASS_MISC, + .bDeviceSubClass = MISC_SUBCLASS_COMMON, + .bDeviceProtocol = MISC_PROTOCOL_IAD, + + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + .bNumConfigurations = 0x01, + .bReserved = 0x00 +}; + +// Invoked when received GET DEVICE QUALIFIER DESCRIPTOR request +// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete. +// device_qualifier descriptor describes information about a high-speed capable device that would +// change if the device were operating at the other speed. If not highspeed capable stall this request. +uint8_t const *tud_descriptor_device_qualifier_cb(void) { + return (uint8_t const *) &desc_device_qualifier; +} + +// Invoked when received GET OTHER SEED CONFIGURATION DESCRIPTOR request +// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete +// Configuration descriptor in the other speed e.g if high speed then this is for full speed and vice versa +uint8_t const *tud_descriptor_other_speed_configuration_cb(uint8_t index) { + (void) index; // for multiple configurations + + // if link speed is high return fullspeed config, and vice versa + // Note: the descriptor type is OHER_SPEED_CONFIG instead of CONFIG + memcpy(desc_other_speed_config, + (tud_speed_get() == TUSB_SPEED_HIGH) ? desc_fs_configuration : desc_hs_configuration, + CONFIG_TOTAL_LEN); + desc_other_speed_config[1] = TUSB_DESC_OTHER_SPEED_CONFIG; + return desc_other_speed_config; +} + +#endif // highspeed + // Invoked when received GET CONFIGURATION DESCRIPTOR // Application return pointer to descriptor // Descriptor contents must exist long enough for transfer to complete -uint8_t const *tud_descriptor_configuration_cb(uint8_t index) { +const uint8_t*tud_descriptor_configuration_cb(uint8_t index) { (void) index; // for multiple configurations +#if TUD_OPT_HIGH_SPEED + // Although we are highspeed, host may be fullspeed. + return (tud_speed_get() == TUSB_SPEED_HIGH) ? desc_hs_configuration : desc_fs_configuration; +#else return desc_fs_configuration; +#endif } //--------------------------------------------------------------------+ @@ -173,11 +234,12 @@ uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid) { } const char *str = string_desc_arr[index]; - // Cap at max char chr_count = strlen(str); - size_t const max_count = sizeof(_desc_str) / sizeof(_desc_str[0]) - 1; // -1 for string type - if ( chr_count > max_count ) chr_count = max_count; + const size_t max_count = sizeof(_desc_str) / sizeof(_desc_str[0]) - 1; // -1 for string type + if ( chr_count > max_count ) { + chr_count = max_count; + } // Convert ASCII string into UTF-16 for ( size_t i = 0; i < chr_count; i++ ) { diff --git a/src/device/usbd.h b/src/device/usbd.h index 1dc3761d9..a4104e47d 100644 --- a/src/device/usbd.h +++ b/src/device/usbd.h @@ -276,7 +276,7 @@ bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_requ // Length of template descriptor: 30 bytes #define TUD_MTP_DESC_LEN (9 + 7 + 7 + 7) -// Interface number, string index, EP Out & EP In address, EP size +// Interface number, string index, EP event, EP event size, EP event polling, EP Out & EP In address, EP size #define TUD_MTP_DESCRIPTOR(_itfnum, _stridx, _ep_evt, _ep_evt_size, _ep_evt_polling_interval, _epout, _epin, _epsize) \ /* Interface */\ 9, TUSB_DESC_INTERFACE, _itfnum, 0, 3, TUSB_CLASS_IMAGE, MTP_SUBCLASS_STILL_IMAGE, MTP_PROTOCOL_PIMA_15470, _stridx,\ From 3773d60a1edb85b8f96800cd920b08b857f88390 Mon Sep 17 00:00:00 2001 From: hathach Date: Thu, 2 Oct 2025 17:15:22 +0700 Subject: [PATCH 41/43] fix typos --- examples/device/cdc_msc/src/usb_descriptors.c | 2 +- examples/device/cdc_msc_freertos/src/usb_descriptors.c | 2 +- examples/device/cdc_uac2/src/usb_descriptors.c | 2 +- examples/device/hid_composite/src/usb_descriptors.c | 2 +- examples/device/hid_composite_freertos/src/usb_descriptors.c | 2 +- examples/device/mtp/src/usb_descriptors.c | 2 +- examples/dual/host_hid_to_device_cdc/src/usb_descriptors.c | 2 +- examples/dual/host_info_to_device_cdc/src/usb_descriptors.c | 2 +- test/fuzz/device/cdc/src/usb_descriptors.cc | 2 +- test/fuzz/device/msc/src/usb_descriptors.cc | 2 +- test/fuzz/device/net/src/usb_descriptors.cc | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/device/cdc_msc/src/usb_descriptors.c b/examples/device/cdc_msc/src/usb_descriptors.c index edcee0462..597a6b1e6 100644 --- a/examples/device/cdc_msc/src/usb_descriptors.c +++ b/examples/device/cdc_msc/src/usb_descriptors.c @@ -184,7 +184,7 @@ uint8_t const *tud_descriptor_other_speed_configuration_cb(uint8_t index) { (void) index; // for multiple configurations // if link speed is high return fullspeed config, and vice versa - // Note: the descriptor type is OHER_SPEED_CONFIG instead of CONFIG + // Note: the descriptor type is OTHER_SPEED_CONFIG instead of CONFIG memcpy(desc_other_speed_config, (tud_speed_get() == TUSB_SPEED_HIGH) ? desc_fs_configuration : desc_hs_configuration, CONFIG_TOTAL_LEN); diff --git a/examples/device/cdc_msc_freertos/src/usb_descriptors.c b/examples/device/cdc_msc_freertos/src/usb_descriptors.c index a55fa3675..cb440c209 100644 --- a/examples/device/cdc_msc_freertos/src/usb_descriptors.c +++ b/examples/device/cdc_msc_freertos/src/usb_descriptors.c @@ -186,7 +186,7 @@ uint8_t const* tud_descriptor_other_speed_configuration_cb(uint8_t index) { (void) index; // for multiple configurations // if link speed is high return fullspeed config, and vice versa - // Note: the descriptor type is OHER_SPEED_CONFIG instead of CONFIG + // Note: the descriptor type is OTHER_SPEED_CONFIG instead of CONFIG memcpy(desc_other_speed_config, (tud_speed_get() == TUSB_SPEED_HIGH) ? desc_fs_configuration : desc_hs_configuration, CONFIG_TOTAL_LEN); diff --git a/examples/device/cdc_uac2/src/usb_descriptors.c b/examples/device/cdc_uac2/src/usb_descriptors.c index 22d3cf05a..da55bdb5a 100644 --- a/examples/device/cdc_uac2/src/usb_descriptors.c +++ b/examples/device/cdc_uac2/src/usb_descriptors.c @@ -176,7 +176,7 @@ uint8_t const *tud_descriptor_other_speed_configuration_cb(uint8_t index) { (void) index; // for multiple configurations // if link speed is high return fullspeed config, and vice versa - // Note: the descriptor type is OHER_SPEED_CONFIG instead of CONFIG + // Note: the descriptor type is OTHER_SPEED_CONFIG instead of CONFIG memcpy(desc_other_speed_config, (tud_speed_get() == TUSB_SPEED_HIGH) ? desc_fs_configuration : desc_hs_configuration, CONFIG_TOTAL_LEN); diff --git a/examples/device/hid_composite/src/usb_descriptors.c b/examples/device/hid_composite/src/usb_descriptors.c index 15c6e1f73..ce7fbd13f 100644 --- a/examples/device/hid_composite/src/usb_descriptors.c +++ b/examples/device/hid_composite/src/usb_descriptors.c @@ -154,7 +154,7 @@ uint8_t const* tud_descriptor_other_speed_configuration_cb(uint8_t index) { (void) index; // for multiple configurations - // other speed config is basically configuration with type = OHER_SPEED_CONFIG + // other speed config is basically configuration with type = OTHER_SPEED_CONFIG memcpy(desc_other_speed_config, desc_configuration, CONFIG_TOTAL_LEN); desc_other_speed_config[1] = TUSB_DESC_OTHER_SPEED_CONFIG; diff --git a/examples/device/hid_composite_freertos/src/usb_descriptors.c b/examples/device/hid_composite_freertos/src/usb_descriptors.c index 85820de55..3f231fecc 100644 --- a/examples/device/hid_composite_freertos/src/usb_descriptors.c +++ b/examples/device/hid_composite_freertos/src/usb_descriptors.c @@ -153,7 +153,7 @@ uint8_t const* tud_descriptor_other_speed_configuration_cb(uint8_t index) { (void) index; // for multiple configurations - // other speed config is basically configuration with type = OHER_SPEED_CONFIG + // other speed config is basically configuration with type = OTHER_SPEED_CONFIG memcpy(desc_other_speed_config, desc_configuration, CONFIG_TOTAL_LEN); desc_other_speed_config[1] = TUSB_DESC_OTHER_SPEED_CONFIG; diff --git a/examples/device/mtp/src/usb_descriptors.c b/examples/device/mtp/src/usb_descriptors.c index cabb96439..80345d8f8 100644 --- a/examples/device/mtp/src/usb_descriptors.c +++ b/examples/device/mtp/src/usb_descriptors.c @@ -161,7 +161,7 @@ uint8_t const *tud_descriptor_other_speed_configuration_cb(uint8_t index) { (void) index; // for multiple configurations // if link speed is high return fullspeed config, and vice versa - // Note: the descriptor type is OHER_SPEED_CONFIG instead of CONFIG + // Note: the descriptor type is OTHER_SPEED_CONFIG instead of CONFIG memcpy(desc_other_speed_config, (tud_speed_get() == TUSB_SPEED_HIGH) ? desc_fs_configuration : desc_hs_configuration, CONFIG_TOTAL_LEN); diff --git a/examples/dual/host_hid_to_device_cdc/src/usb_descriptors.c b/examples/dual/host_hid_to_device_cdc/src/usb_descriptors.c index 9d57737fb..b7cffe23d 100644 --- a/examples/dual/host_hid_to_device_cdc/src/usb_descriptors.c +++ b/examples/dual/host_hid_to_device_cdc/src/usb_descriptors.c @@ -175,7 +175,7 @@ uint8_t const* tud_descriptor_other_speed_configuration_cb(uint8_t index) { (void) index; // for multiple configurations // if link speed is high return fullspeed config, and vice versa - // Note: the descriptor type is OHER_SPEED_CONFIG instead of CONFIG + // Note: the descriptor type is OTHER_SPEED_CONFIG instead of CONFIG memcpy(desc_other_speed_config, (tud_speed_get() == TUSB_SPEED_HIGH) ? desc_fs_configuration : desc_hs_configuration, CONFIG_TOTAL_LEN); diff --git a/examples/dual/host_info_to_device_cdc/src/usb_descriptors.c b/examples/dual/host_info_to_device_cdc/src/usb_descriptors.c index 9d57737fb..b7cffe23d 100644 --- a/examples/dual/host_info_to_device_cdc/src/usb_descriptors.c +++ b/examples/dual/host_info_to_device_cdc/src/usb_descriptors.c @@ -175,7 +175,7 @@ uint8_t const* tud_descriptor_other_speed_configuration_cb(uint8_t index) { (void) index; // for multiple configurations // if link speed is high return fullspeed config, and vice versa - // Note: the descriptor type is OHER_SPEED_CONFIG instead of CONFIG + // Note: the descriptor type is OTHER_SPEED_CONFIG instead of CONFIG memcpy(desc_other_speed_config, (tud_speed_get() == TUSB_SPEED_HIGH) ? desc_fs_configuration : desc_hs_configuration, CONFIG_TOTAL_LEN); diff --git a/test/fuzz/device/cdc/src/usb_descriptors.cc b/test/fuzz/device/cdc/src/usb_descriptors.cc index 0f636f05d..c26bd18c3 100644 --- a/test/fuzz/device/cdc/src/usb_descriptors.cc +++ b/test/fuzz/device/cdc/src/usb_descriptors.cc @@ -148,7 +148,7 @@ uint8_t const *tud_descriptor_other_speed_configuration_cb(uint8_t index) { (void)index; // for multiple configurations // if link speed is high return fullspeed config, and vice versa - // Note: the descriptor type is OHER_SPEED_CONFIG instead of CONFIG + // Note: the descriptor type is OTHER_SPEED_CONFIG instead of CONFIG memcpy(desc_other_speed_config, (tud_speed_get() == TUSB_SPEED_HIGH) ? desc_fs_configuration : desc_hs_configuration, diff --git a/test/fuzz/device/msc/src/usb_descriptors.cc b/test/fuzz/device/msc/src/usb_descriptors.cc index efe4d0a3c..6d9c4cd96 100644 --- a/test/fuzz/device/msc/src/usb_descriptors.cc +++ b/test/fuzz/device/msc/src/usb_descriptors.cc @@ -142,7 +142,7 @@ uint8_t const *tud_descriptor_other_speed_configuration_cb(uint8_t index) { (void)index; // for multiple configurations // if link speed is high return fullspeed config, and vice versa - // Note: the descriptor type is OHER_SPEED_CONFIG instead of CONFIG + // Note: the descriptor type is OTHER_SPEED_CONFIG instead of CONFIG memcpy(desc_other_speed_config, (tud_speed_get() == TUSB_SPEED_HIGH) ? desc_fs_configuration : desc_hs_configuration, diff --git a/test/fuzz/device/net/src/usb_descriptors.cc b/test/fuzz/device/net/src/usb_descriptors.cc index 5597d49d5..e57a791b6 100644 --- a/test/fuzz/device/net/src/usb_descriptors.cc +++ b/test/fuzz/device/net/src/usb_descriptors.cc @@ -148,7 +148,7 @@ uint8_t const *tud_descriptor_other_speed_configuration_cb(uint8_t index) { (void)index; // for multiple configurations // if link speed is high return fullspeed config, and vice versa - // Note: the descriptor type is OHER_SPEED_CONFIG instead of CONFIG + // Note: the descriptor type is OTHER_SPEED_CONFIG instead of CONFIG memcpy(desc_other_speed_config, (tud_speed_get() == TUSB_SPEED_HIGH) ? desc_fs_configuration : desc_hs_configuration, From 9cd5fb6f38b95edb70841071007d5ec85b8e0ea8 Mon Sep 17 00:00:00 2001 From: hathach Date: Thu, 2 Oct 2025 17:58:51 +0700 Subject: [PATCH 42/43] skip mtp for now --- test/hil/hil_test.py | 46 +++++++++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/test/hil/hil_test.py b/test/hil/hil_test.py index eb36b156e..2e6840a6d 100755 --- a/test/hil/hil_test.py +++ b/test/hil/hil_test.py @@ -49,6 +49,7 @@ STATUS_FAILED = "\033[31mFailed\033[0m" STATUS_SKIPPED = "\033[33mSkipped\033[0m" verbose = False +test_only = [] WCH_RISCV_CONTENT = """ adapter driver wlinke @@ -145,8 +146,11 @@ def open_mtp_dev(uid): # run_cmd(f"gio mount -u mtp://TinyUsb_TinyUsb_Device_{uid}/") for raw in mtp.detect_devices(): mtp.device = mtp.mtp.LIBMTP_Open_Raw_Device(ctypes.byref(raw)) - if mtp.device and mtp.get_serialnumber().decode('utf-8') == uid: - return mtp + if mtp.device: + sn = mtp.get_serialnumber().decode('utf-8') + #print(f'mtp serial = {sn}') + if sn == uid: + return mtp time.sleep(1) timeout -= 1 return None @@ -579,7 +583,7 @@ device_tests = [ 'device/dfu_runtime', 'device/cdc_msc_freertos', 'device/hid_boot_interface', - 'device/mtp' + # 'device/mtp' ] dual_tests = [ @@ -656,21 +660,24 @@ def test_board(board): # default to all tests test_list = [] - if 'tests' in board: - board_tests = board['tests'] - if 'device' in board_tests and board_tests['device'] == True: - test_list += list(device_tests) - if 'dual' in board_tests and board_tests['dual'] == True: - test_list += dual_tests - if 'host' in board_tests and board_tests['host'] == True: - test_list += host_test - if 'only' in board_tests: - test_list = board_tests['only'] - if 'skip' in board_tests: - for skip in board_tests['skip']: - if skip in test_list: - test_list.remove(skip) - print(f'{name:25} {skip:30} ... Skip') + if len(test_only) > 0: + test_list = test_only + else: + if 'tests' in board: + board_tests = board['tests'] + if 'device' in board_tests and board_tests['device'] == True: + test_list += list(device_tests) + if 'dual' in board_tests and board_tests['dual'] == True: + test_list += dual_tests + if 'host' in board_tests and board_tests['host'] == True: + test_list += host_test + if 'only' in board_tests: + test_list = board_tests['only'] + if 'skip' in board_tests: + for skip in board_tests['skip']: + if skip in test_list: + test_list.remove(skip) + print(f'{name:25} {skip:30} ... Skip') err_count = 0 flags_on_list = [""] @@ -692,6 +699,7 @@ def main(): Hardware test on specified boards """ global verbose + global test_only duration = time.time() @@ -699,6 +707,7 @@ def main(): parser.add_argument('config_file', help='Configuration JSON file') parser.add_argument('-b', '--board', action='append', default=[], help='Boards to test, all if not specified') parser.add_argument('-s', '--skip', action='append', default=[], help='Skip boards from test') + parser.add_argument('-t', '--test-only', action='append', default=[], help='Tests to run, all if not specified') parser.add_argument('-v', '--verbose', action='store_true', help='Verbose output') args = parser.parse_args() @@ -706,6 +715,7 @@ def main(): boards = args.board skip_boards = args.skip verbose = args.verbose + test_only = args.test_only # if config file is not found, try to find it in the same directory as this script if not os.path.exists(config_file): From 610f353d8d602c3192f9edc03669ab792056f7da Mon Sep 17 00:00:00 2001 From: hathach Date: Thu, 2 Oct 2025 18:54:31 +0700 Subject: [PATCH 43/43] use cache to store skip board in hil ci --- .github/workflows/build.yml | 11 ++++++++--- test/hil/hil_test.py | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 676e2d428..9243c866d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -199,23 +199,28 @@ jobs: runs-on: [self-hosted, X64, hathach, hardware-in-the-loop] steps: - name: Clean workspace - if: github.run_attempt == '1' run: | echo "Cleaning up for the first run" rm -rf "${{ github.workspace }}" mkdir -p "${{ github.workspace }}" - name: Checkout TinyUSB - if: github.run_attempt == '1' uses: actions/checkout@v4 - name: Download Artifacts - if: github.run_attempt == '1' uses: actions/download-artifact@v4 with: path: cmake-build merge-multiple: true + - name: Cache skip list + uses: actions/cache@v4 + with: + path: ${{ env.HIL_JSON }}.skip + key: hil-skip-${{ github.run_id }}-${{ github.run_attempt }} + restore-keys: | + hil-skip-${{ github.run_id }}- + - name: Test on actual hardware run: | ls cmake-build/ diff --git a/test/hil/hil_test.py b/test/hil/hil_test.py index 2e6840a6d..fc8255f1b 100755 --- a/test/hil/hil_test.py +++ b/test/hil/hil_test.py @@ -583,7 +583,7 @@ device_tests = [ 'device/dfu_runtime', 'device/cdc_msc_freertos', 'device/hid_boot_interface', - # 'device/mtp' + 'device/mtp' ] dual_tests = [