Skip to content

Conversation

roundby
Copy link

@roundby roundby commented Mar 12, 2025

Describe the PR
Add Media Transfer Protocol (MTP) device class support

Additional context
Feature request reference: #1079

The implementation has been tested and found functional at prototype level. We provide an example tested on STM32F3. It makes use of blulk and interrupt EPs only, so it should be supported by most platforms.

The class supports a single session only, but it allows multiple objects and nested associations (i.e. files and folders). We did not add support for object properties access. There are some other limitations but for the purpose of exposing file trees when mass storage class is not advised, the library is fully functional.

This implementation has been written from the ground up, with minimal requirements on buffers and keeping the code as simple as possible. Most of the buffer allocation for object I/O is demanded to the underlying implementation, as such, the RAM footprint of the library is limited.

The working mechanism of MTP (persistent 32 bit file handles) may not scale well on microcontrollers with constrained resources, depending on the application requirements. In the example, we just provide a fixed filesystem with files residing in RAM. It would be possible, depending on the application requirements, to build filesystem based I/O on top of this, by using the API provided in mtp_device.h.

The sources are available here

@roundby roundby marked this pull request as draft March 12, 2025 08:37
@nbmaurizio nbmaurizio force-pushed the mtp branch 4 times, most recently from b734659 to 8c0a674 Compare March 12, 2025 09:25
@roundby roundby marked this pull request as ready for review March 12, 2025 10:22
@roundby roundby changed the title Add support for MTP class device Add support for MTP device class Mar 13, 2025
@WerWolv
Copy link

WerWolv commented Mar 14, 2025

Congrats!
I started working on my own MTP driver about a week ago and just got the first few things working now before I found your PR. Nowhere near done yet but here's my branch in case it helps anybody: https://github.com/WerWolv/tinyusb/tree/sicd

image

@roundby
Copy link
Author

roundby commented Mar 15, 2025

@WerWolv thank you for sharing!
I am giving some context here, as it might be beneficial for review. I had a short look at your code, the implementation is not that far from the one we're suggesting. Since you already have a hands-on experience on how the MTP class works, it would be great if you could take a look at our code too (feel free to add comments or propose corrections) and maybe perform a test on a physical device (since now, we tested it on STM32F3 and on a custom PSoC5LP port, on Windows hosts). Hopefully, the example should work out of the box on most of the already supported demo boards.

This code has been designed after the need to expose a set of files and folders stored in an SPI memory managed by littlefs, and we took the chance to do it within this library (we already used TinyUSB on some of our designs, and we hope this is a way to contribute back). While reading the code and the example, one should remind that the specification requires the device to provide a session unique 32 bit identifier for each object (e.g. file or directory), cached by the host when traversing storage content, able to be recalled any time and never re-used if an object is deleted. The implementation of this on the application side and in case of static files is easy, but it's not trivial on mutable file system where file I/O is performed by both host and (internally) by the device. Moreover, we found that, under Windows (appearing in contrast with MTP requirements) the object IDs are still cached if the session is closed and reopened, unless a bus reset or physical unplug is performed.

