implement control request

This commit is contained in:
hathach
2025-09-27 16:15:04 +07:00
parent f9d4bc7981
commit cb21ca1b0c
7 changed files with 169 additions and 157 deletions

View File

@ -23,7 +23,6 @@
*
*/
#include "class/mtp/mtp_device_storage.h"
#include "tusb.h"
#include "tinyusb_logo_png.h"
@ -188,6 +187,40 @@ static inline uint8_t* fs_malloc(size_t size) {
#endif
}
//--------------------------------------------------------------------+
// Control Request callback
//--------------------------------------------------------------------+
bool tud_mtp_request_cancel_cb(tud_mtp_request_cb_data_t* cb_data) {
mtp_request_reset_cancel_data_t cancel_data;
memcpy(&cancel_data, cb_data->buf, sizeof(cancel_data));
(void) cancel_data.code;
(void ) cancel_data.transaction_id;
return true;
}
// Invoked when received Device Reset request
// return false to stall the request
bool tud_mtp_request_device_reset_cb(tud_mtp_request_cb_data_t* cb_data) {
(void) cb_data;
return true;
}
// Invoked when received Get Extended Event request. Application fill callback data's buffer for response
// return negative to stall the request
int32_t tud_mtp_request_get_extended_event_cb(tud_mtp_request_cb_data_t* cb_data) {
(void) cb_data;
return false; // not implemented yet
}
// Invoked when received Get DeviceStatus request. Application fill callback data's buffer for response
// return negative to stall the request
int32_t tud_mtp_request_get_device_status_cb(tud_mtp_request_cb_data_t* cb_data) {
uint16_t* buf16 = (uint16_t*)(uintptr_t) cb_data->buf;
buf16[0] = 4; // length
buf16[1] = MTP_RESP_OK; // status
return 4;
}
//--------------------------------------------------------------------+
// Bulk Only Protocol
//--------------------------------------------------------------------+
@ -531,9 +564,3 @@ int32_t tud_mtp_response_complete_cb(tud_mtp_cb_data_t* cb_data) {
(void) cb_data;
return 0; // nothing to do
}
void tud_mtp_storage_cancel(void) {
}
void tud_mtp_storage_reset(void) {
}

View File

@ -93,6 +93,7 @@
//------------- CLASS -------------//
#define CFG_TUD_MTP 1
#define CFG_TUD_MTP_EP_BUFSIZE 512
#define CFG_TUD_MTP_EP_CONTROL_BUFSIZE 16 // should be enough to hold data in MTP control request
//------------- MTP device info -------------//
#define CFG_TUD_MTP_DEVICEINFO_EXTENSIONS "microsoft.com: 1.0; "
@ -131,10 +132,6 @@
MTP_OBJ_FORMAT_TEXT, \
MTP_OBJ_FORMAT_PNG
#define CFG_TUD_MANUFACTURER "TinyUsb Manufacturer"
#define CFG_TUD_MODEL "TinyUsb Device"
#define CFG_MTP_INTERFACE (CFG_TUD_MODEL " MTP")
#ifdef __cplusplus
}
#endif

View File

