mirror of
https://github.com/hathach/tinyusb.git
synced 2025-10-14 01:58:41 +08:00
implement MTP_OP_SEND_OBJECT_INFO, refactor fs example
This commit is contained in:
@@ -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];
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user