Comparing with your code:

  • mtp.h contains some of the definitions you have in sicd.h. We added packed data structures for fixed data as we found this easier to read and similar to what is already done (see mass storage device class).
  • We provided application callbacks in mtp_device_storage.h, in order for the application to provide the interface to enumerate, read and write objects. Comments should be self-explanatory, but feel free to ask in case of doubts.
  • mtp_device.c contains the actual implementation (similar to your sicd_device.c). We did not require a minimum EP size, as the buffer I/O is managed by the application (usually the filesystem itself), but we statically allocated a single buffer with size MTP_MAX_PACKET_SIZE (512). With this single buffer, the code supports arbitrary object sizes. Each command is handled in a single function and information is taken from tud_mtp_* callbacks.
  • The example code is under examples/device/mtp/src/mtp_fs_example.c. A few MTP class specific definitions, namely CFG_MTP_*, are added to tusb_config.h.
  • We did not add support for properties (we don't need them, and they would have increased the amount of code to review).

The code is functional, and it is expected to be ready for exposing file and directory level I/O when a block device is not advisable:

  • Storage is visible in Explorer
  • Objects (files and directories) can be enumerated
  • Object reading, creation and deletion works

I recognize that the introduction of a new class (and it's the first time we're contributing to TinyUSB) could require some time for a review and refinement, so, we'll wait for indications from the maintainers about the interest in merging this, asking for changes, moving to a different branch for testing/integration with additional features or any other suggestion. So far, we corrected all the notified build errors and most of the code scanning notices.

@simon88
Copy link

simon88 commented Mar 31, 2025

Hi @roundby
Great work, do you try it with littlefs?

@f-hoepfinger-hr-agrartechnik

Hi @roundby Great work, do you try it with littlefs?

littlefs must work for us, to be useful.
Thanks for this great work!

@f-hoepfinger-hr-agrartechnik

Hi @roundby Great work, do you try it with littlefs?

i guess it will be hard with littlefs.
easier to make 1 FAT Partition, and copy the stuff over to littlefs later.

@diancity025
Copy link

Hi, I'm having some issues testing the features from https://github.com/ennebi/tinyusb/tree/mtp, copying or creating files in Windows 10 doesn't work properly.
I printed some logs and found that the mtpd_handle_cmd_send_object_info function was called when the file was created, and the mtpd_handle_dto_send_object_info function was not called to write after that.
How do I solve the problem? These are my test environments:
Hardware: RP2040 SDK2.1.1

@roundby
Copy link
Author

roundby commented May 21, 2025

I printed some logs and found that the mtpd_handle_cmd_send_object_info function was called when the file was created, and the mtpd_handle_dto_send_object_info function was not called to write after that.<

Hi @diancity025.
Is the example code (read only FS) working?

Can you check if the bulk endpoints are numbered and opened correctly in the initialization sequence? The command phase is supposed to be followed by a data phase on bulk EP, unless there is an issue with the sequence.
Would you have the chance to share your code? I could check it on a RBPI in a while.

@diancity025
Copy link

diancity025 commented Jun 11, 2025

Hi @roundby Read-only works normally. The problem I encountered before could not create files and folders in WIN10. I added some changes through debugging and now it works fine. I'm not sure if this is a BUG.

diff --git "a/src/class/mtp/mtp_device.c" "b/src/class/mtp/mtp_device.c"
index 51407b4f4..f92d1a76e 100644
--- "a/src/class/mtp/mtp_device.c"
+++ "b/src/class/mtp/mtp_device.c"
@@ -228,6 +228,7 @@ bool mtpd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t
 // Transfer on bulk endpoints
 bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes)
 {
+    uint32_t datalen;
   mtpd_interface_t *p_mtp = &_mtpd_itf;
   const unsigned dir = tu_edpt_dir(ep_addr);
 
@@ -362,7 +363,8 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t
       }
 
       // A zero-length or a short packet termination
-      if (xferred_bytes < CFG_MTP_EP_SIZE)
+      if(((p_mtp->total_len - p_mtp->xferred_len) == 0)&&(xferred_bytes!=64))
       {
         p_mtp->xfer_completed = true;
         // Handle data block
@@ -386,26 +388,15 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t
       else
       {
         // Handle data block when container is full
-        if (p_mtp->xferred_len - p_mtp->handled_len >= MTP_MAX_PACKET_SIZE - CFG_MTP_EP_SIZE)
+        if ((p_mtp->xferred_len - p_mtp->handled_len) >= (MTP_MAX_PACKET_SIZE + ((p_mtp->handled_len == 0)?MTP_GENERIC_DATA_BLOCK_LENGTH:0)-CFG_MTP_EP_SIZE))
         {
           _mtpd_itf.phase = mtpd_handle_data();
           p_mtp->handled_len = p_mtp->xferred_len;
         }
-        // Transfer completed: wait for zero-lenght packet
-        if ((p_mtp->total_len - p_mtp->xferred_len) == 0)
-        {
-          TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_out, ((uint8_t *)(&_mtpd_gct.data)), CFG_MTP_EP_SIZE), 0);
-        }
-        // First data block includes container header + container data
-        else if (p_mtp->handled_len == 0)
-        {
-          TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_out, ((uint8_t *)(&_mtpd_gct)) + p_mtp->xferred_len, p_mtp->total_len - p_mtp->xferred_len));
-        }
-        else
-        // Successive data block includes only container data
-        {
-          TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_out, ((uint8_t *)(&_mtpd_gct.data)) + p_mtp->xferred_len - p_mtp->handled_len, p_mtp->total_len - p_mtp->xferred_len));
-        }
+        datalen=(MTP_MAX_PACKET_SIZE+((p_mtp->handled_len == 0)?MTP_GENERIC_DATA_BLOCK_LENGTH:0))-(p_mtp->xferred_len-p_mtp->handled_len);
+        datalen=MIN(datalen,CFG_MTP_EP_SIZE);
+        datalen=MIN(datalen,_mtpd_itf.total_len-p_mtp->xferred_len);
+        TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_out, ((uint8_t *)(&_mtpd_gct))+ ((p_mtp->handled_len==0)?0:MTP_GENERIC_DATA_BLOCK_LENGTH) + (p_mtp->xferred_len-p_mtp->handled_len), datalen));
       }
     }
   }

