rockbox/firmware/usbstack/usb_iap.c
mojyack 142e1864ef usb: fix get_max_packet_size is called before endpoints are allocated
retrive the requirements like others rather than callback

Change-Id: I20efce76a418ebd7aa6943f02e53b3f7a8fd2797
2026-05-04 16:46:57 -04:00

699 lines
24 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
*
* Copyright (C) 2025 by Sho Tanimoto
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include "audio.h"
#include "pcm_mixer.h"
#include "pcm_sink.h"
#include "playback.h"
#include "powermgmt.h"
#include "timefuncs.h"
#include "usb_core.h"
#include "usb_drv.h"
#include "panic.h"
#include "iap/audio.h"
#include "iap/libiap/iap.h"
#include "iap/libiap/platform.h"
#include "iap/macros.h"
#include "iap/platform-macros.h"
#include "iap/platform.h"
#include "usb_audio_def.h"
#include "usb_class_driver.h"
#include "usb_hid_def.h"
#include "usb_iap.h"
struct usb_class_driver_ep_allocation usb_iap_ep_allocs[2] = {
/* uac input */
{.type = USB_ENDPOINT_XFER_ISOC, .dir = DIR_IN, .optional = false, .mps = 1024},
/* hid input */
{.type = USB_ENDPOINT_XFER_INT, .dir = DIR_IN, .optional = false, .mps = 64},
};
/* interface 0 (audio control) */
static struct usb_interface_descriptor ipod_audio_control_desc = {
.bLength = USB_DT_INTERFACE_SIZE,
.bDescriptorType = USB_DT_INTERFACE,
.bInterfaceNumber = -1, /* dynamic */
.bAlternateSetting = 0,
.bNumEndpoints = 0,
.bInterfaceClass = USB_CLASS_AUDIO,
.bInterfaceSubClass = USB_SUBCLASS_AUDIO_CONTROL,
};
static struct usb_ac_header ipod_audio_control_uac_header = {
.bLength = USB_AC_SIZEOF_HEADER(1),
.bDescriptorType = USB_DT_CS_INTERFACE,
.bDescriptorSubType = USB_AC_HEADER,
.bcdADC = 0x0100, /* 1.00 */
.wTotalLength = -1, /* dynamic */
.bInCollection = 1,
.baInterfaceNr = {-1}, /* dynamic */
};
static struct usb_ac_input_terminal ipod_audio_control_uac_input_terminal = {
.bLength = sizeof(struct usb_ac_input_terminal),
.bDescriptorType = USB_DT_CS_INTERFACE,
.bDescriptorSubType = USB_AC_INPUT_TERMINAL,
.bTerminalId = 1,
.wTerminalType = USB_AC_INPUT_TERMINAL_MICROPHONE,
.bAssocTerminal = 2, /* ipod_audio_control_uac_output_terminal */
.bNrChannels = 2,
.wChannelConfig = USB_AC_CHANNELS_LEFT_RIGHT_FRONT,
};
static struct usb_ac_output_terminal ipod_audio_control_uac_output_terminal = {
.bLength = sizeof(struct usb_ac_output_terminal),
.bDescriptorType = USB_DT_CS_INTERFACE,
.bDescriptorSubType = USB_AC_OUTPUT_TERMINAL,
.bTerminalId = 2,
.wTerminalType = USB_AC_TERMINAL_STREAMING,
.bAssocTerminal = 1, /* ipod_audio_control_uac_input_terminal */
.bSourceId = 1,
};
/* interface 1 (audio stream) */
/* interface 1 alt 0 */
static struct usb_interface_descriptor ipod_audio_stream_0_desc = {
.bLength = USB_DT_INTERFACE_SIZE,
.bDescriptorType = USB_DT_INTERFACE,
.bInterfaceNumber = -1, /* dynamic */
.bAlternateSetting = 0,
.bNumEndpoints = 0,
.bInterfaceClass = USB_CLASS_AUDIO,
.bInterfaceSubClass = USB_SUBCLASS_AUDIO_STREAMING,
};
/* interface 1 alt 1 */
static struct usb_interface_descriptor ipod_audio_stream_1_desc = {
.bLength = USB_DT_INTERFACE_SIZE,
.bDescriptorType = USB_DT_INTERFACE,
.bInterfaceNumber = -1, /* dynamic */
.bAlternateSetting = 1,
.bNumEndpoints = 1,
.bInterfaceClass = USB_CLASS_AUDIO,
.bInterfaceSubClass = USB_SUBCLASS_AUDIO_STREAMING,
};
static struct usb_as_interface ipod_audio_stream_1_uac_header = {
.bLength = sizeof(struct usb_as_interface),
.bDescriptorType = USB_DT_CS_INTERFACE,
.bDescriptorSubType = USB_AS_GENERAL,
.bTerminalLink = 2, /* ipod_audio_control_uac_output_terminal */
.bDelay = 1,
.wFormatTag = USB_AS_FORMAT_TYPE_I_PCM,
};
/* TODO: remove unsupported freqs */
static struct usb_as_format_type_i_discrete ipod_audio_stream_1_uac_discrete = {
.bLength = USB_AS_SIZEOF_FORMAT_TYPE_I_DISCRETE(9),
.bDescriptorType = USB_DT_CS_INTERFACE,
.bDescriptorSubType = USB_AS_FORMAT_TYPE,
.bFormatType = USB_AS_FORMAT_TYPE_I,
.bNrChannels = 2,
.bSubframeSize = 2, /* bBitResolution / 8 */
.bBitResolution = 16,
.bSamFreqType = 9,
.tSamFreq = {
{0x40, 0x1F, 0x00}, /* 8000 */
{0x11, 0x2B, 0x00}, /* 11025 */
{0xE0, 0x2E, 0x00}, /* 12000 */
{0x80, 0x3E, 0x00}, /* 16000 */
{0x22, 0x56, 0x00}, /* 22050 */
{0xC0, 0x5D, 0x00}, /* 24000 */
{0x00, 0x7D, 0x00}, /* 32000 */
{0x44, 0xAC, 0x00}, /* 44100 */
{0x80, 0xBB, 0x00}, /* 48000 */
},
};
/* interface 1 endpoint 0 */
static struct usb_as_iso_audio_endpoint ipod_audio_stream_1_endpoint = {
.bLength = USB_DT_ENDPOINT_AUDIO_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = -1, /* dynamic */
.bmAttributes = USB_ENDPOINT_XFER_ISOC,
.wMaxPacketSize = AS_PACKET_SIZE,
.bInterval = -1, /* dynamic */
.bRefresh = 0,
.bSynchAddress = 0,
};
static struct usb_as_iso_ctrldata_endpoint ipod_audio_stream_1_endpoint_uac = {
.bLength = sizeof(struct usb_as_iso_ctrldata_endpoint),
.bDescriptorType = USB_DT_CS_ENDPOINT,
.bDescriptorSubType = USB_AS_EP_GENERAL,
.bmAttributes = USB_AS_EP_CS_SAMPLING_FREQ_CTL,
.bLockDelayUnits = 0,
.wLockDelay = 0,
};
/* interface 2 (hid) */
static struct usb_interface_descriptor ipod_hid_desc = {
.bLength = USB_DT_INTERFACE_SIZE,
.bDescriptorType = USB_DT_INTERFACE,
.bInterfaceNumber = -1, /* dynamic */
.bAlternateSetting = 0,
.bNumEndpoints = 1,
.bInterfaceClass = USB_CLASS_HID,
.bInterfaceSubClass = 0,
};
#define INPUT_REPORT(id, count) 0x09, 0x01, /* Usage 0x01 */ \
0x85, id, /* Report ID */ \
0x95, count, /* Report Count */ \
0x82, 0x02, 0x01 /* Input 0x0102 (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Buffered Bytes) */
#define OUTPUT_REPORT(id, count) 0x09, 0x01, /* Usage 0x01 */ \
0x85, id, /* Report ID */ \
0x95, count, /* Report Count */ \
0x92, 0x02, 0x01 /* Output 0x0102 (...) */
#define INPUT_REPORT2(id, count1, count2) 0x09, 0x01, /* Usage 0x01 */ \
0x85, id, /* Report ID */ \
0x96, count1, count2, /* Report Count */ \
0x82, 0x02, 0x01 /* Input 0x0102 (...) */
#define OUTPUT_REPORT2(id, count1, count2) 0x09, 0x01, /* Usage 0x01 */ \
0x85, id, /* Report ID */ \
0x96, count1, count2, /* Report Count */ \
0x92, 0x02, 0x01 /* Output 0x0102 (...) */
// clang-format off
static const uint8_t ipod_hid_report_fs[] = {
0x06, 0x00, 0xFF, /* Usage Page 0xFF00 (Vendor-defined) */
0x09, 0x01, /* Usage 0x01 */
0xA1, 0x01, /* Collection 0x01 (Application) */
0x75, 0x08, /* Report Size 0x08 */
0x26, 0x80, 0x00, /* Logical Maximum 0x0081 (128) */
0x15, 0x00, /* Logical Minumum 0x0000 (0) */
INPUT_REPORT(0x01, 0x0C),
INPUT_REPORT(0x02, 0x0E),
INPUT_REPORT(0x03, 0x14),
INPUT_REPORT(0x04, 0x3F),
OUTPUT_REPORT(0x05, 0x08),
OUTPUT_REPORT(0x06, 0x0A),
OUTPUT_REPORT(0x07, 0x0E),
OUTPUT_REPORT(0x08, 0x14),
OUTPUT_REPORT(0x09, 0x3F),
0xC0, /* End Collection */
};
static const uint8_t ipod_hid_report_hs[] = {
0x06, 0x00, 0xFF, /* Usage Page 0xFF00 (Vendor-defined) */
0x09, 0x01, /* Usage 0x01 */
0xA1, 0x01, /* Collection 0x01 (Application) */
0x75, 0x08, /* Report Size 0x08 */
0x26, 0x80, 0x00, /* Logical Maximum 0x0081 (128) */
0x15, 0x00, /* Logical Minumum 0x0000 (0) */
INPUT_REPORT(0x01, 0x05),
INPUT_REPORT(0x02, 0x09),
INPUT_REPORT(0x03, 0x0D),
INPUT_REPORT(0x04, 0x11),
INPUT_REPORT(0x05, 0x19),
INPUT_REPORT(0x06, 0x31),
INPUT_REPORT(0x07, 0x5F),
INPUT_REPORT(0x08, 0xC1),
INPUT_REPORT2(0x09, 0x01, 0x01),
INPUT_REPORT2(0x0A, 0x81, 0x01),
INPUT_REPORT2(0x0B, 0x01, 0x02),
INPUT_REPORT2(0x0C, 0xFF, 0x02),
OUTPUT_REPORT(0x0D, 0x05),
OUTPUT_REPORT(0x0E, 0x09),
OUTPUT_REPORT(0x1F, 0x0D),
OUTPUT_REPORT(0x10, 0x11),
OUTPUT_REPORT(0x11, 0x19),
OUTPUT_REPORT(0x12, 0x31),
OUTPUT_REPORT(0x13, 0x5F),
OUTPUT_REPORT(0x14, 0xC1),
OUTPUT_REPORT(0x15, 0xFF),
0xC0, /* End Collection */
};
// clang-format on
static struct usb_hid_descriptor ipod_hid_hid_desc = {
.bLength = sizeof(struct usb_hid_descriptor),
.bDescriptorType = USB_DT_HID,
.wBcdHID = 0x0111, /* 1.11 */
.bCountryCode = 0,
.bNumDescriptors = 1,
.bDescriptorType0 = USB_DT_REPORT,
.wDescriptorLength0 = -1, /* dynamic */
};
/* interface 2 endpoint 0 */
static struct usb_endpoint_descriptor ipod_hid_endpoint = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = -1, /* dynamic */
.bmAttributes = USB_ENDPOINT_XFER_INT,
.wMaxPacketSize = -1, /* dynamic */
.bInterval = 1,
};
static struct {
int interface;
} ctrl;
static struct {
uint32_t sample_rate;
int interface;
int8_t alt;
} stream;
static struct {
int interface;
} hid;
static struct mutex iap_ctx_mutex;
static struct Platform platform;
static struct timeout tick_tmo;
static bool iap_ctx_mutex_initialized = false;
struct IAPContext* _iap_acquire_ctx(bool lock) {
static struct IAPContext ctx;
if(lock) {
mutex_lock(&iap_ctx_mutex);
}
return &ctx;
}
void _iap_release_ctx() {
mutex_unlock(&iap_ctx_mutex);
}
bool iap_initialized;
/* those notifications need pollling */
static enum charge_state_type last_charge_state;
static uint8_t last_battery_level;
static int8_t last_minute;
static int8_t last_hold_switch_state;
enum Notify {
Notify_Tick,
};
static int tick_callback(struct timeout* tmo) {
(void)tmo;
usb_signal_class_notify(USB_DRIVER_IAP, Notify_Tick);
return HZ / 10;
}
static int usb_iap_set_first_interface(int interface) {
ctrl.interface = interface + 0;
stream.interface = interface + 1;
hid.interface = interface + 2;
return interface + 3;
}
#define PACK_DESC(desc) pack_data(&dest, &desc, ((struct usb_descriptor_header*)&desc)->bLength)
static int usb_iap_get_config_descriptor(unsigned char* dest, int max_packet_size) {
(void)max_packet_size;
unsigned char* orig_dest = dest;
ipod_audio_control_desc.bInterfaceNumber = ctrl.interface;
PACK_DESC(ipod_audio_control_desc);
ipod_audio_control_uac_header.baInterfaceNr[0] = stream.interface;
ipod_audio_control_uac_header.wTotalLength =
sizeof(ipod_audio_control_uac_header) +
sizeof(ipod_audio_control_uac_input_terminal) +
sizeof(ipod_audio_control_uac_output_terminal);
PACK_DESC(ipod_audio_control_uac_header);
PACK_DESC(ipod_audio_control_uac_input_terminal);
PACK_DESC(ipod_audio_control_uac_output_terminal);
ipod_audio_stream_0_desc.bInterfaceNumber = stream.interface;
ipod_audio_stream_1_desc.bInterfaceNumber = stream.interface;
ipod_audio_stream_1_endpoint.bEndpointAddress = AS_EP_IN;
ipod_audio_stream_1_endpoint.bInterval = usb_drv_port_speed() ? 4 : 1;
PACK_DESC(ipod_audio_stream_0_desc);
PACK_DESC(ipod_audio_stream_1_desc);
PACK_DESC(ipod_audio_stream_1_uac_header);
PACK_DESC(ipod_audio_stream_1_uac_discrete);
PACK_DESC(ipod_audio_stream_1_endpoint);
PACK_DESC(ipod_audio_stream_1_endpoint_uac);
ipod_hid_desc.bInterfaceNumber = hid.interface;
ipod_hid_endpoint.bEndpointAddress = HID_EP_IN;
if(usb_drv_port_speed()) {
ipod_hid_hid_desc.wDescriptorLength0 = sizeof(ipod_hid_report_hs);
ipod_hid_endpoint.wMaxPacketSize = 64;
} else {
ipod_hid_hid_desc.wDescriptorLength0 = sizeof(ipod_hid_report_fs);
ipod_hid_endpoint.wMaxPacketSize = 64;
}
PACK_DESC(ipod_hid_desc);
PACK_DESC(ipod_hid_hid_desc);
PACK_DESC(ipod_hid_endpoint);
return dest - orig_dest;
}
static int usb_iap_init_connection(void) {
stream.sample_rate = 48000;
last_charge_state = -1;
last_minute = -1;
last_hold_switch_state = -1;
iap_debug_reset_timestamp();
/* init audio sink */
check_act(iap_audio_init(), return -1);
/* init libiap */
if(!iap_ctx_mutex_initialized) {
iap_ctx_mutex_initialized = true;
mutex_init(&iap_ctx_mutex);
}
struct IAPContext* ctx = _iap_acquire_ctx(true);
const struct IAPOpts opts = {
.usb_highspeed = usb_drv_port_speed(),
.ignore_hid_report_id = iap_true,
.artwork_single_report = iap_false,
.enable_packet_dump = iap_false,
};
check_act(iap_init_ctx(ctx, opts, &platform), goto cleanup_audio);
_iap_release_ctx();
/* prepare artwork */
struct dim dim = {IAP_ARTWORK_WIDTH, IAP_ARTWORK_HEIGHT};
platform.aa_slot = playback_claim_aa_slot(&dim);
if(platform.aa_slot < 0) {
ERROR("failed to claim albumart slot");
}
platform.control_pending = false;
/* register timer */
timeout_register(&tick_tmo, tick_callback, HZ / 10, 0);
iap_initialized = true;
LOG("initialized");
return 0;
cleanup_audio:
_iap_release_ctx();
iap_audio_deinit();
return -1;
}
static int usb_iap_set_interface(int intf, int alt) {
LOG("set interface interface=%d alt=%d", intf, alt);
check_act(intf == stream.interface, return -1);
if(alt == 0) {
check_act(iap_audio_disable(), return -1);
} else if(alt == 1) {
check_act(iap_audio_enable(), return -1);
} else {
ERROR("invalid alt %d", alt);
return -1;
}
return 0;
}
static int usb_iap_get_interface(int intf) {
LOG("get interface interface=%d", intf);
check_act(intf == stream.interface, return -1);
return stream.alt;
}
static void usb_iap_init(void) {
LOG("init");
}
static void usb_iap_disconnect(void) {
iap_initialized = false;
audio_pause();
mixer_switch_sink(PCM_SINK_BUILTIN);
timeout_cancel(&tick_tmo);
if(platform.aa_slot >= 0) {
playback_release_aa_slot(platform.aa_slot);
}
struct IAPContext* ctx = _iap_acquire_ctx(true);
check_act(iap_deinit_ctx(ctx), );
_iap_release_ctx();
check_act(iap_audio_deinit(), );
LOG("disconnected");
}
static void usb_iap_transfer_complete(int ep, int dir, int status, int length) {
(void)length;
if((ep | dir) == HID_EP_IN) {
check_act(status == 0, return);
#if DEBUG_DUMP_TX
LOG("ep=%d dir=%d state=%d length=%d", ep, dir, status, length);
#endif
struct IAPContext* ctx = _iap_acquire_ctx(true);
check_act(iap_notify_send_complete(ctx), );
_iap_release_ctx();
}
}
static bool usb_iap_fast_transfer_complete(int ep, int dir, int status, int length) {
(void)status;
(void)length;
return (ep | dir) == AS_EP_IN;
}
static unsigned char ctrl_buf[256] USB_DEVBSS_ATTR;
static void respond_zero(struct usb_ctrlrequest* req) {
if(req->wLength > sizeof(ctrl_buf)) {
ERROR("required data too long %u > %u", req->wLength, sizeof(ctrl_buf));
usb_drv_control_response(USB_CONTROL_STALL, NULL, 0);
} else {
memset(ctrl_buf, 0, req->wLength);
usb_drv_control_response(USB_CONTROL_ACK, ctrl_buf, req->wLength);
}
}
/* returns true when ctrl_buf has received data */
static bool receive_data(struct usb_ctrlrequest* req, void* reqdata) {
if(reqdata == NULL) {
/* setup */
if(req->wLength > sizeof(ctrl_buf)) {
ERROR("parameter too long");
usb_drv_control_response(USB_CONTROL_STALL, NULL, 0);
} else {
usb_drv_control_response(USB_CONTROL_RECEIVE, ctrl_buf, req->wLength);
}
return false;
} else {
/* data */
return true;
}
}
static bool control_request_if_std(struct usb_ctrlrequest* req, void* reqdata, unsigned char* dest) {
(void)reqdata;
unsigned char* const orig_dest = dest;
switch(req->bRequest) {
case USB_REQ_GET_DESCRIPTOR: {
const uint8_t desc_type = req->wValue >> 8;
const uint8_t desc_index = req->wValue & 0xff;
LOG("descriptor request type=%x index=%x", desc_type, desc_index);
(void)desc_index;
switch(desc_type) {
case USB_DT_HID:
PACK_DATA(&dest, ipod_hid_hid_desc);
break;
case USB_DT_REPORT:
if(usb_drv_port_speed()) {
PACK_DATA(&dest, ipod_hid_report_hs);
} else {
PACK_DATA(&dest, ipod_hid_report_fs);
}
break;
}
if(dest != orig_dest) {
usb_drv_control_response(USB_CONTROL_ACK, orig_dest, MIN(dest - orig_dest, req->wLength));
return true;
}
} break;
}
return false;
}
static bool control_request_if_class(struct usb_ctrlrequest* req, void* reqdata, unsigned char* dest) {
(void)dest;
const uint8_t recip_interface = req->wIndex & 0xff;
if(recip_interface == hid.interface) {
switch(req->bRequest) {
case USB_HID_GET_REPORT:
respond_zero(req);
return true;
case USB_HID_SET_REPORT: {
if(!receive_data(req, reqdata)) {
return true;
}
#if DEBUG_DUMP_RX == 1
logf("==== acc: %u bytes ====", req->wLength);
iap_platform_dump_hex(reqdata, req->wLength);
#endif
struct IAPContext* ctx = _iap_acquire_ctx(true);
const bool ret = iap_feed_hid_report(ctx, reqdata, req->wLength);
_iap_release_ctx();
check_act(ret, return false);
usb_drv_control_response(USB_CONTROL_ACK, NULL, 0);
return true;
}
case USB_HID_SET_IDLE:
usb_drv_control_response(USB_CONTROL_ACK, NULL, 0);
return true;
}
}
return false;
}
static bool control_request_if_endpoint(struct usb_ctrlrequest* req, void* reqdata, unsigned char* dest) {
(void)dest;
LOG("ctrl to endpoint %x (stream=%x, hid=%x)", req->wIndex, AS_EP_IN, HID_EP_IN);
if(req->wIndex == AS_EP_IN) {
const uint8_t recip_entity = req->wIndex >> 8;
const uint8_t control_selector = req->wValue >> 8;
(void)recip_entity;
switch(req->bRequest) {
case USB_AC_SET_CUR:
if(!receive_data(req, reqdata)) {
return true;
}
LOG("audio ctrl to stream endpoint entity=0x%02X request=0x%02X length=%u", recip_entity, req->bRequest, req->wLength);
switch(control_selector) {
case USB_AS_EP_CS_SAMPLING_FREQ_CTL:
check_act(req->wLength == 3, goto stall);
stream.sample_rate = ctrl_buf[0] | (ctrl_buf[1] << 8) | (ctrl_buf[2] << 16);
LOG("audio stream sampling rate %lu", stream.sample_rate);
check_act(iap_audio_set_sampr(stream.sample_rate), goto stall);
break;
}
usb_drv_control_response(USB_CONTROL_ACK, NULL, 0);
return true;
case USB_AC_GET_CUR:
switch(control_selector) {
case USB_AS_EP_CS_SAMPLING_FREQ_CTL:
check_act(req->wLength == 3, goto stall);
ctrl_buf[2] = (stream.sample_rate >> 16) & 0xff;
ctrl_buf[1] = (stream.sample_rate >> 8) & 0xff;
ctrl_buf[0] = (stream.sample_rate & 0xff);
usb_drv_control_response(USB_CONTROL_ACK, ctrl_buf, req->wLength);
return true;
}
/* fallthrough */
case USB_AC_GET_MIN:
case USB_AC_GET_MAX:
case USB_AC_GET_RES:
respond_zero(req);
return true;
stall:
usb_drv_control_response(USB_CONTROL_STALL, NULL, 0);
return true;
}
}
return false;
}
static bool usb_iap_control_request(struct usb_ctrlrequest* req, void* reqdata, unsigned char* dest) {
const uint8_t req_recipient = req->bRequestType & USB_RECIP_MASK;
const uint8_t req_type = req->bRequestType & USB_TYPE_MASK;
#if 0
LOG("bRequestType=%x, bRequest=%x, wValue=%x, wIndex=%x, wLength=%x", req->bRequestType, req->bRequest, req->wValue, req->wIndex, req->wLength);
LOG("recip=%x type=%x", req_recipient, req_type);
#endif
if(req_recipient == USB_RECIP_INTERFACE && req_type == USB_TYPE_STANDARD) {
return control_request_if_std(req, reqdata, dest);
} else if(req_recipient == USB_RECIP_INTERFACE && req_type == USB_TYPE_CLASS) {
return control_request_if_class(req, reqdata, dest);
} else if(req_recipient == USB_RECIP_ENDPOINT && req_type == USB_TYPE_CLASS) {
return control_request_if_endpoint(req, reqdata, dest);
}
return false;
}
static void usb_iap_notify_event(intptr_t data) {
switch(data) {
case Notify_Tick: {
struct IAPContext* ctx = _iap_acquire_ctx(true);
struct Platform* plt = ctx->platform;
if(plt->control_pending) {
/* waiting for playback begins */
_iap_release_ctx();
return;
}
if(last_charge_state < 0 || last_charge_state != charge_state || last_battery_level != battery_level()) {
last_charge_state = charge_state;
last_battery_level = battery_level();
iap_notify_power_state(ctx, _iap_convert_charge_status(last_charge_state), _iap_convert_battery_level(last_battery_level));
}
struct tm* tm = get_time();
if(last_minute == -1 || last_minute != tm->tm_min) {
last_minute = tm->tm_min;
struct IAPDateTime time;
_iap_convert_datetime(get_time(), &time);
iap_notify_time_setting(ctx, &time);
}
#ifdef HAS_BUTTON_HOLD
int8_t hold = button_hold() ? 1 : 0;
if(hold != last_hold_switch_state) {
last_hold_switch_state = hold;
iap_notify_hold_switch_state(ctx, last_hold_switch_state);
}
#endif
check_act(iap_periodic_tick(ctx), );
_iap_release_ctx();
} break;
}
}
struct usb_class_driver usb_cdrv_iap = {
.needs_exclusive_storage = false,
.needs_cpu_boost = true,
.config = 2,
.ep_allocs_size = ARRAYLEN(usb_iap_ep_allocs),
.ep_allocs = usb_iap_ep_allocs,
.set_first_interface = usb_iap_set_first_interface,
.get_config_descriptor = usb_iap_get_config_descriptor,
.init_connection = usb_iap_init_connection,
.init = usb_iap_init,
.disconnect = usb_iap_disconnect,
.transfer_complete = usb_iap_transfer_complete,
.fast_transfer_complete = usb_iap_fast_transfer_complete,
.control_request = usb_iap_control_request,
.set_interface = usb_iap_set_interface,
.get_interface = usb_iap_get_interface,
.notify_event = usb_iap_notify_event,
};