@ -140,10 +140,10 @@ enum {
char const *string_desc_arr[] =
{
(const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409)
CFG_TUD_MANUFACTURER, // 1: Manufacturer
CFG_TUD_MODEL, // 2: Product
"TinyUsb", // 1: Manufacturer
"TinyUsb Device", // 2: Product
NULL, // 3: Serials will use unique ID if possible
CFG_MTP_INTERFACE, // 4: MTP Interface
"TinyUSBB MTP", // 4: MTP Interface
};
static uint16_t _desc_str[32 + 1];
@ -168,7 +168,9 @@ uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid) {
// Note: the 0xEE index string is a Microsoft OS 1.0 Descriptors.
// https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors
if ( !(index < sizeof(string_desc_arr) / sizeof(string_desc_arr[0])) ) return NULL;
if ( !(index < sizeof(string_desc_arr) / sizeof(string_desc_arr[0])) ) {
return NULL;
}
const char *str = string_desc_arr[index];

View File

@ -38,8 +38,6 @@
extern "C" {
#endif
typedef uint16_t wchar16_t;
//--------------------------------------------------------------------+
// Media Transfer Protocol Class Constant
//--------------------------------------------------------------------+
@ -759,15 +757,10 @@ typedef struct TU_ATTR_PACKED {
};
typedef struct TU_ATTR_PACKED {
uint16_t wLength;
uint16_t code;
} mtp_device_status_res_t;
typedef struct TU_ATTR_PACKED {
uint32_t object_handle;
uint32_t storage_id;
uint32_t parent_object_handle;
} mtp_basic_object_info_t;
uint32_t transaction_id;
} mtp_request_reset_cancel_data_t;
TU_VERIFY_STATIC(sizeof(mtp_request_reset_cancel_data_t) == 6, "size is not correct");
//--------------------------------------------------------------------+
// Container helper function

View File

@ -31,12 +31,11 @@
//--------------------------------------------------------------------+
// INCLUDE
//--------------------------------------------------------------------+
#include "device/dcd.h" // for faking dcd_event_xfer_complete
#include "device/dcd.h"
#include "device/usbd.h"
#include "device/usbd_pvt.h"
#include "mtp_device.h"
#include "mtp_device_storage.h"
// Level where CFG_TUSB_DEBUG must be at least for this driver is logged
#ifndef CFG_TUD_MTP_LOG_LEVEL
@ -45,7 +44,45 @@
#define TU_LOG_DRV(...) TU_LOG(CFG_TUD_MTP_LOG_LEVEL, __VA_ARGS__)
#define BULK_PACKET_SIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
//--------------------------------------------------------------------+
// Weak stubs: invoked if no strong implementation is available
//--------------------------------------------------------------------+
TU_ATTR_WEAK bool tud_mtp_request_cancel_cb(tud_mtp_request_cb_data_t* cb_data) {
(void) cb_data;
return false;
}
TU_ATTR_WEAK bool tud_mtp_request_device_reset_cb(tud_mtp_request_cb_data_t* cb_data) {
(void) cb_data;
return false;
}
TU_ATTR_WEAK int32_t tud_mtp_request_get_extended_event_cb(tud_mtp_request_cb_data_t* cb_data) {
(void) cb_data;
return -1;
}
TU_ATTR_WEAK int32_t tud_mtp_request_get_device_status_cb(tud_mtp_request_cb_data_t* cb_data) {
(void) cb_data;
return -1;
}
TU_ATTR_WEAK bool tud_mtp_request_vendor_cb(tud_mtp_request_cb_data_t* cb_data) {
(void) cb_data;
return false;
}
TU_ATTR_WEAK int32_t tud_mtp_command_received_cb(tud_mtp_cb_data_t * cb_data) {
(void) cb_data;
return -1;
}
TU_ATTR_WEAK int32_t tud_mtp_data_xfer_cb(tud_mtp_cb_data_t* cb_data) {
(void) cb_data;
return -1;
}
TU_ATTR_WEAK int32_t tud_mtp_data_complete_cb(tud_mtp_cb_data_t* cb_data) {
(void) cb_data;
return -1;
}
TU_ATTR_WEAK int32_t tud_mtp_response_complete_cb(tud_mtp_cb_data_t* cb_data) {
(void) cb_data;
return -1;
}
//--------------------------------------------------------------------+
// STRUCT
@ -66,6 +103,8 @@ typedef struct {
uint32_t session_id;
mtp_container_command_t command;
mtp_container_header_t io_header;
TU_ATTR_ALIGNED(4) uint8_t control_buf[CFG_TUD_MTP_EP_CONTROL_BUFSIZE];
} mtpd_interface_t;
typedef struct {
@ -76,16 +115,10 @@ typedef struct {
//--------------------------------------------------------------------+
// INTERNAL FUNCTION DECLARATION
//--------------------------------------------------------------------+
static void process_cmd(mtpd_interface_t* p_mtp, tud_mtp_cb_data_t* cb_data);
//--------------------------------------------------------------------+
// MTP variable declaration
//--------------------------------------------------------------------+
static mtpd_interface_t _mtpd_itf;
CFG_TUD_MEM_SECTION static mtpd_epbuf_t _mtpd_epbuf;
CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN static mtp_device_status_res_t _mtpd_device_status_res;
CFG_TUD_MEM_SECTION CFG_TUSB_MEM_ALIGN static mtp_basic_object_info_t _mtpd_soi;
static void preprocess_cmd(mtpd_interface_t* p_mtp, tud_mtp_cb_data_t* cb_data);
//--------------------------------------------------------------------+
// Debug
@ -226,7 +259,6 @@ bool tud_mtp_event_send(mtp_event_t* event) {
//--------------------------------------------------------------------+
void mtpd_init(void) {
tu_memclr(&_mtpd_itf, sizeof(mtpd_interface_t));
tu_memclr(&_mtpd_soi, sizeof(mtp_basic_object_info_t));
}
bool mtpd_deinit(void) {
@ -236,8 +268,6 @@ bool mtpd_deinit(void) {
void mtpd_reset(uint8_t rhport) {
(void) rhport;
tu_memclr(&_mtpd_itf, sizeof(mtpd_interface_t));
tu_memclr(&_mtpd_epbuf, sizeof(mtpd_epbuf_t));
tu_memclr(&_mtpd_soi, sizeof(mtp_basic_object_info_t));
}
uint16_t mtpd_open(uint8_t rhport, tusb_desc_interface_t const* itf_desc, uint16_t max_len) {
@ -264,7 +294,6 @@ uint16_t mtpd_open(uint8_t rhport, tusb_desc_interface_t const* itf_desc, uint16
// Open endpoint pair
TU_ASSERT(usbd_open_edpt_pair(rhport, tu_desc_next(ep_desc), 2, TUSB_XFER_BULK, &p_mtp->ep_out, &p_mtp->ep_in), 0);
TU_ASSERT(prepare_new_command(p_mtp), 0);
return mtpd_itf_size;
@ -274,40 +303,63 @@ uint16_t mtpd_open(uint8_t rhport, tusb_desc_interface_t const* itf_desc, uint16
// Driver response accordingly to the request and the transfer stage (setup/data/ack)
// return false to stall control endpoint (e.g unsupported request)
bool mtpd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const* request) {
if (stage != CONTROL_STAGE_SETUP) {
return true; // nothing to do with DATA & ACK stage
}
mtpd_interface_t* p_mtp = &_mtpd_itf;
tud_mtp_request_cb_data_t cb_data = {
.idx = 0,
.stage = stage,
.request = request,
.buf = p_mtp->control_buf,
.bufsize = tu_le16toh(request->wLength),
};
switch (request->bRequest) {
case MTP_REQ_CANCEL:
TU_LOG_DRV(" MTP request: MTP_REQ_CANCEL\n");
tud_mtp_storage_cancel();
TU_LOG_DRV(" MTP request: Cancel\n");
if (stage == CONTROL_STAGE_SETUP) {
return tud_control_xfer(rhport, request, p_mtp->control_buf, CFG_TUD_MTP_EP_CONTROL_BUFSIZE);
} else if (stage == CONTROL_STAGE_ACK) {
return tud_mtp_request_cancel_cb(&cb_data);
}
break;
case MTP_REQ_GET_EXT_EVENT_DATA:
TU_LOG_DRV(" MTP request: MTP_REQ_GET_EXT_EVENT_DATA\n");
TU_LOG_DRV(" MTP request: Get Extended Event Data\n");
if (stage == CONTROL_STAGE_SETUP) {
const int32_t len = tud_mtp_request_get_extended_event_cb(&cb_data);
TU_VERIFY(len > 0);
return tud_control_xfer(rhport,request, p_mtp->control_buf, (uint16_t) len);
}
break;
case MTP_REQ_RESET:
TU_LOG_DRV(" MTP request: MTP_REQ_RESET\n");
tud_mtp_storage_reset();
// Prepare for a new command
TU_ASSERT(usbd_edpt_xfer(rhport, _mtpd_itf.ep_out, _mtpd_epbuf.buf, CFG_TUD_MTP_EP_BUFSIZE));
TU_LOG_DRV(" MTP request: Device Reset\n");
// used by the host to return the Still Image Capture Device to the Idle state after the Bulk-pipe has stalled
if (stage == CONTROL_STAGE_SETUP) {
// clear stalled
if (usbd_edpt_stalled(rhport, p_mtp->ep_out)) {
usbd_edpt_clear_stall(rhport, p_mtp->ep_out);
}
if (usbd_edpt_stalled(rhport, p_mtp->ep_in)) {
usbd_edpt_clear_stall(rhport, p_mtp->ep_in);
}
} else if (stage == CONTROL_STAGE_ACK) {
prepare_new_command(p_mtp);
return tud_mtp_request_device_reset_cb(&cb_data);
}
break;
case MTP_REQ_GET_DEVICE_STATUS: {
TU_LOG_DRV(" MTP request: MTP_REQ_GET_DEVICE_STATUS\n");
uint16_t len = 4;
_mtpd_device_status_res.wLength = len;
// Cancel is synchronous, always answer OK
_mtpd_device_status_res.code = MTP_RESP_OK;
TU_ASSERT(tud_control_xfer(rhport, request, (uint8_t *)&_mtpd_device_status_res , len));
TU_LOG_DRV(" MTP request: Get Device Status\n");
if (stage == CONTROL_STAGE_SETUP) {
const int32_t len = tud_mtp_request_get_device_status_cb(&cb_data);
TU_VERIFY(len > 0);
return tud_control_xfer(rhport, request, p_mtp->control_buf, (uint16_t) len);
}
break;
}
default:
TU_LOG_DRV(" MTP request: invalid request\r\n");
return false; // stall unsupported request
return tud_mtp_request_vendor_cb(&cb_data);
}
return true;
@ -354,7 +406,7 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t
TU_VERIFY(ep_addr == p_mtp->ep_out && p_container->header.type == MTP_CONTAINER_TYPE_COMMAND_BLOCK);
memcpy(&p_mtp->command, p_container, sizeof(mtp_container_command_t)); // save new command
p_container->header.len = sizeof(mtp_container_header_t); // default container to header only
process_cmd(p_mtp, &cb_data);
preprocess_cmd(p_mtp, &cb_data);
if (tud_mtp_command_received_cb(&cb_data) < 0) {
p_mtp->phase = MTP_PHASE_ERROR;
}
@ -435,7 +487,7 @@ bool mtpd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t
//--------------------------------------------------------------------+
// pre-processed commands
void process_cmd(mtpd_interface_t* p_mtp, tud_mtp_cb_data_t* cb_data) {
void preprocess_cmd(mtpd_interface_t* p_mtp, tud_mtp_cb_data_t* cb_data) {
switch (p_mtp->command.code) {
case MTP_OP_GET_DEVICE_INFO: {
tud_mtp_device_info_t dev_info = {

View File

@ -36,6 +36,7 @@
extern "C" {
#endif
// callback data for Bulk Only Transfer (BOT) protocol
typedef struct {
uint8_t idx; // mtp instance
const mtp_container_command_t* command_container;
@ -45,6 +46,16 @@ typedef struct {
uint32_t total_xferred_bytes; // number of bytes transferred so far in this phase
} tud_mtp_cb_data_t;
// callback data for Control requests
typedef struct {
uint8_t idx;
uint8_t stage; // control stage
const tusb_control_request_t* request;
// buffer for data stage
uint8_t* buf;
uint16_t bufsize;
} tud_mtp_request_cb_data_t;
// Number of supported operations, events, device properties, capture formats, playback formats
// and max number of characters for strings manufacturer, model, device_version, serial_number
#define MTP_DEVICE_INFO_STRUCT(_extension_nchars, _op_count, _event_count, _devprop_count, _capture_count, _playback_count) \
@ -89,24 +100,47 @@ bool tud_mtp_event_send(mtp_event_t* event);
//--------------------------------------------------------------------+
// Control request Callbacks
//--------------------------------------------------------------------+
// bool tud_mtp_control_xfer_cb(uint8_t idx, uint8_t stage, tusb_control_request_t const *p_request);
// Invoked when received Cancel request. Data is available in callback data's buffer
// return false to stall the request
bool tud_mtp_request_cancel_cb(tud_mtp_request_cb_data_t* cb_data);
// Invoked when received Device Reset request
// return false to stall the request
bool tud_mtp_request_device_reset_cb(tud_mtp_request_cb_data_t* cb_data);
// Invoked when received Get Extended Event request. Application fill callback data's buffer for response
// return negative to stall the request
int32_t tud_mtp_request_get_extended_event_cb(tud_mtp_request_cb_data_t* cb_data);
// Invoked when received Get DeviceStatus request. Application fill callback data's buffer for response
// return negative to stall the request
int32_t tud_mtp_request_get_device_status_cb(tud_mtp_request_cb_data_t* cb_data);
// Invoked when received vendor-specific request not in the above standard MTP requests
// return false to stall the request
bool tud_mtp_request_vendor_cb(tud_mtp_request_cb_data_t* cb_data);
//--------------------------------------------------------------------+
// Bulk only protocol Callbacks
//--------------------------------------------------------------------+
/* Invoked when new command is received. Application fill the cb_data->reply with either DATA or RESPONSE and call
* tud_mtp_data_send() or tud_mtp_response_send(). Return negative to stall the endpoints
*/
// Invoked when new command is received. Application fill the cb_data->io_container and call tud_mtp_data_send() or
// tud_mtp_response_send() for Data or Response phase.
// Return negative to stall the endpoints
int32_t tud_mtp_command_received_cb(tud_mtp_cb_data_t * cb_data);
// Invoked when a data packet is transferred, and more data is expected
// Invoked when a data packet is transferred. If data spans over multiple packets, application can use
// total_xferred_bytes and io_container's payload_bytes to determine the offset and remaining bytes to be transferred.
// Return negative to stall the endpoints
int32_t tud_mtp_data_xfer_cb(tud_mtp_cb_data_t* cb_data);
// Invoked when all bytes in DATA phase is complete. A response packet is expected
// Return negative to stall the endpoints
int32_t tud_mtp_data_complete_cb(tud_mtp_cb_data_t* cb_data);
// Invoked when response phase is complete
// Return negative to stall the endpoints
int32_t tud_mtp_response_complete_cb(tud_mtp_cb_data_t* cb_data);
//--------------------------------------------------------------------+

View File

@ -1,93 +0,0 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2025 Ennebi Elettronica (https://ennebielettronica.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* This file is part of the TinyUSB stack.
*/
#ifndef _TUSB_MTP_DEVICE_STORAGE_H_
#define _TUSB_MTP_DEVICE_STORAGE_H_
#include "common/tusb_common.h"
#include "mtp.h"
#if (CFG_TUD_ENABLED && CFG_TUD_MTP)
#ifdef __cplusplus
extern "C" {
#endif
//--------------------------------------------------------------------+
// Storage Application Callbacks
//--------------------------------------------------------------------+
/*
* The entire MTP functionality is based on object handles, as described in MTP Specs v. 1.1 under 3.4.1.
* The major weakness of the protocol is that those handles are supposed to be unique within a session
* and for every enumerated object. There is no specified lifetime limit or way to control the expiration:
* once given, they have to persist for an indefinite time and number of iterations.
* If the filesystem does not provide unique persistent object handle for every entry, the best approach
* would be to keep a full association between generated handles and full file paths. The suggested
* approach with memory constrained devices is to keep a hard ID associated with each file or a volatile
* ID generated on the fly and invalidated on each operation that may rearrange the order.
* In order to invalidate existing IDS, it might be necessary to invalidate the whole session from
* the device side.
* Depending on the application, the handle could be also be the file name or a tag (i.e. host-only file access)
*/
// Format the specified storage
mtp_response_t tud_mtp_storage_format(uint32_t storage_id);
// Called with the creation of a new object is requested.
// The handle of the new object shall be returned in new_object_handle.
// The structure info contains the information to be used for file creation, as passted by the host.
// Note that the variable information (e.g. wstring file name, dates and tags shall be retrieved by using the library functions)
mtp_response_t tud_mtp_storage_object_write_info(uint32_t storage_id, uint32_t parent_object, uint32_t *new_object_handle, const mtp_object_info_header_t *info);
// Write object data
//
// The function shall open the object for writing if not already open.
// The binary data shall be written to the file in full before this function is returned.
mtp_response_t tud_mtp_storage_object_write(uint32_t object_handle, const uint8_t *buffer, uint32_t buffer_size);
// Move an object to a new parent
mtp_response_t tud_mtp_storage_object_move(uint32_t object_handle, uint32_t new_parent_object_handle);
// Delete the specified object
mtp_response_t tud_mtp_storage_object_delete(uint32_t object_handle);
// Issued when IO operation has been terminated (e.g. read, traverse), close open file handles
void tud_mtp_storage_object_done(void);
// Cancel any pending operation. Current operation shall be discarded.
void tud_mtp_storage_cancel(void);
// Restore the operation out of reset. Cancel any pending operation and close the session.
void tud_mtp_storage_reset(void);
#ifdef __cplusplus
}
#endif
#endif /* CFG_TUD_ENABLED && CFG_TUD_MTP */
#endif /* _TUSB_MTP_DEVICE_STORAGE_H_ */