@kaimac1
Copy link

kaimac1 commented Jun 11, 2025

Hi @roundby, thanks very much for this!

I have encountered the same thing as @diancity025. I've extended the read-only example to allow adding new objects to the list (while ignoring the file contents for now). Without @diancity025's changes, I just get a progress bar stuck at 0% while copying a file to the device. With their changes everything works as expected. I'm running this on an RP2350 connected to a Windows machine (it's also sort of working on Linux using android-file-transfer, and not at all with libmtp/nemo - will look into that properly later).

Thanks @diancity025 for that fix

edit: file copying is also broken on Linux without the change, but is working as expected with it. Perhaps this is something specific to the RP2040/RP2350 that we are using.

@roundby
Copy link
Author

roundby commented Jul 4, 2025

Hi @diancity025.
Thank you for your tests. I checked directly on a RBPI Pico2 and investigated the issue. It is caused by the way mtpd_xfer_cb is implemented, as it was not expecting xferred_bytes to be more than CFG_MTP_EP_SIZE, but the call to usbd_edpt_xfer was requiring the whole remaining data size and the library does not enforce a limit if the hardware supports a larger endpoint buffer.

We tested the fix together with some corrections along with other refinements, a better example code with read and write capabilities and the fix on the typo identified by @kaimac1. I just pushed the changes.

Tests on a RBPI Pico 2 are confirmed it to be working on Windows 11, Windows 10 and briefly checked under Ubuntu (within nautilus).

@roundby
Copy link
Author

roundby commented Jul 4, 2025

Hi @f-hoepfinger-hr-agrartechnik

littlefs must work for us, to be useful.
i guess it will be hard with littlefs. easier to make 1 FAT Partition, and copy the stuff over to littlefs later.

Lately, I am replying to this.

We used this change in an internal test project, specifically designed to expose files from an SPI memory managed by littlefs. I can confirm that this setup is perfectly feasible, though there are some limitations depending on platform constraints.

You can imagine that including a complete example with littlefs as a dependency would be too heavy for many microcontrollers, and likely wouldn’t be accepted. However, implementation is straightforward—as long as you account for the system's limitations.

One option is to implement a flat filesystem (i.e., avoid creating associations), storing the unique identifier in either the file name or file metadata. If the platform has sufficient RAM, you could instead track queried objects and construct a mapping between identifiers and file paths. Of course there might be other valid approaches.

This mapping strategy is likely the only major design decision you'll need to make. Everything else should be relatively straightforward.

@diancity025
Copy link

Hi @roundby, thanks very much for this!
I recently had an issue where I couldn't store large files when I was using RP2040 to test mtp docking with littlefs, I saved the file handle via the LFS_TYPE_USERATTR attribute of the littlefs file. At the moment it is normal when reading and writing small files. But when I write to a relatively large file (368k), I have the problem that the transfer timeout cannot be completed. Can you help with that? Thank you.
Here are the logs I scraped.

