2536 lines
101 KiB
C
2536 lines
101 KiB
C
/*
|
|
*--------------------------------------
|
|
* Program Name: TRANSFER
|
|
* Author: Jacob "jacobly" Young
|
|
* License: Public Domain
|
|
* Description: File Transfer Program
|
|
*--------------------------------------
|
|
*/
|
|
|
|
/* MTP Structure Forward Declarations */
|
|
|
|
typedef struct mtp_global mtp_global_t;
|
|
#define usb_callback_data_t mtp_global_t
|
|
#define usb_transfer_data_t mtp_global_t
|
|
|
|
/* Includes */
|
|
#include "ui.h"
|
|
#include "var.h"
|
|
|
|
#include <keypadc.h>
|
|
#include <fileioc.h>
|
|
#include <usbdrvce.h>
|
|
|
|
#include <debug.h>
|
|
#include <tice.h>
|
|
|
|
#include <inttypes.h>
|
|
#include <limits.h>
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
/* Macros */
|
|
|
|
#define lengthof(array) (sizeof(array) / sizeof(*(array)))
|
|
|
|
#define COUNT_EACH(...) +1
|
|
|
|
#define OBJECT_BUFFER \
|
|
((void *)((mtp_byte_t *)lcd_Ram + LCD_WIDTH * LCD_HEIGHT))
|
|
|
|
#define FOR_EACH_SUPP_OPR(X) \
|
|
X(GET_DEVICE_INFO) \
|
|
X(OPEN_SESSION) \
|
|
X(CLOSE_SESSION) \
|
|
X(GET_STORAGE_IDS) \
|
|
X(GET_STORAGE_INFO) \
|
|
X(GET_NUM_OBJECTS) \
|
|
X(GET_OBJECT_HANDLES) \
|
|
X(GET_OBJECT_INFO) \
|
|
X(GET_OBJECT) \
|
|
X(DELETE_OBJECT) \
|
|
X(SEND_OBJECT_INFO) \
|
|
X(SEND_OBJECT) \
|
|
X(GET_DEVICE_PROP_DESC) \
|
|
X(GET_DEVICE_PROP_VALUE) \
|
|
X(SET_DEVICE_PROP_VALUE) \
|
|
X(MOVE_OBJECT)
|
|
|
|
#define FOR_EACH_SUPP_EVT(X)
|
|
|
|
#define FOR_EACH_SUPP_DP(X) \
|
|
X(uint8, BATTERY_LEVEL, RANGE) \
|
|
X(datetime, DATE_TIME, NONE) \
|
|
X(uint32, PERCEIVED_DEVICE_TYPE, NONE)
|
|
|
|
#define FOR_EACH_SUPP_CF(X) \
|
|
X(UNDEFINED)
|
|
|
|
#define FOR_EACH_SUPP_PF(X) \
|
|
FOR_EACH_SUPP_CF(X)
|
|
|
|
#define FOR_EACH_STRING_DESCRIPTOR(X) \
|
|
X(const, manufacturer) \
|
|
X(const, product) \
|
|
X( , serial_number) \
|
|
X(const, charging_cfg) \
|
|
X(const, mtp_interface)
|
|
|
|
#define DEFINE_STRING_DESCRIPTOR(const, name) \
|
|
static const usb_string_descriptor_t name = { \
|
|
.bLength = sizeof(L##name), \
|
|
.bDescriptorType = USB_STRING_DESCRIPTOR, \
|
|
.bString = L##name, \
|
|
};
|
|
|
|
#define FOR_EACH_STORAGE(X) \
|
|
X(ram) \
|
|
X(arc)
|
|
|
|
#define FOR_EACH_STORAGE_NONE(X) \
|
|
X(none) \
|
|
FOR_EACH_STORAGE(X)
|
|
|
|
#define FOR_EACH_STORAGE_TOTAL(X) \
|
|
FOR_EACH_STORAGE(X) \
|
|
X(total)
|
|
|
|
#define FOR_EACH_STORAGE_NONE_TOTAL(X) \
|
|
X(none) \
|
|
FOR_EACH_STORAGE(X) \
|
|
X(total)
|
|
|
|
#define get_ram_free_space_in_bytes \
|
|
os_MemChk(NULL)
|
|
#define get_arc_free_space_in_bytes \
|
|
(os_ArcChk(), os_TempFreeArc)
|
|
|
|
#define BATTERY_LEVEL_MIN 0
|
|
#define BATTERY_LEVEL_MAX 4
|
|
#define BATTERY_LEVEL_STEP 1
|
|
#define BATTERY_LEVEL_DEF 0
|
|
#define BATTERY_LEVEL_GET_SET 0
|
|
#define BATTERY_LEVEL_GET(current) \
|
|
current = boot_GetBatteryStatus()
|
|
#define BATTERY_LEVEL_SET(new)
|
|
|
|
#define DATE_TIME_DEF { \
|
|
.length = lengthof(Lfactory_datetime), \
|
|
.string = Lfactory_datetime, \
|
|
}
|
|
#define DATE_TIME_GET_SET 1
|
|
#define DATE_TIME_GET(current) \
|
|
get_datetime(current.string)
|
|
#define DATE_TIME_SET(new)
|
|
|
|
#define PERCEIVED_DEVICE_TYPE_DEF 0
|
|
#define PERCEIVED_DEVICE_TYPE_GET_SET 0
|
|
#define PERCEIVED_DEVICE_TYPE_GET(current) \
|
|
current = 3
|
|
#define PERCEIVED_DEVICE_TYPE_SET(new)
|
|
|
|
/* MTP Types */
|
|
|
|
typedef uint8_t mtp_byte_t;
|
|
typedef uint16_t mtp_version_t;
|
|
typedef uint16_t mtp_enum_t;
|
|
typedef uint32_t mtp_size_t;
|
|
typedef uint32_t mtp_id_t;
|
|
typedef uint32_t mtp_param_t;
|
|
typedef uint64_t mtp_uint64_t;
|
|
#define DECLARE_TRUNC_TYPE(name) \
|
|
typedef union mtp_trunc_##name { \
|
|
mtp_##name##_t name; \
|
|
size_t word; \
|
|
mtp_byte_t byte; \
|
|
} mtp_trunc_##name##_t;
|
|
DECLARE_TRUNC_TYPE(size)
|
|
DECLARE_TRUNC_TYPE(id)
|
|
DECLARE_TRUNC_TYPE(param)
|
|
DECLARE_TRUNC_TYPE(uint64)
|
|
|
|
typedef usb_error_t(*mtp_transaction_callback_t)(
|
|
mtp_global_t global);
|
|
|
|
/* MTP Constants */
|
|
|
|
#define MTP_ROOT_OBJECT_HANDLE UINT32_C(0xFFFFFFFF)
|
|
|
|
#define Los_specific L"MSFT100\1"
|
|
#define Lmtp_extensions L"microsoft.com: 1.0;"
|
|
#define Lmanufacturer L"Texas Instruments Incorporated"
|
|
#define Lproduct L"TI-83 Premium CE" /* default must be longer than alt */
|
|
#define Lproduct84 L"TI-84 Plus CE"
|
|
#define Ldevice_version L"2.20"
|
|
#define Lserial_number L"0000000000000000"
|
|
#define Lcharging_cfg L"Charging"
|
|
#define Lmtp_interface L"MTP" /* magic string to aid detection */
|
|
#define Lram_storage_desc L"RAM"
|
|
#define Lram_volume_id Lserial_number"R"
|
|
#define Larc_storage_desc L"Archive"
|
|
#define Larc_volume_id Lserial_number"A"
|
|
#define Lfactory_datetime L"20150101T000000"
|
|
|
|
typedef enum string_id {
|
|
Imissing,
|
|
#define DEFINE_STRING_DESCRIPTOR_ID(const, name) I##name,
|
|
FOR_EACH_STRING_DESCRIPTOR(DEFINE_STRING_DESCRIPTOR_ID)
|
|
Inum_strings,
|
|
} string_id_t;
|
|
|
|
#define Inone UINT32_C(0x00000000)
|
|
#define Iram UINT32_C(0x00010001)
|
|
#define Iarc UINT32_C(0x00020001)
|
|
#define Itotal UINT32_C(0xFFFFFFFF)
|
|
#define storage_count 2
|
|
|
|
#define storage_flag 0x80
|
|
#define reserved_flag 0x40
|
|
|
|
#define none_mask 0x00
|
|
#define ram_mask 0x80
|
|
#define arc_mask 0x80
|
|
#define total_mask 0x00
|
|
|
|
#define none_flag 0x80
|
|
#define ram_flag 0x00
|
|
#define arc_flag 0x80
|
|
#define total_flag 0x00
|
|
|
|
#define ram_max_capacity 0x0002577Fu
|
|
#define arc_max_capacity 0x002F0000u
|
|
|
|
#define MTP_EP_CONTROL (0)
|
|
#define MTP_EP_DATA_IN (USB_DEVICE_TO_HOST | 1)
|
|
#define MTP_EP_DATA_OUT (USB_HOST_TO_DEVICE | 2)
|
|
#define MTP_EP_INT (USB_DEVICE_TO_HOST | 3)
|
|
#define MTP_MAX_EVT_PARAMS (3)
|
|
#define MTP_MAX_SOI_PARAMS (3)
|
|
#define MTP_MAX_PARAMS (5)
|
|
#define MTP_MAX_PENDING_EVENTS (2)
|
|
#define MTP_MAX_BULK_PKT_SZ (0x40)
|
|
#define MTP_MAX_INT_PKT_SZ (sizeof(mtp_event_t) + 1)
|
|
|
|
/* MTP Enumerations */
|
|
|
|
typedef enum mtp_request {
|
|
GET_EXTENDED_EVENT_DATA = 0x65,
|
|
DEVICE_RESET_REQUEST,
|
|
GET_DEVICE_STATUS,
|
|
} mtp_request_t;
|
|
|
|
typedef enum mtp_block_type {
|
|
MTP_BT_UNDEFINED,
|
|
MTP_BT_COMMAND,
|
|
MTP_BT_DATA,
|
|
MTP_BT_RESPONSE,
|
|
MTP_BT_EVENT,
|
|
} mtp_block_type_t;
|
|
|
|
typedef enum mtp_form {
|
|
MTP_FORM_NONE,
|
|
MTP_FORM_RANGE,
|
|
MTP_FORM_ENUM,
|
|
MTP_FORM_DATE_TIME,
|
|
MTP_FORM_FIXED_LENGTH_ARRAY,
|
|
MTP_FORM_REGULAR_EXPRESSION,
|
|
MTP_FORM_BYTE_ARRAY,
|
|
MTP_FORM_LONG_STRING = 0xFF,
|
|
} mtp_form_t;
|
|
|
|
typedef enum mtp_type_code {
|
|
MTP_TC_undef /* Undefined */ = 0x0000,
|
|
MTP_TC_int8 /* Signed 8-bit integer */ = 0x0001,
|
|
MTP_TC_uint8 /* Unsigned 8-bit integer */ = 0x0002,
|
|
MTP_TC_int16 /* Signed 16-bit integer */ = 0x0003,
|
|
MTP_TC_uint16 /* Unsigned 16-bit integer */ = 0x0004,
|
|
MTP_TC_int32 /* Signed 32-bit integer */ = 0x0005,
|
|
MTP_TC_uint32 /* Unsigned 32-bit integer */ = 0x0006,
|
|
MTP_TC_int64 /* Signed 64-bit integer */ = 0x0007,
|
|
MTP_TC_uint64 /* Unsigned 64-bit integer */ = 0x0008,
|
|
MTP_TC_int128 /* Signed 128-bit integer */ = 0x0009,
|
|
MTP_TC_uint128 /* Unsigned 128-bit integer */ = 0x000A,
|
|
MTP_TC_aint8 /* Array of signed 8-bit integers */ = 0x4001,
|
|
MTP_TC_auint8 /* Array of unsigned 8-bit integers */ = 0x4002,
|
|
MTP_TC_aint16 /* Array of signed 16-bit integers */ = 0x4003,
|
|
MTP_TC_auint16 /* Array of unsigned 16-bit integers */ = 0x4004,
|
|
MTP_TC_aint32 /* Array of signed 32-bit integers */ = 0x4005,
|
|
MTP_TC_auint32 /* Array of unsigned 32-bit integers */ = 0x4006,
|
|
MTP_TC_aint64 /* Array of signed 64-bit integers */ = 0x4007,
|
|
MTP_TC_auint64 /* Array of unsigned 64-bit integers */ = 0x4008,
|
|
MTP_TC_aint128 /* Array of signed 128-bit integers */ = 0x4009,
|
|
MTP_TC_auint128 /* Array of unsigned 128-bit integers */ = 0x400A,
|
|
MTP_TC_str /* Variable-length Unicode string */ = 0xFFFF,
|
|
MTP_TC_datetime /* ISO 8601 Unicode string */ = 0xFFFF,
|
|
} mtp_type_code_t;
|
|
|
|
typedef struct datetime {
|
|
mtp_byte_t length;
|
|
wchar_t string[lengthof(Lfactory_datetime)];
|
|
} datetime_t;
|
|
|
|
typedef enum mtp_functional_mode {
|
|
MTP_MODE_STANDARD_MODE = 0x0000,
|
|
MTP_MODE_SLEEP_STATE = 0x0001,
|
|
MTP_MODE_NON_RESPONSIVE_PLAYBACK = 0xC001,
|
|
MTP_MODE_RESPONSIVE_PLAYBACK = 0xC002,
|
|
} mtp_functional_mode_t;
|
|
|
|
typedef enum mtp_storage_type {
|
|
MTP_ST_UNDEFINED = 0x0000,
|
|
MTP_ST_FIXED_ROM = 0x0001,
|
|
MTP_ST_REMOVABLE_ROM = 0x0002,
|
|
MTP_ST_FIXED_RAM = 0x0003,
|
|
MTP_ST_REMOVABLE_RAM = 0x0004,
|
|
} mtp_storage_type_t;
|
|
|
|
typedef enum mtp_filesystem_type {
|
|
MTP_FT_UNDEFINED = 0x0000,
|
|
MTP_FT_GENERIC_FLAT = 0x0001,
|
|
MTP_FT_GENERIC_HIERARCHICAL = 0x0002,
|
|
MTP_FT_DCF = 0x0003,
|
|
} mtp_filesystem_type_t;
|
|
|
|
typedef enum mtp_access_capability {
|
|
MTP_AC_READ_WRITE = 0x0000,
|
|
MTP_AC_READ_ONLY_WITHOUT_OBJECT_DELETION = 0x0001,
|
|
MTP_AC_READ_ONLY_WITH_OBJECT_DELETION = 0x0002,
|
|
} mtp_access_capability_t;
|
|
|
|
typedef enum mtp_protection_status {
|
|
MTP_PS_NO_PROTECTION = 0x0000,
|
|
MTP_PS_READ_ONLY = 0x0001,
|
|
MTP_PS_READ_ONLY_DATA = 0x8002,
|
|
MTP_PS_NOT_TRANSFERABLE_DATA = 0x8003,
|
|
} mtp_protection_status_t;
|
|
|
|
typedef enum mtp_operation_code {
|
|
MTP_OPR_GET_DEVICE_INFO = 0x1001,
|
|
MTP_OPR_OPEN_SESSION = 0x1002,
|
|
MTP_OPR_CLOSE_SESSION = 0x1003,
|
|
MTP_OPR_GET_STORAGE_IDS = 0x1004,
|
|
MTP_OPR_GET_STORAGE_INFO = 0x1005,
|
|
MTP_OPR_GET_NUM_OBJECTS = 0x1006,
|
|
MTP_OPR_GET_OBJECT_HANDLES = 0x1007,
|
|
MTP_OPR_GET_OBJECT_INFO = 0x1008,
|
|
MTP_OPR_GET_OBJECT = 0x1009,
|
|
MTP_OPR_GET_THUMB = 0x100A,
|
|
MTP_OPR_DELETE_OBJECT = 0x100B,
|
|
MTP_OPR_SEND_OBJECT_INFO = 0x100C,
|
|
MTP_OPR_SEND_OBJECT = 0x100D,
|
|
MTP_OPR_INITIATE_CAPTURE = 0x100E,
|
|
MTP_OPR_FORMAT_STORE = 0x100F,
|
|
MTP_OPR_RESET_DEVICE = 0x1010,
|
|
MTP_OPR_SELF_TEST = 0x1011,
|
|
MTP_OPR_SET_OBJECT_PROTECTION = 0x1012,
|
|
MTP_OPR_POWER_DOWN = 0x1013,
|
|
MTP_OPR_GET_DEVICE_PROP_DESC = 0x1014,
|
|
MTP_OPR_GET_DEVICE_PROP_VALUE = 0x1015,
|
|
MTP_OPR_SET_DEVICE_PROP_VALUE = 0x1016,
|
|
MTP_OPR_RESET_DEVICE_PROP_VALUE = 0x1017,
|
|
MTP_OPR_TERMINATE_OPEN_CAPTURE = 0x1018,
|
|
MTP_OPR_MOVE_OBJECT = 0x1019,
|
|
MTP_OPR_COPY_OBJECT = 0x101A,
|
|
MTP_OPR_GET_PARTIAL_OBJECT = 0x101B,
|
|
MTP_OPR_INITIATE_OPEN_CAPTURE = 0x101C,
|
|
MTP_OPR_GET_OBJECT_PROPS_SUPPORTED = 0x9801,
|
|
MTP_OPR_GET_OBJECT_PROP_DESC = 0x9802,
|
|
MTP_OPR_GET_OBJECT_PROP_VALUE = 0x9803,
|
|
MTP_OPR_SET_OBJECT_PROP_VALUE = 0x9804,
|
|
MTP_OPR_GET_OBJECT_PROP_LIST = 0x9805,
|
|
MTP_OPR_SET_OBJECT_PROP_LIST = 0x9806,
|
|
MTP_OPR_GET_INTERDEPENDENT_PROP_DESC = 0x9807,
|
|
MTP_OPR_SEND_OBJECT_PROP_LIST = 0x9808,
|
|
MTP_OPR_GET_OBJECT_REFERENCES = 0x9810,
|
|
MTP_OPR_SET_OBJECT_REFERENCES = 0x9811,
|
|
MTP_OPR_SKIP = 0x9820,
|
|
} mtp_operation_code_t;
|
|
|
|
typedef enum mtp_response_code {
|
|
MTP_RSP_UNDEFINED = 0x2000,
|
|
MTP_RSP_OK = 0x2001,
|
|
MTP_RSP_GENERAL_ERROR = 0x2002,
|
|
MTP_RSP_SESSION_NOT_OPEN = 0x2003,
|
|
MTP_RSP_INVALID_TRANSACTION_ID = 0x2004,
|
|
MTP_RSP_OPERATION_NOT_SUPPORTED = 0x2005,
|
|
MTP_RSP_PARAMETER_NOT_SUPPORTED = 0x2006,
|
|
MTP_RSP_INCOMPLETE_TRANSFER = 0x2007,
|
|
MTP_RSP_INVALID_STORAGE_ID = 0x2008,
|
|
MTP_RSP_INVALID_OBJECT_HANDLE = 0x2009,
|
|
MTP_RSP_DEVICE_PROP_NOT_SUPPORTED = 0x200A,
|
|
MTP_RSP_INVALID_OBJECT_FORMAT_CODE = 0x200B,
|
|
MTP_RSP_STORE_FULL = 0x200C,
|
|
MTP_RSP_OBJECT_WRITE_PROTECTED = 0x200D,
|
|
MTP_RSP_STORE_READ_ONLY = 0x200E,
|
|
MTP_RSP_ACCESS_DENIED = 0x200F,
|
|
MTP_RSP_NO_THUMBNAIL_PRESENT = 0x2010,
|
|
MTP_RSP_SELF_TEST_FAILED = 0x2011,
|
|
MTP_RSP_PARTIAL_DELETION = 0x2012,
|
|
MTP_RSP_STORE_NOT_AVAILABLE = 0x2013,
|
|
MTP_RSP_SPECIFICATION_BY_FORMAT_UNSUPPORTED = 0x2014,
|
|
MTP_RSP_NO_VALID_OBJECT_INFO = 0x2015,
|
|
MTP_RSP_INVALID_CODE_FORMAT = 0x2016,
|
|
MTP_RSP_UNKNOWN_VENDOR_CODE = 0x2017,
|
|
MTP_RSP_CAPTURE_ALREADY_TERMINATED = 0x2018,
|
|
MTP_RSP_DEVICE_BUSY = 0x2019,
|
|
MTP_RSP_INVALID_PARENT_OBJECT = 0x201A,
|
|
MTP_RSP_INVALID_DEVICE_PROP_FORMAT = 0x201B,
|
|
MTP_RSP_INVALID_DEVICE_PROP_VALUE = 0x201C,
|
|
MTP_RSP_INVALID_PARAMETER = 0x201D,
|
|
MTP_RSP_SESSION_ALREADY_OPEN = 0x201E,
|
|
MTP_RSP_TRANSACTION_CANCELLED = 0x201F,
|
|
MTP_RSP_SPECIFICATION_OF_DESTINATION_UNSUPPORTED = 0x2020,
|
|
MTP_RSP_INVALID_OBJECT_PROPCODE = 0xA801,
|
|
MTP_RSP_INVALID_OBJECT_PROP_FORMAT = 0xA802,
|
|
MTP_RSP_INVALID_OBJECT_PROP_VALUE = 0xA803,
|
|
MTP_RSP_INVALID_OBJECT_REFERENCE = 0xA804,
|
|
MTP_RSP_GROUP_NOT_SUPPORTED = 0xA805,
|
|
MTP_RSP_INVALID_DATASET = 0xA806,
|
|
MTP_RSP_SPECIFICATION_BY_GROUP_UNSUPPORTED = 0xA807,
|
|
MTP_RSP_SPECIFICATION_BY_DEPTH_UNSUPPORTED = 0xA808,
|
|
MTP_RSP_OBJECT_TOO_LARGE = 0xA809,
|
|
MTP_RSP_OBJECT_PROP_NOT_SUPPORTED = 0xA80A,
|
|
} mtp_response_code_t;
|
|
|
|
typedef enum mtp_object_format_code {
|
|
MTP_OF_UNDEFINED = 0x3000,
|
|
MTP_OF_ASSOCIATION = 0x3001,
|
|
} mtp_object_format_code_t;
|
|
|
|
typedef enum mtp_event_code {
|
|
MTP_EVT_UNDEFINED = 0x4000,
|
|
MTP_EVT_CANCEL_TRANSACTION = 0x4001,
|
|
MTP_EVT_OBJECT_ADDED = 0x4002,
|
|
MTP_EVT_OBJECT_REMOVED = 0x4003,
|
|
MTP_EVT_STORE_ADDED = 0x4004,
|
|
MTP_EVT_STORE_REMOVED = 0x4005,
|
|
MTP_EVT_DEVICE_PROP_CHANGED = 0x4006,
|
|
MTP_EVT_OBJECT_INFO_CHANGED = 0x4007,
|
|
MTP_EVT_DEVICE_INFO_CHANGED = 0x4008,
|
|
MTP_EVT_REQUEST_OBJECT_TRANSFER = 0x4009,
|
|
MTP_EVT_STORE_FULL = 0x400A,
|
|
MTP_EVT_DEVICE_RESET = 0x400B,
|
|
MTP_EVT_STORAGE_INFO_CHANGED = 0x400C,
|
|
MTP_EVT_CAPTURE_COMPLETE = 0x400D,
|
|
MTP_EVT_UNREPORTED_STATUS = 0x400E,
|
|
MTP_EVT_OBJECT_PROP_CHANGED = 0xC801,
|
|
MTP_EVT_OBJECT_PROP_DESC_CHANGED = 0xC802,
|
|
MTP_EVT_OBJECT_REFERENCES_CHANGED = 0xC803,
|
|
} mtp_event_code_t;
|
|
|
|
typedef enum mtp_device_property_code {
|
|
MTP_DP_UNDEFINED = 0x5000,
|
|
MTP_DP_BATTERY_LEVEL = 0x5001,
|
|
MTP_DP_FUNCTIONAL_MODE = 0x5002,
|
|
MTP_DP_IMAGE_SIZE = 0x5003,
|
|
MTP_DP_COMPRESSION_SETTING = 0x5004,
|
|
MTP_DP_WHITE_BALANCE = 0x5005,
|
|
MTP_DP_RGB_GAIN = 0x5006,
|
|
MTP_DP_F_NUMBER = 0x5007,
|
|
MTP_DP_FOCAL_LENGTH = 0x5008,
|
|
MTP_DP_FOCUS_DISTANCE = 0x5009,
|
|
MTP_DP_FOCUS_MODE = 0x500A,
|
|
MTP_DP_EXPOSURE_METERING_MODE = 0x500B,
|
|
MTP_DP_FLASH_MODE = 0x500C,
|
|
MTP_DP_EXPOSURE_TIME = 0x500D,
|
|
MTP_DP_EXPOSURE_PROGRAM_MODE = 0x500E,
|
|
MTP_DP_EXPOSURE_INDEX = 0x500F,
|
|
MTP_DP_EXPOSURE_BIAS_COMPENSATION = 0x5010,
|
|
MTP_DP_DATE_TIME = 0x5011,
|
|
MTP_DP_CAPTURE_DELAY = 0x5012,
|
|
MTP_DP_STILL_CAPTURE_MODE = 0x5013,
|
|
MTP_DP_CONTRAST = 0x5014,
|
|
MTP_DP_SHARPNESS = 0x5015,
|
|
MTP_DP_DIGITAL_ZOOM = 0x5016,
|
|
MTP_DP_EFFECT_MODE = 0x5017,
|
|
MTP_DP_BURST_NUMBER = 0x5018,
|
|
MTP_DP_BURST_INTERVAL = 0x5019,
|
|
MTP_DP_TIMELAPSE_NUMBER = 0x501A,
|
|
MTP_DP_TIMELAPSE_INTERVAL = 0x501B,
|
|
MTP_DP_FOCUS_METERING_MODE = 0x501C,
|
|
MTP_DP_UPLOAD_URL = 0x501D,
|
|
MTP_DP_ARTIST = 0x501E,
|
|
MTP_DP_COPYRIGHT_INFO = 0x501F,
|
|
MTP_DP_SYNCHRONIZATION_PARTNER = 0xD401,
|
|
MTP_DP_DEVICE_FRIENDLY_NAME = 0xD402,
|
|
MTP_DP_VOLUME = 0xD403,
|
|
MTP_DP_SUPPORTED_FORMATSORDERED = 0xD404,
|
|
MTP_DP_DEVICE_ICON = 0xD405,
|
|
MTP_DP_PLAYBACK_RATE = 0xD410,
|
|
MTP_DP_PLAYBACK_OBJECT = 0xD411,
|
|
MTP_DP_PLAYBACK_CONTAINER_INDEX = 0xD412,
|
|
MTP_DP_SESSION_INITIATOR_VERSION_INFO = 0xD406,
|
|
MTP_DP_PERCEIVED_DEVICE_TYPE = 0xD407,
|
|
} mtp_device_property_code_t;
|
|
|
|
typedef enum mtp_object_property_code {
|
|
MTP_OP_STORAGE_ID = 0xDC01,
|
|
MTP_OP_OBJECT_FORMAT = 0xDC02,
|
|
MTP_OP_PROTECTION_STATUS = 0xDC03,
|
|
MTP_OP_OBJECT_SIZE = 0xDC04,
|
|
MTP_OP_ASSOCIATION_TYPE = 0xDC05,
|
|
MTP_OP_ASSOCIATION_DESC = 0xDC06,
|
|
MTP_OP_OBJECT_FILE_NAME = 0xDC07,
|
|
MTP_OP_DATE_CREATED = 0xDC08,
|
|
MTP_OP_DATE_MODIFIED = 0xDC09,
|
|
MTP_OP_KEYWORDS = 0xDC0A,
|
|
MTP_OP_PARENT_OBJECT = 0xDC0B,
|
|
MTP_OP_ALLOWED_FOLDER_CONTENTS = 0xDC0C,
|
|
MTP_OP_HIDDEN = 0xDC0D,
|
|
MTP_OP_SYSTEM_OBJECT = 0xDC0E,
|
|
MTP_OP_PERSISTENT_UNIQUE_OBJECT_IDENTIFIER = 0xDC41,
|
|
MTP_OP_SYNC_ID = 0xDC42,
|
|
MTP_OP_PROPERTY_BAG = 0xDC43,
|
|
MTP_OP_NAME = 0xDC44,
|
|
MTP_OP_CREATED_BY = 0xDC45,
|
|
MTP_OP_ARTIST = 0xDC46,
|
|
MTP_OP_DATE_AUTHORED = 0xDC47,
|
|
MTP_OP_DESCRIPTION = 0xDC48,
|
|
MTP_OP_URL_REFERENCE = 0xDC49,
|
|
MTP_OP_LANGUAGE_LOCALE = 0xDC4A,
|
|
MTP_OP_COPYRIGHT_INFORMATION = 0xDC4B,
|
|
MTP_OP_SOURCE = 0xDC4C,
|
|
MTP_OP_ORIGIN_LOCATION = 0xDC4D,
|
|
MTP_OP_DATE_ADDED = 0xDC4E,
|
|
MTP_OP_NON_CONSUMABLE = 0xDC4F,
|
|
MTP_OP_CORRUPT_UNPLAYABLE = 0xDC50,
|
|
MTP_OP_PRODUCER_SERIAL_NUMBER = 0xDC51,
|
|
MTP_OP_REPRESENTATIVE_SAMPLE_FORMAT = 0xDC81,
|
|
MTP_OP_REPRESENTATIVE_SAMPLE_SIZE = 0xDC82,
|
|
MTP_OP_REPRESENTATIVE_SAMPLE_HEIGHT = 0xDC83,
|
|
MTP_OP_REPRESENTATIVE_SAMPLE_WIDTH = 0xDC84,
|
|
MTP_OP_REPRESENTATIVE_SAMPLE_DURATION = 0xDC85,
|
|
MTP_OP_REPRESENTATIVE_SAMPLE_DATA = 0xDC86,
|
|
MTP_OP_WIDTH = 0xDC87,
|
|
MTP_OP_HEIGHT = 0xDC88,
|
|
MTP_OP_DURATION = 0xDC89,
|
|
MTP_OP_RATING = 0xDC8A,
|
|
MTP_OP_TRACK = 0xDC8B,
|
|
MTP_OP_GENRE = 0xDC8C,
|
|
MTP_OP_CREDITS = 0xDC8D,
|
|
MTP_OP_LYRICS = 0xDC8E,
|
|
MTP_OP_SUBSCRIPTION_CONTENT_ID = 0xDC8F,
|
|
MTP_OP_PRODUCED_BY = 0xDC90,
|
|
MTP_OP_USE_COUNT = 0xDC91,
|
|
MTP_OP_SKIP_COUNT = 0xDC92,
|
|
MTP_OP_LAST_ACCESSED = 0xDC93,
|
|
MTP_OP_PARENTAL_RATING = 0xDC94,
|
|
MTP_OP_META_GENRE = 0xDC95,
|
|
MTP_OP_COMPOSER = 0xDC96,
|
|
MTP_OP_EFFECTIVE_RATING = 0xDC97,
|
|
MTP_OP_SUBTITLE = 0xDC98,
|
|
MTP_OP_ORIGINAL_RELEASE_DATE = 0xDC99,
|
|
MTP_OP_ALBUM_NAME = 0xDC9A,
|
|
MTP_OP_ALBUM_ARTIST = 0xDC9B,
|
|
MTP_OP_MOOD = 0xDC9C,
|
|
MTP_OP_DRM_STATUS = 0xDC9D,
|
|
MTP_OP_SUB_DESCRIPTION = 0xDC9E,
|
|
MTP_OP_IS_CROPPED = 0xDCD1,
|
|
MTP_OP_IS_COLOUR_CORRECTED = 0xDCD2,
|
|
MTP_OP_IMAGE_BIT_DEPTH = 0xDCD3,
|
|
MTP_OP_F_NUMBER = 0xDCD4,
|
|
MTP_OP_EXPOSURE_TIME = 0xDCD5,
|
|
MTP_OP_EXPOSURE_INDEX = 0xDCD6,
|
|
MTP_OP_TOTAL_BIT_RATE = 0xDE91,
|
|
MTP_OP_BIT_RATE_TYPE = 0xDE92,
|
|
MTP_OP_SAMPLE_RATE = 0xDE93,
|
|
MTP_OP_NUMBER_OF_CHANNELS = 0xDE94,
|
|
MTP_OP_AUDIO_BIT_DEPTH = 0xDE95,
|
|
MTP_OP_SCAN_TYPE = 0xDE97,
|
|
MTP_OP_AUDIO_WAVE_CODEC = 0xDE99,
|
|
MTP_OP_AUDIO_BIT_RATE = 0xDE9A,
|
|
MTP_OP_VIDEO_FOURCC_CODEC = 0xDE9B,
|
|
MTP_OP_VIDEO_BIT_RATE = 0xDE9C,
|
|
MTP_OP_FRAMES_PER_THOUSAND_SECONDS = 0xDE9D,
|
|
MTP_OP_KEY_FRAME_DISTANCE = 0xDE9E,
|
|
MTP_OP_BUFFER_SIZE = 0xDE9F,
|
|
MTP_OP_ENCODING_QUALITY = 0xDEA0,
|
|
MTP_OP_ENCODING_PROFILE = 0xDEA1,
|
|
MTP_OP_DISPLAY_NAME = 0xDCE0,
|
|
MTP_OP_BODY_TEXT = 0xDCE1,
|
|
MTP_OP_SUBJECT = 0xDCE2,
|
|
MTP_OP_PRIORITY = 0xDCE3,
|
|
MTP_OP_GIVEN_NAME = 0xDD00,
|
|
MTP_OP_MIDDLE_NAMES = 0xDD01,
|
|
MTP_OP_FAMILY_NAME = 0xDD02,
|
|
MTP_OP_PREFIX = 0xDD03,
|
|
MTP_OP_SUFFIX = 0xDD04,
|
|
MTP_OP_PHONETIC_GIVEN_NAME = 0xDD05,
|
|
MTP_OP_PHONETIC_FAMILY_NAME = 0xDD06,
|
|
MTP_OP_EMAIL_PRIMARY = 0xDD07,
|
|
MTP_OP_EMAIL_PERSONAL_1 = 0xDD08,
|
|
MTP_OP_EMAIL_PERSONAL_2 = 0xDD09,
|
|
MTP_OP_EMAIL_BUSINESS_1 = 0xDD0A,
|
|
MTP_OP_EMAIL_BUSINESS_2 = 0xDD0B,
|
|
MTP_OP_EMAIL_OTHERS = 0xDD0C,
|
|
MTP_OP_PHONE_NUMBER_PRIMARY = 0xDD0D,
|
|
MTP_OP_PHONE_NUMBER_PERSONAL = 0xDD0E,
|
|
MTP_OP_PHONE_NUMBER_PERSONAL_2 = 0xDD0F,
|
|
MTP_OP_PHONE_NUMBER_BUSINESS = 0xDD10,
|
|
MTP_OP_PHONE_NUMBER_BUSINESS_2 = 0xDD11,
|
|
MTP_OP_PHONE_NUMBER_MOBILE = 0xDD12,
|
|
MTP_OP_PHONE_NUMBER_MOBILE_2 = 0xDD13,
|
|
MTP_OP_FAX_NUMBER_PRIMARY = 0xDD14,
|
|
MTP_OP_FAX_NUMBER_PERSONAL = 0xDD15,
|
|
MTP_OP_FAX_NUMBER_BUSINESS = 0xDD16,
|
|
MTP_OP_PAGER_NUMBER = 0xDD17,
|
|
MTP_OP_PHONE_NUMBER_OTHERS = 0xDD18,
|
|
MTP_OP_PRIMARY_WEB_ADDRESS = 0xDD19,
|
|
MTP_OP_PERSONAL_WEB_ADDRESS = 0xDD1A,
|
|
MTP_OP_BUSINESS_WEB_ADDRESS = 0xDD1B,
|
|
MTP_OP_INSTANT_MESSENGER_ADDRESS = 0xDD1C,
|
|
MTP_OP_INSTANT_MESSENGER_ADDRESS_2 = 0xDD1D,
|
|
MTP_OP_INSTANT_MESSENGER_ADDRESS_3 = 0xDD1E,
|
|
MTP_OP_POSTAL_ADDRESS_PERSONAL_FULL = 0xDD1F,
|
|
MTP_OP_POSTAL_ADDRESS_PERSONAL_LINE_1 = 0xDD20,
|
|
MTP_OP_POSTAL_ADDRESS_PERSONAL_LINE_2 = 0xDD21,
|
|
MTP_OP_POSTAL_ADDRESS_PERSONAL_CITY = 0xDD22,
|
|
MTP_OP_POSTAL_ADDRESS_PERSONAL_REGION = 0xDD23,
|
|
MTP_OP_POSTAL_ADDRESS_PERSONAL_POSTAL_CODE = 0xDD24,
|
|
MTP_OP_POSTAL_ADDRESS_PERSONAL_COUNTRY = 0xDD25,
|
|
MTP_OP_POSTAL_ADDRESS_BUSINESS_FULL = 0xDD26,
|
|
MTP_OP_POSTAL_ADDRESS_BUSINESS_LINE_1 = 0xDD27,
|
|
MTP_OP_POSTAL_ADDRESS_BUSINESS_LINE_2 = 0xDD28,
|
|
MTP_OP_POSTAL_ADDRESS_BUSINESS_CITY = 0xDD29,
|
|
MTP_OP_POSTAL_ADDRESS_BUSINESS_REGION = 0xDD2A,
|
|
MTP_OP_POSTAL_ADDRESS_BUSINESS_POSTAL_CODE = 0xDD2B,
|
|
MTP_OP_POSTAL_ADDRESS_BUSINESS_COUNTRY = 0xDD2C,
|
|
MTP_OP_POSTAL_ADDRESS_OTHER_FULL = 0xDD2D,
|
|
MTP_OP_POSTAL_ADDRESS_OTHER_LINE_1 = 0xDD2E,
|
|
MTP_OP_POSTAL_ADDRESS_OTHER_LINE_2 = 0xDD2F,
|
|
MTP_OP_POSTAL_ADDRESS_OTHER_CITY = 0xDD30,
|
|
MTP_OP_POSTAL_ADDRESS_OTHER_REGION = 0xDD31,
|
|
MTP_OP_POSTAL_ADDRESS_OTHER_POSTAL_CODE = 0xDD32,
|
|
MTP_OP_POSTAL_ADDRESS_OTHER_COUNTRY = 0xDD33,
|
|
MTP_OP_ORGANIZATION_NAME = 0xDD34,
|
|
MTP_OP_PHONETIC_ORGANIZATION_NAME = 0xDD35,
|
|
MTP_OP_ROLE = 0xDD36,
|
|
MTP_OP_BIRTHDATE = 0xDD37,
|
|
MTP_OP_MESSAGE_TO = 0xDD40,
|
|
MTP_OP_MESSAGE_CC = 0xDD41,
|
|
MTP_OP_MESSAGE_BCC = 0xDD42,
|
|
MTP_OP_MESSAGE_READ = 0xDD43,
|
|
MTP_OP_MESSAGE_RECEIVED_TIME = 0xDD44,
|
|
MTP_OP_MESSAGE_SENDER = 0xDD45,
|
|
MTP_OP_ACTIVITY_BEGIN_TIME = 0xDD50,
|
|
MTP_OP_ACTIVITY_END_TIME = 0xDD51,
|
|
MTP_OP_ACTIVITY_LOCATION = 0xDD52,
|
|
MTP_OP_ACTIVITY_REQUIRED_ATTENDEES = 0xDD54,
|
|
MTP_OP_ACTIVITY_OPTIONAL_ATTENDEES = 0xDD55,
|
|
MTP_OP_ACTIVITY_RESOURCES = 0xDD56,
|
|
MTP_OP_ACTIVITY_ACCEPTED = 0xDD57,
|
|
MTP_OP_ACTIVITY_TENTATIVE = 0xDD58,
|
|
MTP_OP_ACTIVITY_DECLINED = 0xDD59,
|
|
MTP_OP_ACTIVITY_REMINDER_TIME = 0xDD5A,
|
|
MTP_OP_ACTIVITY_OWNER = 0xDD5B,
|
|
MTP_OP_ACTIVITY_STATUS = 0xDD5C,
|
|
MTP_OP_OWNER = 0xDD5D,
|
|
MTP_OP_EDITOR = 0xDD5E,
|
|
MTP_OP_WEBMASTER = 0xDD5F,
|
|
MTP_OP_URL_SOURCE = 0xDD60,
|
|
MTP_OP_URL_DESTINATION = 0xDD61,
|
|
MTP_OP_TIME_BOOKMARK = 0xDD62,
|
|
MTP_OP_OBJECT_BOOKMARK = 0xDD63,
|
|
MTP_OP_BYTE_BOOKMARK = 0xDD64,
|
|
MTP_OP_LAST_BUILD_DATE = 0xDD70,
|
|
MTP_OP_TIME_TO_LIVE = 0xDD71,
|
|
MTP_OP_MEDIA_GUID = 0xDD72,
|
|
} mtp_object_property_code_t;
|
|
|
|
/* MTP Structures */
|
|
|
|
typedef struct mtp_object_info_header {
|
|
mtp_trunc_id_t storage_id;
|
|
mtp_enum_t object_format;
|
|
mtp_enum_t protection_status;
|
|
mtp_size_t object_compressed_size;
|
|
mtp_enum_t thumb_format;
|
|
mtp_size_t thumb_compressed_size;
|
|
mtp_size_t thumb_pix_width;
|
|
mtp_size_t thumb_pix_height;
|
|
mtp_size_t image_pix_width;
|
|
mtp_size_t image_pix_height;
|
|
mtp_size_t image_bit_depth;
|
|
mtp_id_t parent_object;
|
|
mtp_enum_t association_type;
|
|
mtp_id_t association_description;
|
|
mtp_size_t sequence_number;
|
|
} mtp_object_info_header_t;
|
|
|
|
#define DECLARE_OBJECT_INFO(name, file_name_alloc) \
|
|
typedef struct mtp_object_info_##name { \
|
|
mtp_object_info_header_t header; \
|
|
mtp_byte_t file_name_length; \
|
|
wchar_t file_name[file_name_alloc]; \
|
|
mtp_byte_t date_created_length; \
|
|
wchar_t date_created[0]; \
|
|
mtp_byte_t date_modified_length; \
|
|
wchar_t date_modified[0]; \
|
|
mtp_byte_t keywords_length; \
|
|
wchar_t keywords[0]; \
|
|
} mtp_object_info_##name##_t;
|
|
DECLARE_OBJECT_INFO(min, 0)
|
|
DECLARE_OBJECT_INFO(max, MAX_FILE_NAME_LENGTH)
|
|
|
|
#define DECLARE_STORAGE_INFO_TYPE(name) \
|
|
typedef struct name##_mtp_storage_info { \
|
|
mtp_enum_t storage_type; \
|
|
mtp_enum_t filesystem_type; \
|
|
mtp_enum_t access_capability; \
|
|
mtp_trunc_uint64_t max_capacity; \
|
|
mtp_trunc_uint64_t free_space_in_bytes; \
|
|
mtp_size_t free_space_in_objects; \
|
|
mtp_byte_t storage_description_length; \
|
|
wchar_t storage_description[ \
|
|
lengthof(L##name##_storage_desc)]; \
|
|
mtp_byte_t volume_identifier_length; \
|
|
wchar_t volume_identifier[ \
|
|
lengthof(L##name##_volume_id)]; \
|
|
} name##_mtp_storage_info_t;
|
|
FOR_EACH_STORAGE(DECLARE_STORAGE_INFO_TYPE)
|
|
|
|
typedef struct mtp_device_info {
|
|
mtp_version_t standard_version;
|
|
mtp_id_t mtp_vendor_extension_id;
|
|
mtp_version_t mtp_version;
|
|
mtp_byte_t mtp_extensions_length;
|
|
wchar_t mtp_extensions[lengthof(Lmtp_extensions)];
|
|
mtp_enum_t functional_mode;
|
|
mtp_size_t operations_supported_length;
|
|
mtp_enum_t operations_supported[0 FOR_EACH_SUPP_OPR(COUNT_EACH)];
|
|
mtp_size_t events_supported_length;
|
|
mtp_enum_t events_supported[0 FOR_EACH_SUPP_EVT(COUNT_EACH)];
|
|
mtp_size_t device_properties_length;
|
|
mtp_enum_t device_properties[0 FOR_EACH_SUPP_DP(COUNT_EACH)];
|
|
mtp_size_t capture_formats_length;
|
|
mtp_enum_t capture_formats[0 FOR_EACH_SUPP_CF(COUNT_EACH)];
|
|
mtp_size_t playback_formats_length;
|
|
mtp_enum_t playback_formats[0 FOR_EACH_SUPP_PF(COUNT_EACH)];
|
|
mtp_byte_t manufacturer_length;
|
|
wchar_t manufacturer[lengthof(Lmanufacturer)];
|
|
mtp_byte_t model_length;
|
|
wchar_t model[lengthof(Lproduct)];
|
|
mtp_byte_t device_version_length;
|
|
wchar_t device_version[lengthof(Ldevice_version)];
|
|
mtp_byte_t serial_number_length;
|
|
wchar_t serial_number[lengthof(Lserial_number Lserial_number)];
|
|
} mtp_device_info_t;
|
|
|
|
typedef struct mtp_container {
|
|
mtp_trunc_size_t length;
|
|
mtp_enum_t type, code;
|
|
mtp_id_t transaction;
|
|
} mtp_container_t;
|
|
|
|
typedef union mtp_transaction_payload {
|
|
mtp_param_t params[MTP_MAX_PARAMS];
|
|
mtp_byte_t buffer[MTP_MAX_BULK_PKT_SZ];
|
|
mtp_size_t count;
|
|
mtp_id_t handles[MTP_MAX_BULK_PKT_SZ /
|
|
sizeof(mtp_id_t)];
|
|
mtp_object_info_header_t object_info;
|
|
} mtp_transaction_payload_t;
|
|
|
|
enum mtp_send_object_info_state {
|
|
SEND_OBJECT_INFO_CONTAINER_STATE,
|
|
SEND_OBJECT_INFO_DATA_STATE,
|
|
SEND_OBJECT_INFO_REST_STATE,
|
|
} mtp_send_object_info_state_t;
|
|
|
|
typedef union mtp_transaction_state {
|
|
struct {
|
|
size_t id;
|
|
mtp_byte_t mask;
|
|
mtp_byte_t flag;
|
|
} get_object_handles;
|
|
struct {
|
|
mtp_byte_t *data;
|
|
size_t remaining;
|
|
unsigned checksum;
|
|
} get_object;
|
|
struct {
|
|
mtp_byte_t state;
|
|
mtp_enum_t response;
|
|
mtp_param_t params[MTP_MAX_SOI_PARAMS];
|
|
} send_object_info;
|
|
struct {
|
|
mtp_byte_t extra;
|
|
mtp_enum_t response;
|
|
} send_object;
|
|
} mtp_transaction_state_t;
|
|
|
|
typedef struct mtp_transaction_pending {
|
|
struct {
|
|
mtp_byte_t flag;
|
|
mtp_byte_t mask;
|
|
size_t handle;
|
|
} send_object;
|
|
} mtp_transaction_pending_t;
|
|
|
|
typedef struct mtp_transaction {
|
|
mtp_container_t container;
|
|
mtp_transaction_payload_t payload;
|
|
mtp_transaction_state_t state;
|
|
mtp_transaction_pending_t pending;
|
|
} mtp_transaction_t;
|
|
|
|
typedef struct mtp_event {
|
|
mtp_container_t container;
|
|
mtp_param_t params[MTP_MAX_EVT_PARAMS];
|
|
} mtp_event_t;
|
|
|
|
struct mtp_global {
|
|
/* Other State */
|
|
bool exiting;
|
|
/* MTP State */
|
|
bool reset;
|
|
mtp_id_t session;
|
|
mtp_transaction_t transaction;
|
|
mtp_event_t events[MTP_MAX_PENDING_EVENTS];
|
|
/* MTP Info */
|
|
size_t device_info_size;
|
|
const mtp_device_info_t *device_info;
|
|
#define DECLARE_STORAGE_INFO(name) \
|
|
name##_mtp_storage_info_t *name##_storage_info;
|
|
FOR_EACH_STORAGE(DECLARE_STORAGE_INFO)
|
|
/* Name List */
|
|
#define DECLARE_STORAGE_NAME_COUNT(name) \
|
|
mtp_trunc_param_t name##_name_count;
|
|
FOR_EACH_STORAGE_NONE_TOTAL(DECLARE_STORAGE_NAME_COUNT)
|
|
size_t max_name_id, free_name;
|
|
var_name_t var_names[5000];
|
|
};
|
|
|
|
/* MTP Forward Function Declarations */
|
|
|
|
#define DECLARE_CALLBACK(name) \
|
|
static usb_error_t name##_complete( \
|
|
usb_endpoint_t endpoint, \
|
|
usb_transfer_status_t status, \
|
|
size_t transferred, \
|
|
mtp_global_t *global)
|
|
DECLARE_CALLBACK(control);
|
|
DECLARE_CALLBACK(command);
|
|
DECLARE_CALLBACK(get_object_handles);
|
|
DECLARE_CALLBACK(get_object);
|
|
DECLARE_CALLBACK(send_object_info);
|
|
DECLARE_CALLBACK(send_object_container);
|
|
DECLARE_CALLBACK(send_object);
|
|
DECLARE_CALLBACK(final_data_in);
|
|
DECLARE_CALLBACK(response);
|
|
DECLARE_CALLBACK(event);
|
|
|
|
/* Other Forward Function Declarations */
|
|
usb_error_t wait_for_usb(mtp_global_t *global);
|
|
|
|
/* MTP Function Definitions */
|
|
|
|
static usb_endpoint_t get_endpoint(
|
|
usb_endpoint_t endpoint,
|
|
mtp_byte_t address) {
|
|
return usb_GetDeviceEndpoint(
|
|
usb_GetEndpointDevice(endpoint),
|
|
address);
|
|
}
|
|
|
|
static usb_error_t stall_data_endpoints(
|
|
usb_endpoint_t endpoint) {
|
|
printf("stalling data endpoints\n");
|
|
#if 0
|
|
usb_error_t error;
|
|
error = usb_StallEndpoint(
|
|
get_endpoint(endpoint, MTP_EP_DATA_IN));
|
|
if (error == USB_SUCCESS)
|
|
error = usb_StallEndpoint(
|
|
get_endpoint(endpoint, MTP_EP_DATA_OUT));
|
|
return error;
|
|
#else
|
|
(void)endpoint;
|
|
return USB_ERROR_FAILED;
|
|
#endif
|
|
}
|
|
|
|
static usb_error_t schedule_event(
|
|
usb_endpoint_t endpoint,
|
|
mtp_event_code_t code,
|
|
mtp_param_t *params,
|
|
mtp_byte_t param_count,
|
|
mtp_global_t *global) {
|
|
usb_error_t error = USB_SUCCESS;
|
|
bool notified = false;
|
|
while (!global->exiting && error == USB_SUCCESS) {
|
|
for (mtp_byte_t i = 0;
|
|
i != MTP_MAX_PENDING_EVENTS; ++i) {
|
|
if (global->events[i].container.length.byte)
|
|
continue;
|
|
mtp_byte_t params_size =
|
|
param_count * sizeof(mtp_param_t);
|
|
mtp_byte_t block_size =
|
|
sizeof(mtp_container_t) +
|
|
params_size;
|
|
global->events[i].container.length.byte =
|
|
block_size;
|
|
global->events[i].container.code = code;
|
|
global->events[i].container.transaction =
|
|
global->transaction.container.transaction;
|
|
memcpy(global->events[i].params,
|
|
params, params_size);
|
|
return usb_ScheduleInterruptTransfer(
|
|
get_endpoint(endpoint, MTP_EP_INT),
|
|
&global->events[i], block_size,
|
|
event_complete,
|
|
(mtp_global_t *)&global->events[i]);
|
|
}
|
|
if (!notified) {
|
|
printf("event queue full");
|
|
notified = true;
|
|
}
|
|
error = wait_for_usb(global);
|
|
}
|
|
return error;
|
|
}
|
|
|
|
static usb_error_t schedule_command(
|
|
usb_endpoint_t endpoint,
|
|
mtp_global_t *global) {
|
|
global->transaction.container.transaction = 0;
|
|
return usb_ScheduleBulkTransfer(
|
|
get_endpoint(
|
|
endpoint,
|
|
MTP_EP_DATA_OUT),
|
|
&global->transaction,
|
|
MTP_MAX_BULK_PKT_SZ,
|
|
command_complete,
|
|
global);
|
|
}
|
|
|
|
static usb_error_t schedule_response(
|
|
usb_endpoint_t endpoint,
|
|
mtp_enum_t code,
|
|
mtp_param_t *params,
|
|
mtp_byte_t param_count,
|
|
mtp_global_t *global) {
|
|
mtp_byte_t params_size =
|
|
param_count *
|
|
sizeof(mtp_param_t);
|
|
mtp_byte_t container_size =
|
|
sizeof(mtp_container_t) +
|
|
params_size;
|
|
global->transaction.container.length.size =
|
|
container_size;
|
|
global->transaction.container.type =
|
|
MTP_BT_RESPONSE;
|
|
global->transaction.container.code = code;
|
|
memcpy(global->transaction.payload.params,
|
|
params,
|
|
params_size);
|
|
if (code != MTP_RSP_OK)
|
|
printf("response: %04X\n", code);
|
|
return usb_ScheduleBulkTransfer(
|
|
get_endpoint(endpoint, MTP_EP_DATA_IN),
|
|
&global->transaction,
|
|
container_size,
|
|
response_complete,
|
|
global);
|
|
}
|
|
|
|
static usb_error_t schedule_error_response(
|
|
usb_endpoint_t endpoint,
|
|
mtp_enum_t code,
|
|
mtp_global_t *global) {
|
|
return schedule_response(
|
|
endpoint, code, NULL, 0, global);
|
|
}
|
|
|
|
static usb_error_t schedule_ok_response(
|
|
usb_endpoint_t endpoint,
|
|
mtp_param_t *params,
|
|
mtp_byte_t param_count,
|
|
mtp_global_t *global) {
|
|
return schedule_response(
|
|
endpoint, MTP_RSP_OK,
|
|
params, param_count, global);
|
|
}
|
|
|
|
static usb_error_t schedule_data_in_response(
|
|
usb_endpoint_t endpoint,
|
|
const void *data,
|
|
size_t data_size,
|
|
mtp_global_t *global) {
|
|
usb_error_t error;
|
|
global->transaction.container.length.size =
|
|
sizeof(mtp_container_t) +
|
|
data_size;
|
|
global->transaction.container.type =
|
|
MTP_BT_DATA;
|
|
error = usb_ScheduleBulkTransfer(
|
|
endpoint = get_endpoint(
|
|
endpoint, MTP_EP_DATA_IN),
|
|
&global->transaction,
|
|
sizeof(mtp_container_t),
|
|
NULL,
|
|
global);
|
|
if (error) return error;
|
|
return usb_ScheduleBulkTransfer(
|
|
endpoint,
|
|
(void *)data,
|
|
data_size,
|
|
final_data_in_complete,
|
|
global);
|
|
}
|
|
|
|
static usb_error_t schedule_data_in(
|
|
usb_endpoint_t endpoint,
|
|
size_t data_size,
|
|
usb_transfer_callback_t complete,
|
|
mtp_global_t *global) {
|
|
global->transaction.container.length.size =
|
|
sizeof(mtp_container_t) +
|
|
data_size;
|
|
global->transaction.container.type =
|
|
MTP_BT_DATA;
|
|
return usb_ScheduleBulkTransfer(
|
|
get_endpoint(endpoint,
|
|
MTP_EP_DATA_IN),
|
|
&global->transaction,
|
|
sizeof(mtp_container_t),
|
|
complete,
|
|
global);
|
|
}
|
|
|
|
static usb_error_t status_error(
|
|
usb_transfer_status_t status) {
|
|
printf("transfer status = %02X\n", status);
|
|
return USB_SUCCESS;
|
|
}
|
|
|
|
static mtp_id_t alloc_object_handle(
|
|
mtp_global_t *global) {
|
|
mtp_id_t handle = global->free_name;
|
|
if (handle)
|
|
global->free_name =
|
|
global->var_names[handle - 1].next;
|
|
else if ((handle = ++global->max_name_id) >
|
|
lengthof(global->var_names))
|
|
return --global->max_name_id, 0;
|
|
return handle;
|
|
}
|
|
|
|
static void free_object_handle(
|
|
mtp_id_t handle,
|
|
var_name_t *var_name,
|
|
mtp_global_t *global) {
|
|
if (!(var_name->type & reserved_flag)) {
|
|
--*(var_name->type & storage_flag
|
|
? &global->arc_name_count.word
|
|
: &global->ram_name_count.word);
|
|
--global->total_name_count.word;
|
|
}
|
|
var_name->type = 0;
|
|
var_name->valid = '\0';
|
|
var_name->next = global->free_name;
|
|
global->free_name = handle;
|
|
}
|
|
|
|
static var_name_t *lookup_object_handle(
|
|
mtp_id_t handle,
|
|
mtp_global_t *global) {
|
|
if (!handle || handle > global->max_name_id)
|
|
return NULL;
|
|
var_name_t *var_name =
|
|
&global->var_names[handle - 1];
|
|
return var_name->valid ? var_name : NULL;
|
|
}
|
|
|
|
static uint16_t compute_checksum(
|
|
const void *data,
|
|
size_t size) {
|
|
unsigned sum = 0;
|
|
const uint8_t *pointer = data;
|
|
while (size--)
|
|
sum += *pointer++;
|
|
return sum;
|
|
}
|
|
|
|
static void get_datetime(
|
|
wchar_t result[lengthof(Lfactory_datetime)]) {
|
|
uint16_t year;
|
|
uint8_t month, day, hour, minute, second,
|
|
i = lengthof(Lfactory_datetime);
|
|
char string[lengthof(Lfactory_datetime)],
|
|
*pointer = string;
|
|
boot_GetDate(&day, &month, &year);
|
|
boot_GetTime(&second, &minute, &hour);
|
|
sprintf(string, "%04u%02u%02uT%02u%02u%02u",
|
|
year, month, day, hour, minute, second);
|
|
do {
|
|
*(char *)result = *pointer;
|
|
++result;
|
|
++pointer;
|
|
} while (--i);
|
|
}
|
|
|
|
static int delete_object(
|
|
usb_endpoint_t endpoint,
|
|
mtp_id_t handle,
|
|
mtp_global_t *global) {
|
|
var_name_t *var_name =
|
|
lookup_object_handle(handle, global);
|
|
if (!var_name)
|
|
return MTP_RSP_INVALID_OBJECT_HANDLE;
|
|
if (var_name->type & reserved_flag) {
|
|
free_object_handle(
|
|
handle, var_name, global);
|
|
return MTP_RSP_OK;
|
|
}
|
|
switch (delete_var(var_name)) {
|
|
case DELETE_VAR_NOT_DELETED:
|
|
return MTP_RSP_ACCESS_DENIED;
|
|
case DELETE_VAR_DELETED:
|
|
case DELETE_VAR_TRUNCATED:
|
|
free_object_handle(
|
|
handle, var_name, global);
|
|
break;
|
|
case DELETE_VAR_ZEROED: {
|
|
usb_error_t error = schedule_event(
|
|
endpoint, MTP_EVT_OBJECT_ADDED,
|
|
&handle, 1, global);
|
|
if (error != USB_SUCCESS)
|
|
return error;
|
|
break;
|
|
}
|
|
}
|
|
return MTP_RSP_OK;
|
|
}
|
|
|
|
static int send_object(
|
|
usb_endpoint_t endpoint,
|
|
mtp_id_t handle,
|
|
size_t size,
|
|
mtp_global_t *global) {
|
|
size += global->transaction.state
|
|
.send_object.extra;
|
|
const var_file_header_t *header = OBJECT_BUFFER;
|
|
if (size <=
|
|
offsetof(var_file_header_t, entry.data) +
|
|
sizeof(uint16_t) ||
|
|
memcmp(header->signature,
|
|
VAR_FILE_SIGNATURE,
|
|
sizeof(header->signature)))
|
|
return MTP_RSP_INCOMPLETE_TRANSFER;
|
|
const mtp_byte_t *pointer =
|
|
(const mtp_byte_t *)&header->entry;
|
|
size -= offsetof(var_file_header_t, entry) +
|
|
sizeof(uint16_t);
|
|
if (header->file_data_length != size ||
|
|
compute_checksum(pointer, size) !=
|
|
*(uint16_t *)&pointer[size])
|
|
return MTP_RSP_INCOMPLETE_TRANSFER;
|
|
size_t count = 0;
|
|
while (size) {
|
|
const var_entry_t *entry =
|
|
(const var_entry_t *)pointer;
|
|
if (size < offsetof(var_entry_t, data) -
|
|
sizeof(entry->version) -
|
|
sizeof(entry->flag) ||
|
|
entry->var_data_length_1 < 2)
|
|
return MTP_RSP_INCOMPLETE_TRANSFER;
|
|
size_t entry_size =
|
|
sizeof(entry->var_header_length) +
|
|
sizeof(entry->var_data_length_1) +
|
|
entry->var_header_length +
|
|
entry->var_data_length_1;
|
|
switch (entry->var_header_length) {
|
|
case offsetof(var_entry_t, data) -
|
|
sizeof(entry->var_header_length) -
|
|
sizeof(entry->var_data_length_1) -
|
|
sizeof(entry->version) -
|
|
sizeof(entry->flag):
|
|
break;
|
|
case offsetof(var_entry_t, data) -
|
|
sizeof(entry->var_header_length) -
|
|
sizeof(entry->var_data_length_1):
|
|
if (entry->flag & ~storage_flag)
|
|
return MTP_RSP_INCOMPLETE_TRANSFER;
|
|
break;
|
|
default:
|
|
return MTP_RSP_INCOMPLETE_TRANSFER;
|
|
}
|
|
if (size < entry_size ||
|
|
*(const uint16_t *)&pointer[
|
|
sizeof(entry->var_header_length) +
|
|
sizeof(entry->var_data_length_1) +
|
|
entry->var_header_length -
|
|
sizeof(entry->var_data_length_2)] !=
|
|
entry->var_data_length_1)
|
|
return MTP_RSP_INCOMPLETE_TRANSFER;
|
|
pointer += entry_size;
|
|
size -= entry_size;
|
|
++count;
|
|
}
|
|
pointer = (const mtp_byte_t *)&header->entry;
|
|
bool first = true;
|
|
do {
|
|
const var_entry_t *entry =
|
|
(const var_entry_t *)pointer;
|
|
pointer +=
|
|
sizeof(entry->var_header_length) +
|
|
sizeof(entry->var_data_length_1) +
|
|
entry->var_header_length;
|
|
mtp_byte_t version = 0, flag = 0;
|
|
if (entry->var_header_length ==
|
|
offsetof(var_entry_t, data) -
|
|
sizeof(entry->var_header_length) -
|
|
sizeof(entry->var_data_length_1)) {
|
|
version = entry->version;
|
|
flag = entry->flag;
|
|
}
|
|
flag &= global->transaction.pending
|
|
.send_object.mask;
|
|
flag |= global->transaction.pending
|
|
.send_object.flag;
|
|
usb_error_t error;
|
|
switch (create_var(&entry->var_name, pointer,
|
|
entry->var_data_length_1)) {
|
|
case CREATE_VAR_NOT_CREATED:
|
|
break;
|
|
case CREATE_VAR_RECREATED:
|
|
for (mtp_id_t id = 0;
|
|
id != global->max_name_id; ) {
|
|
var_name_t *var_name =
|
|
&global->var_names[id++];
|
|
if (!(var_name->type &
|
|
reserved_flag) &&
|
|
!var_name_cmp(
|
|
&entry->var_name,
|
|
var_name)) {
|
|
free_object_handle(
|
|
id, var_name, global);
|
|
if ((error = schedule_event(
|
|
endpoint,
|
|
MTP_EVT_OBJECT_REMOVED,
|
|
&id, 1, global)) !=
|
|
USB_SUCCESS)
|
|
return error;
|
|
break;
|
|
}
|
|
}
|
|
__attribute__((fallthrough));
|
|
case CREATE_VAR_CREATED: {
|
|
if (first)
|
|
first = false; /* TODO: maybe update info? */
|
|
else if (!(handle = alloc_object_handle(
|
|
global)))
|
|
return MTP_RSP_OK;
|
|
else if ((error = schedule_event(
|
|
endpoint,
|
|
MTP_EVT_OBJECT_ADDED,
|
|
&handle, 1, global)) !=
|
|
USB_SUCCESS)
|
|
return error;
|
|
var_name_t *var_name =
|
|
&global->var_names[handle - 1];
|
|
--*(var_name->type & storage_flag
|
|
? &global->arc_name_count.word
|
|
: &global->ram_name_count.word);
|
|
memcpy(var_name,
|
|
&entry->var_name,
|
|
sizeof(entry->var_name));
|
|
var_name->type &= type_mask;
|
|
if (flag && !arc_unarc_var(var_name))
|
|
var_name->type |= flag;
|
|
++*(var_name->type & storage_flag
|
|
? &global->arc_name_count.word
|
|
: &global->ram_name_count.word);
|
|
break;
|
|
}
|
|
}
|
|
pointer += entry->var_data_length_1;
|
|
} while (--count);
|
|
return first
|
|
? MTP_RSP_INCOMPLETE_TRANSFER
|
|
: MTP_RSP_OK;
|
|
}
|
|
|
|
#define DEFINE_CALLBACK(name) \
|
|
static usb_error_t name##_complete( \
|
|
usb_endpoint_t endpoint, \
|
|
usb_transfer_status_t status, \
|
|
size_t transferred, \
|
|
mtp_global_t *global)
|
|
|
|
DEFINE_CALLBACK(control) {
|
|
(void)endpoint;
|
|
(void)transferred;
|
|
(void)global;
|
|
if (status != USB_TRANSFER_COMPLETED)
|
|
return status_error(status);
|
|
return USB_SUCCESS;
|
|
}
|
|
|
|
DEFINE_CALLBACK(command) {
|
|
if (status != USB_TRANSFER_COMPLETED)
|
|
return status_error(status);
|
|
global->reset = false;
|
|
if (transferred < sizeof(mtp_container_t) ||
|
|
global->transaction.container
|
|
.length.size != transferred ||
|
|
global->transaction.container.type !=
|
|
MTP_BT_COMMAND)
|
|
return stall_data_endpoints(endpoint);
|
|
if (!global->session &&
|
|
global->transaction.container.code >
|
|
MTP_OPR_OPEN_SESSION)
|
|
return schedule_error_response(
|
|
endpoint,
|
|
MTP_RSP_SESSION_NOT_OPEN,
|
|
global);
|
|
mtp_byte_t params_size =
|
|
transferred - sizeof(mtp_container_t);
|
|
if (params_size % sizeof(mtp_param_t) ||
|
|
params_size > MTP_MAX_PARAMS * sizeof(mtp_param_t))
|
|
return stall_data_endpoints(endpoint);
|
|
memset(&global->transaction.payload.params[
|
|
params_size / sizeof(mtp_param_t)], 0,
|
|
MTP_MAX_PARAMS * sizeof(mtp_param_t) - params_size);
|
|
size_t pending_handle =
|
|
global->transaction.pending
|
|
.send_object.handle;
|
|
global->transaction.pending
|
|
.send_object.handle = 0;
|
|
switch (global->transaction.container.code) {
|
|
#define MAX_PARAMS(max) \
|
|
do \
|
|
for (mtp_byte_t i = max; i != MTP_MAX_PARAMS; ++i) \
|
|
if (global->transaction.payload.params[i]) \
|
|
return schedule_error_response( \
|
|
endpoint, \
|
|
MTP_RSP_PARAMETER_NOT_SUPPORTED, \
|
|
global); \
|
|
while (0)
|
|
case MTP_OPR_GET_DEVICE_INFO:
|
|
MAX_PARAMS(0);
|
|
return schedule_data_in_response(
|
|
endpoint, global->device_info,
|
|
global->device_info_size, global);
|
|
case MTP_OPR_OPEN_SESSION:
|
|
MAX_PARAMS(1);
|
|
if (!global->transaction.payload.params[0])
|
|
return schedule_error_response(
|
|
endpoint, MTP_RSP_INVALID_PARAMETER,
|
|
global);
|
|
if (global->session)
|
|
return schedule_response(
|
|
endpoint, MTP_RSP_SESSION_ALREADY_OPEN,
|
|
&global->session, 1, global);
|
|
global->session =
|
|
global->transaction.payload.params[0];
|
|
return schedule_ok_response(
|
|
endpoint, NULL, 0, global);
|
|
case MTP_OPR_CLOSE_SESSION:
|
|
MAX_PARAMS(0);
|
|
global->session = 0;
|
|
return schedule_ok_response(
|
|
endpoint, NULL, 0, global);
|
|
case MTP_OPR_GET_STORAGE_IDS: {
|
|
static const mtp_id_t storage_ids[] = {
|
|
storage_count,
|
|
#define LIST_STORAGE_ID(name) I##name,
|
|
FOR_EACH_STORAGE(LIST_STORAGE_ID)
|
|
};
|
|
MAX_PARAMS(0);
|
|
return schedule_data_in_response(
|
|
endpoint, storage_ids,
|
|
sizeof(storage_ids), global);
|
|
}
|
|
case MTP_OPR_GET_STORAGE_INFO:
|
|
MAX_PARAMS(1);
|
|
#define GET_STORAGE_INFO_RESPONSE(name) \
|
|
if (global->transaction.payload.params[0] == I##name) { \
|
|
global->name##_storage_info->free_space_in_bytes.word = \
|
|
get_##name##_free_space_in_bytes; \
|
|
return schedule_data_in_response( \
|
|
endpoint, global->name##_storage_info, \
|
|
sizeof(name##_mtp_storage_info_t), global); \
|
|
}
|
|
FOR_EACH_STORAGE(GET_STORAGE_INFO_RESPONSE)
|
|
return schedule_error_response(
|
|
endpoint,
|
|
MTP_RSP_INVALID_STORAGE_ID,
|
|
global);
|
|
case MTP_OPR_GET_NUM_OBJECTS:
|
|
MAX_PARAMS(3);
|
|
if (global->transaction.payload.params[1] &&
|
|
global->transaction.payload.params[1] !=
|
|
MTP_OF_UNDEFINED)
|
|
return schedule_ok_response(
|
|
endpoint,
|
|
&global->none_name_count.param, 1,
|
|
global);
|
|
if (global->transaction.payload.params[2] &&
|
|
global->transaction.payload.params[2] !=
|
|
MTP_ROOT_OBJECT_HANDLE)
|
|
return schedule_error_response(
|
|
endpoint,
|
|
lookup_object_handle(
|
|
global->transaction.payload.params[2],
|
|
global)
|
|
? MTP_RSP_INVALID_PARENT_OBJECT
|
|
: MTP_RSP_INVALID_OBJECT_HANDLE,
|
|
global);
|
|
#define GET_NUM_OBJECTS_RESPONSE(name) \
|
|
if (global->transaction.payload.params[0] == \
|
|
I##name) \
|
|
return schedule_ok_response( \
|
|
endpoint, \
|
|
&global->name##_name_count.param, \
|
|
1, global);
|
|
FOR_EACH_STORAGE_TOTAL(GET_NUM_OBJECTS_RESPONSE)
|
|
return schedule_error_response(
|
|
endpoint,
|
|
MTP_RSP_INVALID_STORAGE_ID,
|
|
global);
|
|
case MTP_OPR_GET_OBJECT_HANDLES: {
|
|
MAX_PARAMS(3);
|
|
global->transaction.state.get_object_handles.id = 0;
|
|
if (global->transaction.payload.params[2] &&
|
|
global->transaction.payload.params[2] !=
|
|
MTP_ROOT_OBJECT_HANDLE)
|
|
return schedule_error_response(
|
|
endpoint,
|
|
lookup_object_handle(
|
|
global->transaction.payload.params[2],
|
|
global)
|
|
? MTP_RSP_INVALID_PARENT_OBJECT
|
|
: MTP_RSP_INVALID_OBJECT_HANDLE,
|
|
global);
|
|
#define GET_OBJECT_HANDLES_RESPONSE(name) \
|
|
if (global->transaction.payload.params[0] == \
|
|
I##name) { \
|
|
size_t name_count = \
|
|
global->name##_name_count.word; \
|
|
global->transaction.state \
|
|
.get_object_handles.mask = name##_mask; \
|
|
global->transaction.state \
|
|
.get_object_handles.flag = name##_flag; \
|
|
if (global->transaction.payload.params[1] && \
|
|
global->transaction.payload.params[1] != \
|
|
MTP_OF_UNDEFINED) { \
|
|
name_count = 0; \
|
|
global->transaction.state. \
|
|
get_object_handles.mask = none_mask; \
|
|
global->transaction.state. \
|
|
get_object_handles.flag = none_flag; \
|
|
} \
|
|
global->transaction.payload.count = \
|
|
name_count; \
|
|
return schedule_data_in( \
|
|
endpoint, sizeof(mtp_size_t) + \
|
|
name_count * sizeof(mtp_id_t), \
|
|
get_object_handles_complete, \
|
|
global); \
|
|
}
|
|
FOR_EACH_STORAGE_TOTAL(GET_OBJECT_HANDLES_RESPONSE)
|
|
return schedule_error_response(
|
|
endpoint,
|
|
MTP_RSP_INVALID_STORAGE_ID,
|
|
global);
|
|
}
|
|
case MTP_OPR_GET_OBJECT_INFO: {
|
|
static mtp_object_info_max_t object_info;
|
|
MAX_PARAMS(1);
|
|
var_name_t *var_name = lookup_object_handle(
|
|
global->transaction.payload.params[0],
|
|
global);
|
|
if (!var_name)
|
|
return schedule_error_response(
|
|
endpoint,
|
|
MTP_RSP_INVALID_OBJECT_HANDLE,
|
|
global);
|
|
object_info.header.storage_id.word =
|
|
var_name->type & storage_flag ? Iarc : Iram;
|
|
object_info.header.object_format = MTP_OF_UNDEFINED;
|
|
object_info.header.object_compressed_size = 0;
|
|
mtp_byte_t file_name_length = 0;
|
|
memset(object_info.file_name, 0,
|
|
sizeof(object_info.file_name));
|
|
if (!(var_name->type & reserved_flag)) {
|
|
object_info.header.object_compressed_size =
|
|
offsetof(var_file_header_t, entry.data) +
|
|
get_var_data_size(var_name) +
|
|
sizeof(uint16_t);
|
|
file_name_length = get_var_file_name(
|
|
var_name,
|
|
object_info.file_name);
|
|
}
|
|
object_info.file_name_length =
|
|
file_name_length;
|
|
return schedule_data_in_response(
|
|
endpoint,
|
|
&object_info,
|
|
sizeof(object_info) -
|
|
sizeof(object_info.file_name) +
|
|
file_name_length *
|
|
sizeof(*object_info.file_name),
|
|
global);
|
|
}
|
|
case MTP_OPR_GET_OBJECT: {
|
|
static var_file_header_t header = {
|
|
.signature = VAR_FILE_SIGNATURE,
|
|
.global_version = 0,
|
|
.comment = VAR_FILE_COMMENT,
|
|
.file_data_length = 0,
|
|
.entry = {
|
|
.var_header_length =
|
|
offsetof(var_file_header_t,
|
|
entry.var_data_length_2) -
|
|
offsetof(var_file_header_t,
|
|
entry.var_data_length_1),
|
|
.var_data_length_1 = 0,
|
|
.var_name = {
|
|
.type = 0,
|
|
.name = "",
|
|
},
|
|
.version = 0,
|
|
.flag = 0,
|
|
.var_data_length_2 = 0,
|
|
.data = {},
|
|
},
|
|
};
|
|
usb_error_t error;
|
|
mtp_byte_t *data, size;
|
|
size_t remaining;
|
|
unsigned checksum = 0;
|
|
MAX_PARAMS(1);
|
|
var_name_t *var_name = lookup_object_handle(
|
|
global->transaction.payload.params[0],
|
|
global);
|
|
if (!var_name)
|
|
return schedule_error_response(
|
|
endpoint,
|
|
MTP_RSP_INVALID_OBJECT_HANDLE,
|
|
global);
|
|
if (var_name->type & reserved_flag)
|
|
return schedule_data_in(
|
|
endpoint, 0,
|
|
final_data_in_complete,
|
|
global);
|
|
remaining = get_var_data_size(var_name);
|
|
error = schedule_data_in(
|
|
endpoint,
|
|
offsetof(var_file_header_t,
|
|
entry.data) +
|
|
remaining +
|
|
sizeof(uint16_t),
|
|
NULL, global);
|
|
if (error != USB_SUCCESS) return error;
|
|
data = get_var_data_ptr(var_name);
|
|
header.file_data_length = remaining +
|
|
offsetof(var_entry_t, data);
|
|
header.entry.var_name.type =
|
|
var_name->type & type_mask;
|
|
memcpy(header.entry.var_name.name,
|
|
var_name->name,
|
|
sizeof(header.entry.var_name.name));
|
|
header.entry.version =
|
|
((mtp_byte_t *)get_var_vat_ptr(var_name))[-2];
|
|
header.entry.flag =
|
|
var_name->type & storage_flag;
|
|
header.entry.var_data_length_1 =
|
|
header.entry.var_data_length_2 =
|
|
remaining;
|
|
if (remaining < sizeof(header.entry.data))
|
|
size = remaining;
|
|
else
|
|
size = sizeof(header.entry.data);
|
|
memcpy(header.entry.data, data, size);
|
|
global->transaction.state
|
|
.get_object.data = data + size;
|
|
checksum += compute_checksum(
|
|
&header.entry,
|
|
offsetof(var_entry_t, data) +
|
|
size);
|
|
global->transaction.state
|
|
.get_object.checksum = checksum;
|
|
remaining -= size - sizeof(uint16_t);
|
|
if (remaining == 2 &&
|
|
size < sizeof(header.entry.data)) {
|
|
header.entry.data[size++] =
|
|
checksum >> 0;
|
|
--remaining;
|
|
}
|
|
if (remaining == 1 &&
|
|
size < sizeof(header.entry.data)) {
|
|
header.entry.data[size++] =
|
|
checksum >> 8;
|
|
--remaining;
|
|
}
|
|
global->transaction.state
|
|
.get_object.remaining = remaining;
|
|
return usb_ScheduleBulkTransfer(
|
|
get_endpoint(endpoint,
|
|
MTP_EP_DATA_IN),
|
|
&header,
|
|
offsetof(var_file_header_t,
|
|
entry.data) + size,
|
|
size == sizeof(header.entry.data)
|
|
? get_object_complete
|
|
: final_data_in_complete,
|
|
global);
|
|
}
|
|
case MTP_OPR_GET_THUMB:
|
|
printf("GetThumb");
|
|
break;
|
|
case MTP_OPR_DELETE_OBJECT: {
|
|
int response = MTP_RSP_OK;
|
|
if (global->transaction.payload.params[0] !=
|
|
MTP_ROOT_OBJECT_HANDLE) {
|
|
MAX_PARAMS(1);
|
|
response = delete_object(
|
|
endpoint,
|
|
global->transaction.payload.params[0],
|
|
global);
|
|
if (response < MTP_RSP_UNDEFINED)
|
|
return response;
|
|
} else {
|
|
MAX_PARAMS(2);
|
|
if (!global->transaction.payload.params[1] ||
|
|
global->transaction.payload.params[1] ==
|
|
MTP_OF_UNDEFINED)
|
|
for (size_t id = 0; id++ != global->max_name_id &&
|
|
response >= MTP_RSP_UNDEFINED; ) {
|
|
int result = delete_object(
|
|
endpoint, id, global);
|
|
if (result < MTP_RSP_UNDEFINED)
|
|
return result;
|
|
else if (result == MTP_RSP_ACCESS_DENIED)
|
|
response = MTP_RSP_PARTIAL_DELETION;
|
|
}
|
|
}
|
|
return schedule_response(
|
|
endpoint, response, NULL, 0, global);
|
|
}
|
|
case MTP_OPR_SEND_OBJECT_INFO:
|
|
global->transaction.pending
|
|
.send_object.handle = pending_handle;
|
|
global->transaction.state
|
|
.send_object_info.state =
|
|
SEND_OBJECT_INFO_CONTAINER_STATE;
|
|
global->transaction.state
|
|
.send_object_info.response = MTP_RSP_OK;
|
|
for (mtp_byte_t i = 2; i != MTP_MAX_PARAMS; ++i)
|
|
if (global->transaction.payload.params[i])
|
|
global->transaction.state
|
|
.send_object_info.response =
|
|
MTP_RSP_PARAMETER_NOT_SUPPORTED;
|
|
#define SEND_OBJECT_INFO_IS_STORAGE(name) \
|
|
if (global->transaction.payload \
|
|
.params[0] == I##name) { \
|
|
global->transaction.pending \
|
|
.send_object.flag = \
|
|
name##_flag & name##_mask; \
|
|
global->transaction.pending \
|
|
.send_object.mask = \
|
|
name##_mask ^ storage_flag; \
|
|
} else
|
|
FOR_EACH_STORAGE_NONE(SEND_OBJECT_INFO_IS_STORAGE)
|
|
if (global->transaction.state
|
|
.send_object_info.response ==
|
|
MTP_RSP_OK)
|
|
global->transaction.state
|
|
.send_object_info.response =
|
|
MTP_RSP_INVALID_STORAGE_ID;
|
|
if (global->transaction.state
|
|
.send_object_info.response ==
|
|
MTP_RSP_OK &&
|
|
global->transaction.payload.params[1] &&
|
|
global->transaction.payload.params[1] !=
|
|
MTP_ROOT_OBJECT_HANDLE)
|
|
global->transaction.state
|
|
.send_object_info.response =
|
|
lookup_object_handle(
|
|
global->transaction.payload.params[1],
|
|
global)
|
|
? MTP_RSP_INVALID_PARENT_OBJECT
|
|
: MTP_RSP_INVALID_OBJECT_HANDLE;
|
|
global->transaction.state
|
|
.send_object_info.params[0] =
|
|
global->transaction.payload.params[0] == Inone
|
|
? Iram : global->transaction.payload.params[0];
|
|
global->transaction.state
|
|
.send_object_info.params[1] =
|
|
MTP_ROOT_OBJECT_HANDLE;
|
|
return usb_ScheduleBulkTransfer(
|
|
endpoint,
|
|
&global->transaction,
|
|
MTP_MAX_BULK_PKT_SZ,
|
|
send_object_info_complete,
|
|
global);
|
|
case MTP_OPR_SEND_OBJECT:
|
|
global->transaction.pending
|
|
.send_object.handle = pending_handle;
|
|
global->transaction.state
|
|
.send_object.response = MTP_RSP_OK;
|
|
for (mtp_byte_t i = 0; i != MTP_MAX_PARAMS; ++i)
|
|
if (global->transaction.payload.params[i])
|
|
global->transaction.state
|
|
.send_object.response =
|
|
MTP_RSP_PARAMETER_NOT_SUPPORTED;
|
|
if (global->transaction.state
|
|
.send_object.response ==
|
|
MTP_RSP_OK &&
|
|
!global->transaction.pending
|
|
.send_object.handle)
|
|
global->transaction.state
|
|
.send_object.response =
|
|
MTP_RSP_NO_VALID_OBJECT_INFO;
|
|
return usb_ScheduleBulkTransfer(
|
|
endpoint,
|
|
&global->transaction,
|
|
MTP_MAX_BULK_PKT_SZ,
|
|
send_object_container_complete,
|
|
global);
|
|
case MTP_OPR_INITIATE_CAPTURE:
|
|
printf("InitiateCapture");
|
|
break;
|
|
case MTP_OPR_FORMAT_STORE:
|
|
printf("FormatStore");
|
|
break;
|
|
case MTP_OPR_RESET_DEVICE:
|
|
printf("ResetDevice");
|
|
break;
|
|
case MTP_OPR_SELF_TEST:
|
|
printf("SelfTest");
|
|
break;
|
|
case MTP_OPR_SET_OBJECT_PROTECTION:
|
|
printf("SetObjectProtection");
|
|
break;
|
|
case MTP_OPR_POWER_DOWN:
|
|
printf("PowerDown");
|
|
break;
|
|
case MTP_OPR_GET_DEVICE_PROP_DESC:
|
|
MAX_PARAMS(1);
|
|
#define DECLARE_FORM_NONE(type)
|
|
#define DECLARE_FORM_RANGE(type) \
|
|
type##_t minimum_value; \
|
|
type##_t maximum_value; \
|
|
type##_t step_size;
|
|
#define DEFINE_FORM_NONE(name)
|
|
#define DEFINE_FORM_RANGE(name) \
|
|
.minimum_value = name##_MIN, \
|
|
.maximum_value = name##_MAX, \
|
|
.step_size = name##_STEP,
|
|
#define GET_DEVICE_PROP_DESC_RESPONSE(type, name, form) \
|
|
if (global->transaction.payload.params[0] == \
|
|
MTP_DP_##name) { \
|
|
static struct name##_DESC { \
|
|
mtp_enum_t device_property_code; \
|
|
mtp_enum_t datatype; \
|
|
mtp_byte_t get_set; \
|
|
type##_t factory_default_value; \
|
|
type##_t current_value; \
|
|
mtp_byte_t form_flag; \
|
|
DECLARE_FORM_##form(type) \
|
|
} name = { \
|
|
.device_property_code = MTP_DP_##name, \
|
|
.datatype = MTP_TC_##type, \
|
|
.get_set = name##_GET_SET, \
|
|
.factory_default_value = name##_DEF, \
|
|
.current_value = name##_DEF, \
|
|
.form_flag = MTP_FORM_##form, \
|
|
DEFINE_FORM_##form(name) \
|
|
}; \
|
|
name##_GET(name.current_value); \
|
|
return schedule_data_in_response( \
|
|
endpoint, &name, \
|
|
sizeof(name), global); \
|
|
}
|
|
FOR_EACH_SUPP_DP(GET_DEVICE_PROP_DESC_RESPONSE)
|
|
return schedule_error_response(
|
|
endpoint,
|
|
MTP_RSP_DEVICE_PROP_NOT_SUPPORTED,
|
|
global);
|
|
case MTP_OPR_GET_DEVICE_PROP_VALUE:
|
|
#define GET_DEVICE_PROP_VALUE_RESPONSE(type, name, form) \
|
|
if (global->transaction.payload.params[0] == \
|
|
MTP_DP_##name) { \
|
|
type##_t current; \
|
|
name##_GET(current); \
|
|
return schedule_data_in_response( \
|
|
endpoint, ¤t, \
|
|
sizeof(current), global); \
|
|
}
|
|
FOR_EACH_SUPP_DP(GET_DEVICE_PROP_VALUE_RESPONSE)
|
|
return schedule_error_response(
|
|
endpoint,
|
|
MTP_RSP_DEVICE_PROP_NOT_SUPPORTED,
|
|
global);
|
|
case MTP_OPR_SET_DEVICE_PROP_VALUE:
|
|
usb_ScheduleBulkTransfer(
|
|
endpoint,
|
|
&global->transaction.payload,
|
|
MTP_MAX_BULK_PKT_SZ,
|
|
NULL,
|
|
global);
|
|
return schedule_error_response(
|
|
endpoint,
|
|
MTP_RSP_ACCESS_DENIED,
|
|
global);
|
|
case MTP_OPR_RESET_DEVICE_PROP_VALUE:
|
|
printf("ResetDevicePropValue");
|
|
break;
|
|
case MTP_OPR_TERMINATE_OPEN_CAPTURE:
|
|
printf("TerminateOpenCapture");
|
|
break;
|
|
case MTP_OPR_MOVE_OBJECT: {
|
|
mtp_enum_t response = MTP_RSP_INVALID_STORAGE_ID;
|
|
MAX_PARAMS(3);
|
|
var_name_t *var_name = lookup_object_handle(
|
|
global->transaction.payload.params[0],
|
|
global);
|
|
if (!var_name)
|
|
response = MTP_RSP_INVALID_OBJECT_HANDLE;
|
|
else if (global->transaction.payload.params[2])
|
|
response = lookup_object_handle(
|
|
global->transaction.payload.params[2],
|
|
global)
|
|
? MTP_RSP_INVALID_PARENT_OBJECT
|
|
: MTP_RSP_INVALID_OBJECT_HANDLE;
|
|
#define MOVE_OBJECT_RESPONSE(name) \
|
|
else if (global->transaction.payload.params[1] == \
|
|
I##name) \
|
|
response = \
|
|
(var_name->type & name##_mask) != \
|
|
name##_flag && \
|
|
!arc_unarc_var(var_name) \
|
|
? var_name->type ^= storage_flag, MTP_RSP_OK \
|
|
: MTP_RSP_STORE_FULL;
|
|
FOR_EACH_STORAGE(MOVE_OBJECT_RESPONSE)
|
|
return schedule_response(
|
|
endpoint, response,
|
|
NULL, 0, global);
|
|
}
|
|
case MTP_OPR_COPY_OBJECT:
|
|
printf("CopyObject");
|
|
break;
|
|
case MTP_OPR_GET_PARTIAL_OBJECT:
|
|
printf("GetPartialObject");
|
|
break;
|
|
case MTP_OPR_INITIATE_OPEN_CAPTURE:
|
|
printf("InitiateOpenCapture");
|
|
break;
|
|
case MTP_OPR_GET_OBJECT_PROPS_SUPPORTED:
|
|
printf("GetObjectPropsSupported");
|
|
break;
|
|
case MTP_OPR_GET_OBJECT_PROP_DESC:
|
|
printf("GetObjectPropDesc");
|
|
break;
|
|
case MTP_OPR_GET_OBJECT_PROP_VALUE:
|
|
printf("GetObjectPropValue");
|
|
break;
|
|
case MTP_OPR_SET_OBJECT_PROP_VALUE:
|
|
printf("SetObjectPropValue");
|
|
break;
|
|
case MTP_OPR_GET_OBJECT_REFERENCES:
|
|
printf("GetObjectReferences");
|
|
break;
|
|
case MTP_OPR_SET_OBJECT_REFERENCES:
|
|
printf("SetObjectReferences");
|
|
break;
|
|
case MTP_OPR_SKIP:
|
|
printf("Skip");
|
|
break;
|
|
}
|
|
printf(": (%08" PRIX32 ")",
|
|
global->transaction.container.length.size);
|
|
for (mtp_byte_t i = 0; params_size -= 4; ++i)
|
|
printf(" %08" PRIX32,
|
|
global->transaction.payload.params[i]);
|
|
printf("\n");
|
|
return schedule_error_response(
|
|
endpoint,
|
|
MTP_RSP_OPERATION_NOT_SUPPORTED,
|
|
global);
|
|
}
|
|
|
|
DEFINE_CALLBACK(get_object_handles) {
|
|
mtp_id_t *handles =
|
|
global->transaction.payload.handles;
|
|
mtp_byte_t size = 0;
|
|
size_t id =
|
|
global->transaction.state
|
|
.get_object_handles.id;
|
|
mtp_byte_t mask =
|
|
global->transaction.state
|
|
.get_object_handles.mask;
|
|
mtp_byte_t flag =
|
|
global->transaction.state
|
|
.get_object_handles.flag;
|
|
(void)transferred;
|
|
if (status != USB_TRANSFER_COMPLETED)
|
|
return status_error(status);
|
|
if (global->reset)
|
|
return schedule_command(endpoint, global);
|
|
if (!id) {
|
|
++handles;
|
|
size = sizeof(mtp_size_t);
|
|
}
|
|
while (size != MTP_MAX_BULK_PKT_SZ &&
|
|
id != global->max_name_id) {
|
|
var_name_t *name = &global->var_names[id++];
|
|
if (name->valid && (name->type & mask) == flag) {
|
|
*handles++ = id;
|
|
size += sizeof(mtp_id_t);
|
|
}
|
|
}
|
|
global->transaction.state
|
|
.get_object_handles.id = id;
|
|
return usb_ScheduleBulkTransfer(
|
|
endpoint,
|
|
&global->transaction.payload,
|
|
size,
|
|
size == MTP_MAX_BULK_PKT_SZ
|
|
? get_object_handles_complete
|
|
: final_data_in_complete,
|
|
global);
|
|
}
|
|
|
|
DEFINE_CALLBACK(get_object) {
|
|
size_t remaining =
|
|
global->transaction.state
|
|
.get_object.remaining;
|
|
unsigned checksum =
|
|
global->transaction.state
|
|
.get_object.checksum;
|
|
mtp_byte_t size;
|
|
(void)transferred;
|
|
if (status != USB_TRANSFER_COMPLETED)
|
|
return status_error(status);
|
|
if (global->reset)
|
|
return schedule_command(endpoint, global);
|
|
if (remaining <= 2)
|
|
size = 0;
|
|
else if (remaining <= MTP_MAX_BULK_PKT_SZ + 2)
|
|
size = remaining - sizeof(uint16_t);
|
|
else
|
|
size = MTP_MAX_BULK_PKT_SZ;
|
|
memcpy(global->transaction.payload.buffer,
|
|
global->transaction.state
|
|
.get_object.data,
|
|
size);
|
|
global->transaction.state
|
|
.get_object.data += size;
|
|
checksum += compute_checksum(
|
|
global->transaction.payload.buffer,
|
|
size);
|
|
global->transaction.state
|
|
.get_object.checksum = checksum;
|
|
remaining -= size;
|
|
if (remaining == 2 &&
|
|
size < MTP_MAX_BULK_PKT_SZ) {
|
|
global->transaction.payload.buffer[size++] =
|
|
checksum >> 0;
|
|
--remaining;
|
|
}
|
|
if (remaining == 1 &&
|
|
size < MTP_MAX_BULK_PKT_SZ) {
|
|
global->transaction.payload.buffer[size++] =
|
|
checksum >> 8;
|
|
--remaining;
|
|
}
|
|
global->transaction.state
|
|
.get_object.remaining = remaining;
|
|
return usb_ScheduleBulkTransfer(
|
|
endpoint,
|
|
&global->transaction.payload,
|
|
size,
|
|
size == MTP_MAX_BULK_PKT_SZ
|
|
? get_object_complete
|
|
: final_data_in_complete,
|
|
global);
|
|
}
|
|
|
|
DEFINE_CALLBACK(send_object_info) {
|
|
if (status != USB_TRANSFER_COMPLETED)
|
|
return status_error(status);
|
|
if (global->reset)
|
|
return schedule_command(endpoint, global);
|
|
mtp_byte_t object_info_size = transferred;
|
|
switch (global->transaction.state
|
|
.send_object_info.state) {
|
|
case SEND_OBJECT_INFO_CONTAINER_STATE:
|
|
if ((transferred != sizeof(mtp_container_t) &&
|
|
transferred != MTP_MAX_BULK_PKT_SZ) ||
|
|
global->transaction.container.length.size <
|
|
sizeof(mtp_container_t) +
|
|
sizeof(mtp_object_info_min_t) ||
|
|
global->transaction.container.type !=
|
|
MTP_BT_DATA ||
|
|
global->transaction.container.code !=
|
|
MTP_OPR_SEND_OBJECT_INFO)
|
|
return stall_data_endpoints(endpoint);
|
|
global->transaction.state
|
|
.send_object_info.state =
|
|
SEND_OBJECT_INFO_DATA_STATE;
|
|
if (!(object_info_size -=
|
|
sizeof(mtp_container_t))) {
|
|
transferred = MTP_MAX_BULK_PKT_SZ;
|
|
break;
|
|
}
|
|
__attribute__((fallthrough));
|
|
case SEND_OBJECT_INFO_DATA_STATE:
|
|
if (global->transaction.state
|
|
.send_object_info.response !=
|
|
MTP_RSP_OK);
|
|
else if (object_info_size <
|
|
sizeof(mtp_object_info_header_t))
|
|
global->transaction.state
|
|
.send_object_info.response =
|
|
MTP_RSP_INVALID_DATASET;
|
|
else if (global->transaction.payload
|
|
.object_info.object_format !=
|
|
MTP_OF_UNDEFINED)
|
|
global->transaction.state
|
|
.send_object_info.response =
|
|
MTP_RSP_INVALID_OBJECT_FORMAT_CODE;
|
|
else if (global->transaction.payload.object_info
|
|
.object_compressed_size &&
|
|
global->transaction.payload.object_info
|
|
.object_compressed_size <
|
|
offsetof(var_file_header_t,
|
|
entry.data) +
|
|
sizeof(uint16_t))
|
|
global->transaction.state
|
|
.send_object_info.response =
|
|
MTP_RSP_INVALID_DATASET;
|
|
else if (global->transaction.payload.object_info
|
|
.object_compressed_size >
|
|
offsetof(var_file_header_t,
|
|
entry.data) +
|
|
0xFFFF + sizeof(uint16_t))
|
|
global->transaction.state
|
|
.send_object_info.response =
|
|
MTP_RSP_OBJECT_TOO_LARGE;
|
|
else if (global->transaction.pending
|
|
.send_object.handle)
|
|
global->transaction.state
|
|
.send_object_info.params[2] =
|
|
global->transaction.pending
|
|
.send_object.handle;
|
|
else if (!(global->transaction.state
|
|
.send_object_info.params[2] =
|
|
alloc_object_handle(global)))
|
|
global->transaction.state
|
|
.send_object_info.response =
|
|
MTP_RSP_STORE_FULL;
|
|
else {
|
|
var_name_t *var_name = &global->var_names[
|
|
global->transaction.state
|
|
.send_object_info.params[2] - 1];
|
|
var_name->type =
|
|
global->transaction.pending
|
|
.send_object.flag | reserved_flag;
|
|
var_name->valid = 1;
|
|
++*(var_name->type & storage_flag
|
|
? &global->arc_name_count.word
|
|
: &global->ram_name_count.word);
|
|
++global->total_name_count.word;
|
|
}
|
|
global->transaction.state
|
|
.send_object_info.state =
|
|
SEND_OBJECT_INFO_REST_STATE;
|
|
__attribute__((fallthrough));
|
|
case SEND_OBJECT_INFO_REST_STATE:
|
|
break;
|
|
}
|
|
if (transferred == MTP_MAX_BULK_PKT_SZ)
|
|
return usb_ScheduleBulkTransfer(
|
|
endpoint,
|
|
&global->transaction.payload,
|
|
MTP_MAX_BULK_PKT_SZ,
|
|
send_object_info_complete,
|
|
global);
|
|
if (global->transaction.state
|
|
.send_object_info.response !=
|
|
MTP_RSP_OK)
|
|
return schedule_error_response(
|
|
endpoint,
|
|
global->transaction.state
|
|
.send_object_info.response,
|
|
global);
|
|
global->transaction.pending
|
|
.send_object.handle =
|
|
global->transaction.state
|
|
.send_object_info.params[2];
|
|
return schedule_ok_response(
|
|
endpoint,
|
|
global->transaction.state
|
|
.send_object_info.params,
|
|
lengthof(global->transaction.state
|
|
.send_object_info.params),
|
|
global);
|
|
}
|
|
|
|
DEFINE_CALLBACK(send_object_container) {
|
|
if (status != USB_TRANSFER_COMPLETED)
|
|
return status_error(status);
|
|
if (global->reset)
|
|
return schedule_command(endpoint, global);
|
|
if (transferred < sizeof(mtp_container_t) ||
|
|
global->transaction.container.length.size >
|
|
sizeof(mtp_container_t) +
|
|
offsetof(var_file_header_t,
|
|
entry.data) +
|
|
0xFFFF + sizeof(uint16_t) ||
|
|
global->transaction.container.length.word <
|
|
transferred ||
|
|
(transferred != sizeof(mtp_container_t) &&
|
|
transferred != MTP_MAX_BULK_PKT_SZ &&
|
|
transferred != global->transaction
|
|
.container.length.word) ||
|
|
global->transaction.container.type !=
|
|
MTP_BT_DATA ||
|
|
global->transaction.container.code !=
|
|
MTP_OPR_SEND_OBJECT)
|
|
return stall_data_endpoints(endpoint);
|
|
if (global->transaction.container.length.word ==
|
|
sizeof(mtp_container_t))
|
|
return schedule_ok_response(
|
|
endpoint,
|
|
NULL, 0, global);
|
|
mtp_byte_t extra =
|
|
global->transaction.state
|
|
.send_object.extra =
|
|
transferred -
|
|
sizeof(mtp_container_t);
|
|
memcpy(OBJECT_BUFFER,
|
|
global->transaction.payload.buffer,
|
|
extra);
|
|
if (extra && transferred != MTP_MAX_BULK_PKT_SZ)
|
|
return send_object_complete(
|
|
endpoint, status, extra, global);
|
|
return usb_ScheduleBulkTransfer(
|
|
endpoint,
|
|
OBJECT_BUFFER + extra,
|
|
global->transaction.container.length.word -
|
|
sizeof(mtp_container_t) - extra,
|
|
send_object_complete,
|
|
global);
|
|
}
|
|
|
|
DEFINE_CALLBACK(send_object) {
|
|
size_t handle =
|
|
global->transaction.pending
|
|
.send_object.handle;
|
|
global->transaction.pending
|
|
.send_object.handle = 0;
|
|
if (status != USB_TRANSFER_COMPLETED)
|
|
return status_error(status);
|
|
if (global->reset)
|
|
return schedule_command(endpoint, global);
|
|
mtp_enum_t response =
|
|
global->transaction.state
|
|
.send_object.response;
|
|
if (response == MTP_RSP_OK)
|
|
response = send_object(
|
|
endpoint, handle,
|
|
transferred, global);
|
|
if (response < MTP_RSP_UNDEFINED)
|
|
return response;
|
|
return schedule_response(
|
|
endpoint, response, NULL, 0, global);
|
|
}
|
|
|
|
DEFINE_CALLBACK(final_data_in) {
|
|
(void)transferred;
|
|
if (status != USB_TRANSFER_COMPLETED)
|
|
return status_error(status);
|
|
if (global->reset)
|
|
return schedule_command(endpoint, global);
|
|
return schedule_ok_response(endpoint, NULL, 0, global);
|
|
}
|
|
|
|
DEFINE_CALLBACK(response) {
|
|
(void)transferred;
|
|
if (status != USB_TRANSFER_COMPLETED)
|
|
return status_error(status);
|
|
return schedule_command(endpoint, global);
|
|
}
|
|
|
|
DEFINE_CALLBACK(event) {
|
|
(void)endpoint;
|
|
(void)transferred;
|
|
((mtp_event_t *)global)->container.length.byte = 0;
|
|
if (status != USB_TRANSFER_COMPLETED)
|
|
return status_error(status);
|
|
printf("event complete\n");
|
|
return USB_SUCCESS;
|
|
}
|
|
|
|
static usb_error_t usb_event(usb_event_t event,
|
|
void *event_data,
|
|
mtp_global_t *global) {
|
|
usb_error_t error = USB_SUCCESS;
|
|
if (!(usb_GetRole() & USB_ROLE_DEVICE))
|
|
return error;
|
|
usb_endpoint_t control = usb_GetDeviceEndpoint(
|
|
usb_FindDevice(NULL, NULL, USB_SKIP_HUBS),
|
|
MTP_EP_CONTROL);
|
|
if (event < USB_DEFAULT_SETUP_EVENT)
|
|
printf("event: %d\n", event);
|
|
if (event == 99)
|
|
printf("debug: %06X\n", (unsigned)event_data);
|
|
switch (event) {
|
|
case USB_DEFAULT_SETUP_EVENT: {
|
|
usb_control_setup_t *setup = event_data;
|
|
if (setup->bmRequestType ==
|
|
(USB_DEVICE_TO_HOST |
|
|
USB_STANDARD_REQUEST |
|
|
USB_RECIPIENT_DEVICE) &&
|
|
setup->bRequest == USB_GET_DESCRIPTOR &&
|
|
setup->wValue == 0x03EE && !setup->wIndex) {
|
|
DEFINE_STRING_DESCRIPTOR(const, os_specific);
|
|
error = usb_ScheduleTransfer(
|
|
control, (void *)&os_specific,
|
|
sizeof(os_specific),
|
|
control_complete, global);
|
|
} else if (setup->bmRequestType ==
|
|
(USB_DEVICE_TO_HOST |
|
|
USB_VENDOR_REQUEST |
|
|
USB_RECIPIENT_DEVICE) &&
|
|
setup->bRequest == 1 &&
|
|
!setup->wValue &&
|
|
setup->wIndex == 4) {
|
|
static const usb_descriptor_t control_message = {
|
|
.bLength = sizeof(control_message),
|
|
.bDescriptorType = 0,
|
|
.data = {
|
|
0x00, 0x00, 0x00, 0x01,
|
|
0x04, 0x00, 0x01, 0x00,
|
|
0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x01,
|
|
0x4D, 0x54, 0x50, 0x00,
|
|
0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00
|
|
},
|
|
};
|
|
error = usb_ScheduleTransfer(
|
|
control, (void *)&control_message,
|
|
sizeof(control_message),
|
|
control_complete, global);
|
|
} else if (setup->bmRequestType << 1 ==
|
|
(USB_CLASS_REQUEST |
|
|
USB_RECIPIENT_INTERFACE) << 1 &&
|
|
!setup->wValue && !setup->wIndex) {
|
|
if (!(setup->bmRequestType & USB_DEVICE_TO_HOST)) {
|
|
if (setup->bRequest != DEVICE_RESET_REQUEST ||
|
|
setup->wLength)
|
|
break;
|
|
global->reset = true;
|
|
global->session = 0;
|
|
error = usb_ScheduleTransfer(
|
|
control, NULL, 0,
|
|
control_complete, global);
|
|
} else if (setup->bRequest == GET_EXTENDED_EVENT_DATA) {
|
|
printf("GET_EXTENDED_EVENT_DATA\n");
|
|
break;
|
|
} else if (setup->bRequest == GET_DEVICE_STATUS) {
|
|
printf("GET_DEVICE_STATUS\n");
|
|
break;
|
|
} else break;
|
|
} else break;
|
|
if (error == USB_SUCCESS)
|
|
error = USB_IGNORE;
|
|
break;
|
|
}
|
|
case USB_HOST_CONFIGURE_EVENT:
|
|
global->session = 0;
|
|
error = schedule_command(control, global);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return error;
|
|
}
|
|
|
|
int main(void) {
|
|
static mtp_global_t global;
|
|
usb_error_t error;
|
|
ui_Init();
|
|
printf(" TRANSFER v0.0.1b\n"
|
|
" Connect USB to PC.\n"
|
|
" Press [clear] to exit.\n"
|
|
"--------------------------------");
|
|
static mtp_device_info_t device_info = {
|
|
.standard_version = 100, /* 1.00 */
|
|
.mtp_vendor_extension_id = 6,
|
|
.mtp_version = 101, /* 1.01 */
|
|
.mtp_extensions_length = lengthof(device_info.mtp_extensions),
|
|
.mtp_extensions = Lmtp_extensions,
|
|
.functional_mode = MTP_MODE_STANDARD_MODE,
|
|
.operations_supported_length =
|
|
lengthof(device_info.operations_supported),
|
|
.operations_supported = {
|
|
#define LIST_SUPP_OPR(name) \
|
|
MTP_OPR_##name,
|
|
FOR_EACH_SUPP_OPR(LIST_SUPP_OPR)
|
|
},
|
|
.events_supported_length = lengthof(device_info.events_supported),
|
|
.events_supported = {},
|
|
.device_properties_length = lengthof(device_info.device_properties),
|
|
.device_properties = {
|
|
#define LIST_SUPP_DP(type, name, form) \
|
|
MTP_DP_##name,
|
|
FOR_EACH_SUPP_DP(LIST_SUPP_DP)
|
|
},
|
|
.capture_formats_length = lengthof(device_info.capture_formats),
|
|
.capture_formats = {
|
|
#define LIST_SUPP_CF(name) \
|
|
MTP_OF_##name,
|
|
FOR_EACH_SUPP_CF(LIST_SUPP_CF)
|
|
},
|
|
.playback_formats_length = lengthof(device_info.playback_formats),
|
|
.playback_formats = {
|
|
#define LIST_SUPP_PF(name) \
|
|
MTP_OF_##name,
|
|
FOR_EACH_SUPP_PF(LIST_SUPP_PF)
|
|
},
|
|
.manufacturer_length = lengthof(device_info.manufacturer),
|
|
.manufacturer = Lmanufacturer,
|
|
.model_length = lengthof(Lproduct),
|
|
.model = Lproduct,
|
|
.device_version_length = lengthof(device_info.device_version),
|
|
.device_version = Ldevice_version,
|
|
.serial_number_length = lengthof(device_info.serial_number),
|
|
.serial_number = Lserial_number Lserial_number,
|
|
};
|
|
#define DEFINE_STORAGE_INFO(name) \
|
|
static name##_mtp_storage_info_t name##_storage_info = { \
|
|
.storage_type = MTP_ST_FIXED_RAM, \
|
|
.filesystem_type = MTP_FT_GENERIC_FLAT, \
|
|
.access_capability = MTP_AC_READ_WRITE, \
|
|
.max_capacity = { .uint64 = name##_max_capacity }, \
|
|
.free_space_in_bytes = { .uint64 = 0 }, \
|
|
.free_space_in_objects = UINT32_MAX, \
|
|
.storage_description_length = \
|
|
lengthof(L##name##_storage_desc), \
|
|
.storage_description = L##name##_storage_desc, \
|
|
.volume_identifier_length = \
|
|
lengthof(L##name##_volume_id), \
|
|
.volume_identifier = L##name##_volume_id, \
|
|
};
|
|
FOR_EACH_STORAGE(DEFINE_STORAGE_INFO)
|
|
/* Standard USB Descriptors */
|
|
FOR_EACH_STRING_DESCRIPTOR(DEFINE_STRING_DESCRIPTOR)
|
|
DEFINE_STRING_DESCRIPTOR(const, product84)
|
|
const static usb_string_descriptor_t *strings[] = {
|
|
#define ADDRESSOF_STRING_DESCRIPTOR(const, name) &name,
|
|
FOR_EACH_STRING_DESCRIPTOR(ADDRESSOF_STRING_DESCRIPTOR)
|
|
};
|
|
static const usb_string_descriptor_t langids = {
|
|
.bLength = sizeof(langids) + sizeof(wchar_t) * 1,
|
|
.bDescriptorType = USB_STRING_DESCRIPTOR,
|
|
.bString = {
|
|
[0] = 0x0409u,
|
|
},
|
|
};
|
|
static const struct configuration1 {
|
|
usb_configuration_descriptor_t configuration;
|
|
struct configuration1_interface0 {
|
|
usb_interface_descriptor_t interface;
|
|
usb_endpoint_descriptor_t endpoints[3];
|
|
} interface0;
|
|
} configuration1 = {
|
|
.configuration = {
|
|
.bLength = sizeof(configuration1.configuration),
|
|
.bDescriptorType = USB_CONFIGURATION_DESCRIPTOR,
|
|
.wTotalLength = sizeof(configuration1),
|
|
.bNumInterfaces = 1u,
|
|
.bConfigurationValue = 1u,
|
|
.iConfiguration = Icharging_cfg,
|
|
.bmAttributes = USB_CONFIGURATION_ATTRIBUTES |
|
|
USB_SELF_POWERED | USB_NO_REMOTE_WAKEUP,
|
|
.bMaxPower = 500u / 2u,
|
|
},
|
|
.interface0 = {
|
|
.interface = {
|
|
.bLength = sizeof(configuration1.interface0.interface),
|
|
.bDescriptorType = USB_INTERFACE_DESCRIPTOR,
|
|
.bInterfaceNumber = 0u,
|
|
.bAlternateSetting = 0u,
|
|
.bNumEndpoints = lengthof(configuration1.interface0.endpoints),
|
|
.bInterfaceClass = USB_IMAGE_CLASS,
|
|
.bInterfaceSubClass = 1u,
|
|
.bInterfaceProtocol = 1u,
|
|
.iInterface = Imtp_interface,
|
|
},
|
|
.endpoints = {
|
|
[0] = {
|
|
.bLength = sizeof(configuration1.interface0.endpoints[0]),
|
|
.bDescriptorType = USB_ENDPOINT_DESCRIPTOR,
|
|
.bEndpointAddress = MTP_EP_DATA_IN,
|
|
.bmAttributes = USB_BULK_TRANSFER,
|
|
.wMaxPacketSize = MTP_MAX_BULK_PKT_SZ,
|
|
.bInterval = 0u,
|
|
},
|
|
[1] = {
|
|
.bLength = sizeof(configuration1.interface0.endpoints[1]),
|
|
.bDescriptorType = USB_ENDPOINT_DESCRIPTOR,
|
|
.bEndpointAddress = MTP_EP_DATA_OUT,
|
|
.bmAttributes = USB_BULK_TRANSFER,
|
|
.wMaxPacketSize = MTP_MAX_BULK_PKT_SZ,
|
|
.bInterval = 0u,
|
|
},
|
|
[2] = {
|
|
.bLength = sizeof(configuration1.interface0.endpoints[2]),
|
|
.bDescriptorType = USB_ENDPOINT_DESCRIPTOR,
|
|
.bEndpointAddress = MTP_EP_INT,
|
|
.bmAttributes = USB_INTERRUPT_TRANSFER,
|
|
.wMaxPacketSize = MTP_MAX_INT_PKT_SZ,
|
|
.bInterval = 6u,
|
|
},
|
|
},
|
|
},
|
|
};
|
|
static const usb_configuration_descriptor_t *configurations[] = {
|
|
[0] = &configuration1.configuration
|
|
};
|
|
static usb_device_descriptor_t device = {
|
|
.bLength = sizeof(device),
|
|
.bDescriptorType = USB_DEVICE_DESCRIPTOR,
|
|
.bcdUSB = 0x200u, /* 2.00 */
|
|
.bDeviceClass = USB_INTERFACE_SPECIFIC_CLASS,
|
|
.bDeviceSubClass = 0u,
|
|
.bDeviceProtocol = 0u,
|
|
.bMaxPacketSize0 = 0x40u,
|
|
.idVendor = 0x0451u,
|
|
.idProduct = 0xE010u,
|
|
.bcdDevice = 0x260u, /* 2.60 */
|
|
.iManufacturer = Imanufacturer,
|
|
.iProduct = Iproduct,
|
|
.iSerialNumber = Iserial_number,
|
|
.bNumConfigurations = lengthof(configurations),
|
|
};
|
|
static const usb_standard_descriptors_t descriptors = {
|
|
.device = &device,
|
|
.configurations = configurations,
|
|
.langids = &langids,
|
|
.numStrings = Inum_strings,
|
|
.strings = strings,
|
|
};
|
|
const system_info_t *info = os_GetSystemInfo();
|
|
wchar_t *serial_numbers[] = {
|
|
&serial_number.bString[2 * lengthof(info->calcid)],
|
|
&device_info.serial_number[4 * lengthof(info->calcid)],
|
|
#define LIST_STORAGE_INFO_SERIAL(name) \
|
|
&name##_storage_info.volume_identifier[2 * lengthof(info->calcid)],
|
|
FOR_EACH_STORAGE(LIST_STORAGE_INFO_SERIAL)
|
|
};
|
|
for (mtp_byte_t i = 0; i != MTP_MAX_PENDING_EVENTS; ++i)
|
|
global.events[i].container.type = MTP_BT_EVENT;
|
|
if (info->hardwareType & 1)
|
|
global.device_info_size = sizeof(mtp_device_info_t);
|
|
else {
|
|
*(mtp_byte_t *)&device.bcdDevice = 0x40; /* 2.40 */
|
|
device_info.model_length = lengthof(Lproduct84);
|
|
memcpy(device_info.model, Lproduct84, sizeof(Lproduct84));
|
|
memmove(&device_info.model[lengthof(Lproduct84)],
|
|
&device_info.device_version_length,
|
|
sizeof(mtp_device_info_t) -
|
|
offsetof(mtp_device_info_t, device_version_length));
|
|
global.device_info_size = sizeof(mtp_device_info_t) -
|
|
sizeof(Lproduct) + sizeof(Lproduct84);
|
|
strings[Iproduct - 1] = &product84;
|
|
var_extensions[0x23][0] = 'e';
|
|
}
|
|
global.device_info = &device_info;
|
|
#define SET_STORAGE_INFO_PTR(name) \
|
|
global.name##_storage_info = &name##_storage_info;
|
|
FOR_EACH_STORAGE(SET_STORAGE_INFO_PTR)
|
|
for (mtp_byte_t i = 2 * lengthof(info->calcid); i; ) {
|
|
mtp_byte_t nibble = info->calcid[--i >> 1];
|
|
if (!(i & 1))
|
|
nibble >>= 4;
|
|
nibble &= 0xF;
|
|
if (nibble >= 10)
|
|
nibble += 'A' - '0' - 10;
|
|
nibble += '0';
|
|
for (mtp_byte_t j = lengthof(serial_numbers); j; )
|
|
*--serial_numbers[--j] = nibble;
|
|
}
|
|
{
|
|
void *entry = os_GetSymTablePtr(), *data;
|
|
uint24_t type, name_length;
|
|
char name[9];
|
|
var_name_t *var_name = global.var_names;
|
|
do {
|
|
entry = os_NextSymEntry(
|
|
entry, &type, &name_length,
|
|
name, &data);
|
|
if ((type &= type_mask) == TI_EQU_TYPE &&
|
|
!((equ_t *)data)->len)
|
|
continue;
|
|
if (data >= (void *)os_RamStart) {
|
|
var_name->type = type | ram_flag;
|
|
++global.ram_name_count.word;
|
|
} else {
|
|
var_name->type = type | arc_flag;
|
|
++global.arc_name_count.word;
|
|
}
|
|
memcpy(var_name->name, name, name_length);
|
|
++var_name;
|
|
if (++global.max_name_id ==
|
|
lengthof(global.var_names))
|
|
break;
|
|
} while (entry);
|
|
global.total_name_count.word =
|
|
global.max_name_id;
|
|
}
|
|
do {
|
|
if (usb_Init(usb_event, &global, &descriptors,
|
|
USB_DEFAULT_INIT_FLAGS) != USB_SUCCESS)
|
|
break;
|
|
do
|
|
error = wait_for_usb(&global);
|
|
while (!global.exiting && error == USB_SUCCESS);
|
|
if (error != USB_SUCCESS)
|
|
printf("error: %06X\n", error);
|
|
} while (!global.exiting);
|
|
usb_Cleanup();
|
|
ui_Cleanup();
|
|
return 0;
|
|
}
|
|
|
|
usb_error_t wait_for_usb(mtp_global_t *global) {
|
|
usb_error_t error;
|
|
kb_SetMode(MODE_2_SINGLE);
|
|
do
|
|
if ((error = usb_HandleEvents()) != USB_SUCCESS)
|
|
return error;
|
|
while (kb_GetMode() != MODE_0_IDLE);
|
|
if (kb_IsDown(kb_KeyClear))
|
|
global->exiting = true;
|
|
if (kb_IsDown(kb_KeyGraphVar)) {
|
|
var_name_t var_name = {
|
|
.type = TI_REAL_TYPE,
|
|
.name = "A",
|
|
};
|
|
mtp_byte_t *vat = get_var_vat_ptr(&var_name);
|
|
mtp_byte_t *data = get_var_data_ptr(&var_name);
|
|
size_t size = get_var_data_size(&var_name);
|
|
printf("vat");
|
|
for (int i = -9; i <= 0; ++i)
|
|
printf(" %02X", vat[i]);
|
|
printf("\ndata");
|
|
for (size_t i = 0; i != size; ++i)
|
|
printf(" %02X", data[i]);
|
|
printf("\n");
|
|
}
|
|
return USB_SUCCESS;
|
|
}
|