diff --git a/.gitignore b/.gitignore index 61eb050..0f5a07c 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,4 @@ managed_components dependencies.lock # VS Code Settings -.vscode/ \ No newline at end of file +.vscode/ diff --git a/Kconfig b/Kconfig index d7ad4d0..8eded0c 100644 --- a/Kconfig +++ b/Kconfig @@ -6,6 +6,37 @@ menu "ESP CherryUSB" bool default y if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 + choice USB_DBG_LEVEL + bool "Default log verbosity" + default USB_DBG_LEVEL_INFO + help + Specify how much output to see in logs by default. + You can set lower verbosity level at runtime using + esp_log_level_set function. + + By default, this setting limits which log statements + are compiled into the program. For example, selecting + "Warning" would mean that changing log level to "Debug" + at runtime will not be possible. To allow increasing log + level above the default at runtime, see the next option. + + config USB_DBG_LEVEL_ERROR + bool "Error" + config USB_DBG_LEVEL_WARN + bool "Warning" + config USB_DBG_LEVEL_INFO + bool "Info" + config USB_DBG_LEVEL_DEBUG + bool "Debug" + endchoice + + config USB_DBG_LEVEL + int + default 0 if USB_DBG_LEVEL_ERROR + default 1 if USB_DBG_LEVEL_WARN + default 2 if USB_DBG_LEVEL_INFO + default 3 if USB_DBG_LEVEL_DEBUG + menuconfig CHERRYUSBD_ENABLED bool "Enable CherryUSB Device" depends on CHERRYUSB_SUPPORTED diff --git a/examples/device/cherryusb_device_cdc/main/CMakeLists.txt b/examples/device/cherryusb_device_cdc/main/CMakeLists.txt index b7d0e2a..6795aea 100644 --- a/examples/device/cherryusb_device_cdc/main/CMakeLists.txt +++ b/examples/device/cherryusb_device_cdc/main/CMakeLists.txt @@ -1,5 +1,3 @@ -idf_component_register(SRCS - "device_cdc_main.c" - INCLUDE_DIRS - "." +idf_component_register(SRCS "device_cdc_main.c" + INCLUDE_DIRS "." ) diff --git a/examples/device/cherryusb_device_mtp/CMakeLists.txt b/examples/device/cherryusb_device_mtp/CMakeLists.txt new file mode 100644 index 0000000..ed957fe --- /dev/null +++ b/examples/device/cherryusb_device_mtp/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(cherryusb_device_mtp) diff --git a/examples/device/cherryusb_device_mtp/components/esp_mtp/CMakeLists.txt b/examples/device/cherryusb_device_mtp/components/esp_mtp/CMakeLists.txt new file mode 100644 index 0000000..0a36aa3 --- /dev/null +++ b/examples/device/cherryusb_device_mtp/components/esp_mtp/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register(SRCS "esp_mtp.c" "esp_mtp_helper.c" + INCLUDE_DIRS "include" + PRIV_REQUIRES fatfs +) diff --git a/examples/device/cherryusb_device_mtp/components/esp_mtp/esp_mtp.c b/examples/device/cherryusb_device_mtp/components/esp_mtp/esp_mtp.c new file mode 100644 index 0000000..5f02455 --- /dev/null +++ b/examples/device/cherryusb_device_mtp/components/esp_mtp/esp_mtp.c @@ -0,0 +1,1109 @@ +/* + * Copyright (c) 2024, udoudou + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_mtp.h" +#include "esp_mtp_def.h" +#include "esp_mtp_helper.h" + +#include "string.h" +#include "dirent.h" +#include "sys/stat.h" +#include "unistd.h" +#include "fcntl.h" +#include "utime.h" + +#include "esp_vfs_fat.h" +#include "esp_log.h" +#ifdef CONFIG_SPIRAM_BOOT_INIT +#include "esp_heap_caps.h" +#endif + +static char *TAG = "esp_mtp"; + +#define ASYNC_READ_NOTIFY_BIT BIT0 +#define ASYNC_WRITE_NOTIFY_BIT BIT1 + +typedef struct esp_mtp { + void *pipe_context; + void (*wait_start)(void *pipe_context); + int (*read)(void *pipe_context, uint8_t *buffer, int len); + int (*write)(void *pipe_context, const uint8_t *buffer, int len); + esp_mtp_flags_t flags; + TaskHandle_t task_hdl; + int async_read_len; + esp_mtp_file_handle_list_t handle_list; + uint32_t buffer_size; + uint8_t buff[0]; +}esp_mtp_t; + +const mtp_operation_code_t supported_operation_codes[] = { + MTP_OPERATION_GET_DEVICE_INFO, + MTP_OPERATION_OPEN_SESSION, + MTP_OPERATION_CLOSE_SESSION, + MTP_OPERATION_GET_STORAGE_IDS, + MTP_OPERATION_GET_STORAGE_INFO, + MTP_OPERATION_GET_OBJECT_HANDLES, + MTP_OPERATION_GET_OBJECT_INFO, + MTP_OPERATION_GET_OBJECT, + MTP_OPERATION_DELETE_OBJECT, + MTP_OPERATION_SEND_OBJECT_INFO, + MTP_OPERATION_SEND_OBJECT, + MTP_OPERATION_GET_PARTIAL_OBJECT, + + // MTP_OPERATION_GET_OBJECT_PROPS_SUPPORTED, + // MTP_OPERATION_GET_OBJECT_PROP_DESC, + // MTP_OPERATION_GET_OBJECT_PROP_VALUE, + // MTP_OPERATION_SET_OBJECT_PROP_VALUE +}; + +//todo 不必要 +const mtp_operation_code_t supported_event_codes[] = { + +}; + +static void check_usb_len_mps_and_send_end(esp_mtp_handle_t handle, uint32_t len) +{ + bool need_send_end = false; + if (handle->flags & (ESP_MTP_FLAG_USB_FS | ESP_MTP_FLAG_USB_HS)) { + if (handle->flags & ESP_MTP_FLAG_USB_FS) { + if (len % 64 == 0) { + need_send_end = true; + } + } else { + if (len % 512 == 0) { + need_send_end = true; + } + } + if (need_send_end) { + handle->write(handle->pipe_context, NULL, 0); + if (handle->flags & ESP_MTP_FLAG_ASYNC_WRITE) { + uint32_t notify_value; + xTaskNotifyWait(0x0, ASYNC_WRITE_NOTIFY_BIT, ¬ify_value, portMAX_DELAY); + } + } + } +} + + +static mtp_response_code_t open_session(esp_mtp_handle_t handle) +{ + return MTP_RESPONSE_OK; +} + +static mtp_response_code_t get_thumb(esp_mtp_handle_t handle) +{ + return MTP_RESPONSE_OK; +} + +static mtp_response_code_t get_device_info(esp_mtp_handle_t handle) +{ + mtp_container_t *container = (mtp_container_t *)handle->buff; + uint8_t *data = container->data; + container->type = MTP_CONTAINER_DATA; + *(uint16_t *)data = MTP_STANDARD_VERSION; // Standard Version + data += 2; + *(uint32_t *)data = MTP_VENDOR_EXTN_ID; // MTP Vendor Extension ID + data += 4; + *(uint16_t *)data = MTP_VENDOR_EXTN_VERSION; // MTP Version + data += 2; + *data = handle->buff + handle->buffer_size - data; + data = (uint8_t *)esp_mtp_utf8_to_utf16(MTP_VENDOR_EXTENSIONDESC_CHAR, (char *)data + 1, data); // MTP Extensions + *(mtp_functional_mode_t *)data = MTP_FUNCTIONAL_STANDARD; // Functional Mode + data += sizeof(mtp_functional_mode_t); + + *(uint32_t *)data = sizeof(supported_operation_codes) / sizeof(supported_operation_codes[0]); + data += 4; + memcpy(data, supported_operation_codes, sizeof(supported_operation_codes)); // Operations Supported + data += sizeof(supported_operation_codes); + + *(uint32_t *)data = sizeof(supported_event_codes) / sizeof(supported_event_codes[0]); + data += 4; + memcpy(data, supported_event_codes, sizeof(supported_event_codes)); // Events Supported + data += sizeof(supported_event_codes); + + // Supported device properties + *(uint32_t *)data = 0; + data += 4; + // *(uint32_t *)data = 2; + // data += 4; + // *(uint16_t *)data = MTP_DEV_PROP_BATTERY_LEVEL; + // data += 2; + // *(uint16_t *)data = MTP_DEV_PROP_DEVICE_FRIENDLY_NAME; + // data += 2; + + // Supported formats + *(uint32_t *)data = 0; + data += 4; + + // Playback Formats + // *(uint32_t *)data = 0; + // data += 4; + *(uint32_t *)data = 2; + data += 4; + *(mtp_object_format_code_t *)data = MTP_OBJECT_FORMAT_UNDEFINED; + data += 2; + *(mtp_object_format_code_t *)data = MTP_OBJECT_FORMAT_ASSOCIATION; + data += sizeof(mtp_object_format_code_t); + + *data = handle->buff + handle->buffer_size - data; + data = (uint8_t *)esp_mtp_utf8_to_utf16("Espressif", (char *)data + 1, data); // Manufacturer + + *data = handle->buff + handle->buffer_size - data; + data = (uint8_t *)esp_mtp_utf8_to_utf16("ESP32-S3", (char *)data + 1, data); // Model + + *data = handle->buff + handle->buffer_size - data; + data = (uint8_t *)esp_mtp_utf8_to_utf16("0.0.1", (char *)data + 1, data); // Device Version + + *data = handle->buff + handle->buffer_size - data; + data = (uint8_t *)esp_mtp_utf8_to_utf16("123456", (char *)data + 1, data); // Serial Number + + container->len = data - handle->buff; + handle->write(handle->pipe_context, handle->buff, container->len); + if (handle->flags & ESP_MTP_FLAG_ASYNC_WRITE) { + uint32_t notify_value; + xTaskNotifyWait(0x0, ASYNC_WRITE_NOTIFY_BIT, ¬ify_value, portMAX_DELAY); + } + check_usb_len_mps_and_send_end(handle, container->len); + + return MTP_RESPONSE_OK; +} + +static mtp_response_code_t get_storage_ids(esp_mtp_handle_t handle) +{ + mtp_container_t *container = (mtp_container_t *)handle->buff; + uint8_t *data = container->data; + container->type = MTP_CONTAINER_DATA; + //Storage ID array + *(uint32_t *)data = 1; + data += 4; + *(uint32_t *)data = 0x010001; + data += 4; + container->len = data - handle->buff; + handle->write(handle->pipe_context, handle->buff, container->len); + if (handle->flags & ESP_MTP_FLAG_ASYNC_WRITE) { + uint32_t notify_value; + xTaskNotifyWait(0x0, ASYNC_WRITE_NOTIFY_BIT, ¬ify_value, portMAX_DELAY); + } + // 长度不会达到 MPS,无需检查 + // check_usb_len_mps_and_send_end(handle, container->len); + + return MTP_RESPONSE_OK; +} + +static mtp_response_code_t get_storage_info(esp_mtp_handle_t handle) +{ + mtp_container_t *container = (mtp_container_t *)handle->buff; + uint8_t *data = container->data; + // container->operation.get_storage_info.storage_id; + + container->type = MTP_CONTAINER_DATA; + //StorageInfo dataset + + *(mtp_storage_type_t *)data = MTP_STORAGE_FIXED_RAM; // Storage Type + data += sizeof(mtp_storage_type_t); + + *(mtp_file_system_type_t *)data = MTP_FILE_SYSTEM_GENERIC_HIERARCH; // Filesystem Type + data += sizeof(mtp_file_system_type_t); + + *(mtp_access_cap_t *)data = MTP_ACCESS_CAP_RW; // Access Capability + data += sizeof(mtp_access_cap_t); + + uint64_t total_bytes = 0, out_free_bytes = 0; + esp_vfs_fat_info("/sdcard", &total_bytes, &out_free_bytes); + *(uint64_t *)data = total_bytes; // Max Capacity + data += sizeof(uint64_t); + + *(uint64_t *)data = out_free_bytes; // Free space in Bytes + data += sizeof(uint64_t); + + *(uint32_t *)data = 0xFFFFFFFF; // Free Space In Objects + data += sizeof(uint32_t); + + + *data = handle->buff + handle->buffer_size - data; + data = (uint8_t *)esp_mtp_utf8_to_utf16("test", (char *)data + 1, data); // Storage Description + + *data = handle->buff + handle->buffer_size - data; + data = (uint8_t *)esp_mtp_utf8_to_utf16("0", (char *)data + 1, data); // Volume Identifier + + container->len = data - handle->buff; + handle->write(handle->pipe_context, handle->buff, container->len); + if (handle->flags & ESP_MTP_FLAG_ASYNC_WRITE) { + uint32_t notify_value; + xTaskNotifyWait(0x0, ASYNC_WRITE_NOTIFY_BIT, ¬ify_value, portMAX_DELAY); + } + check_usb_len_mps_and_send_end(handle, container->len); + return MTP_RESPONSE_OK; +} + +static mtp_response_code_t get_object_handles(esp_mtp_handle_t handle) +{ + mtp_container_t *container = (mtp_container_t *)handle->buff; + uint8_t *data = container->data; + uint32_t parent_handle; + + //todo + // 0xFFFFFFFF 代表获取所有 storage 上的 object_handles + // container->operation.get_object_handles.storage_id + + if (container->operation.get_object_handles.object_format_code != 0) { + return MTP_RESPONSE_SPECIFICATION_BY_FORMAT_UNSUPPORTED; + } + + parent_handle = container->operation.get_object_handles.parent_handle; + // 0xFFFFFFFF 代表获取根目录下的 object_handles + if (parent_handle == 0xFFFFFFFF) { + parent_handle = 0; + } + + container->type = MTP_CONTAINER_DATA; + *(uint32_t *)container->data = handle->buff + handle->buffer_size - container->data - 4; // Number of Object Handles + //todo 暂未支持多个 storage + data = esp_mtp_file_list_fill_handle_array(&handle->handle_list, parent_handle, container->data + 4, (uint32_t *)container->data); // Object Handles + + if (*(uint32_t *)container->data == 0) { + strcpy((char *)container->data, "/sdcard"); + data = container->data + strlen((char *)container->data); + if (parent_handle != 0) { + if (esp_mtp_file_list_find(&handle->handle_list, parent_handle, (char *)data, handle->buff + handle->buffer_size - data) == NULL) { + return MTP_RESPONSE_INVALID_PARENT_OBJECT; + } + } + + { + DIR *dir; + struct dirent *file; + dir = opendir((char *)container->data); + if (dir == NULL) { + ESP_LOGW(TAG, "Failed to open dir for reading:%s", container->data); + return MTP_RESPONSE_INVALID_PARENT_OBJECT; + } + data = container->data + sizeof(uint32_t); + while ((file = readdir(dir)) != NULL) { + *(uint32_t *)data = esp_mtp_file_list_add(&handle->handle_list, 0x00010001, parent_handle, file->d_name); + if (*(uint32_t *)data == 0) { + ESP_LOGW(TAG, "add file list fail"); + break; + } + data += sizeof(uint32_t); + } + closedir(dir); + *(uint32_t *)container->data = (data - (container->data + sizeof(uint32_t))) / sizeof(uint32_t); + } + } + + container->len = data - handle->buff; + handle->write(handle->pipe_context, handle->buff, container->len); + if (handle->flags & ESP_MTP_FLAG_ASYNC_WRITE) { + uint32_t notify_value; + xTaskNotifyWait(0x0, ASYNC_WRITE_NOTIFY_BIT, ¬ify_value, portMAX_DELAY); + } + check_usb_len_mps_and_send_end(handle, container->len); + return MTP_RESPONSE_OK; +} + +static mtp_response_code_t get_object_info(esp_mtp_handle_t handle) +{ + uint8_t *data; + uint32_t object_handle; + const esp_mtp_file_entry_t *entry; + mtp_container_t *container = (mtp_container_t *)handle->buff; + + object_handle = container->operation.get_object_info.object_handle; + strcpy((char *)container->data, "/sdcard"); + data = container->data + strlen((char *)container->data); + entry = esp_mtp_file_list_find(&handle->handle_list, object_handle, (char *)data, handle->buff + handle->buffer_size - data); + if (entry == NULL) { + return MTP_RESPONSE_INVALID_OBJECT_HANDLE; + } + ESP_LOGD(TAG, "%s %s", __FUNCTION__, (char *)container->data); + struct stat st; + if (stat((char *)container->data, &st) != 0) { + return MTP_RESPONSE_INVALID_OBJECT_HANDLE; + } + + container->type = MTP_CONTAINER_DATA; + data = container->data; + + *(uint32_t *)data = entry->storage_id; // StorageID + *(uint32_t *)data = 0x010001; + data += sizeof(uint32_t); + + if (S_ISDIR(st.st_mode)) { + *(uint16_t *)data = MTP_OBJECT_FORMAT_ASSOCIATION; // ObjectFormat Code + } else { + *(uint16_t *)data = MTP_OBJECT_FORMAT_UNDEFINED; // ObjectFormat Code + } + data += sizeof(uint16_t); + + *(uint16_t *)data = 0x0000; // Protection Status + data += sizeof(uint16_t); + + *(uint32_t *)data = st.st_size; // Object Compressed Size + data += sizeof(uint32_t); + + *(uint16_t *)data = 0x0000; // Thumb Format(未使用) + data += sizeof(uint16_t); + + *(uint32_t *)data = 0x0000; // Thumb Compressed Size(未使用) + data += sizeof(uint32_t); + + *(uint32_t *)data = 0x0000; // Thumb Pix Width(未使用) + data += sizeof(uint32_t); + + *(uint32_t *)data = 0x0000; // Thumb Pix Height(未使用) + data += sizeof(uint32_t); + + *(uint32_t *)data = 0x0000; // Image Pix Width(未使用) + data += sizeof(uint32_t); + + *(uint32_t *)data = 0x0000; // Image Pix Height(未使用) + data += sizeof(uint32_t); + + *(uint32_t *)data = 0x0000; // Image Bit Depth(未使用) + data += sizeof(uint32_t); + + *(uint32_t *)data = entry->parent; // Parent Object + data += sizeof(uint32_t); + + if (S_ISDIR(st.st_mode)) { + *(mtp_association_type_t *)data = MTP_ASSOCIATION_GENERIC_FOLDER; // Association Type + } else { + *(mtp_association_type_t *)data = MTP_ASSOCIATION_UNDEFINED; // Association Type + } + data += sizeof(mtp_association_type_t); + + *(uint32_t *)data = 0x0; // Association Description + data += sizeof(uint32_t); + + *(uint32_t *)data = 0x0; // Sequence Number(未使用) + data += sizeof(uint32_t); + + *data = handle->buff + handle->buffer_size - data; + data = (uint8_t *)esp_mtp_utf8_to_utf16(entry->name, (char *)data + 1, data); // Filename + + // Date Created "YYYYMMDDThhmmss.s" + *data = handle->buff + handle->buffer_size - data; + data = (uint8_t *)esp_mtp_time_to_utf16_datatime(st.st_ctime, (char *)data + 1, data); + + // Date Modified "YYYYMMDDThhmmss.s" + *data = handle->buff + handle->buffer_size - data; + data = (uint8_t *)esp_mtp_time_to_utf16_datatime(st.st_mtime, (char *)data + 1, data); + + // *data = handle->buff + handle->buffer_size - data; + // data = (uint8_t*)esp_mtp_utf8_to_utf16("", (char*)data + 1, data); // Keywords(未使用) + *data = 0x0; + data++; + + container->len = data - handle->buff; + handle->write(handle->pipe_context, handle->buff, container->len); + if (handle->flags & ESP_MTP_FLAG_ASYNC_WRITE) { + uint32_t notify_value; + xTaskNotifyWait(0x0, ASYNC_WRITE_NOTIFY_BIT, ¬ify_value, portMAX_DELAY); + } + check_usb_len_mps_and_send_end(handle, container->len); + return MTP_RESPONSE_OK; +} + +static mtp_response_code_t _get_object_common(esp_mtp_handle_t handle, uint32_t object_handle, uint32_t offset, uint32_t max_bytes, uint32_t *actual_bytes) +{ + uint8_t *data; + mtp_container_t *container = (mtp_container_t *)handle->buff; + + strcpy((char *)container->data, "/sdcard"); + data = container->data + strlen((char *)container->data); + if (esp_mtp_file_list_find(&handle->handle_list, object_handle, (char *)data, handle->buff + handle->buffer_size - data) == NULL) { + return MTP_RESPONSE_INVALID_OBJECT_HANDLE; + } + ESP_LOGD(TAG, "%s %s", __FUNCTION__, (char *)container->data); + + int fd; + struct stat st; + if (stat((char *)container->data, &st) != 0) { + return MTP_RESPONSE_ACCESS_DENIED; + } + fd = open((char *)container->data, O_RDONLY); + if (fd < 0) { + return MTP_RESPONSE_ACCESS_DENIED; + } + if (offset) { + if (offset > st.st_size) { + offset = st.st_size; + } + lseek(fd, offset, SEEK_SET); + } + + uint32_t trans_id; + trans_id = container->trans_id; + + uint32_t file_size; + uint32_t max_len; + uint32_t read_len; + uint32_t last_write_len = 0; + + file_size = st.st_size - offset; + if (file_size > max_bytes) { + st.st_size = max_bytes; + file_size = max_bytes; + } + + container->type = MTP_CONTAINER_DATA; + container->len = MTP_CONTAINER_HEAD_LEN + st.st_size; + data = container->data; + + max_len = handle->buffer_size - MTP_CONTAINER_HEAD_LEN; + max_len = max_len / 2; + max_len = max_len & (~0x1ff); //512对齐 + ESP_LOGD(TAG, "max_len:%"PRIu32, max_len); + mtp_response_code_t res = MTP_RESPONSE_OK; + + if (file_size == 0) { + handle->write(handle->pipe_context, handle->buff, MTP_CONTAINER_HEAD_LEN); + last_write_len = MTP_CONTAINER_HEAD_LEN; + } + + while (file_size) { + read_len = file_size > max_len ? max_len : file_size; + if (read(fd, data, read_len) != read_len) { + ESP_LOGE(TAG, "file read error"); + last_write_len = 0; + res = MTP_RESPONSE_INCOMPLETE_TRANSFER; + break; + } + if (file_size != st.st_size) { + if (handle->flags & ESP_MTP_FLAG_ASYNC_WRITE) { + uint32_t notify_value; + xTaskNotifyWait(0x0, ASYNC_WRITE_NOTIFY_BIT, ¬ify_value, portMAX_DELAY); + } + } + file_size -= read_len; + last_write_len = file_size > 0 ? read_len : MTP_CONTAINER_HEAD_LEN + read_len; + + if (data == container->data) { + handle->write(handle->pipe_context, handle->buff, last_write_len); + data = container->data + max_len; + } else { + handle->write(handle->pipe_context, handle->buff + max_len, last_write_len); + memcpy(handle->buff, data + read_len - MTP_CONTAINER_HEAD_LEN, MTP_CONTAINER_HEAD_LEN); + data = container->data; + } + } + close(fd); + if (handle->flags & ESP_MTP_FLAG_ASYNC_WRITE) { + uint32_t notify_value; + xTaskNotifyWait(0x0, ASYNC_WRITE_NOTIFY_BIT, ¬ify_value, portMAX_DELAY); + } + check_usb_len_mps_and_send_end(handle, last_write_len); + if (actual_bytes) { + *actual_bytes = st.st_size - file_size; + } + container->trans_id = trans_id; + return res; +} + +static mtp_response_code_t get_object(esp_mtp_handle_t handle) +{ +#if 0 + uint8_t *data; + uint32_t object_handle; + mtp_container_t *container = (mtp_container_t *)handle->buff; + object_handle = container->operation.get_object.object_handle; + strcpy((char *)container->data, "/sdcard"); + data = container->data + strlen((char *)container->data); + if (esp_mtp_file_list_find(&handle->handle_list, object_handle, (char *)data, handle->buff + handle->buffer_size - data) == NULL) { + return MTP_RESPONSE_INVALID_OBJECT_HANDLE; + } + ESP_LOGW(TAG, "%s %s", __FUNCTION__, (char *)container->data); + + int fd; + struct stat st; + if (stat((char *)container->data, &st) != 0) { + return MTP_RESPONSE_ACCESS_DENIED; + } + fd = open((char *)container->data, O_RDONLY); + if (fd < 0) { + return MTP_RESPONSE_ACCESS_DENIED; + } + + uint32_t trans_id; + trans_id = container->trans_id; + + uint32_t file_size; + uint32_t max_len; + uint32_t read_len; + uint32_t last_write_len = 0; + file_size = st.st_size; + container->type = MTP_CONTAINER_DATA; + container->len = MTP_CONTAINER_HEAD_LEN + st.st_size; + data = container->data; + + max_len = handle->buffer_size - MTP_CONTAINER_HEAD_LEN; + max_len = max_len / 2; + max_len = max_len & (~0x1ff); //512对齐 + ESP_LOGD(TAG, "max_len:%"PRIu32, max_len); + mtp_response_code_t res = MTP_RESPONSE_OK; + + while (file_size) { + read_len = file_size > max_len ? max_len : file_size; + if (read(fd, data, read_len) != read_len) { + ESP_LOGE(TAG, "file read error"); + last_write_len = 0; + res = MTP_RESPONSE_INCOMPLETE_TRANSFER; + break; + } + if (file_size != st.st_size) { + if (handle->flags & ESP_MTP_FLAG_ASYNC_WRITE) { + uint32_t notify_value; + xTaskNotifyWait(0x0, ASYNC_WRITE_NOTIFY_BIT, ¬ify_value, portMAX_DELAY); + } + } + file_size -= read_len; + last_write_len = file_size > 0 ? read_len : MTP_CONTAINER_HEAD_LEN + read_len; + + if (data == container->data) { + handle->write(handle->pipe_context, handle->buff, last_write_len); + data = container->data + max_len; + } else { + handle->write(handle->pipe_context, handle->buff + max_len, last_write_len); + memcpy(handle->buff, data + read_len - MTP_CONTAINER_HEAD_LEN, MTP_CONTAINER_HEAD_LEN); + data = container->data; + } + } + close(fd); + if (handle->flags & ESP_MTP_FLAG_ASYNC_WRITE) { + uint32_t notify_value; + xTaskNotifyWait(0x0, ASYNC_WRITE_NOTIFY_BIT, ¬ify_value, portMAX_DELAY); + } + check_usb_len_mps_and_send_end(handle, last_write_len); + container->trans_id = trans_id; + return res; +#else + uint32_t object_handle; + mtp_container_t *container = (mtp_container_t *)handle->buff; + object_handle = container->operation.get_object.object_handle; + return _get_object_common(handle, object_handle, 0x0, 0xFFFFFFFF, NULL); +#endif +} + +static mtp_response_code_t send_object_info(esp_mtp_handle_t handle) +{ + uint8_t *data; + uint32_t parent_handle; + uint32_t object_handle; + uint32_t storage_id; + mtp_container_t *container = (mtp_container_t *)handle->buff; + + storage_id = container->operation.send_object_info.storage_id; + parent_handle = container->operation.send_object_info.parent_handle; + if (parent_handle == 0xFFFFFFFF) { + parent_handle = 0; + } + + int len; + len = handle->read(handle->pipe_context, handle->buff, handle->buffer_size); + if (handle->flags & ESP_MTP_FLAG_ASYNC_READ) { + uint32_t notify_value; + do { + xTaskNotifyWait(0x0, ASYNC_READ_NOTIFY_BIT | ASYNC_WRITE_NOTIFY_BIT, ¬ify_value, portMAX_DELAY); + } while (!(notify_value & ASYNC_READ_NOTIFY_BIT)); + len = handle->async_read_len; + } + + data = container->data; + //Skip No Use StorageID + data += sizeof(uint32_t); + + //ObjectFormat Code + mtp_object_format_code_t object_format; + object_format = *(mtp_object_format_code_t *)data; + data += sizeof(mtp_object_format_code_t); + + //Skip No Use Protection Status + data += sizeof(uint16_t); + + uint32_t file_size; + file_size = *(uint32_t *)data; //Object Compressed Size + data += sizeof(uint32_t); + + //Skip No Use Thumb Format + data += sizeof(uint16_t); + + //Skip No Use Thumb Compressed Size + data += sizeof(uint32_t); + + //Skip No Use Thumb Pix Width + data += sizeof(uint32_t); + + //Skip No Use Thumb Pix Height + data += sizeof(uint32_t); + + //Skip No Use Image Pix Width + data += sizeof(uint32_t); + + //Skip No Use Image Pix Height + data += sizeof(uint32_t); + + //Skip No Use Image Bit Depth + data += sizeof(uint32_t); + + //Skip No Use Parent Object + data += sizeof(uint32_t); + + //Ignore Association Type + data += sizeof(mtp_association_type_t); + + //Ignore Association Desc + data += sizeof(uint32_t); + + //Skip No Use Sequence Number + data += sizeof(uint32_t); + + //Filename + uint8_t str_len; + str_len = *data; + data += sizeof(uint8_t); + char filename[255]; + uint8_t temp_len = sizeof(filename); + esp_mtp_utf16_to_utf8((char *)data, filename, &temp_len); + ESP_LOGD(TAG, "%s %s", __FUNCTION__, filename); + + data += (str_len * 2); + + struct utimbuf times; + //Date Created "YYYYMMDDThhmmss.s" + str_len = *data; + data += sizeof(uint8_t); + times.actime = esp_mtp_utf16_datatime_to_time((char *)data); + data += (str_len * 2); + + //Date Modified "YYYYMMDDThhmmss.s" + str_len = *data; + data += sizeof(uint8_t); + times.modtime = esp_mtp_utf16_datatime_to_time((char *)data); + data += (str_len * 2); + + //Skip No Use Keywords + str_len = *data; + data += sizeof(uint8_t); + data += (str_len * 2); + + if (parent_handle == 0) { + if (storage_id == 0) { + return MTP_RESPONSE_INVALID_PARAMETER; + } + } + //todo + strcpy((char *)container->data, "/sdcard"); + data = container->data + strlen((char *)container->data); + const esp_mtp_file_entry_t *entry = NULL; + if (parent_handle != 0) { + entry = esp_mtp_file_list_find(&handle->handle_list, parent_handle, (char *)data, handle->buff + handle->buffer_size - data); + if (entry == NULL) { + return MTP_RESPONSE_INVALID_OBJECT_HANDLE; + } + data += strlen((char *)data); + } + + if (handle->buff + handle->buffer_size - data < strlen(filename) + 2) { + return MTP_RESPONSE_ACCESS_DENIED; + } + *data = '/'; + data++; + strcpy((char *)data, filename); + ESP_LOGD(TAG, "%s %s", __FUNCTION__, (char *)container->data); + + int fd = -1; + if (object_format != MTP_OBJECT_FORMAT_ASSOCIATION) { + fd = open((char *)container->data, O_WRONLY | O_CREAT | O_EXCL); + if (fd < 0) { + return MTP_RESPONSE_ACCESS_DENIED; + } + + } else { + if (mkdir((char *)container->data, 755) != 0) { + return MTP_RESPONSE_ACCESS_DENIED; + } + } + + mtp_response_code_t req = MTP_RESPONSE_MAX; + + container->response.send_object_info.storage_id = storage_id; + container->response.send_object_info.parent_handle = parent_handle; + if (container->response.send_object_info.parent_handle == 0) { + container->response.send_object_info.parent_handle = 0xFFFFFFFF; + } + object_handle = esp_mtp_file_list_add(&handle->handle_list, 0x00010001, parent_handle, filename); + if (object_handle == 0) { + ESP_LOGW(TAG, "add file list fail"); + req = MTP_RESPONSE_ACCESS_DENIED; + goto exit; + } + container->response.send_object_info.object_handle = object_handle; + container->len = MTP_CONTAINER_HEAD_LEN + 12; + container->type = MTP_CONTAINER_RESPONSE; + container->res = MTP_RESPONSE_OK; + handle->write(handle->pipe_context, handle->buff, container->len); + + if (fd >= 0) { + + if (file_size) { + uint32_t max_len; + uint32_t read_len; + uint32_t last_write_len = 0; + + max_len = handle->buffer_size - MTP_CONTAINER_HEAD_LEN; + max_len = max_len / 2; + max_len = max_len & (~0x1ff); //512对齐 + + len = handle->read(handle->pipe_context, handle->buff, max_len); + if (handle->flags & ESP_MTP_FLAG_ASYNC_READ) { + uint32_t notify_value; + do { + xTaskNotifyWait(0x0, ASYNC_READ_NOTIFY_BIT | ASYNC_WRITE_NOTIFY_BIT, ¬ify_value, portMAX_DELAY); + } while (!(notify_value & ASYNC_READ_NOTIFY_BIT)); + len = handle->async_read_len; + } + ESP_LOGI(TAG, "%d recv %d", __LINE__, len); + + if (container->type != MTP_CONTAINER_OPERATION || container->opt != MTP_OPERATION_SEND_OBJECT) { + req = MTP_RESPONSE_PARAMETER_NOT_SUPPORTED; + goto exit; + } + + //DWC2 read len 为非 MPS 倍数时,如果主机发送大于 read len 的数据会产生错误 + //len = handle->read(handle->pipe_context, handle->buff, MTP_CONTAINER_HEAD_LEN + max_len); + len = handle->read(handle->pipe_context, handle->buff, max_len); + if (handle->flags & ESP_MTP_FLAG_ASYNC_READ) { + uint32_t notify_value; + do { + xTaskNotifyWait(0x0, ASYNC_READ_NOTIFY_BIT | ASYNC_WRITE_NOTIFY_BIT, ¬ify_value, portMAX_DELAY); + } while (!(notify_value & ASYNC_READ_NOTIFY_BIT)); + len = handle->async_read_len; + } + ESP_LOGI(TAG, "%d recv %d", __LINE__, len); + + if (container->len != MTP_CONTAINER_HEAD_LEN + file_size || container->type != MTP_CONTAINER_DATA || container->opt != MTP_OPERATION_SEND_OBJECT) { + req = MTP_RESPONSE_PARAMETER_NOT_SUPPORTED; + goto exit; + } + last_write_len = len - MTP_CONTAINER_HEAD_LEN; + data = container->data; + while (1) { + file_size -= last_write_len; + if (file_size) { + read_len = file_size > max_len ? max_len : file_size; + if (data == container->data) { + len = handle->read(handle->pipe_context, container->data + max_len, max_len); + } else { + len = handle->read(handle->pipe_context, container->data, max_len); + } + } + if (write(fd, data, last_write_len) != last_write_len) { + req = MTP_RESPONSE_ACCESS_DENIED; + goto exit; + } + if (file_size == 0) { + break; + } + if (handle->flags & ESP_MTP_FLAG_ASYNC_READ) { + uint32_t notify_value; + do { + xTaskNotifyWait(0x0, ASYNC_READ_NOTIFY_BIT | ASYNC_WRITE_NOTIFY_BIT, ¬ify_value, portMAX_DELAY); + } while (!(notify_value & ASYNC_READ_NOTIFY_BIT)); + len = handle->async_read_len; + } + if (len != read_len) { + req = MTP_RESPONSE_PARAMETER_NOT_SUPPORTED; + goto exit; + } + last_write_len = len; + if (data == container->data) { + data = container->data + max_len; + } else { + data = container->data; + } + } + req = MTP_RESPONSE_OK; + ESP_LOGI(TAG, "recv file ok"); + } + } + +exit: + if (fd >= 0) { + close(fd); + } + if (object_handle != 0) { + //todo + strcpy((char *)container->data, "/sdcard"); + data = container->data + strlen((char *)container->data); + if (esp_mtp_file_list_find(&handle->handle_list, object_handle, (char *)data, handle->buff + handle->buffer_size - data) != NULL) { + utime((char *)container->data, ×); + } + } + return req; +} + +static mtp_response_code_t send_object(esp_mtp_handle_t handle) +{ + int len; + mtp_container_t *container = (mtp_container_t *)handle->buff; + len = handle->read(handle->pipe_context, handle->buff, handle->buffer_size); + if (handle->flags & ESP_MTP_FLAG_ASYNC_READ) { + uint32_t notify_value; + do { + xTaskNotifyWait(0x0, ASYNC_READ_NOTIFY_BIT | ASYNC_WRITE_NOTIFY_BIT, ¬ify_value, portMAX_DELAY); + } while (!(notify_value & ASYNC_READ_NOTIFY_BIT)); + len = handle->async_read_len; + } + // 有文件内容接收已在 send_object_info 中进行处理,此处仅处理无内容接收的包 + if (len != container->len || container->len != MTP_CONTAINER_HEAD_LEN || container->type != MTP_CONTAINER_DATA || container->opt != MTP_OPERATION_SEND_OBJECT) { + return MTP_RESPONSE_PARAMETER_NOT_SUPPORTED; + } + return MTP_RESPONSE_OK; +} + +static void delete_dir(char *path, uint32_t path_len, uint32_t max_len) +{ + DIR *dir; + struct dirent *file; + char *data; + dir = opendir(path); + if (dir == NULL) { + ESP_LOGW(TAG, "Failed to open dir for reading:%s", path); + return; + } + data = path + path_len; + *data = '/'; + data++; + while ((file = readdir(dir)) != NULL) { + if (strcmp(file->d_name, ".") == 0 || strcmp(file->d_name, "..") == 0) { + continue; + } + uint32_t len = strlen(file->d_name); + if (path_len + len + 2 > max_len) { + ESP_LOGW(TAG, "path too long:%s", file->d_name); + continue; + } + strcpy(data, file->d_name); + if (file->d_type == DT_DIR) { + delete_dir(path, path_len + len + 1, max_len); + } else { + unlink(path); + } + } + data--; + *data = '\0'; + closedir(dir); + rmdir(path); +} + +static mtp_response_code_t delete_object(esp_mtp_handle_t handle) +{ + uint8_t *data; + uint32_t object_handle; + mtp_container_t *container = (mtp_container_t *)handle->buff; + + object_handle = container->operation.delete_object.object_handle; + if (object_handle == 0x0) { + return MTP_RESPONSE_INVALID_OBJECT_HANDLE; + } + if (object_handle == 0xFFFFFFFF && container->operation.delete_object.object_format_code != 0x0) { + return MTP_RESPONSE_SPECIFICATION_BY_FORMAT_UNSUPPORTED; + } + strcpy((char *)container->data, "/sdcard"); + data = container->data + strlen((char *)container->data); + const esp_mtp_file_entry_t *entry = NULL; + if (object_handle != 0) { + entry = esp_mtp_file_list_find(&handle->handle_list, object_handle, (char *)data, handle->buff + handle->buffer_size - data); + if (entry == NULL) { + return MTP_RESPONSE_INVALID_OBJECT_HANDLE; + } + data += strlen((char *)data); + } + + struct stat st; + if (stat((char *)container->data, &st) != 0) { + return MTP_RESPONSE_INVALID_OBJECT_HANDLE; + } + if (S_ISDIR(st.st_mode)) { + delete_dir((char *)container->data, data - container->data, handle->buffer_size - MTP_CONTAINER_HEAD_LEN); + } else { + unlink((char *)container->data); + } + + return MTP_RESPONSE_OK; +} + +static mtp_response_code_t get_partial_object(esp_mtp_handle_t handle) +{ + mtp_response_code_t res; + mtp_container_t *container = (mtp_container_t *)handle->buff; + uint32_t len = 0; + res = _get_object_common(handle, container->operation.get_partial_object.object_handle, container->operation.get_partial_object.offset, container->operation.get_partial_object.max_bytes, &len); + if (res != MTP_RESPONSE_OK) { + return res; + } + container->response.get_partial_object.actual_bytes = len; + container->len = MTP_CONTAINER_HEAD_LEN + 4; + container->type = MTP_CONTAINER_RESPONSE; + container->res = MTP_RESPONSE_OK; + handle->write(handle->pipe_context, handle->buff, container->len); + return MTP_RESPONSE_MAX; +} + +static void esp_mtp_task(void *args) +{ + int len; + esp_mtp_handle_t handle = (esp_mtp_handle_t)args; + mtp_container_t *container; + container = (mtp_container_t *)handle->buff; + +wait: + esp_mtp_file_list_clean(&handle->handle_list); + handle->wait_start(handle->pipe_context); + ESP_LOGW(TAG, "MTP task start"); + while (1) { + len = handle->read(handle->pipe_context, handle->buff, handle->buffer_size); + if (handle->flags & ESP_MTP_FLAG_ASYNC_READ) { + uint32_t notify_value; + do { + xTaskNotifyWait(0x0, ASYNC_READ_NOTIFY_BIT | ASYNC_WRITE_NOTIFY_BIT, ¬ify_value, portMAX_DELAY); + } while (!(notify_value & ASYNC_READ_NOTIFY_BIT)); + len = handle->async_read_len; + } + if (len < 0) { + break; + } + if (len == 0) { + goto wait; + } + if (len != container->len || container->type != MTP_CONTAINER_OPERATION) { + continue; + } + + mtp_response_code_t res = MTP_RESPONSE_MAX; + switch (container->opt) { + case MTP_OPERATION_OPEN_SESSION: + res = open_session(handle); + break; + case MTP_OPERATION_GET_THUMB: + res = get_thumb(handle); + break; + case MTP_OPERATION_GET_DEVICE_INFO: + res = get_device_info(handle); + break; + case MTP_OPERATION_GET_STORAGE_IDS: + res = get_storage_ids(handle); + break; + case MTP_OPERATION_GET_STORAGE_INFO: + res = get_storage_info(handle); + break; + case MTP_OPERATION_GET_OBJECT_HANDLES: + res = get_object_handles(handle); + break; + case MTP_OPERATION_GET_OBJECT_INFO: + res = get_object_info(handle); + break; + case MTP_OPERATION_GET_OBJECT: + res = get_object(handle); + break; + case MTP_OPERATION_SEND_OBJECT_INFO: + res = send_object_info(handle); + break; + case MTP_OPERATION_SEND_OBJECT: + res = send_object(handle); + break; + case MTP_OPERATION_DELETE_OBJECT: + res = delete_object(handle); + break; + case MTP_OPERATION_GET_PARTIAL_OBJECT: + res = get_partial_object(handle); + break; + default: + ESP_LOGW(TAG, "Undefine handle 0x%"PRIx16"(len:%"PRIu32")", container->opt, container->len); + res = MTP_RESPONSE_OPERATION_NOT_SUPPORTED; + break; + } + if (res != MTP_RESPONSE_MAX) { + container->len = MTP_CONTAINER_HEAD_LEN; + container->type = MTP_CONTAINER_RESPONSE; + container->res = res; + handle->write(handle->pipe_context, handle->buff, container->len); + // 长度不会达到 MPS,无需检查 + // check_usb_len_mps_and_send_end(handle, container->len); + } + + } + ESP_LOGW(TAG, "MTP task exit"); + esp_mtp_file_list_clean(&handle->handle_list); + free(handle); + vTaskDelete(NULL); +} + +esp_mtp_handle_t esp_mtp_init(const esp_mtp_config_t *config) +{ + esp_mtp_handle_t handle; + uint32_t buffer_size; + buffer_size = config->buffer_size; + if (buffer_size < 1024) { + buffer_size = 1024; + } +#ifndef CONFIG_SPIRAM_USE_MALLOC + handle = malloc(sizeof(esp_mtp_t) + MTP_CONTAINER_HEAD_LEN + buffer_size); +#else + handle = heap_caps_malloc(sizeof(esp_mtp_t) + MTP_CONTAINER_HEAD_LEN + buffer_size, MALLOC_CAP_DEFAULT | MALLOC_CAP_INTERNAL); +#endif + + esp_mtp_file_list_init(&handle->handle_list); + + handle->pipe_context = config->pipe_context; + handle->wait_start = config->wait_start; + handle->read = config->read; + handle->write = config->write; + handle->flags = config->flags; + handle->buffer_size = MTP_CONTAINER_HEAD_LEN + buffer_size; + if (xTaskCreate(esp_mtp_task, "esp_mtp_task", 4096, + handle, 5, &handle->task_hdl) != pdTRUE) { + goto _exit; + } + return handle; +_exit: + free(handle); + return NULL; +} + +void esp_mtp_read_async_cb(esp_mtp_handle_t handle, int len) +{ + handle->async_read_len = len; + if (xPortInIsrContext()) { + BaseType_t high_task_wakeup; + high_task_wakeup = pdFALSE; + xTaskNotifyFromISR(handle->task_hdl, ASYNC_READ_NOTIFY_BIT, eSetBits, &high_task_wakeup); + if (high_task_wakeup == pdTRUE) { + portYIELD_FROM_ISR(); + } + } else { + xTaskNotify(handle->task_hdl, ASYNC_READ_NOTIFY_BIT, eSetBits); + } +} + +void esp_mtp_write_async_cb(esp_mtp_handle_t handle, int len) +{ + if (xPortInIsrContext()) { + BaseType_t high_task_wakeup = pdFALSE; + xTaskNotifyFromISR(handle->task_hdl, ASYNC_WRITE_NOTIFY_BIT, eSetBits, &high_task_wakeup); + if (high_task_wakeup == pdTRUE) { + portYIELD_FROM_ISR(); + } + } else { + xTaskNotify(handle->task_hdl, ASYNC_WRITE_NOTIFY_BIT, eSetBits); + } +} + +TaskHandle_t esp_mtp_get_task_handle(esp_mtp_handle_t handle) +{ + return handle->task_hdl; +} \ No newline at end of file diff --git a/examples/device/cherryusb_device_mtp/components/esp_mtp/esp_mtp_helper.c b/examples/device/cherryusb_device_mtp/components/esp_mtp/esp_mtp_helper.c new file mode 100644 index 0000000..bc98b09 --- /dev/null +++ b/examples/device/cherryusb_device_mtp/components/esp_mtp/esp_mtp_helper.c @@ -0,0 +1,247 @@ +/* + * Copyright (c) 2024, udoudou + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_mtp_helper.h" + +#include "string.h" +#include +#include "inttypes.h" + +#ifdef CONFIG_SPIRAM_BOOT_INIT +#include "esp_heap_caps.h" +#endif + +char *esp_mtp_utf8_to_utf16(const char *utf8, char *out, uint8_t *len) +{ + uint16_t *utf16_ptr = (uint16_t *)out; + uint16_t *utf16_end_ptr = utf16_ptr + (*len / 2) - 1; + while (*utf8 != '\0' && utf16_ptr < utf16_end_ptr) { + uint16_t temp; + temp = *(utf8++); + if (temp & 0x80) { + uint8_t first_byte = temp; + if ((first_byte & 0xE0) == 0xC0) { + temp = (first_byte & 0x1F) << 6; + first_byte = *(utf8++); + if ((first_byte & 0xC0) != 0x80) { + break; + } + temp |= (first_byte & 0x3F); + } else if ((first_byte & 0xF0) == 0xE0) { + temp = (first_byte & 0x0F) << 12; + first_byte = *(utf8++); + if ((first_byte & 0xC0) != 0x80) { + break; + } + temp |= ((first_byte & 0x3F) << 6); + first_byte = *(utf8++); + if ((first_byte & 0xC0) != 0x80) { + break; + } + temp |= (first_byte & 0x3F); + } else { + break; + } + } + *(utf16_ptr++) = temp; + } + *(utf16_ptr++) = 0x0000; + *len = utf16_ptr - (uint16_t *)out; + return (char *)utf16_ptr; +} + +char *esp_mtp_time_to_utf16_datatime(time_t time, char *out, uint8_t *len) +{ + struct tm timeinfo; + char buff[24]; + localtime_r(&time, &timeinfo); + // snprintf(buff, sizeof(buff), "%.4d%.2d%.2dT%.2d%.2d%.2d", 1900 + timeinfo.tm_year, timeinfo.tm_mon + 1, timeinfo.tm_mday, timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec); + strftime(buff, sizeof(buff), "%Y%m%dT%H%M%S", &timeinfo); + return esp_mtp_utf8_to_utf16(buff, out, len); +} + +char *esp_mtp_utf16_to_utf8(const char *utf16, char *out, uint8_t *len) +{ + uint16_t *utf16_ptr = (uint16_t *)utf16; + char *out_end = out + *len - 1; + *len = 1; + while (*utf16_ptr != 0x0) { + uint16_t temp = *(utf16_ptr++); + if (temp <= 0x7F) { + if (out >= out_end) { + break; + } + *(out++) = temp; + } else if (temp <= 0x7FF) { + if (out + 1 >= out_end) { + break; + } + *(out++) = (temp >> 6) | 0xC0; + *(out++) = (temp & 0x3F) | 0x80; + } else { + if (out + 2 >= out_end) { + break; + } + *(out++) = (temp >> 12) | 0xE0; + *(out++) = ((temp >> 6) & 0x3F) | 0x80; + *(out++) = (temp & 0x3F) | 0x80; + } + *len = *len + 1; + } + *(out++) = 0x00; + return out; +} + +time_t esp_mtp_utf16_datatime_to_time(const char *utf16) +{ + //"YYYYMMDDThhmmss.s",".s" 可选 + char buff[24]; + uint16_t *utf16_ptr = (uint16_t *)utf16; + uint16_t *time_end = utf16_ptr + (sizeof("YYYYMMDDThhmmss") - 1); + if (*time_end != 0 && *time_end != '.') { + return 0; + } + char *data = buff; + while (utf16_ptr < time_end) { + if (*utf16_ptr > 0x7F) { + return 0; + } + *(data++) = *(utf16_ptr++); + uint32_t off; + off = utf16_ptr - (uint16_t *)utf16; + if (off == 4 || off == 6 || off == 11 || off == 13) { + *(data++) = '-'; + } + } + *(data) = '\0'; + struct tm tm_time; + if (strptime((char *)buff, "%Y-%m-%dT%H-%M-%S", &tm_time) != data) { + return 0; + } + return mktime(&tm_time); +} + +void esp_mtp_file_list_init(esp_mtp_file_handle_list_t *file_list) +{ + memset(file_list, 0, sizeof(esp_mtp_file_handle_list_t)); +} + +uint32_t esp_mtp_file_list_add(esp_mtp_file_handle_list_t *file_list, uint32_t storage_id, uint32_t parent, const char *name) +{ + esp_mtp_file_list_t *list; + list = &file_list->list; + for (uint32_t i = 1; i <= file_list->count / MTP_FILE_LIST_SIZE; i++) { + if (list->next == NULL) { +#if defined CONFIG_SPIRAM_USE_MALLOC || defined CONFIG_SPIRAM_USE_CAPS_ALLOC + list->next = (esp_mtp_file_list_t *)heap_caps_calloc(1, sizeof(esp_mtp_file_list_t), MALLOC_CAP_DEFAULT | MALLOC_CAP_SPIRAM); +#else + list->next = (esp_mtp_file_list_t *)calloc(1, sizeof(esp_mtp_file_list_t)); +#endif + if (list->next == NULL) { + return 0; + } + } + list = list->next; + } +#if defined CONFIG_SPIRAM_USE_MALLOC || defined CONFIG_SPIRAM_USE_CAPS_ALLOC + list->entry_list[file_list->count % MTP_FILE_LIST_SIZE].name = (char *)heap_caps_malloc(strlen(name) + 1, MALLOC_CAP_DEFAULT | MALLOC_CAP_SPIRAM); +#else + list->entry_list[file_list->count % MTP_FILE_LIST_SIZE].name = (char *)malloc(strlen(name) + 1); +#endif + if (list->entry_list[file_list->count % MTP_FILE_LIST_SIZE].name == NULL) { + return 0; + } + strcpy(list->entry_list[file_list->count % MTP_FILE_LIST_SIZE].name, name); + list->entry_list[file_list->count % MTP_FILE_LIST_SIZE].storage_id = storage_id; + list->entry_list[file_list->count % MTP_FILE_LIST_SIZE].parent = parent; + file_list->count++; + // printf("add hande: %"PRIu32"(%"PRIu32")\n", file_list->count, parent); + return file_list->count; +} + +const esp_mtp_file_entry_t *esp_mtp_file_list_find(esp_mtp_file_handle_list_t *file_list, uint32_t handle, char *path, uint32_t max_len) +{ + esp_mtp_file_entry_t *entry; + esp_mtp_file_list_t *list; + uint32_t path_len = 0; + + if (handle > file_list->count) { + return NULL; + } + handle = handle - 1; + list = &file_list->list; + for (uint32_t i = 1; i <= handle / MTP_FILE_LIST_SIZE; i++) { + list = list->next; + } + entry = &list->entry_list[handle % MTP_FILE_LIST_SIZE]; + if (entry->name == NULL) { + return NULL; + } + // printf("find: %"PRIu32"(%"PRIu16")\n", handle + 1, entry->parent); + if (entry->parent != 0) { + if (esp_mtp_file_list_find(file_list, entry->parent, path, max_len) == NULL) { + return NULL; + } + path_len = strlen(path); + } + + if (path_len + strlen(entry->name) + 2 > max_len) { + return NULL; + } + path[path_len++] = '/'; + strcpy(path + path_len, entry->name); + return entry; +} + +void esp_mtp_file_list_clean(esp_mtp_file_handle_list_t *file_list) +{ + esp_mtp_file_list_t *list; + esp_mtp_file_list_t *next; + + list = &file_list->list; + do { + for (uint32_t i = 0; i < MTP_FILE_LIST_SIZE; i++) { + if (list->entry_list[i].name) { + free(list->entry_list[i].name); + } + } + next = list->next; + if (list != &file_list->list) { + free(list); + } + list = next; + } while (list != NULL); + memset(&file_list->list, 0, sizeof(file_list->list)); + file_list->count = 0; +} + +uint8_t *esp_mtp_file_list_fill_handle_array(esp_mtp_file_handle_list_t *file_list, uint32_t parent, uint8_t *out, uint32_t *len) +{ + uint32_t max_count; + uint32_t count = 0; + esp_mtp_file_list_t *list; + + if (parent < file_list->count) { + list = &file_list->list; + for (uint32_t i = 1; i <= parent / MTP_FILE_LIST_SIZE; i++) { + list = list->next; + } + max_count = *len / sizeof(uint32_t); + for (uint32_t i = parent; i < file_list->count; i++) { + if (list->entry_list[i % MTP_FILE_LIST_SIZE].parent == parent) { + *(uint32_t *)out = i + 1; + out += sizeof(uint32_t); + count++; + if (count == max_count) { + break;; + } + } + } + } + + *len = count; + return out; +} \ No newline at end of file diff --git a/examples/device/cherryusb_device_mtp/components/esp_mtp/include/esp_mtp.h b/examples/device/cherryusb_device_mtp/components/esp_mtp/include/esp_mtp.h new file mode 100644 index 0000000..ddeec05 --- /dev/null +++ b/examples/device/cherryusb_device_mtp/components/esp_mtp/include/esp_mtp.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2024, udoudou + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include "esp_err.h" + +typedef struct esp_mtp *esp_mtp_handle_t; + +typedef enum { + ESP_MTP_FLAG_NONE = 0, + ESP_MTP_FLAG_USB_FS = 1 << 0, + ESP_MTP_FLAG_USB_HS = 1 << 1, + ESP_MTP_FLAG_ASYNC_READ = 1 << 2, + ESP_MTP_FLAG_ASYNC_WRITE = 1 << 3, + ESP_MTP_FLAG_MAX = 0xFFFFFFFF, +} __attribute__((packed)) esp_mtp_flags_t; + +#define ESP_MTP_STOP_CMD 0 +#define ESP_MTP_EXIT_CMD -1 + +typedef struct { + void *pipe_context; + void (*wait_start)(void *pipe_context); + int (*read)(void *pipe_context, uint8_t *buffer, int len); + int (*write)(void *pipe_context, const uint8_t *buffer, int len); + esp_mtp_flags_t flags; + uint32_t buffer_size; +}esp_mtp_config_t; + +esp_mtp_handle_t esp_mtp_init(const esp_mtp_config_t *config); + +void esp_mtp_read_async_cb(esp_mtp_handle_t handle, int len); + +void esp_mtp_write_async_cb(esp_mtp_handle_t handle, int len); + +TaskHandle_t esp_mtp_get_task_handle(esp_mtp_handle_t handle); diff --git a/examples/device/cherryusb_device_mtp/components/esp_mtp/include/esp_mtp_def.h b/examples/device/cherryusb_device_mtp/components/esp_mtp/include/esp_mtp_def.h new file mode 100644 index 0000000..c884586 --- /dev/null +++ b/examples/device/cherryusb_device_mtp/components/esp_mtp/include/esp_mtp_def.h @@ -0,0 +1,426 @@ +/* + * Copyright (c) 2024, udoudou + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#define MTP_STANDARD_VERSION 100 +#define MTP_VENDOR_EXTN_ID 0x06 +#define MTP_VENDOR_EXTN_VERSION 100 +#define MTP_VENDOR_EXTENSIONDESC_CHAR "microsoft.com: 1.0; android.com: 1.0;" + + /* + * Functional Modes: + */ +typedef enum { + MTP_FUNCTIONAL_STANDARD = 0x0000, + MTP_FUNCTIONAL_SLEEP = 0x0001, + MTP_FUNCTIONAL_MAX = 0xFFFF, +}__attribute__((packed)) mtp_functional_mode_t; + +/* MTP Storage Type */ +typedef enum { + MTP_STORAGE_UNDEFINED = 0x0000, + MTP_STORAGE_FIXED_ROM = 0x0001, + MTP_STORAGE_REMOVABLE_ROM = 0x0002, + MTP_STORAGE_FIXED_RAM = 0x0003, + MTP_STORAGE_REMOVABLE_RAM = 0x0004, + MTP_STORAGE_MAX = 0xFFFF, +}__attribute__((packed)) mtp_storage_type_t; + +/* MTP File System Type */ +typedef enum { + MTP_FILE_SYSTEM_UNDEFINED = 0x0000, + MTP_FILE_SYSTEM_GENERIC_FLAT = 0x0001, + MTP_FILE_SYSTEM_GENERIC_HIERARCH = 0x0002, + MTP_FILE_SYSTEM_DCF = 0x0003, + MTP_FILE_SYSTEM_MAX = 0xFFFF, +}__attribute__((packed)) mtp_file_system_type_t; + +/* MTP Class Requests */ +#define MTP_REQUEST_CANCEL 0x64U +#define MTP_REQUEST_GET_EXT_EVENT_DATA 0x65U +#define MTP_REQUEST_RESET 0x66U +#define MTP_REQUEST_GET_DEVICE_STATUS 0x67U + +/* MTP Container Types */ +typedef enum { + MTP_CONTAINER_UNDEFINED = 0, + MTP_CONTAINER_OPERATION = 1, + MTP_CONTAINER_DATA = 2, + MTP_CONTAINER_RESPONSE = 3, + MTP_CONTAINER_EVENT = 4, + MTP_CONTAINER_MAX = 0xFFFF, +} __attribute__((packed)) mtp_container_type_t; + +/* MTP Access Capability */ +typedef enum { + MTP_ACCESS_CAP_RW = 0x0000, + MTP_ACCESS_CAP_RO_WITHOUT_DEL = 0x0001, + MTP_ACCESS_CAP_RO_WITH_DEL = 0x0002, + MTP_ACCESS_CAP_MAX = 0xFFFF, +} __attribute__((packed)) mtp_access_cap_t; + +/* + * MTP Class Specification Revision 1.1 + */ + + /* MTP Standard Data Types Supported */ +typedef enum { + MTP_DATA_TYPE_UNDEF = 0x0000, + MTP_DATA_TYPE_INT8 = 0x0001, + MTP_DATA_TYPE_UINT8 = 0x0002, + MTP_DATA_TYPE_INT16 = 0x0003, + MTP_DATA_TYPE_UINT16 = 0x0004, + MTP_DATA_TYPE_INT32 = 0x0005, + MTP_DATA_TYPE_UINT32 = 0x0006, + MTP_DATA_TYPE_INT64 = 0x0007, + MTP_DATA_TYPE_UINT64 = 0x0008, + MTP_DATA_TYPE_INT128 = 0x0009, + MTP_DATA_TYPE_UINT128 = 0x000A, +#define MTP_DATA_TYPE_ARRAY BIT14 + MTP_DATA_TYPE_AINT8 = 0x4001, + MTP_DATA_TYPE_AUINT8 = 0x4002, + MTP_DATA_TYPE_AINT16 = 0x4003, + MTP_DATA_TYPE_AUINT16 = 0x4004, + MTP_DATA_TYPE_AINT32 = 0x4005, + MTP_DATA_TYPE_AUINT32 = 0x4006, + MTP_DATA_TYPE_AINT64 = 0x4007, + MTP_DATA_TYPE_AUINT64 = 0x4008, + MTP_DATA_TYPE_AINT128 = 0x4009, + MTP_DATA_TYPE_AUINT128 = 0x400A, + MTP_DATA_TYPE_STR = 0xFFFF, +} __attribute__((packed)) mtp_data_type_t; + +/* Appendix A - Object Formats */ + +/* MTP Object Format Codes */ +typedef enum { + MTP_OBJECT_FORMAT_UNDEFINED = 0x3000, + MTP_OBJECT_FORMAT_ASSOCIATION = 0x3001, + MTP_OBJECT_FORMAT_SCRIPT = 0x3002, + MTP_OBJECT_FORMAT_EXECUTABLE = 0x3003, + MTP_OBJECT_FORMAT_TEXT = 0x3004, + MTP_OBJECT_FORMAT_HTML = 0x3005, + MTP_OBJECT_FORMAT_DPOF = 0x3006, + MTP_OBJECT_FORMAT_AIFF = 0x3007, + MTP_OBJECT_FORMAT_WAVE = 0x3008, + MTP_OBJECT_FORMAT_MP3 = 0x3009, + MTP_OBJECT_FORMAT_AVI = 0x300A, + MTP_OBJECT_FORMAT_MPEG = 0x300B, + MTP_OBJECT_FORMAT_ASF = 0x300C, + MTP_OBJECT_FORMAT_UNDEFINED_IMAGE = 0x3800, + MTP_OBJECT_FORMAT_EXIF_JPEG = 0x3801, + MTP_OBJECT_FORMAT_TIFF_EP = 0x3802, + MTP_OBJECT_FORMAT_FLASHPIX = 0x3803, + MTP_OBJECT_FORMAT_BMP = 0x3804, + MTP_OBJECT_FORMAT_CIFF = 0x3805, + MTP_OBJECT_FORMAT_UNDEFINED_RESERVED0 = 0x3806, + MTP_OBJECT_FORMAT_GIF = 0x3807, + MTP_OBJECT_FORMAT_JFIF = 0x3808, + MTP_OBJECT_FORMAT_CD = 0x3809, + MTP_OBJECT_FORMAT_PICT = 0x380A, + MTP_OBJECT_FORMAT_PNG = 0x380B, + MTP_OBJECT_FORMAT_UNDEFINED_RESERVED1 = 0x380C, + MTP_OBJECT_FORMAT_TIFF = 0x380D, + MTP_OBJECT_FORMAT_TIFF_IT = 0x380E, + MTP_OBJECT_FORMAT_JP2 = 0x380F, + MTP_OBJECT_FORMAT_JPX = 0x3810, + MTP_OBJECT_FORMAT_UNDEFINED_FIRMWARE = 0xB802, + MTP_OBJECT_FORMAT_WINDOWS_IMAGE_FORMAT = 0xB881, + MTP_OBJECT_FORMAT_WBMP = 0xB803, + MTP_OBJECT_FORMAT_JPEG_XR = 0xB804, + MTP_OBJECT_FORMAT_UNDEFINED_AUDIO = 0xB900, + MTP_OBJECT_FORMAT_WMA = 0xB901, + MTP_OBJECT_FORMAT_OGG = 0xB902, + MTP_OBJECT_FORMAT_AAC = 0xB903, + MTP_OBJECT_FORMAT_AUDIBLE = 0xB904, + MTP_OBJECT_FORMAT_FLAC = 0xB906, + MTP_OBJECT_FORMAT_QCELP = 0xB907, + MTP_OBJECT_FORMAT_AMR = 0xB908, + MTP_OBJECT_FORMAT_UNDEFINED_VIDEO = 0xB980, + MTP_OBJECT_FORMAT_WMV = 0xB981, + MTP_OBJECT_FORMAT_MP4_CONTAINER = 0xB982, + MTP_OBJECT_FORMAT_MP2 = 0xB983, + MTP_OBJECT_FORMAT_3GP_CONTAINER = 0xB984, + MTP_OBJECT_FORMAT_3G2 = 0xB985, + MTP_OBJECT_FORMAT_AVCHD = 0xB986, + MTP_OBJECT_FORMAT_ATSC_TS = 0xB987, + MTP_OBJECT_FORMAT_DVB_TS = 0xB988, + MTP_OBJECT_FORMAT_UNDEFINED_COLLECTION = 0xBA00, + MTP_OBJECT_FORMAT_ABSTRACT_MULTIMEDIA_ALBUM = 0xBA01, + MTP_OBJECT_FORMAT_ABSTRACT_IMAGE_ALBUM = 0xBA02, + MTP_OBJECT_FORMAT_ABSTRACT_AUDIO_ALBUM = 0xBA03, + MTP_OBJECT_FORMAT_ABSTRACT_VIDEO_ALBUM = 0xBA04, + MTP_OBJECT_FORMAT_ABSTRACT_AUDIO_VIDEO_PLAYLIST = 0xBA05, + MTP_OBJECT_FORMAT_ABSTRACT_CONTACT_GROUP = 0xBA06, + MTP_OBJECT_FORMAT_ABSTRACT_MESSAGE_FOLDER = 0xBA07, + MTP_OBJECT_FORMAT_ABSTRACT_CHAPTERED_PRODUCTION = 0xBA08, + MTP_OBJECT_FORMAT_ABSTRACT_AUDIO_PLAYLIST = 0xBA09, + MTP_OBJECT_FORMAT_ABSTRACT_VIDEO_PLAYLIST = 0xBA0A, + MTP_OBJECT_FORMAT_ABSTRACT_MEDIACAST = 0xBA0B, + MTP_OBJECT_FORMAT_WPL_PLAYLIST = 0xBA10, + MTP_OBJECT_FORMAT_M3U_PLAYLIST = 0xBA11, + MTP_OBJECT_FORMAT_MPL_PLAYLIST = 0xBA12, + MTP_OBJECT_FORMAT_ASX_PLAYLIST = 0xBA13, + MTP_OBJECT_FORMAT_PLS_PLAYLIST = 0xBA14, + MTP_OBJECT_FORMAT_UNDEFINED_DOCUMENT = 0xBA80, + MTP_OBJECT_FORMAT_ABSTRACT_DOCUMENT = 0xBA81, + MTP_OBJECT_FORMAT_XML_DOCUMENT = 0xBA82, + MTP_OBJECT_FORMAT_MICROSOFT_WORD_DOCUMENT = 0xBA83, + MTP_OBJECT_FORMAT_MHT_COMPILED_HTML_DOCUMENT = 0xBA84, + MTP_OBJECT_FORMAT_MICROSOFT_EXCEL_SPREADSHEET = 0xBA85, + MTP_OBJECT_FORMAT_MICROSOFT_POWERPOINT_PRESEMTATION = 0xBA86, + MTP_OBJECT_FORMAT_UNDEFINED_MESSAGE = 0xBB00, + MTP_OBJECT_FORMAT_ABSTRACT_MESSAGE = 0xBB01, + MTP_OBJECT_FORMAT_UNDEFINED_BOOKMARK = 0xBB10, + MTP_OBJECT_FORMAT_ABSTRACT_BOOKMARK = 0xBB11, + MTP_OBJECT_FORMAT_UNDEFINED_APPOINTMENT = 0xBB20, + MTP_OBJECT_FORMAT_ABSTRACT_APPOINTMENT = 0xBB21, + MTP_OBJECT_FORMAT_VCALENDAR_1_0 = 0xBB22, + MTP_OBJECT_FORMAT_UNDEFINED_TASK = 0xBB40, + MTP_OBJECT_FORMAT_ABSTRACT_TASK = 0xBB41, + MTP_OBJECT_FORMAT_ICALENDAR = 0xBB42, + MTP_OBJECT_FORMAT_UNDEFINED_NOTE = 0xBB60, + MTP_OBJECT_FORMAT_ABSTRACT_NOTE = 0xBB61, + MTP_OBJECT_FORMAT_UNDEFINED_CONTACT = 0xBB80, + MTP_OBJECT_FORMAT_ABSTRACT_CONTACT = 0xBB81, + MTP_OBJECT_FORMAT_VCARD_2 = 0xBB82, + MTP_OBJECT_FORMAT_VCARD_3 = 0xBB83, + + MTP_OBJECT_FORMAT_MAX = 0xFFFF, +} __attribute__((packed)) mtp_object_format_code_t; + +/* Operations Codes */ +typedef enum { + /* Appendix D - Operations */ + //PTP Operation Code 0x1000-0x1FFF + MTP_OPERATION_GET_DEVICE_INFO = 0x1001, + MTP_OPERATION_OPEN_SESSION = 0x1002, + MTP_OPERATION_CLOSE_SESSION = 0x1003, + MTP_OPERATION_GET_STORAGE_IDS = 0x1004, + MTP_OPERATION_GET_STORAGE_INFO = 0x1005, + MTP_OPERATION_GET_NUM_OBJECTS = 0x1006, + MTP_OPERATION_GET_OBJECT_HANDLES = 0x1007, + MTP_OPERATION_GET_OBJECT_INFO = 0x1008, + MTP_OPERATION_GET_OBJECT = 0x1009, + MTP_OPERATION_GET_THUMB = 0x100A, + MTP_OPERATION_DELETE_OBJECT = 0x100B, + MTP_OPERATION_SEND_OBJECT_INFO = 0x100C, + MTP_OPERATION_SEND_OBJECT = 0x100D, + MTP_OPERATION_INITIATE_CAPTURE = 0x100E, + MTP_OPERATION_FORMAT_STORE = 0x100F, + MTP_OPERATION_RESET_DEVICE = 0x1010, + MTP_OPERATION_SELF_TEST = 0x1011, + MTP_OPERATION_SET_OBJECT_PROTECTION = 0x1012, + MTP_OPERATION_POWER_DOWN = 0x1013, + MTP_OPERATION_GET_DEVICE_PROP_DESC = 0x1014, + MTP_OPERATION_GET_DEVICE_PROP_VALUE = 0x1015, + MTP_OPERATION_SET_DEVICE_PROP_VALUE = 0x1016, + MTP_OPERATION_RESET_DEVICE_PROP_VALUE = 0x1017, + MTP_OPERATION_TERMINATE_OPEN_CAPTURE = 0x1018, + MTP_OPERATION_MOVE_OBJECT = 0x1019, + MTP_OPERATION_COPY_OBJECT = 0x101A, + MTP_OPERATION_GET_PARTIAL_OBJECT = 0x101B, + MTP_OPERATION_INITIATE_OPEN_CAPTURE = 0x101C, + //MTP Operation Code 0x9800-0x9FFF + MTP_OPERATION_GET_OBJECT_PROPS_SUPPORTED = 0x9801, + MTP_OPERATION_GET_OBJECT_PROP_DESC = 0x9802, + MTP_OPERATION_GET_OBJECT_PROP_VALUE = 0x9803, + MTP_OPERATION_SET_OBJECT_PROP_VALUE = 0x9804, + + MTP_OPERATION_GET_OBJECT_REFERENCES = 0x9810, + MTP_OPERATION_SET_OBJECT_REFERENCES = 0x9811, + MTP_OPERATION_SKIP = 0x9820, + + /* Appendix E - Enhanced Operations */ + MTP_OPERATION_GET_OBJECT_PROP_LIST = 0x9805, + MTP_OPERATION_SET_OBJECT_PROP_LIST = 0x9806, + MTP_OPERATION_GET_INTERDEPENDENT_PROP_DESC = 0x9807, + MTP_OPERATION_GET_SEND_OBJECT_PROP_LIST = 0x9808, + + MTP_OPERATION_MAX = 0xFFFF, +} __attribute__((packed)) mtp_operation_code_t; + +typedef struct { + union { + struct { + uint32_t parameter1; + uint32_t parameter2; + uint32_t parameter3; + uint32_t parameter4; + uint32_t parameter5; + }undefine; + + struct { + uint32_t object_handle; + }get_thumb; + + struct { + uint32_t storage_id; + }get_storage_info; + + struct { + uint32_t storage_id; + uint32_t object_format_code; //可选参数 + uint32_t parent_handle; //可选参数 + }get_object_handles; + + struct { + uint32_t object_handle; + }get_object_info; + + struct { + uint32_t object_handle; + }get_object; + + struct { + uint32_t storage_id; //可选参数 + uint32_t parent_handle; //可选参数 + }send_object_info; + + struct { + uint32_t object_handle; + uint32_t object_format_code; //可选参数 + }delete_object; + + struct { + uint32_t object_handle; + uint32_t offset; + uint32_t max_bytes; + }get_partial_object; + }; +}mtp_operation_container_t; + +/* Appendix F - Response */ + +/* MTP Response Codes */ +typedef enum { + //PTP Response Code 0x2000-0x2FFF + MTP_RESPONSE_UNDEFINED = 0x2000, + MTP_RESPONSE_OK = 0x2001, + MTP_RESPONSE_GENERAL_ERROR = 0x2002, + MTP_RESPONSE_SESSION_NOT_OPEN = 0x2003, + MTP_RESPONSE_INVALID_TRANSACTION_ID = 0x2004, + MTP_RESPONSE_OPERATION_NOT_SUPPORTED = 0x2005, + MTP_RESPONSE_PARAMETER_NOT_SUPPORTED = 0x2006, + MTP_RESPONSE_INCOMPLETE_TRANSFER = 0x2007, + MTP_RESPONSE_INVALID_STORAGE_ID = 0x2008, + MTP_RESPONSE_INVALID_OBJECT_HANDLE = 0x2009, + MTP_RESPONSE_DEVICE_PROP_NOT_SUPPORTED = 0x200A, + MTP_RESPONSE_INVALID_OBJECT_FORMAT_CODE = 0x200B, + MTP_RESPONSE_STORE_FULL = 0x200C, + MTP_RESPONSE_OBJECT_WRITE_PROTECTED = 0x200D, + MTP_RESPONSE_STORE_READ_ONLY = 0x200E, + MTP_RESPONSE_ACCESS_DENIED = 0x200F, + MTP_RESPONSE_NO_THUMBNAIL_PRESENT = 0x2010, + MTP_RESPONSE_SELF_TEST_FAILED = 0x2011, + MTP_RESPONSE_PARTIAL_DELETION = 0x2012, + MTP_RESPONSE_STORE_NOT_AVAILABLE = 0x2013, + MTP_RESPONSE_SPECIFICATION_BY_FORMAT_UNSUPPORTED = 0x2014, + MTP_RESPONSE_NO_VALID_OBJECT_INFO = 0x2015, + MTP_RESPONSE_INVALID_CODE_FORMAT = 0x2016, + MTP_RESPONSE_UNKNOWN_VENDOR_CODE = 0x2017, + MTP_RESPONSE_CAPTURE_ALREADY_TERMINATED = 0x2018, + MTP_RESPONSE_DEVICE_BUSY = 0x2019, + MTP_RESPONSE_INVALID_PARENT_OBJECT = 0x201A, + MTP_RESPONSE_INVALID_DEVICE_PROP_FORMAT = 0x201B, + MTP_RESPONSE_INVALID_DEVICE_PROP_VALUE = 0x201C, + MTP_RESPONSE_INVALID_PARAMETER = 0x201D, + MTP_RESPONSE_SESSION_ALREADY_OPEN = 0x201E, + MTP_RESPONSE_TRANSACTION_CANCELLED = 0x201F, + MTP_RESPONSE_SPECIFICATION_OF_DESTINATION_UNSUPPORTED = 0x2020, + //MTP Response Code 0xA800-0xAFFF + MTP_RESPONSE_INVALID_OBJECT_PROP_CODE = 0xA801, + MTP_RESPONSE_INVALID_OBJECT_PROP_FORMAT = 0xA802, + MTP_RESPONSE_INVALID_OBJECT_PROP_VALUE = 0xA803, + MTP_RESPONSE_INVALID_OBJECT_REFERENCE = 0xA804, + MTP_RESPONSE_GROUP_NOT_SUPPORTED = 0xA805, + MTP_RESPONSE_INVALID_DATASET = 0xA806, + MTP_RESPONSE_SPECIFICATION_BY_GROUP_UNSUPPORTED = 0xA807, + MTP_RESPONSE_SPECIFICATION_BY_DEPTH_UNSUPPORTED = 0xA808, + MTP_RESPONSE_OBJECT_TOO_LARGE = 0xA809, + MTP_RESPONSE_OBJECT_PROP_NOT_SUPPORTED = 0xA80A, + + MTP_RESPONSE_MAX = 0xFFFF, +} __attribute__((packed)) mtp_response_code_t; + +typedef struct { + union { + struct { + uint32_t parameter1; + uint32_t parameter2; + uint32_t parameter3; + uint32_t parameter4; + uint32_t parameter5; + }undefine; + + struct { + uint32_t storage_id; + uint32_t parent_handle;; + uint32_t object_handle; + }send_object_info; + + struct { + uint32_t actual_bytes; + }get_partial_object; + }; +}mtp_response_container_t; + + +typedef struct { + mtp_operation_code_t code; + uint32_t transaction_id; + uint8_t payload[0]; +}mtp_data_container_t; + +/* MTP Association Type */ +typedef enum { + MTP_ASSOCIATION_UNDEFINED = 0x0000, + MTP_ASSOCIATION_GENERIC_FOLDER = 0x0001, + MTP_ASSOCIATION_MAX = 0xFFFF, +}__attribute__((packed)) mtp_association_type_t; + +/* Appendix G. Events */ + +/* MTP Event Codes */ +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 = 0x400A, + MTP_EVENT_DEVICE_RESET = 0x400B, + MTP_EVENT_STORAGE_INFO_CHANGED = 0x400C, + MTP_EVENT_CAPTURE_COMPLETE = 0x400D, + MTP_EVENT_UNREPORTED_STATUS = 0x400E, + MTP_EVENT_OBJECT_PROP_CHANGED = 0xC801, + MTP_EVENT_DEVICE_PROP_DESC_CHANGED = 0xC802, + MTP_EVENT_OBJECT_REFERENCES_CHANGED = 0xC803, + MTP_EVENT_MAX = 0xFFFF, +}__attribute__((packed)) mtp_event_code_t; + +typedef struct { + uint32_t len; + mtp_container_type_t type; + union { + mtp_operation_code_t opt; + mtp_response_code_t res; + }; + uint32_t trans_id; + union { + mtp_operation_container_t operation; + uint8_t data[0]; + mtp_response_container_t response; + }; +}mtp_container_t; + +#define MTP_CONTAINER_HEAD_LEN offsetof(mtp_container_t, data) diff --git a/examples/device/cherryusb_device_mtp/components/esp_mtp/include/esp_mtp_helper.h b/examples/device/cherryusb_device_mtp/components/esp_mtp/include/esp_mtp_helper.h new file mode 100644 index 0000000..ad0a6ae --- /dev/null +++ b/examples/device/cherryusb_device_mtp/components/esp_mtp/include/esp_mtp_helper.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2024, udoudou + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include + +#define MTP_FILE_LIST_SIZE 64 + +typedef struct { + uint16_t storage_id; + uint16_t parent; + char *name; +}esp_mtp_file_entry_t; + +typedef struct esp_mtp_file_list { + esp_mtp_file_entry_t entry_list[MTP_FILE_LIST_SIZE]; + struct esp_mtp_file_list *next; +}esp_mtp_file_list_t; + +typedef struct { + uint32_t count; + esp_mtp_file_list_t list; +}esp_mtp_file_handle_list_t; + +/** @brief UTF8 转 UTF16 + * + * @param utf16 需要转换的 UTF8 字符串 + * @param[out] out 保存转换后的 UTF16 字符串 + * @param[in out] len 输入保存空间的长度,输出完成转换的 UTF16 字符数(含结束符) + * + * @return 保存结束符的下一个内存地址 + */ +char *esp_mtp_utf8_to_utf16(const char *utf8, char *out, uint8_t *len); + +char *esp_mtp_time_to_utf16_datatime(time_t time, char *out, uint8_t *len); + +/** @brief UTF16 转 UTF8 + * + * @param utf16 需要转换的 UTF16 字符串 + * @param[out] out 保存转换后的 UTF8 字符串 + * @param[in out] len 输入保存空间的长度,输出完成转换的 UTF8 字符数(含结束符) + * + * @return 保存结束符的下一个内存地址 + */ +char *esp_mtp_utf16_to_utf8(const char *utf16, char *out, uint8_t *len); + +time_t esp_mtp_utf16_datatime_to_time(const char *utf16); + +void esp_mtp_file_list_init(esp_mtp_file_handle_list_t *file_list); + +uint32_t esp_mtp_file_list_add(esp_mtp_file_handle_list_t *file_list, uint32_t storage_id, uint32_t parent, const char *name); + +const esp_mtp_file_entry_t *esp_mtp_file_list_find(esp_mtp_file_handle_list_t *file_list, uint32_t handle, char *path, uint32_t max_len); + +void esp_mtp_file_list_clean(esp_mtp_file_handle_list_t *file_list); + +uint8_t *esp_mtp_file_list_fill_handle_array(esp_mtp_file_handle_list_t *file_list, uint32_t parent, uint8_t *out, uint32_t *len); \ No newline at end of file diff --git a/examples/device/cherryusb_device_mtp/components/usb_mtp/CMakeLists.txt b/examples/device/cherryusb_device_mtp/components/usb_mtp/CMakeLists.txt new file mode 100644 index 0000000..4909309 --- /dev/null +++ b/examples/device/cherryusb_device_mtp/components/usb_mtp/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register(SRCS "usb_mtp.c" + INCLUDE_DIRS "include" + PRIV_REQUIRES esp_cherryusb esp_mtp +) diff --git a/examples/device/cherryusb_device_mtp/components/usb_mtp/include/usb_mtp.h b/examples/device/cherryusb_device_mtp/components/usb_mtp/include/usb_mtp.h new file mode 100644 index 0000000..0b19b02 --- /dev/null +++ b/examples/device/cherryusb_device_mtp/components/usb_mtp/include/usb_mtp.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2024, udoudou + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "usbd_core.h" + +#define USB_MTP_CLASS 0x06 + +#define USB_MTP_SUB_CLASS 0x01U +#define USB_MTP_PROTOCOL 0x01U + + /*Length of template descriptor: 23 bytes*/ +#define MTP_DESCRIPTOR_LEN (9 + 7 + 7 + 7) + +// clang-format off +#define MTP_DESCRIPTOR_INIT(bFirstInterface, out_ep, in_ep, int_ep, wMaxPacketSize, str_idx) \ + /* Interface */ \ + 0x09, /* bLength */ \ + USB_DESCRIPTOR_TYPE_INTERFACE, /* bDescriptorType */ \ + bFirstInterface, /* bInterfaceNumber */ \ + 0x00, /* bAlternateSetting */ \ + 0x03, /* bNumEndpoints */ \ + USB_MTP_CLASS, /* bInterfaceClass */ \ + USB_MTP_SUB_CLASS, /* bInterfaceSubClass */ \ + USB_MTP_PROTOCOL, /* bInterfaceProtocol */ \ + str_idx, /* iInterface */ \ + 0x07, /* bLength */ \ + USB_DESCRIPTOR_TYPE_ENDPOINT, /* bDescriptorType */ \ + out_ep, /* bEndpointAddress */ \ + 0x02, /* bmAttributes */ \ + WBVAL(wMaxPacketSize), /* wMaxPacketSize */ \ + 0x00, /* bInterval */ \ + 0x07, /* bLength */ \ + USB_DESCRIPTOR_TYPE_ENDPOINT, /* bDescriptorType */ \ + in_ep, /* bEndpointAddress */ \ + 0x02, /* bmAttributes */ \ + WBVAL(wMaxPacketSize), /* wMaxPacketSize */ \ + 0x00, /* bInterval */ \ + 0x07, /* bLength */ \ + USB_DESCRIPTOR_TYPE_ENDPOINT, /* bDescriptorType */ \ + int_ep, /* bEndpointAddress */ \ + 0x03, /* bmAttributes */ \ + 0x1c, 0x00, /* wMaxPacketSize */ \ + 0x06 /* bInterval */ +// clang-format on + + +struct usbd_interface *usbd_mtp_init_intf(struct usbd_interface *intf, + const uint8_t out_ep, + const uint8_t in_ep, + const uint8_t int_ep); + +void usbd_mtp_deinit(void); \ No newline at end of file diff --git a/examples/device/cherryusb_device_mtp/components/usb_mtp/usb_mtp.c b/examples/device/cherryusb_device_mtp/components/usb_mtp/usb_mtp.c new file mode 100644 index 0000000..bb38297 --- /dev/null +++ b/examples/device/cherryusb_device_mtp/components/usb_mtp/usb_mtp.c @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2024, udoudou + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "usb_mtp.h" +#include "esp_mtp_def.h" +#include "esp_mtp.h" + + /* Max USB packet size */ +#ifndef CONFIG_USB_HS +#define MTP_BULK_EP_MPS 64 +#else +#define MTP_BULK_EP_MPS 512 +#endif + +#define MTP_OUT_EP_IDX 0 +#define MTP_IN_EP_IDX 1 +#define MTP_INT_EP_IDX 2 + +typedef enum { + USB_MTP_CLOSE, + USB_MTP_INIT, + USB_MTP_RUN, + USB_MTP_STOPPING, +} usb_mtp_status_t; + +/* Describe EndPoints configuration */ +static struct usbd_endpoint mtp_ep_data[3]; +static TaskHandle_t s_mtp_task_handle; +esp_mtp_handle_t s_handle; +static usb_mtp_status_t s_mtp_status = USB_MTP_CLOSE; +static portMUX_TYPE s_spinlock = portMUX_INITIALIZER_UNLOCKED; + +static int mtp_class_interface_request_handler(uint8_t busid, struct usb_setup_packet *setup, uint8_t **data, uint32_t *len) +{ + USB_LOG_DBG("MTP Class request: " + "bRequest 0x%02x\r\n", + setup->bRequest); + + switch (setup->bRequest) { + case MTP_REQUEST_CANCEL: + + break; + case MTP_REQUEST_GET_EXT_EVENT_DATA: + + break; + case MTP_REQUEST_RESET: + + break; + case MTP_REQUEST_GET_DEVICE_STATUS: + *(uint16_t *)(*data) = 0x08; + *(mtp_response_code_t *)(*data + 2) = MTP_RESPONSE_OK; + *len = 8; + break; + default: + USB_LOG_WRN("Unhandled MTP Class bRequest 0x%02x\r\n", setup->bRequest); + return -1; + } + + return 0; +} + +static void usbd_mtp_bulk_out(uint8_t busid, uint8_t ep, uint32_t nbytes) +{ + esp_mtp_read_async_cb(s_handle, nbytes); +} + +static void usbd_mtp_bulk_in(uint8_t busid, uint8_t ep, uint32_t nbytes) +{ + esp_mtp_write_async_cb(s_handle, nbytes); +} + +static void mtp_notify_handler(uint8_t busid, uint8_t event, void *arg) +{ + BaseType_t high_task_wakeup = pdFALSE; + switch (event) { + case USBD_EVENT_RESET: + portENTER_CRITICAL_ISR(&s_spinlock); + if (s_mtp_status != USB_MTP_CLOSE && s_mtp_status != USB_MTP_INIT) { + s_mtp_status = USB_MTP_STOPPING; + } + portEXIT_CRITICAL_ISR(&s_spinlock); + break; + case USBD_EVENT_CONFIGURED: + bool need_wake = false; + portENTER_CRITICAL_ISR(&s_spinlock); + if (s_mtp_status == USB_MTP_INIT) { + need_wake = true; + s_mtp_status = USB_MTP_RUN; + } + portEXIT_CRITICAL_ISR(&s_spinlock); + if (need_wake) { + vTaskNotifyGiveFromISR(s_mtp_task_handle, &high_task_wakeup); + } + break; + case USBD_EVENT_DISCONNECTED: + vTaskNotifyGiveFromISR(s_mtp_task_handle, &high_task_wakeup); + break; + default: + break; + } + if (high_task_wakeup == pdTRUE) { + portYIELD_FROM_ISR(); + } +} + +static void usb_wait_start(void *pipe_context) +{ + portENTER_CRITICAL(&s_spinlock); + if (s_mtp_status != USB_MTP_STOPPING) { + portEXIT_CRITICAL(&s_spinlock); + return; + } + s_mtp_status = USB_MTP_INIT; + portEXIT_CRITICAL(&s_spinlock); + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); +} + +static int usb_write(void *pipe_context, const uint8_t *data, int data_size) +{ + if (s_mtp_status != USB_MTP_RUN) { + data_size = (s_mtp_status != USB_MTP_CLOSE) ? ESP_MTP_STOP_CMD : ESP_MTP_EXIT_CMD; + esp_mtp_write_async_cb(s_handle, data_size); + return data_size; + } + usbd_ep_start_write(0, mtp_ep_data[MTP_IN_EP_IDX].ep_addr, data, data_size); + return data_size; +} + +static int usb_read(void *pipe_context, uint8_t *data, int data_size) +{ + if (s_mtp_status != USB_MTP_RUN) { + data_size = (s_mtp_status != USB_MTP_CLOSE) ? ESP_MTP_STOP_CMD : ESP_MTP_EXIT_CMD; + esp_mtp_read_async_cb(s_handle, data_size); + return data_size; + } + usbd_ep_start_read(0, mtp_ep_data[MTP_OUT_EP_IDX].ep_addr, data, data_size); + return data_size; +} + +struct usbd_interface *usbd_mtp_init_intf(struct usbd_interface *intf, + const uint8_t out_ep, + const uint8_t in_ep, + const uint8_t int_ep) +{ + intf->class_interface_handler = mtp_class_interface_request_handler; + intf->class_endpoint_handler = NULL; + intf->vendor_handler = NULL; + intf->notify_handler = mtp_notify_handler; + + mtp_ep_data[MTP_OUT_EP_IDX].ep_addr = out_ep; + mtp_ep_data[MTP_OUT_EP_IDX].ep_cb = usbd_mtp_bulk_out; + mtp_ep_data[MTP_IN_EP_IDX].ep_addr = in_ep; + mtp_ep_data[MTP_IN_EP_IDX].ep_cb = usbd_mtp_bulk_in; + + //EVENT 通道 + mtp_ep_data[MTP_INT_EP_IDX].ep_addr = int_ep; + mtp_ep_data[MTP_INT_EP_IDX].ep_cb = NULL; + + usbd_add_endpoint(0, &mtp_ep_data[MTP_OUT_EP_IDX]); + usbd_add_endpoint(0, &mtp_ep_data[MTP_IN_EP_IDX]); + usbd_add_endpoint(0, &mtp_ep_data[MTP_INT_EP_IDX]); + + s_mtp_status = USB_MTP_STOPPING; + + esp_mtp_config_t config = { + .wait_start = usb_wait_start, + .read = usb_read, + .write = usb_write, + .flags = ESP_MTP_FLAG_ASYNC_READ | ESP_MTP_FLAG_ASYNC_WRITE, + .buffer_size = 4096, + }; +#ifndef CONFIG_USB_HS + config.flags |= ESP_MTP_FLAG_USB_FS; +#else + config.flags |= ESP_MTP_FLAG_USB_HS; +#endif + + s_handle = esp_mtp_init(&config); + + s_mtp_task_handle = esp_mtp_get_task_handle(s_handle); + + return intf; +} + +void usbd_mtp_deinit(void) +{ + usb_mtp_status_t mtp_status; + portENTER_CRITICAL(&s_spinlock); + mtp_status = s_mtp_status; + s_mtp_status = USB_MTP_CLOSE; + portEXIT_CRITICAL(&s_spinlock); + if (mtp_status == USB_MTP_RUN) { + esp_mtp_read_async_cb(s_handle, ESP_MTP_EXIT_CMD); + } else if (mtp_status == USB_MTP_INIT) { + xTaskNotifyGive(s_mtp_task_handle); + } +} \ No newline at end of file diff --git a/examples/device/cherryusb_device_mtp/main/CMakeLists.txt b/examples/device/cherryusb_device_mtp/main/CMakeLists.txt new file mode 100644 index 0000000..7bade98 --- /dev/null +++ b/examples/device/cherryusb_device_mtp/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "device_mtp_main.c" "sd_card_example_main.c" + INCLUDE_DIRS "." +) diff --git a/examples/device/cherryusb_device_mtp/main/Kconfig.projbuild b/examples/device/cherryusb_device_mtp/main/Kconfig.projbuild new file mode 100644 index 0000000..2b2a81a --- /dev/null +++ b/examples/device/cherryusb_device_mtp/main/Kconfig.projbuild @@ -0,0 +1,57 @@ +menu "SD/MMC Example Configuration" + + config EXAMPLE_FORMAT_IF_MOUNT_FAILED + bool "Format the card if mount failed" + default n + help + If this config item is set, format_if_mount_failed will be set to true and the card will be formatted if + the mount has failed. + + choice EXAMPLE_SDMMC_BUS_WIDTH + prompt "SD/MMC bus width" + default EXAMPLE_SDMMC_BUS_WIDTH_4 + help + Select the bus width of SD or MMC interface. + Note that even if 1 line mode is used, D3 pin of the SD card must have a pull-up resistor connected. + Otherwise the card may enter SPI mode, the only way to recover from which is to cycle power to the card. + + config EXAMPLE_SDMMC_BUS_WIDTH_4 + bool "4 lines (D0 - D3)" + + config EXAMPLE_SDMMC_BUS_WIDTH_1 + bool "1 line (D0)" + endchoice + + if SOC_SDMMC_USE_GPIO_MATRIX + + config EXAMPLE_PIN_CMD + int "CMD GPIO number" + default 35 if IDF_TARGET_ESP32S3 + + config EXAMPLE_PIN_CLK + int "CLK GPIO number" + default 36 if IDF_TARGET_ESP32S3 + + config EXAMPLE_PIN_D0 + int "D0 GPIO number" + default 37 if IDF_TARGET_ESP32S3 + + if EXAMPLE_SDMMC_BUS_WIDTH_4 + + config EXAMPLE_PIN_D1 + int "D1 GPIO number" + default 38 if IDF_TARGET_ESP32S3 + + config EXAMPLE_PIN_D2 + int "D2 GPIO number" + default 33 if IDF_TARGET_ESP32S3 + + config EXAMPLE_PIN_D3 + int "D3 GPIO number" + default 34 if IDF_TARGET_ESP32S3 + + endif # EXAMPLE_SDMMC_BUS_WIDTH_4 + + endif # SOC_SDMMC_USE_GPIO_MATRIX + +endmenu diff --git a/examples/device/cherryusb_device_mtp/main/device_mtp_main.c b/examples/device/cherryusb_device_mtp/main/device_mtp_main.c new file mode 100644 index 0000000..9ea5f2b --- /dev/null +++ b/examples/device/cherryusb_device_mtp/main/device_mtp_main.c @@ -0,0 +1,162 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "sdkconfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" + +#include "usbd_core.h" +#include "usb_mtp.h" + +static char *TAG = "device_mtp_main"; + +/*!< endpoint address */ +#define CDC_IN_EP 0x81 +#define CDC_OUT_EP 0x02 +#define CDC_INT_EP 0x83 + +#define USBD_VID 0xFFFE +#define USBD_PID 0xFFFF +#define USBD_MAX_POWER 100 +#define USBD_LANGID_STRING 1033 + +/*!< config descriptor size */ +#define USB_CONFIG_SIZE (9 + MTP_DESCRIPTOR_LEN) + +#ifdef CONFIG_USB_HS +#define MTP_MAX_MPS 512 +#else +#define MTP_MAX_MPS 64 +#endif + +const uint8_t mtp_descriptor[] = { + USB_DEVICE_DESCRIPTOR_INIT(USB_2_0, 0x00, 0x00, 0x00, USBD_VID, USBD_PID, 0x0201, 0x01), + USB_CONFIG_DESCRIPTOR_INIT(USB_CONFIG_SIZE, 0x01, 0x01, USB_CONFIG_BUS_POWERED, USBD_MAX_POWER), + MTP_DESCRIPTOR_INIT(0x00, CDC_OUT_EP, CDC_IN_EP, CDC_INT_EP, MTP_MAX_MPS, 0x02), + /////////////////////////////////////// + /// string0 descriptor + /////////////////////////////////////// + USB_LANGID_INIT(USBD_LANGID_STRING), + /////////////////////////////////////// + /// string1 descriptor + /////////////////////////////////////// + 0x14, /* bLength */ + USB_DESCRIPTOR_TYPE_STRING, /* bDescriptorType */ + 'C', 0x00, /* wcChar0 */ + 'h', 0x00, /* wcChar1 */ + 'e', 0x00, /* wcChar2 */ + 'r', 0x00, /* wcChar3 */ + 'r', 0x00, /* wcChar4 */ + 'y', 0x00, /* wcChar5 */ + 'U', 0x00, /* wcChar6 */ + 'S', 0x00, /* wcChar7 */ + 'B', 0x00, /* wcChar8 */ + /////////////////////////////////////// + /// string2 descriptor + /////////////////////////////////////// + 0x26, /* bLength */ + USB_DESCRIPTOR_TYPE_STRING, /* bDescriptorType */ + 'C', 0x00, /* wcChar0 */ + 'h', 0x00, /* wcChar1 */ + 'e', 0x00, /* wcChar2 */ + 'r', 0x00, /* wcChar3 */ + 'r', 0x00, /* wcChar4 */ + 'y', 0x00, /* wcChar5 */ + 'U', 0x00, /* wcChar6 */ + 'S', 0x00, /* wcChar7 */ + 'B', 0x00, /* wcChar8 */ + ' ', 0x00, /* wcChar9 */ + 'M', 0x00, /* wcChar10 */ + 'T', 0x00, /* wcChar11 */ + 'P', 0x00, /* wcChar12 */ + ' ', 0x00, /* wcChar13 */ + 'D', 0x00, /* wcChar14 */ + 'E', 0x00, /* wcChar15 */ + 'M', 0x00, /* wcChar16 */ + 'O', 0x00, /* wcChar17 */ + /////////////////////////////////////// + /// string3 descriptor + /////////////////////////////////////// + 0x16, /* bLength */ + USB_DESCRIPTOR_TYPE_STRING, /* bDescriptorType */ + '2', 0x00, /* wcChar0 */ + '0', 0x00, /* wcChar1 */ + '2', 0x00, /* wcChar2 */ + '4', 0x00, /* wcChar3 */ + '0', 0x00, /* wcChar4 */ + '6', 0x00, /* wcChar5 */ + '1', 0x00, /* wcChar6 */ + '0', 0x00, /* wcChar7 */ + '0', 0x00, /* wcChar8 */ + '0', 0x00, /* wcChar9 */ +#ifdef CONFIG_USB_HS + /////////////////////////////////////// + /// device qualifier descriptor + /////////////////////////////////////// + 0x0a, + USB_DESCRIPTOR_TYPE_DEVICE_QUALIFIER, + 0x00, + 0x02, + 0x02, + 0x02, + 0x01, + 0x40, + 0x01, + 0x00, +#endif + 0x00 +}; + +void usbd_event_handler(uint8_t busid, uint8_t event) +{ + switch (event) { + case USBD_EVENT_RESET: + break; + case USBD_EVENT_CONNECTED: + break; + case USBD_EVENT_DISCONNECTED: + break; + case USBD_EVENT_RESUME: + break; + case USBD_EVENT_SUSPEND: + break; + case USBD_EVENT_CONFIGURED: + break; + case USBD_EVENT_SET_REMOTE_WAKEUP: + break; + case USBD_EVENT_CLR_REMOTE_WAKEUP: + break; + + default: + break; + } +} + +struct usbd_interface intf0; + +void app_main(void) +{ + void sd_main(void); + sd_main(); + uint32_t before = heap_caps_get_free_size(MALLOC_CAP_DEFAULT); + usbd_desc_register(0, mtp_descriptor); + usbd_add_interface(0, usbd_mtp_init_intf(&intf0, CDC_OUT_EP, CDC_IN_EP, CDC_INT_EP)); + usbd_initialize(0, ESP_USBD_BASE, usbd_event_handler); + while (1){ + vTaskDelay(10000 / portTICK_PERIOD_MS); + usbd_mtp_deinit(); + usbd_deinitialize(0); + vTaskDelay(500 / portTICK_PERIOD_MS); + uint32_t now; + now = heap_caps_get_free_size(MALLOC_CAP_DEFAULT); + ESP_LOGW(TAG, "use %"PRIu32, before - now); + usbd_desc_register(0, mtp_descriptor); + usbd_add_interface(0, usbd_mtp_init_intf(&intf0, CDC_OUT_EP, CDC_IN_EP, CDC_INT_EP)); + usbd_initialize(0, ESP_USBD_BASE, usbd_event_handler); + } +} \ No newline at end of file diff --git a/examples/device/cherryusb_device_mtp/main/idf_component.yml b/examples/device/cherryusb_device_mtp/main/idf_component.yml new file mode 100644 index 0000000..c363614 --- /dev/null +++ b/examples/device/cherryusb_device_mtp/main/idf_component.yml @@ -0,0 +1,8 @@ +targets: + - esp32s2 + - esp32s3 +dependencies: + idf: ">=4.4.1" + udoudou/esp_cherryusb: + version: "0.0.*" + override_path: "../../../.." diff --git a/examples/device/cherryusb_device_mtp/main/sd_card_example_main.c b/examples/device/cherryusb_device_mtp/main/sd_card_example_main.c new file mode 100644 index 0000000..f55b2f2 --- /dev/null +++ b/examples/device/cherryusb_device_mtp/main/sd_card_example_main.c @@ -0,0 +1,97 @@ +/* SD card and FAT filesystem example. + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +// This example uses SDMMC peripheral to communicate with SD card. + +#include +#include +#include +#include "esp_vfs_fat.h" +#include "sdmmc_cmd.h" +#include "driver/sdmmc_host.h" + +static const char *TAG = "example"; + +#define MOUNT_POINT "/sdcard" + + +void sd_main(void) +{ + esp_err_t ret; + + // Options for mounting the filesystem. + // If format_if_mount_failed is set to true, SD card will be partitioned and + // formatted in case when mounting fails. + esp_vfs_fat_sdmmc_mount_config_t mount_config = { +#ifdef CONFIG_EXAMPLE_FORMAT_IF_MOUNT_FAILED + .format_if_mount_failed = true, +#else + .format_if_mount_failed = false, +#endif // EXAMPLE_FORMAT_IF_MOUNT_FAILED + .max_files = 5, + .allocation_unit_size = 16 * 1024 + }; + sdmmc_card_t *card; + const char mount_point[] = MOUNT_POINT; + ESP_LOGI(TAG, "Initializing SD card"); + + // Use settings defined above to initialize SD card and mount FAT filesystem. + // Note: esp_vfs_fat_sdmmc/sdspi_mount is all-in-one convenience functions. + // Please check its source code and implement error recovery when developing + // production applications. + + ESP_LOGI(TAG, "Using SDMMC peripheral"); + sdmmc_host_t host = SDMMC_HOST_DEFAULT(); + + // This initializes the slot without card detect (CD) and write protect (WP) signals. + // Modify slot_config.gpio_cd and slot_config.gpio_wp if your board has these signals. + sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + + // Set bus width to use: +#ifdef CONFIG_EXAMPLE_SDMMC_BUS_WIDTH_4 + slot_config.width = 4; +#else + slot_config.width = 1; +#endif + + // On chips where the GPIOs used for SD card can be configured, set them in + // the slot_config structure: +#ifdef CONFIG_SOC_SDMMC_USE_GPIO_MATRIX + slot_config.clk = CONFIG_EXAMPLE_PIN_CLK; + slot_config.cmd = CONFIG_EXAMPLE_PIN_CMD; + slot_config.d0 = CONFIG_EXAMPLE_PIN_D0; +#ifdef CONFIG_EXAMPLE_SDMMC_BUS_WIDTH_4 + slot_config.d1 = CONFIG_EXAMPLE_PIN_D1; + slot_config.d2 = CONFIG_EXAMPLE_PIN_D2; + slot_config.d3 = CONFIG_EXAMPLE_PIN_D3; +#endif // CONFIG_EXAMPLE_SDMMC_BUS_WIDTH_4 +#endif // CONFIG_SOC_SDMMC_USE_GPIO_MATRIX + + // Enable internal pullups on enabled pins. The internal pullups + // are insufficient however, please make sure 10k external pullups are + // connected on the bus. This is for debug / example purpose only. + slot_config.flags |= SDMMC_SLOT_FLAG_INTERNAL_PULLUP; + + ESP_LOGI(TAG, "Mounting filesystem"); + ret = esp_vfs_fat_sdmmc_mount(mount_point, &host, &slot_config, &mount_config, &card); + + if (ret != ESP_OK) { + if (ret == ESP_FAIL) { + ESP_LOGE(TAG, "Failed to mount filesystem. " + "If you want the card to be formatted, set the EXAMPLE_FORMAT_IF_MOUNT_FAILED menuconfig option."); + } else { + ESP_LOGE(TAG, "Failed to initialize the card (%s). " + "Make sure SD card lines have pull-up resistors in place.", esp_err_to_name(ret)); + } + return; + } + ESP_LOGI(TAG, "Filesystem mounted"); + + // Card has been initialized, print its properties + sdmmc_card_print_info(stdout, card); +} diff --git a/examples/device/cherryusb_device_mtp/sdkconfig.defaults b/examples/device/cherryusb_device_mtp/sdkconfig.defaults new file mode 100644 index 0000000..5d507af --- /dev/null +++ b/examples/device/cherryusb_device_mtp/sdkconfig.defaults @@ -0,0 +1,3 @@ +# ESP CherryUSB +CONFIG_CHERRYUSBD_ENABLED=y +CONFIG_FREERTOS_HZ=1000