Device  Phase  Data                      Description       Cmd.Phase.Ofs(rep)
------  -----  ------------------------  ----------------  ------------------
  41.1  OUT    14 00 00 00  01 00 0c 10  ........                 1.1.0        
               17 00 00 00  01 00 01 00  ........                 1.1.8        
               ff ff ff ff               ....                     1.1.16       
  41.1  OUT    a4 00 00 00  02 00 0c 10  ........                 2.1.0        
               17 00 00 00  00 00 00 00  ........                 2.1.8        
               00 30 00 00  29 05 06 00  .0..)...                 2.1.16       
               00 30 00 00  00 00 00 00  .0......                 2.1.24       
               00 00 00 00  00 00 00 00  ........                 2.1.32       
               00 00 00 00  00 00 00 00  ........                 2.1.40       
               00 00 00 00  00 00 00 00  ........                 2.1.48       
               00 00 00 00  00 00 00 00  ........                 2.1.56       
  41.1  OUT    0c 68 00 6f  00 6c 00 64  .h.o.l.d                 3.1.0        
               00 32 00 32  00 77 00 2e  .2.2.w..                 3.1.8        
               00 7a 00 69  00 70 00 00  .z.i.p..                 3.1.16       
               00 12 32 00  30 00 32 00  ..2.0.2.                 3.1.24       
               35 00 30 00  37 00 30 00  5.0.7.0.                 3.1.32       
               35 00 54 00  31 00 30 00  5.T.1.0.                 3.1.40       
               35 00 35 00  35 00 31 00  5.5.5.1.                 3.1.48       
               2e 00 30 00  00 00 12 32  ..0....2                 3.1.56       
  41.1  OUT    00 30 00 32  00 34 00 31  .0.2.4.1                 4.1.0        
               00 32 00 31  00 30 00 54  .2.1.0.T                 4.1.8        
               00 31 00 37  00 33 00 34  .1.7.3.4                 4.1.16       
               00 34 00 38  00 2e 00 30  .4.8...0                 4.1.24       
               00 00 00 00               ....                     4.1.32       
  41.2  IN     18 00 00 00  03 00 01 20  .......                  5.1.0        
               17 00 00 00  01 00 01 00  ........                 5.1.8        
               00 00 00 00  0a 00 00 00  ........                 5.1.16       
  41.1  OUT    0c 00 00 00  01 00 0d 10  ........                 6.1.0        
               18 00 00 00               ....                     6.1.8        
  41.1  OUT    35 05 06 00  02 00 0d 10  5.......                 7.1.0        
               18 00 00 00  50 4b 03 04  ....PK..                 7.1.8        
               0a 00 00 00  08 00 60 6f  ......`o                 7.1.16       
               73 59 e0 b0  c8 9b da 00  sY......                 7.1.24       
               00 00 53 01  00 00 16 00  ..S.....                 7.1.32       
               09 00 45 76  65 6e 74 52  ..EventR                 7.1.40       
               65 63 6f 72  64 65 72 53  ecorderS                 7.1.48       
               74 75 62 2e  73 63 76 64  tub.scvd                 7.1.56       
  41.0  CTL    a1 67 00 00  02 00 24 00  GET DEVICE STS           8.1.0(10)    
  41.0  IN     04 00 19 20               ...                      8.2.0        
  41.0  CTL    21 64 00 00  02 00 06 00  CANCEL                  18.1.0        
  41.0  USTS   c0010000                  canceled                18.2.0        
  41.0  CTL    a1 67 00 00  02 00 24 00  GET DEVICE STS          19.1.0        
  41.0  IN     04 00 19 20               ...                     19.2.0        
  41.1  OUT    10 00 00 00  01 00 05 10  ........                20.1.0        
               19 00 00 00  01 00 01 00  ........                20.1.8        
  41.2  IN     0c 00 00 00  03 00 01 20  .......                 21.1.0        
               18 00 00 00               ....                    21.1.8        

@hhuynh222
Copy link

Hi @f-hoepfinger-hr-agrartechnik

littlefs must work for us, to be useful.
i guess it will be hard with littlefs. easier to make 1 FAT Partition, and copy the stuff over to littlefs later.

Lately, I am replying to this.

We used this change in an internal test project, specifically designed to expose files from an SPI memory managed by littlefs. I can confirm that this setup is perfectly feasible, though there are some limitations depending on platform constraints.

You can imagine that including a complete example with littlefs as a dependency would be too heavy for many microcontrollers, and likely wouldn’t be accepted. However, implementation is straightforward—as long as you account for the system's limitations.

One option is to implement a flat filesystem (i.e., avoid creating associations), storing the unique identifier in either the file name or file metadata. If the platform has sufficient RAM, you could instead track queried objects and construct a mapping between identifiers and file paths. Of course there might be other valid approaches.

This mapping strategy is likely the only major design decision you'll need to make. Everything else should be relatively straightforward.

Can confirm this works with littlefs. Made some adjustments but can read all files from NOR flash

@jtherin
Copy link

jtherin commented Jul 30, 2025

@hathach ? :)

Copy link
Owner

@hathach hathach left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have reworked the driver and change how it interact and API with application. I think we should keep the driver simpler and give the data/response to application user since it is too specific. I think it make the API easy to use and maintain. It is still WIP @roundby let me know what you think.

@roundby
Copy link
Author

roundby commented Sep 21, 2025

Thank you for the deep review and rework on the driver, @hathach. I had a look at the functional changes, from what I see you are adding a tud_mtp_command_received_cb callback to allow the user code to handle the commands with more flexibility. Responsibility of managing MTP_OP_OPEN_SESSION, MTP_OP_GET_STORAGE_IDS, MTP_OP_GET_STORAGE_INFO, MTP_OP_GET_DEVICE_PROP_DESC and MTP_OP_GET_DEVICE_PROP_VALUE is moved from the driver to the user implementation (provided into the example). MTP_OP_GET_DEVICE_INFO is instead partly handled in the driver and partly in the user code.

Since when we proposed this pull request, we thought there was a tradeoff between providing a simple API that does not require deep protocol understanding and flexibility in multiple use cases. It's more a project strategy, and your directions in this sense are needed and valid. Especially thanks to the addition of tud_mtp_response_send, tud_mtp_data_send and mtp_container_add_x APIs, the implementation of the command callback into the user code doesn't seem heavy even if handles mandatory operations (and it does with full control from the implementer).

The only downside I may think about is that, by providing a hybrid approach or letting on the user the responsibility of command handling, the implementer needs to precisely follow some documentation in order to know how to use the driver, what kind of operations are mandatory and how to overcome some platform specific differences (MTP host is implemented in very different ways under multiple platforms). As an example, the driver works on Windows platform without handling the property MTP_DEV_PROP_DEVICE_FRIENDLY_NAME, but it does't work with libmtp. Documentation for libmtp complains about poor implementations on device side, so I think this is a point to keep in mind. Of course we could help with documentation if needed.

I'll wait to know if you are moving other APIs for further understanding on how you are changing the rest of the driver, and to perform some testing on a building example.

@hathach
Copy link
Owner

hathach commented Sep 22, 2025

@roundby thank you, I am glad the refactor make sense to you. Indeed I think we should let application take full control of the data/response. Since it will be different from app to apps. Some device will has slow block storage such as SDcard and will only need to call the tud_mtp_data_send() when it got the data asynchronously. Actually the driver can handle part of the request e.g get_device_info() the data is pre-filled according to the CFG_TUD_MTP_DEVICE_INFO_* but still presented the whole buffer to application, in case it want to do the full changes. Like msc, I think the driver shouldd focus on making reading/writing large data (e.g few MBs) easier for application. Still working on more update, hopefully we can merge this soon enough.

Comment on lines +418 to +492
switch (command->code) {
case MTP_OP_GET_OBJECT: {
// File contents span over multiple xfers
const uint32_t obj_handle = command->params[0];
fs_file_t* f = fs_get_file(obj_handle);
if (f == NULL) {
resp_code = MTP_RESP_INVALID_OBJECT_HANDLE;
} else {
// file contents offset is xferred byte minus header size
const uint32_t offset = cb_data->total_xferred_bytes - sizeof(mtp_container_header_t);
const uint32_t xact_len = tu_min32(f->size - offset, io_container->payload_bytes);
memcpy(io_container->payload, f->data + offset, xact_len);
tud_mtp_data_send(io_container);
}
break;
}

case MTP_OP_SEND_OBJECT_INFO: {
mtp_object_info_header_t* obj_info = (mtp_object_info_header_t*) io_container->payload;
if (obj_info->storage_id != 0 && obj_info->storage_id != SUPPORTED_STORAGE_ID) {
resp_code = MTP_RESP_INVALID_STORAGE_ID;
break;
}

if (obj_info->parent_object) {
fs_file_t* parent = fs_get_file(obj_info->parent_object);
if (parent == NULL || !parent->association_type) {
resp_code = MTP_RESP_INVALID_PARENT_OBJECT;
break;
}
}

uint8_t* f_buf = fs_malloc(obj_info->object_compressed_size);
if (f_buf == NULL) {
resp_code = MTP_RESP_STORE_FULL;
break;
}
fs_file_t* f = fs_create_file();
if (f == NULL) {
resp_code = MTP_RESP_STORE_FULL;
break;
}

f->object_format = obj_info->object_format;
f->protection_status = obj_info->protection_status;
f->image_pix_width = obj_info->image_pix_width;
f->image_pix_height = obj_info->image_pix_height;
f->image_bit_depth = obj_info->image_bit_depth;
f->parent = obj_info->parent_object;
f->association_type = obj_info->association_type;
f->size = obj_info->object_compressed_size;
f->data = f_buf;
uint8_t* buf = io_container->payload + sizeof(mtp_object_info_header_t);
mtp_container_get_string(buf, f->name);
// ignore date created/modified/keywords
break;
}

case MTP_OP_SEND_OBJECT: {
fs_file_t* f = fs_get_file(send_obj_handle);
if (f == NULL) {
resp_code = MTP_RESP_INVALID_OBJECT_HANDLE;
} else {
// file contents offset is total xferred minus header size minus last received chunk
const uint32_t offset = cb_data->total_xferred_bytes - sizeof(mtp_container_header_t) - io_container->payload_bytes;
memcpy(f->data + offset, io_container->payload, io_container->payload_bytes);
tud_mtp_data_receive(io_container); // receive more data if needed
}
break;
}

default:
resp_code = MTP_RESP_OPERATION_NOT_SUPPORTED;
break;
}

Check notice

Code scanning / CodeQL

Long switch case Note

Switch has at least one case that is too long:
MTP_OP_SEND_OBJECT_INFO (40 lines)
.
Comment on lines +506 to +524
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;
}

Check notice

Code scanning / CodeQL

No trivial switch statements Note

This switch statement should either handle more cases, or be rewritten as an if statement.
Comment on lines +329 to +404
switch (p_mtp->phase) {
case MTP_PHASE_IDLE:
// received new command
TU_VERIFY(ep_addr == p_mtp->ep_out && p_container->header.type == MTP_CONTAINER_TYPE_COMMAND_BLOCK);
p_mtp->phase = MTP_PHASE_COMMAND;
TU_ATTR_FALLTHROUGH; // handle in the next case

case MTP_PHASE_COMMAND: {
memcpy(&p_mtp->command, p_container, sizeof(mtp_container_command_t)); // save new command
p_container->header.len = sizeof(mtp_container_header_t); // default container to header only
process_cmd(p_mtp, &cb_data);
if (tud_mtp_command_received_cb(&cb_data) < 0) {
p_mtp->phase = MTP_PHASE_ERROR;
}
break;
}

case MTP_PHASE_DATA: {
const uint16_t bulk_mps = (tud_speed_get() == TUSB_SPEED_HIGH) ? 512 : 64;
p_mtp->xferred_len += xferred_bytes;
cb_data.total_xferred_bytes = p_mtp->xferred_len;

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) {
is_complete = true;
}

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 {
// 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
cb_data.io_container.payload_bytes = xferred_bytes - sizeof(mtp_container_header_t);
} else {
// 2nd+ packet: payload only
cb_data.io_container = headerless_packet;
cb_data.io_container.payload_bytes = xferred_bytes;
}
tud_mtp_data_xfer_cb(&cb_data);

if (is_complete) {
// back to header + payload for response
cb_data.io_container = headered_packet;
cb_data.io_container.header->len = sizeof(mtp_container_header_t);
tud_mtp_data_complete_cb(&cb_data);
}
}
break;
}

case MTP_PHASE_RESPONSE_QUEUED:
// response phase is complete -> prepare for new command
TU_ASSERT(ep_addr == p_mtp->ep_in);
tud_mtp_response_complete_cb(&cb_data);
prepare_new_command(p_mtp);
break;

case MTP_PHASE_RESPONSE:
case MTP_PHASE_ERROR:
// supposedly to be empty
break;
default: return false;
}

Check notice

Code scanning / CodeQL

Long switch case Note

Switch has at least one case that is too long:
MTP_PHASE_DATA (45 lines)
.
case MTP_PHASE_RESPONSE_QUEUED:
// response phase is complete -> prepare for new command
TU_ASSERT(ep_addr == p_mtp->ep_in);
tud_mtp_response_complete_cb(&cb_data);

Check warning

Code scanning / CodeQL

Expression has no effect Warning

This expression has no effect (because
tud_mtp_response_complete_cb
has no external side effects).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants