hekate/bdk/usb/usbd.c
CTCaer 605f270f98 bdk: usb: fix a race condition in USB2 stack
When RAM is slow (no training), it's possible to have the stack failing to negotiate configuration successfully.

The race condition is caused by not flushing cache before sending a configuration packet reply.
Although, cache is write-through, this needs to happen.
2022-06-14 18:41:33 +03:00

1596 lines
44 KiB
C

/*
* Enhanced USB Device (EDCI) driver for Tegra X1
*
* Copyright (c) 2019-2021 CTCaer
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include <stdlib.h>
#include <usb/usbd.h>
#include <usb/usb_descriptor_types.h>
#include <usb/usb_t210.h>
#include <gfx_utils.h>
#include <soc/bpmp.h>
#include <soc/clock.h>
#include <soc/fuse.h>
#include <soc/gpio.h>
#include <soc/pinmux.h>
#include <soc/pmc.h>
#include <soc/t210.h>
#include <utils/btn.h>
#include <utils/util.h>
#include <memory_map.h>
typedef enum
{
USB_HW_EP0 = 0,
USB_HW_EP1 = 1
} usb_hw_ep_t;
typedef enum
{
USB_EP_STATUS_IDLE = 0,
USB_EP_STATUS_ACTIVE = 1,
USB_EP_STATUS_ERROR = 2,
USB_EP_STATUS_NO_CONFIG = 3,
USB_EP_STATUS_STALLED = 4,
USB_EP_STATUS_DISABLED = 5
} usb_ep_status_t;
typedef enum {
USB_LOW_SPEED = 0,
USB_FULL_SPEED = 1,
USB_HIGH_SPEED = 2,
USB_SUPER_SPEED = 3,
} usb_speed_t;
typedef struct _dTD_t
{
vu32 next_dTD;
vu32 info;
vu32 pages[5];
vu32 reserved;
} dTD_t;
typedef struct _dQH_t
{
vu32 ep_capabilities;
vu32 curr_dTD_ptr;
vu32 next_dTD_ptr;
vu32 token;
vu32 buffers[5]; // hmmm.
vu32 reserved;
vu32 setup[2];
vu32 gap[4];
} dQH_t;
typedef struct _usbd_t
{
volatile dTD_t dtds[4 * 4]; // 4 dTD per endpoint.
volatile dQH_t *qhs;
int ep_configured[4];
int ep_bytes_requested[4];
} usbd_t;
typedef struct _usbd_controller_t
{
u32 port_speed;
t210_usb2d_t *regs;
usb_ctrl_setup_t control_setup;
usb_desc_t *desc;
usb_gadget_type gadget;
u8 config_num;
u8 interface_num;
u8 max_lun;
bool usb_phy_ready;
bool configuration_set;
bool max_lun_set;
bool bulk_reset_req;
bool hid_report_sent;
u32 charger_detect;
} usbd_controller_t;
extern u8 hid_report_descriptor_jc[];
extern u8 hid_report_descriptor_touch[];
extern u32 hid_report_descriptor_jc_size;
extern u32 hid_report_descriptor_touch_size;
extern usb_desc_t usb_gadget_hid_jc_descriptors;
extern usb_desc_t usb_gadget_hid_touch_descriptors;
extern usb_desc_t usb_gadget_ums_descriptors;
usbd_t *usbdaemon;
usbd_controller_t *usbd_otg;
usbd_controller_t usbd_usb_otg_controller_ctxt;
bool usb_init_done = false;
u8 *usb_ep0_ctrl_buf = (u8 *)USB_EP_CONTROL_BUF_ADDR;
static int _usbd_reset_usb_otg_phy_device_mode()
{
usbd_otg->usb_phy_ready = false;
// Clear UTMIP reset.
USB(USB1_IF_USB_SUSP_CTRL) &= ~SUSP_CTRL_UTMIP_RESET;
// Wait for PHY clock to get validated.
u32 retries = 100000; // 200ms timeout.
while (!(USB(USB1_IF_USB_SUSP_CTRL) & SUSP_CTRL_USB_PHY_CLK_VALID))
{
retries--;
if (!retries)
return USB_ERROR_INIT;
usleep(1);
}
usbd_otg->usb_phy_ready = true;
// Clear all device addresses, enabled setup requests and transmit events.
usbd_otg->regs->periodiclistbase = 0;
usbd_otg->regs->endptsetupstat = usbd_otg->regs->endptsetupstat;
usbd_otg->regs->endptcomplete = usbd_otg->regs->endptcomplete;
// Stop device controller.
usbd_otg->regs->usbcmd &= ~USB2D_USBCMD_RUN;
// Set controller mode to idle.
usbd_otg->regs->usbmode &= ~USB2D_USBMODE_CM_MASK;
// Reset the controller.
usbd_otg->regs->usbcmd |= USB2D_USBCMD_RESET;
// Wait for the reset to complete.
retries = 100000; // 200ms timeout.
while (usbd_otg->regs->usbcmd & USB2D_USBCMD_RESET)
{
retries--;
if (!retries)
return USB_ERROR_INIT;
usleep(1);
}
// Wait for PHY clock to get validated after reset.
retries = 100000; // 200ms timeout.
while (!(USB(USB1_IF_USB_SUSP_CTRL) & SUSP_CTRL_USB_PHY_CLK_VALID))
{
retries--;
if (!retries)
return USB_ERROR_INIT;
usleep(1);
}
// Set controller to Device mode.
usbd_otg->regs->usbmode = (usbd_otg->regs->usbmode & ~USB2D_USBMODE_CM_MASK) | USB2D_USBMODE_CM_DEVICE;
// Wait for the selected mode to be enabled.
retries = 100000; // 200ms timeout.
while ((usbd_otg->regs->usbmode & USB2D_USBMODE_CM_MASK) != USB2D_USBMODE_CM_DEVICE)
{
retries--;
if (!retries)
return USB_ERROR_INIT;
usleep(1);
}
// Disable all interrupts.
usbd_otg->regs->usbintr = 0;
// Set the ID pullup and disable all OTGSC interrupts.
usbd_otg->regs->otgsc = USB2D_OTGSC_USB_ID_PULLUP;
// Clear all relevant interrupt statuses.
usbd_otg->regs->usbsts =
USB2D_USBSTS_UI | USB2D_USBSTS_UEI | USB2D_USBSTS_PCI |
USB2D_USBSTS_FRI | USB2D_USBSTS_SEI | USB2D_USBSTS_AAI |
USB2D_USBSTS_URI | USB2D_USBSTS_SRI | USB2D_USBSTS_SLI;
// Disable and clear all OTGSC interrupts.
usbd_otg->regs->otgsc = USB2D_OTGSC_USB_IRQ_STS_MASK;
// Clear EP0, EP1, EP2 setup requests.
usbd_otg->regs->endptsetupstat = 7; //TODO: Shouldn't this be endptsetupstat = endptsetupstat?
// Set all interrupts to immediate.
usbd_otg->regs->usbcmd &= ~USB2D_USBCMD_ITC_MASK;
return USB_RES_OK;
}
static void _usb_charger_detect()
{
// Charger detect init.
usbd_otg->charger_detect = 0;
bool charger_detect_enable = FUSE(FUSE_RESERVED_SW) & 0x10; // Disabled on Switch production.
if (charger_detect_enable)
{
usbd_otg->charger_detect |= 1;
// Configure detect pin.
PINMUX_AUX(PINMUX_AUX_LCD_GPIO1) &= ~(PINMUX_PARKED | PINMUX_TRISTATE | PINMUX_PULL_MASK);
gpio_config(GPIO_PORT_V, GPIO_PIN_3, GPIO_MODE_GPIO);
// Configure charger pin.
PINMUX_AUX(PINMUX_AUX_USB_VBUS_EN1) &=
~(PINMUX_INPUT_ENABLE | PINMUX_PARKED | PINMUX_TRISTATE | PINMUX_PULL_MASK);
gpio_config(GPIO_PORT_CC, GPIO_PIN_5, GPIO_MODE_GPIO);
gpio_output_enable(GPIO_PORT_CC, GPIO_PIN_5, GPIO_OUTPUT_ENABLE);
// Enable charger.
if (gpio_read(GPIO_PORT_V, GPIO_PIN_3))
{
usbd_otg->charger_detect |= 2;
gpio_write(GPIO_PORT_CC, GPIO_PIN_5, GPIO_HIGH);
usbd_otg->charger_detect |= 0x100;
USB(USB1_UTMIP_BAT_CHRG_CFG0) = BAT_CHRG_CFG0_OP_SRC_EN; // Clears UTMIP_PD_CHRG and enables charger detect.
usleep(5000);
}
}
}
static void _usb_init_phy()
{
// Configure and enable PLLU.
clock_enable_pllu();
// Enable USBD clock.
CLOCK(CLK_RST_CONTROLLER_CLK_ENB_L_SET) = BIT(CLK_L_USBD);
usleep(2);
CLOCK(CLK_RST_CONTROLLER_RST_DEV_L_SET) = BIT(CLK_L_USBD);
usleep(2);
CLOCK(CLK_RST_CONTROLLER_RST_DEV_L_CLR) = BIT(CLK_L_USBD);
usleep(2);
// Clear XUSB_PADCTL reset
CLOCK(CLK_RST_CONTROLLER_RST_DEV_W_CLR) = BIT(CLK_W_XUSB_PADCTL);
// Enable USB PHY and reset for programming.
u32 usb_susp_ctrl = USB(USB1_IF_USB_SUSP_CTRL);
USB(USB1_IF_USB_SUSP_CTRL) = usb_susp_ctrl | SUSP_CTRL_UTMIP_RESET;
USB(USB1_IF_USB_SUSP_CTRL) = usb_susp_ctrl | SUSP_CTRL_UTMIP_PHY_ENB | SUSP_CTRL_UTMIP_RESET;
// Enable IDDQ control by software and disable UTMIPLL IDDQ.
CLOCK(CLK_RST_CONTROLLER_UTMIPLL_HW_PWRDN_CFG0) = (CLOCK(CLK_RST_CONTROLLER_UTMIPLL_HW_PWRDN_CFG0) & 0xFFFFFFFC) | 1;
usleep(10);
// Disable crystal clock.
USB(USB1_UTMIP_MISC_CFG1) &= 0xBFFFFFFF;
CLOCK(CLK_RST_CONTROLLER_UTMIP_PLL_CFG2) &= 0xBFFFFFFF;
// Set B_SESS_VLD.
USB(USB1_IF_USB_PHY_VBUS_SENSORS) |= 0x1000;
USB(USB1_IF_USB_PHY_VBUS_SENSORS) |= 0x800;
// Set UTMIPLL dividers and config based on OSC and enable it to 960 MHz.
clock_enable_utmipll();
// Configure UTMIP Transceiver Cells.
u32 fuse_usb_calib = FUSE(FUSE_USB_CALIB);
USB(USB1_UTMIP_XCVR_CFG0) = (((USB(USB1_UTMIP_XCVR_CFG0) & 0xFFFFFFF0) | (fuse_usb_calib & 0xF)) & 0xFE3FFFFF) | ((fuse_usb_calib & 0x3F) << 25 >> 29 << 22);
USB(USB1_UTMIP_XCVR_CFG1) = (USB(USB1_UTMIP_XCVR_CFG1) & 0xFFC3FFFF) | ((fuse_usb_calib << 21) >> 28 << 18);
USB(USB1_UTMIP_XCVR_CFG3) = (USB(USB1_UTMIP_XCVR_CFG3) & 0xFFFFC1FF) | ((FUSE(FUSE_USB_CALIB_EXT) & 0x1F) << 9);
USB(USB1_UTMIP_XCVR_CFG0) &= 0xFFDFFFFF;
USB(USB1_UTMIP_XCVR_CFG2) = (USB(USB1_UTMIP_XCVR_CFG2) & 0xFFFFF1FF) | 0x400;
usleep(1);
// Configure misc UTMIP.
USB(USB1_UTMIP_DEBOUNCE_CFG0) = (USB(USB1_UTMIP_DEBOUNCE_CFG0) & 0xFFFF0000) | 0xBB80;
USB(USB1_UTMIP_BIAS_CFG1) = (USB(USB1_UTMIP_BIAS_CFG1) & 0xFFFFC0FF) | 0x100; // when osc is 38.4KHz
//USB(USB1_UTMIP_SPARE_CFG0) &= 0xFFFFFEE7; unpatched0
USB(USB1_UTMIP_BIAS_CFG2) |= 2; //patched0 - UTMIP_HSSQUELCH_LEVEL_NEW: 2.
USB(USB1_UTMIP_SPARE_CFG0) &= 0xFFFFFE67; //patched0 - FUSE_HS_IREF_CAP_CFG
USB(USB1_UTMIP_TX_CFG0) |= 0x80000;
//USB(USB1_UTMIP_HSRX_CFG0) = (USB(USB1_UTMIP_HSRX_CFG0) & 0xFFF003FF) | 0x88000 | 0x4000; unpatched1
USB(USB1_UTMIP_HSRX_CFG0) = (USB(USB1_UTMIP_HSRX_CFG0) & 0xF0F003FF) | 0x88000 | 0x4000; //patched1 - reset UTMIP_PCOUNT_UPDN_DIV: From 1 to 0.
USB(USB1_UTMIP_BIAS_CFG2) &= 0xFFFFFFF8; //patched1 - UTMIP_HSSQUELCH_LEVEL_NEW: 0
USB(USB1_UTMIP_HSRX_CFG1) = (USB(USB1_UTMIP_HSRX_CFG1) & 0xFFFFFFC1) | 0x12;
USB(USB1_UTMIP_MISC_CFG1) |= 0x40000000;
// Enable crystal clock.
CLOCK(CLK_RST_CONTROLLER_UTMIP_PLL_CFG2) |= 0x40000000;
// Enable USB2 tracking clock.
CLOCK(CLK_RST_CONTROLLER_CLK_ENB_Y_SET) = BIT(CLK_Y_USB2_TRK);
CLOCK(CLK_RST_CONTROLLER_CLK_SOURCE_USB2_HSIC_TRK) = (CLOCK(CLK_RST_CONTROLLER_CLK_SOURCE_USB2_HSIC_TRK) & 0xFFFFFF00) | 6; // Set trank divisor to 4.
USB(USB1_UTMIP_BIAS_CFG1) = (USB(USB1_UTMIP_BIAS_CFG1) & 0xFFC03F07) | 0x78000 | 0x50; // Set delays.
USB(USB1_UTMIP_BIAS_CFG0) &= 0xFFFFFBFF; // Disable Power down bias circuit.
usleep(1);
// Force PDTRK input into power up.
USB(USB1_UTMIP_BIAS_CFG1) = (USB(USB1_UTMIP_BIAS_CFG1) & 0xFFFFFFFE) | 2;
usleep(100);
// TRK cycle done. Force PDTRK input into power down.
USB(USB1_UTMIP_BIAS_CFG1) = (USB(USB1_UTMIP_BIAS_CFG1) & 0xFF7FFFFF) | 1;
usleep(3);
// Force PDTRK input into power up.
USB(USB1_UTMIP_BIAS_CFG1) = USB(USB1_UTMIP_BIAS_CFG1) & 0xFFFFFFFE;
usleep(100);
// TRK cycle done. Force PDTRK input into power down.
USB(USB1_UTMIP_BIAS_CFG1) = (USB(USB1_UTMIP_BIAS_CFG1) & 0xFF7FFFFF) | 1;
// Disable USB2 tracking clock and configure UTMIP misc.
CLOCK(CLK_RST_CONTROLLER_CLK_ENB_Y_CLR) = BIT(CLK_Y_USB2_TRK);
CLOCK(CLK_RST_CONTROLLER_UTMIP_PLL_CFG2) = (CLOCK(CLK_RST_CONTROLLER_UTMIP_PLL_CFG2) & 0xFEFFFFEA) | 0x2000000 | 0x28 | 2;
usleep(1);
USB(USB1_UTMIP_BIAS_CFG0) &= 0xFF3FF7FF;
usleep(1);
// Clear power downs on UTMIP ID and VBUS wake up, PD, PD2, PDZI, PDCHRP, PDDR.
PMC(APBDEV_PMC_USB_AO) &= 0xFFFFFFF3; // UTMIP ID and VBUS wake up.
usleep(1);
USB(USB1_UTMIP_XCVR_CFG0) &= 0xFFFFBFFF; // UTMIP_FORCE_PD_POWERDOWN.
usleep(1);
USB(USB1_UTMIP_XCVR_CFG0) &= 0xFFFEFFFF; // UTMIP_FORCE_PD2_POWERDOWN.
usleep(1);
USB(USB1_UTMIP_XCVR_CFG0) &= 0xFFFBFFFF; // UTMIP_FORCE_PDZI_POWERDOWN.
usleep(1);
USB(USB1_UTMIP_XCVR_CFG1) &= 0xFFFFFFFB; // UTMIP_FORCE_PDCHRP_POWERDOWN.
usleep(1);
USB(USB1_UTMIP_XCVR_CFG1) &= 0xFFFFFFEF; // UTMIP_FORCE_PDDR_POWERDOWN.
usleep(1);
}
int usb_device_init()
{
if (usb_init_done)
return USB_RES_OK;
// Initialize USB2 controller PHY.
_usb_init_phy();
// AHB USB performance cfg.
AHB_GIZMO(AHB_GIZMO_AHB_MEM) |= AHB_MEM_DONT_SPLIT_AHB_WR | AHB_MEM_ENB_FAST_REARBITRATE;
AHB_GIZMO(AHB_GIZMO_USB) |= AHB_GIZMO_IMMEDIATE;
AHB_GIZMO(AHB_ARBITRATION_PRIORITY_CTRL) = PRIORITY_CTRL_WEIGHT(7) | PRIORITY_SELECT_USB;
AHB_GIZMO(AHB_AHB_MEM_PREFETCH_CFG1) =
MEM_PREFETCH_ENABLE | MEM_PREFETCH_USB_MST_ID | MEM_PREFETCH_ADDR_BNDRY(12) | 0x1000; // Addr boundary 64KB, Inactivity 4096 cycles.
// Set software and hardware context storage and clear it.
usbdaemon = (usbd_t *)USBD_ADDR; // Depends on USB_TD_BUFFER_PAGE_SIZE aligned address.
usbd_otg = &usbd_usb_otg_controller_ctxt;
memset(usbd_otg, 0, sizeof(usbd_controller_t));
memset(usbdaemon, 0, sizeof(usbd_t));
usbd_otg->regs = (t210_usb2d_t *)USB_OTG_BASE;
usbd_otg->usb_phy_ready = false;
// Initialize USB PHY on the USB_OTG Controller (#1) in Device mode.
int res = _usbd_reset_usb_otg_phy_device_mode();
usbd_otg->configuration_set = false;
_usb_charger_detect();
if (!res)
usb_init_done = true;
return res;
}
static void _usb_device_power_down()
{
// Enable PHY low power suspend.
usbd_otg->regs->hostpc1_devlc |= USB2D_HOSTPC1_DEVLC_PHCD;
// Do not use any controller regs after the above!
// A reset or clear of the PHCD suspend bit must happen.
// Power down OTG and Bias circuits.
USB(USB1_UTMIP_BIAS_CFG0) |= BIT(11) | BIT(10); // UTMIP_OTGPD, UTMIP_BIASPD.
// Power down ID detectors.
USB(USB1_UTMIP_BIAS_CFG0) |= BIT(23) | BIT(22); // UTMIP_IDPD_SEL, UTMIP_IDPD_VAL.
if (usbd_otg->charger_detect)
{
USB(USB1_UTMIP_BAT_CHRG_CFG0) = 1; //UTMIP_PD_CHRG
usbd_otg->charger_detect = 0;
}
// Power down the UTMIP transceivers.
// UTMIP_FORCE_PDZI_POWERDOWN, UTMIP_FORCE_PD2_POWERDOWN, UTMIP_FORCE_PD_POWERDOWN.
USB(USB1_UTMIP_XCVR_CFG0) |= BIT(18) | BIT(16) |BIT(14);
// UTMIP_FORCE_PDDR_POWERDOWN, UTMIP_FORCE_PDCHRP_POWERDOWN, UTMIP_FORCE_PDDISC_POWERDOWN.
USB(USB1_UTMIP_XCVR_CFG1) |= BIT(4) | BIT(2) | BIT(0);
// Keep UTMIP in reset.
USB(USB1_IF_USB_SUSP_CTRL) |= SUSP_CTRL_UTMIP_RESET;
// Power down PD trunk.
USB(USB1_UTMIP_BIAS_CFG1) |= BIT(0); //UTMIP_FORCE_PDTRK_POWERDOWN.
// Force UTMIP_PLL power down.
CLOCK(CLK_RST_CONTROLLER_UTMIP_PLL_CFG1) |= BIT(14); // UTMIP_FORCE_PLL_ENABLE_POWERDOWN.
CLOCK(CLK_RST_CONTROLLER_UTMIP_PLL_CFG1) |= BIT(12); // UTMIP_FORCE_PLL_ACTIVE_POWERDOWN.
CLOCK(CLK_RST_CONTROLLER_UTMIP_PLL_CFG2) |= BIT(4) | BIT(0); // UTMIP_FORCE_PD_SAMP_A/C_POWERDOWN.
CLOCK(CLK_RST_CONTROLLER_UTMIP_PLL_CFG1) |= BIT(16); // UTMIP_FORCE_PLLU_POWERDOWN.
// Disable crystal clock.
USB(USB1_UTMIP_MISC_CFG1) &= 0xBFFFFFFF;
// Force enable UTMIPLL IDDQ.
CLOCK(CLK_RST_CONTROLLER_UTMIPLL_HW_PWRDN_CFG0) |= 3;
// Set XUSB_PADCTL reset
CLOCK(CLK_RST_CONTROLLER_RST_DEV_W_SET) = BIT(CLK_W_XUSB_PADCTL);
// Disable USBD clock.
CLOCK(CLK_RST_CONTROLLER_CLK_ENB_L_CLR) = BIT(CLK_L_USBD);
// Disable PLLU.
clock_disable_pllu();
usb_init_done = false;
}
static void _usbd_stall_reset_ep1(usb_dir_t direction, usb_ep_cfg_t stall)
{
stall &= 1;
if (direction == USB_DIR_IN)
{
usbd_otg->regs->endptctrl[1] = (usbd_otg->regs->endptctrl[1] & ~USB2D_ENDPTCTRL_TX_EP_STALL) | ((u32)stall << 16);
if (!stall)
usbd_otg->regs->endptctrl[1] |= USB2D_ENDPTCTRL_TX_EP_RESET;
}
else
{
usbd_otg->regs->endptctrl[1] = (usbd_otg->regs->endptctrl[1] & ~USB2D_ENDPTCTRL_RX_EP_STALL) | stall;
if (!stall)
usbd_otg->regs->endptctrl[1] |= USB2D_ENDPTCTRL_RX_EP_RESET;
}
}
void usb_device_stall_ep1_bulk_out()
{
_usbd_stall_reset_ep1(USB_DIR_OUT, USB_EP_CFG_STALL);
}
void usb_device_stall_ep1_bulk_in()
{
_usbd_stall_reset_ep1(USB_DIR_IN, USB_EP_CFG_STALL);
}
static int _usbd_get_max_pkt_length(int endpoint)
{
switch (endpoint)
{
case USB_EP_CTRL_OUT:
case USB_EP_CTRL_IN:
return 64;
case USB_EP_BULK_OUT:
case USB_EP_BULK_IN:
if (usbd_otg->port_speed == USB_HIGH_SPEED)
return 512;
else
return 64;
default:
return 64;
}
}
static void _usbd_initialize_ep_ctrl(u32 endpoint)
{
usb_hw_ep_t actual_ep = (endpoint & 2) >> 1;
usb_dir_t direction = endpoint & 1;
memset((void *)&usbdaemon->qhs[endpoint], 0, sizeof(dQH_t));
if (!endpoint)
usbdaemon->qhs[endpoint].ep_capabilities = USB_QHD_EP_CAP_IOS_ENABLE;
usbdaemon->qhs[endpoint].next_dTD_ptr = 1; // TERMINATE_SET
u32 max_packet_len = _usbd_get_max_pkt_length(endpoint) & USB_QHD_EP_CAP_MAX_PKT_LEN_MASK;
usbdaemon->qhs[endpoint].ep_capabilities |= max_packet_len << 16;
if (direction == USB_DIR_IN)
{
u32 endpoint_type = usbd_otg->regs->endptctrl[actual_ep] & ~USB2D_ENDPTCTRL_TX_EP_TYPE_MASK;
if (actual_ep)
endpoint_type |= usbd_otg->gadget ? USB2D_ENDPTCTRL_TX_EP_TYPE_INTR : USB2D_ENDPTCTRL_TX_EP_TYPE_BULK;
else
endpoint_type |= USB2D_ENDPTCTRL_TX_EP_TYPE_CTRL;
usbd_otg->regs->endptctrl[actual_ep] = endpoint_type;
usbd_otg->regs->endptctrl[actual_ep] &= ~USB2D_ENDPTCTRL_TX_EP_STALL;
if (actual_ep == USB_HW_EP1)
usbd_otg->regs->endptctrl[1] |= USB2D_ENDPTCTRL_TX_EP_RESET;
usbd_otg->regs->endptctrl[actual_ep] |= USB2D_ENDPTCTRL_TX_EP_ENABLE;
}
else // EP Bulk OUT.
{
u32 endpoint_type = usbd_otg->regs->endptctrl[actual_ep] & ~USB2D_ENDPTCTRL_RX_EP_TYPE_MASK;
if (actual_ep)
{
endpoint_type |= usbd_otg->gadget ? USB2D_ENDPTCTRL_RX_EP_TYPE_INTR : USB2D_ENDPTCTRL_RX_EP_TYPE_BULK;
}
else
endpoint_type |= USB2D_ENDPTCTRL_RX_EP_TYPE_CTRL;
usbd_otg->regs->endptctrl[actual_ep] = endpoint_type;
usbd_otg->regs->endptctrl[actual_ep] &= ~USB2D_ENDPTCTRL_RX_EP_STALL;
if (actual_ep == USB_HW_EP1)
usbd_otg->regs->endptctrl[1] |= USB2D_ENDPTCTRL_RX_EP_RESET;
usbd_otg->regs->endptctrl[actual_ep] |= USB2D_ENDPTCTRL_RX_EP_ENABLE;
}
}
static int _usbd_initialize_ep0()
{
memset((void *)usbdaemon->qhs, 0, sizeof(dQH_t) * 4); // Clear all used EP queue heads.
memset((void *)usbdaemon->dtds, 0, sizeof(dTD_t) * 4); // Clear all used EP0 token heads.
usbd_otg->regs->asynclistaddr = (u32)usbdaemon->qhs;
_usbd_initialize_ep_ctrl(USB_EP_CTRL_OUT);
_usbd_initialize_ep_ctrl(USB_EP_CTRL_IN);
// Disable Auto Low Power.
usbd_otg->regs->hostpc1_devlc &= ~USB2D_HOSTPC1_DEVLC_ASUS;
// Initiate an attach event.
usbd_otg->regs->usbcmd |= USB2D_USBCMD_RUN;
u32 retries = 100000; // 200ms timeout.
while (!(usbd_otg->regs->usbcmd & USB2D_USBCMD_RUN))
{
retries--;
if (!retries)
return USB_ERROR_TIMEOUT;
usleep(1);
}
return USB_RES_OK;
}
// static void _disable_usb_wdt4()
// {
// if (TIMER_WDT4_STATUS & 1)// active
// {
// TIMER_TMR0_TMR_PTV &= 0x7FFFFFFF; // Disable timer
// TIMER_WDT4_UNLOCK_PATTERN = 0xC45A; // Alow writes to disable counter bit.
// TIMER_WDT4_COMMAND |= 2; // Disable counter
// TIMER_TMR0_TMR_PCR |= 0x40000000;// INTR_CLR
// }
// }
int usbd_flush_endpoint(u32 endpoint)
{
usb_hw_ep_t actual_ep = (endpoint & 2) >> 1;
usb_dir_t direction = endpoint & 1;
u32 reg_mask = endpoint;
// Flash all endpoints or 1.
if (endpoint != USB_EP_ALL)
{
if (direction == USB_DIR_IN)
reg_mask = USB2D_ENDPT_STATUS_TX_OFFSET << actual_ep;
else
reg_mask = USB2D_ENDPT_STATUS_RX_OFFSET << actual_ep;
}
usbd_otg->regs->endptflush = reg_mask;
u32 retries = 100000; // 200ms timeout.
while (usbd_otg->regs->endptflush & reg_mask)
{
retries--;
if (!retries)
return USB_ERROR_TIMEOUT;
usleep(1);
}
// Wait for the endpoint to finish all transactions (buffer not ready).
retries = 100000; // 200ms timeout.
while (usbd_otg->regs->endptstatus & reg_mask)
{
retries--;
if (!retries)
return USB_ERROR_TIMEOUT;
usleep(1);
}
// Wait for the endpoint to clear the primed status.
retries = 100000; // 200ms timeout.
while (usbd_otg->regs->endptprime & reg_mask)
{
retries--;
if (!retries)
return USB_ERROR_TIMEOUT;
usleep(1);
}
return USB_RES_OK;
}
void usbd_end(bool reset_ep, bool only_controller)
{
if (reset_ep)
{
usbd_flush_endpoint(USB_EP_ALL);
_usbd_stall_reset_ep1(USB_DIR_OUT, USB_EP_CFG_RESET); // EP1 Bulk OUT.
_usbd_stall_reset_ep1(USB_DIR_IN, USB_EP_CFG_RESET); // EP1 Bulk IN.
usbd_otg->config_num = 0;
usbd_otg->interface_num = 0;
usbd_otg->configuration_set = false;
usbd_otg->max_lun_set = false;
}
// Stop device controller.
usbd_otg->regs->usbcmd &= ~USB2D_USBCMD_RUN;
// Enable PHY auto low power suspend.
usbd_otg->regs->hostpc1_devlc |= USB2D_HOSTPC1_DEVLC_ASUS;
if (!only_controller)
_usb_device_power_down();
}
static void _usbd_mark_ep_complete(u32 endpoint)
{
u32 complete_bit;
usb_hw_ep_t actual_ep = (endpoint & 2) >> 1;
usb_dir_t direction = endpoint & 1;
usbd_flush_endpoint(endpoint);
memset((void *)&usbdaemon->dtds[endpoint * 4], 0, sizeof(dTD_t) * 4);
memset((void *)&usbdaemon->qhs[endpoint], 0, sizeof(dQH_t));
usbdaemon->ep_configured[endpoint] = 0;
usbdaemon->ep_bytes_requested[endpoint] = 0;
if (direction == USB_DIR_IN)
complete_bit = USB2D_ENDPT_STATUS_TX_OFFSET << actual_ep;
else
complete_bit = USB2D_ENDPT_STATUS_RX_OFFSET << actual_ep;
usbd_otg->regs->endptcomplete |= complete_bit;
}
static usb_ep_status_t _usbd_get_ep_status(usb_ep_t endpoint)
{
bool status;
u32 reg_val;
u32 reg_mask;
u32 actual_ep = (endpoint & 2) >> 1;
usb_dir_t direction = endpoint & 1;
if (direction == USB_DIR_IN)
reg_mask = USB2D_ENDPT_STATUS_TX_OFFSET << actual_ep;
else
reg_mask = USB2D_ENDPT_STATUS_RX_OFFSET << actual_ep;
if (actual_ep == USB_HW_EP1)
reg_val = usbd_otg->regs->endptctrl[1];
else
reg_val = usbd_otg->regs->endptctrl[0];
// Check stalled status.
if (direction == USB_DIR_IN)
status = reg_val & USB2D_ENDPTCTRL_TX_EP_STALL;
else
status = reg_val & USB2D_ENDPTCTRL_RX_EP_STALL;
if (status)
return USB_EP_STATUS_STALLED;
// Check enabled status.
if (direction == USB_DIR_IN)
status = reg_val & USB2D_ENDPTCTRL_TX_EP_ENABLE;
else
status = reg_val & USB2D_ENDPTCTRL_RX_EP_ENABLE;
if (!status)
return USB_EP_STATUS_DISABLED;
// CHeck qHD error status.
u32 token_error_mask = USB_QHD_TOKEN_HALTED | USB_QHD_TOKEN_BUFFER_ERROR | USB_QHD_TOKEN_XFER_ERROR;
if (usbdaemon->qhs[endpoint].token & token_error_mask)
return USB_EP_STATUS_ERROR;
// Check if endpoint has a request or a ready buffer.
if ((usbd_otg->regs->endptprime & reg_mask) || (usbd_otg->regs->endptstatus & reg_mask))
return USB_EP_STATUS_ACTIVE; // RX/TX active.
// Return idle or not configured status.
if (!usbdaemon->ep_configured[endpoint])
return USB_EP_STATUS_NO_CONFIG;
return USB_EP_STATUS_IDLE;
}
static int _usbd_ep_operation(usb_ep_t endpoint, u8 *buf, u32 len, u32 sync_timeout)
{
if (!buf)
len = 0;
u32 prime_bit;
usb_hw_ep_t actual_ep = (endpoint & 2) >> 1;
usb_dir_t direction = endpoint & 1;
u32 length_left = len;
u32 dtd_ep_idx = endpoint * 4;
_usbd_mark_ep_complete(endpoint);
if (endpoint == USB_EP_CTRL_OUT)
usbdaemon->qhs[endpoint].ep_capabilities = USB_QHD_EP_CAP_IOS_ENABLE;
u32 max_packet_len = _usbd_get_max_pkt_length(endpoint) & USB_QHD_EP_CAP_MAX_PKT_LEN_MASK;
usbdaemon->qhs[endpoint].ep_capabilities |= (max_packet_len << 16) | USB_QHD_EP_CAP_ZERO_LEN_TERM_DIS;
usbdaemon->qhs[endpoint].next_dTD_ptr = 0; // Clear terminate bit.
//usbdaemon->qhs[endpoint].ep_capabilities |= USB_QHD_TOKEN_IRQ_ON_COMPLETE;
usbdaemon->ep_configured[endpoint] = 1;
usbdaemon->ep_bytes_requested[endpoint] = len;
// Configure dTD.
u32 dtd_idx = 0;
do
{
if (dtd_idx)
usbdaemon->dtds[dtd_ep_idx + dtd_idx - 1].next_dTD = (u32)&usbdaemon->dtds[dtd_ep_idx + dtd_idx];
u32 dtd_size = MIN(length_left, USB_TD_BUFFER_MAX_SIZE); // 16KB max per dTD.
usbdaemon->dtds[dtd_ep_idx + dtd_idx].info = (dtd_size << 16) | USB_QHD_TOKEN_ACTIVE;
// usbdaemon->dtds[dtd_ep_idx + dtd_idx].info |= USB_QHD_TOKEN_IRQ_ON_COMPLETE;
// Set buffers addresses to all page pointers.
u32 dt_buffer_offset = dtd_idx * USB_TD_BUFFER_MAX_SIZE;
for (u32 i = 0; i < 4; i++)
usbdaemon->dtds[dtd_ep_idx + dtd_idx].pages[i] =
(u32)&buf[dt_buffer_offset + (USB_TD_BUFFER_PAGE_SIZE * i)];
//usbdaemon->dtds[dtd_ep_idx + dtd_idx].pages[5] =
// (u32)&buf[dt_buffer_offset + (USB_TD_BUFFER_PAGE_SIZE * 4)]; // Last buffer. Unused.
length_left -= dtd_size;
if (length_left)
dtd_idx++;
}
while (length_left);
// Last dTD, terminate it.
usbdaemon->dtds[dtd_ep_idx + dtd_idx].next_dTD = 1;
// Set first dTD address to queue head next dTD.
usbdaemon->qhs[endpoint].next_dTD_ptr |= (u32)&usbdaemon->dtds[dtd_ep_idx] & 0xFFFFFFE0;
// Flush AHB prefetcher.
AHB_GIZMO(AHB_AHB_MEM_PREFETCH_CFG1) &= ~MEM_PREFETCH_ENABLE;
AHB_GIZMO(AHB_AHB_MEM_PREFETCH_CFG1) |= MEM_PREFETCH_ENABLE;
if (direction == USB_DIR_IN)
prime_bit = USB2D_ENDPT_STATUS_TX_OFFSET << actual_ep;
else
prime_bit = USB2D_ENDPT_STATUS_RX_OFFSET << actual_ep;
// Flush data before priming EP.
bpmp_mmu_maintenance(BPMP_MMU_MAINT_CLEAN_WAY, false);
// Prime endpoint.
usbd_otg->regs->endptprime |= prime_bit; // USB2_CONTROLLER_USB2D_ENDPTPRIME.
int res = USB_RES_OK;
usb_ep_status_t ep_status;
if (sync_timeout)
{
ep_status = _usbd_get_ep_status(endpoint);
if (ep_status == USB_EP_STATUS_ACTIVE)
{
u32 retries = sync_timeout;
while (retries)
{
ep_status = _usbd_get_ep_status(endpoint);
if (ep_status != USB_EP_STATUS_ACTIVE)
{
if (ep_status == USB_EP_STATUS_DISABLED)
res = USB2_ERROR_XFER_EP_DISABLED;
goto out;
}
retries--;
usleep(1);
}
res = USB_ERROR_TIMEOUT;
}
else if (ep_status == USB_EP_STATUS_DISABLED)
res = USB2_ERROR_XFER_EP_DISABLED;
out:
if (res)
_usbd_mark_ep_complete(endpoint);
else if (_usbd_get_ep_status(endpoint) != USB_EP_STATUS_IDLE)
res = USB_ERROR_XFER_ERROR;
if (direction == USB_DIR_OUT)
bpmp_mmu_maintenance(BPMP_MMU_MAINT_INVALID_WAY, false);
}
return res;
}
static int _usbd_ep_ack(usb_ep_t ep)
{
return _usbd_ep_operation(ep, NULL, 0, USB_XFER_SYNCED_ENUM);
}
static void _usbd_set_ep0_stall()
{
// EP Control endpoints must be always stalled together.
usbd_otg->regs->endptctrl[0] =
USB2D_ENDPTCTRL_TX_EP_ENABLE | USB2D_ENDPTCTRL_TX_EP_STALL |
USB2D_ENDPTCTRL_RX_EP_ENABLE | USB2D_ENDPTCTRL_RX_EP_STALL;
}
int usbd_set_ep_stall(u32 endpoint, int ep_stall)
{
usb_hw_ep_t actual_ep = (endpoint & 2) >> 1;
usb_dir_t direction = endpoint & 1;
if (ep_stall)
{
if (direction == USB_DIR_IN)
usbd_otg->regs->endptctrl[actual_ep] |= USB2D_ENDPTCTRL_TX_EP_STALL; // Stall EP Bulk IN.
else
usbd_otg->regs->endptctrl[actual_ep] |= USB2D_ENDPTCTRL_RX_EP_STALL; // Stall EP Bulk OUT.
}
else
{
if (direction == USB_DIR_IN)
usbd_otg->regs->endptctrl[actual_ep] &= ~USB2D_ENDPTCTRL_TX_EP_STALL; // Clear stall EP Bulk IN.
else
usbd_otg->regs->endptctrl[actual_ep] &= ~USB2D_ENDPTCTRL_RX_EP_STALL; // Clear stall EP Bulk OUT.
}
return USB_RES_OK;
}
static void _usbd_handle_get_class_request(bool *transmit_data, u8 *descriptor, int *size, bool *ep_stall)
{
u8 _bRequest = usbd_otg->control_setup.bRequest;
u16 _wIndex = usbd_otg->control_setup.wIndex;
u16 _wValue = usbd_otg->control_setup.wValue;
u16 _wLength = usbd_otg->control_setup.wLength;
bool valid_interface = _wIndex == usbd_otg->interface_num;
bool valid_len = (_bRequest == USB_REQUEST_BULK_GET_MAX_LUN) ? 1 : 0;
if (!valid_interface || _wValue != 0 || _wLength != valid_len)
{
*ep_stall = true;
return;
}
switch (_bRequest)
{
case USB_REQUEST_BULK_RESET:
_usbd_ep_ack(USB_EP_CTRL_IN);
usbd_otg->bulk_reset_req = true;
break; // DELAYED_STATUS;
case USB_REQUEST_BULK_GET_MAX_LUN:
*transmit_data = true;
*size = 1;
descriptor[0] = usbd_otg->max_lun; // Set 0 LUN for 1 drive supported.
usbd_otg->max_lun_set = true;
break;
default:
*ep_stall = true;
break;
}
}
static void _usbd_handle_get_descriptor(bool *transmit_data, void **descriptor, int *size, bool *ep_stall)
{
u8 descriptor_type = usbd_otg->control_setup.wValue >> 8;
u8 descriptor_subtype = usbd_otg->control_setup.wValue & 0xFF;
switch (descriptor_type)
{
case USB_DESCRIPTOR_DEVICE:
{
/*
u32 soc_rev = APB_MISC(APB_MISC_GP_HIDREV);
usb_device_descriptor.idProduct = (soc_rev >> 8) & 0xFF; // chip_id.
usb_device_descriptor.idProduct |= ((soc_rev << 4) | (FUSE(FUSE_SKU_INFO) & 0xF)) << 8; // HIDFAM.
usb_device_descriptor.bcdDevice = (soc_rev >> 16) & 0xF; // MINORREV.
usb_device_descriptor.bcdDevice |= ((soc_rev >> 4) & 0xF) << 8; // MAJORREV.
*/
*descriptor = usbd_otg->desc->dev;
*size = usbd_otg->desc->dev->bLength;
*transmit_data = true;
return;
}
case USB_DESCRIPTOR_CONFIGURATION:
if (usbd_otg->gadget == USB_GADGET_UMS)
{
if (usbd_otg->port_speed == USB_HIGH_SPEED) // High speed. 512 bytes.
{
usbd_otg->desc->cfg->endpoint[0].wMaxPacketSize = 0x200;
usbd_otg->desc->cfg->endpoint[1].wMaxPacketSize = 0x200;
}
else // Full speed. 64 bytes.
{
usbd_otg->desc->cfg->endpoint[0].wMaxPacketSize = 0x40;
usbd_otg->desc->cfg->endpoint[1].wMaxPacketSize = 0x40;
}
}
else
{
usb_cfg_hid_descr_t *tmp = (usb_cfg_hid_descr_t *)usbd_otg->desc->cfg;
if (usbd_otg->port_speed == USB_HIGH_SPEED) // High speed. 512 bytes.
{
tmp->endpoint[0].wMaxPacketSize = 0x200;
tmp->endpoint[1].wMaxPacketSize = 0x200;
tmp->endpoint[0].bInterval = usbd_otg->gadget == USB_GADGET_HID_GAMEPAD ? 4 : 3; // 8ms : 4ms.
tmp->endpoint[1].bInterval = usbd_otg->gadget == USB_GADGET_HID_GAMEPAD ? 4 : 3; // 8ms : 4ms.
}
else // Full speed. 64 bytes.
{
tmp->endpoint[0].wMaxPacketSize = 0x40;
tmp->endpoint[1].wMaxPacketSize = 0x40;
tmp->endpoint[0].bInterval = usbd_otg->gadget == USB_GADGET_HID_GAMEPAD ? 8 : 4; // 8ms : 4ms.
tmp->endpoint[1].bInterval = usbd_otg->gadget == USB_GADGET_HID_GAMEPAD ? 8 : 4; // 8ms : 4ms.
}
}
*descriptor = usbd_otg->desc->cfg;
*size = usbd_otg->desc->cfg->config.wTotalLength;
*transmit_data = true;
return;
case USB_DESCRIPTOR_STRING:
switch (descriptor_subtype)
{
case 1:
*descriptor = usbd_otg->desc->vendor;
*size = usbd_otg->desc->vendor[0];
break;
case 2:
*descriptor = usbd_otg->desc->product;
*size = usbd_otg->desc->product[0];
break;
case 3:
*descriptor = usbd_otg->desc->serial;
*size = usbd_otg->desc->serial[0];
break;
case 0xEE:
*descriptor = usbd_otg->desc->ms_os;
*size = usbd_otg->desc->ms_os->bLength;
break;
default:
*descriptor = usbd_otg->desc->lang_id;
*size = 4;
break;
}
*transmit_data = true;
return;
case USB_DESCRIPTOR_DEVICE_QUALIFIER:
if (!usbd_otg->desc->dev_qual)
goto exit;
usbd_otg->desc->dev_qual->bNumOtherConfigs = 1;
*descriptor = usbd_otg->desc->dev_qual;
*size = usbd_otg->desc->dev_qual->bLength;
*transmit_data = true;
return;
case USB_DESCRIPTOR_OTHER_SPEED_CONFIGURATION:
if (!usbd_otg->desc->cfg_other)
goto exit;
if (usbd_otg->port_speed == USB_HIGH_SPEED)
{
usbd_otg->desc->cfg_other->endpoint[0].wMaxPacketSize = 0x40;
usbd_otg->desc->cfg_other->endpoint[1].wMaxPacketSize = 0x40;
}
else
{
usbd_otg->desc->cfg_other->endpoint[0].wMaxPacketSize = 0x200;
usbd_otg->desc->cfg_other->endpoint[1].wMaxPacketSize = 0x200;
}
if ((usbd_otg->charger_detect & 1) && (usbd_otg->charger_detect & 2))
usbd_otg->desc->cfg_other->config.bMaxPower = 500 / 2;
*descriptor = usbd_otg->desc->cfg_other;
*size = usbd_otg->desc->cfg_other->config.wTotalLength;
*transmit_data = true;
return;
case USB_DESCRIPTOR_DEVICE_BINARY_OBJECT:
*descriptor = usbd_otg->desc->dev_bot;
*size = usbd_otg->desc->dev_bot->wTotalLength;
*transmit_data = true;
return;
default:
*transmit_data = false;
*ep_stall = true;
return;
}
exit:
*transmit_data = false;
*ep_stall = true;
return;
}
static int _usbd_handle_set_request(bool *ep_stall)
{
int res = USB_RES_OK;
u8 bRequest = usbd_otg->control_setup.bRequest;
if (bRequest == USB_REQUEST_SET_ADDRESS)
{
res = _usbd_ep_ack(USB_EP_CTRL_IN);
// Set USB address for device mode.
if (!res)
usbd_otg->regs->periodiclistbase = (usbd_otg->regs->periodiclistbase & 0x1FFFFFF) | ((usbd_otg->control_setup.wValue & 0xFF) << 25);
}
else if (bRequest == USB_REQUEST_SET_CONFIGURATION)
{
res = _usbd_ep_ack(USB_EP_CTRL_IN);
if (!res)
{
usbd_otg->config_num = usbd_otg->control_setup.wValue;
_usbd_initialize_ep_ctrl(USB_EP_BULK_OUT);
_usbd_initialize_ep_ctrl(USB_EP_BULK_IN);
usbd_otg->configuration_set = true;
}
}
else
*ep_stall = true;
return res;
}
static int _usbd_handle_ep0_control_transfer()
{
int res = USB_RES_OK;
bool ep_stall = false;
bool transmit_data = false;
u8 *descriptor = (u8 *)USB_DESCRIPTOR_ADDR;
int size = 0;
u8 _bmRequestType = usbd_otg->control_setup.bmRequestType;
u8 _bRequest = usbd_otg->control_setup.bRequest;
u16 _wValue = usbd_otg->control_setup.wValue;
u16 _wIndex = usbd_otg->control_setup.wIndex;
u16 _wLength = usbd_otg->control_setup.wLength;
//gfx_printf("%02X %02X %04X %04X %04X\n", _bmRequestType, _bRequest, _wValue, _wIndex, _wLength);
switch (_bmRequestType)
{
case (USB_SETUP_HOST_TO_DEVICE | USB_SETUP_TYPE_STANDARD | USB_SETUP_RECIPIENT_DEVICE):
res = _usbd_handle_set_request(&ep_stall);
break;
case (USB_SETUP_HOST_TO_DEVICE | USB_SETUP_TYPE_STANDARD | USB_SETUP_RECIPIENT_INTERFACE):
res = _usbd_ep_ack(USB_EP_CTRL_IN);
if (!res)
usbd_otg->interface_num = _wValue;
break;
case (USB_SETUP_HOST_TO_DEVICE | USB_SETUP_TYPE_STANDARD | USB_SETUP_RECIPIENT_ENDPOINT):
switch (_bRequest)
{
case USB_REQUEST_CLEAR_FEATURE:
case USB_REQUEST_SET_FEATURE:
if ((_wValue & 0xFF) == USB_FEATURE_ENDPOINT_HALT)
{
int direction;
switch (_wIndex) // endpoint
{
case USB_EP_ADDR_CTRL_OUT:
direction = 2;
break;
case USB_EP_ADDR_CTRL_IN:
direction = 3;
break;
case USB_EP_ADDR_BULK_OUT:
direction = 0;
break;
case USB_EP_ADDR_BULK_IN:
direction = 1;
break;
default:
_usbd_stall_reset_ep1(3, USB_EP_CFG_STALL);
goto out;
}
if (_bRequest == USB_REQUEST_CLEAR_FEATURE)
_usbd_stall_reset_ep1(direction, USB_EP_CFG_RESET);
else
_usbd_stall_reset_ep1(direction, USB_EP_CFG_STALL);
res = _usbd_ep_ack(USB_EP_CTRL_IN);
}
else
_usbd_stall_reset_ep1(3, USB_EP_CFG_STALL);
break;
default:
ep_stall = true;
break;
}
break;
case (USB_SETUP_HOST_TO_DEVICE | USB_SETUP_TYPE_CLASS | USB_SETUP_RECIPIENT_INTERFACE):
memset(descriptor, 0, _wLength);
_usbd_handle_get_class_request(&transmit_data, descriptor, &size, &ep_stall);
break;
case (USB_SETUP_DEVICE_TO_HOST | USB_SETUP_TYPE_STANDARD | USB_SETUP_RECIPIENT_DEVICE):
switch (_bRequest)
{
case USB_REQUEST_GET_STATUS:
descriptor[0] = USB_STATUS_DEV_SELF_POWERED;
descriptor[1] = 0; // No support for remove wake up.
transmit_data = true;
size = 2;
break;
case USB_REQUEST_GET_DESCRIPTOR:
_usbd_handle_get_descriptor(&transmit_data, (void **)&descriptor, &size, &ep_stall);
break;
case USB_REQUEST_GET_CONFIGURATION:
descriptor = (u8 *)&usbd_otg->config_num;
size = _wLength;
transmit_data = true;
break;
default:
ep_stall = true;
break;
}
break;
case (USB_SETUP_DEVICE_TO_HOST | USB_SETUP_TYPE_STANDARD | USB_SETUP_RECIPIENT_INTERFACE):
if (_bRequest == USB_REQUEST_GET_INTERFACE)
{
memset(descriptor, 0, _wLength);
descriptor[0] = usbd_otg->interface_num;
size = _wLength;
}
else if (_bRequest == USB_REQUEST_GET_STATUS)
{
memset(descriptor, 0, _wLength);
size = _wLength;
}
else if (_bRequest == USB_REQUEST_GET_DESCRIPTOR && (_wValue >> 8) == USB_DESCRIPTOR_HID_REPORT && usbd_otg->gadget > USB_GADGET_UMS)
{
if (usbd_otg->gadget == USB_GADGET_HID_GAMEPAD)
{
descriptor = (u8 *)&hid_report_descriptor_jc;
size = hid_report_descriptor_jc_size;
}
else // USB_GADGET_HID_TOUCHPAD
{
descriptor = (u8 *)&hid_report_descriptor_touch;
size = hid_report_descriptor_touch_size;
}
usbd_otg->hid_report_sent = true;
}
else
{
ep_stall = true;
break;
}
if (_wLength < size)
size = _wLength;
transmit_data = true;
break;
case (USB_SETUP_DEVICE_TO_HOST | USB_SETUP_TYPE_STANDARD | USB_SETUP_RECIPIENT_ENDPOINT):
if (_bRequest == USB_REQUEST_GET_STATUS)
{
int ep_req;
switch (_wIndex)
{
case USB_EP_ADDR_CTRL_OUT:
ep_req = USB_EP_CTRL_OUT;
break;
case USB_EP_ADDR_BULK_OUT:
ep_req = USB_EP_BULK_OUT;
break;
case USB_EP_ADDR_CTRL_IN:
ep_req = USB_EP_CTRL_IN;
break;
case USB_EP_ADDR_BULK_IN:
ep_req = USB_EP_BULK_IN;
break;
default:
_usbd_stall_reset_ep1(3, USB_EP_CFG_STALL);
goto out;
}
size = _wLength;
memset(descriptor, 0, size);
if (_usbd_get_ep_status(ep_req) == USB_EP_STATUS_STALLED)
descriptor[0] = USB_STATUS_EP_HALTED;
else
descriptor[0] = USB_STATUS_EP_OK;
transmit_data = true;
}
else
_usbd_stall_reset_ep1(3, USB_EP_CFG_STALL);
break;
case (USB_SETUP_DEVICE_TO_HOST | USB_SETUP_TYPE_CLASS | USB_SETUP_RECIPIENT_INTERFACE):
memset(descriptor, 0, _wLength);
_usbd_handle_get_class_request(&transmit_data, descriptor, &size, &ep_stall);
break;
case (USB_SETUP_DEVICE_TO_HOST | USB_SETUP_TYPE_VENDOR | USB_SETUP_RECIPIENT_INTERFACE):
case (USB_SETUP_DEVICE_TO_HOST | USB_SETUP_TYPE_VENDOR | USB_SETUP_RECIPIENT_DEVICE):
if (_bRequest == USB_REQUEST_GET_MS_DESCRIPTOR)
{
switch (_wIndex)
{
case USB_DESCRIPTOR_MS_COMPAT_ID:
descriptor = (u8 *)usbd_otg->desc->ms_cid;
size = usbd_otg->desc->ms_cid->dLength;
transmit_data = true;
break;
case USB_DESCRIPTOR_MS_EXTENDED_PROPERTIES:
descriptor = (u8 *)usbd_otg->desc->mx_ext;
size = usbd_otg->desc->mx_ext->dLength;
transmit_data = true;
break;
default:
ep_stall = true;
break;
}
}
else
ep_stall = true;
break;
default:
ep_stall = true;
break;
}
// Transmit data to HOST if any.
if (transmit_data)
{
memcpy(usb_ep0_ctrl_buf, descriptor, size);
if (_wLength < size)
size = _wLength;
res = _usbd_ep_operation(USB_EP_CTRL_IN, usb_ep0_ctrl_buf, size, USB_XFER_SYNCED_ENUM);
if (!res)
res = _usbd_ep_ack(USB_EP_CTRL_OUT);
}
out:
if (ep_stall)
_usbd_set_ep0_stall();
return res;
}
static int _usbd_ep0_initialize()
{
bool enter = false;
if (usbd_otg->configuration_set)
enter = true;
else
{
usbdaemon->qhs = (volatile dQH_t *)USB2_QH_USB2D_QH_EP_BASE;
if (!_usbd_initialize_ep0())
enter = true;
}
if (enter)
{
usbd_otg->configuration_set = false;
usbd_otg->max_lun_set = false;
// Timeout if cable or communication isn't started in 1.5 minutes.
u32 timer = get_tmr_ms() + 90000;
while (true)
{
u32 usb_status_irqs = usbd_otg->regs->usbsts;
// Clear all interrupt statuses.
usbd_otg->regs->usbsts = usb_status_irqs;
// Check if a reset was received.
if (usb_status_irqs & USB2D_USBSTS_URI)
{
//_disable_usb_wdt4();
// Clear all device addresses, enabled setup requests, transmit events and flush all endpoints.
usbd_otg->regs->periodiclistbase = 0;
usbd_otg->regs->endptsetupstat = usbd_otg->regs->endptsetupstat;
usbd_otg->regs->endptcomplete = usbd_otg->regs->endptcomplete;
usbd_flush_endpoint(USB_EP_ALL);
}
// Check if port change happened.
if (usb_status_irqs & USB2D_USBSTS_PCI)
usbd_otg->port_speed = (usbd_otg->regs->hostpc1_devlc & USB2D_HOSTPC1_DEVLC_PSPD_MASK) >> 25;
// Acknowledge setup request for EP0 and copy its configuration.
u32 ep0_setup_req = usbd_otg->regs->endptsetupstat;
if (ep0_setup_req & 1)
{
usbd_otg->regs->endptsetupstat = ep0_setup_req;
memcpy(&usbd_otg->control_setup, (void *)usbdaemon->qhs->setup, 8);
if (_usbd_handle_ep0_control_transfer())
break;
}
if (usbd_otg->configuration_set)
return USB_RES_OK;
if (timer < get_tmr_ms() || btn_read_vol() == (BTN_VOL_UP | BTN_VOL_DOWN))
return USB_ERROR_USER_ABORT;
}
}
return USB_ERROR_TIMEOUT;
}
int usb_device_enumerate(usb_gadget_type gadget)
{
switch (gadget)
{
case USB_GADGET_UMS:
usbd_otg->desc = &usb_gadget_ums_descriptors;
break;
case USB_GADGET_HID_GAMEPAD:
usbd_otg->desc = &usb_gadget_hid_jc_descriptors;
break;
case USB_GADGET_HID_TOUCHPAD:
usbd_otg->desc = &usb_gadget_hid_touch_descriptors;
break;
}
usbd_otg->gadget = gadget;
return _usbd_ep0_initialize();
}
int usbd_handle_ep0_ctrl_setup()
{
// Acknowledge setup request for EP0 and copy its configuration.
u32 ep0_setup_req = usbd_otg->regs->endptsetupstat;
if (ep0_setup_req & 1)
{
usbd_otg->regs->endptsetupstat = ep0_setup_req;
memcpy(&usbd_otg->control_setup, (void *)usbdaemon->qhs->setup, 8);
_usbd_handle_ep0_control_transfer();
memset(usb_ep0_ctrl_buf, 0, USB_TD_BUFFER_PAGE_SIZE);
}
// Only return error if bulk reset was requested.
if (usbd_otg->bulk_reset_req)
{
usbd_otg->bulk_reset_req = false;
return USB_RES_BULK_RESET;
}
return USB_RES_OK;
}
static usb_ep_status_t _usbd_get_ep1_status(usb_dir_t dir)
{
usb_ep_t ep;
if (dir == USB_DIR_OUT)
ep = USB_EP_BULK_OUT;
else
ep = USB_EP_BULK_IN;
return _usbd_get_ep_status(ep);
}
int usb_device_ep1_out_read(u8 *buf, u32 len, u32 *bytes_read, u32 sync_timeout)
{
if ((u32)buf % USB_EP_BUFFER_ALIGN)
return USB2_ERROR_XFER_NOT_ALIGNED;
if (len > USB_EP_BUFFER_MAX_SIZE)
len = USB_EP_BUFFER_MAX_SIZE;
int res = _usbd_ep_operation(USB_EP_BULK_OUT, buf, len, sync_timeout);
if (sync_timeout && bytes_read)
*bytes_read = res ? 0 : len;
return res;
}
int usb_device_ep1_out_read_big(u8 *buf, u32 len, u32 *bytes_read)
{
if ((u32)buf % USB_EP_BUFFER_ALIGN)
return USB2_ERROR_XFER_NOT_ALIGNED;
if (len > USB_EP_BULK_OUT_MAX_XFER)
len = USB_EP_BULK_OUT_MAX_XFER;
int res;
u32 bytes = 0;
*bytes_read = 0;
u8 *buf_curr = buf;
while (len)
{
u32 len_ep = MIN(len, USB_EP_BUFFER_MAX_SIZE);
res = usb_device_ep1_out_read(buf_curr, len_ep, &bytes, USB_XFER_SYNCED_DATA);
if (res)
return res;
len -= len_ep;
buf_curr += len_ep;
*bytes_read = *bytes_read + bytes;
}
return USB_RES_OK;
}
static int _usbd_get_ep1_out_bytes_read()
{
if (_usbd_get_ep_status(USB_EP_BULK_OUT) != USB_EP_STATUS_IDLE)
return 0;
else
return (usbdaemon->ep_bytes_requested[USB_EP_BULK_OUT] - (usbdaemon->qhs[USB_EP_BULK_OUT].token >> 16));
}
int usb_device_ep1_out_reading_finish(u32 *pending_bytes, u32 sync_timeout)
{
usb_ep_status_t ep_status;
do
{
ep_status = _usbd_get_ep1_status(USB_DIR_OUT);
if ((ep_status == USB_EP_STATUS_IDLE) || (ep_status == USB_EP_STATUS_DISABLED))
break;
usbd_handle_ep0_ctrl_setup();
}
while ((ep_status == USB_EP_STATUS_ACTIVE) || (ep_status == USB_EP_STATUS_STALLED));
*pending_bytes = _usbd_get_ep1_out_bytes_read();
bpmp_mmu_maintenance(BPMP_MMU_MAINT_INVALID_WAY, false);
if (ep_status == USB_EP_STATUS_IDLE)
return USB_RES_OK;
else if (ep_status == USB_EP_STATUS_DISABLED)
return USB2_ERROR_XFER_EP_DISABLED;
else
return USB_ERROR_XFER_ERROR;
}
int usb_device_ep1_in_write(u8 *buf, u32 len, u32 *bytes_written, u32 sync_timeout)
{
if ((u32)buf % USB_EP_BUFFER_ALIGN)
return USB2_ERROR_XFER_NOT_ALIGNED;
if (len > USB_EP_BUFFER_MAX_SIZE)
len = USB_EP_BUFFER_MAX_SIZE;
int res = _usbd_ep_operation(USB_EP_BULK_IN, buf, len, sync_timeout);
if (sync_timeout && bytes_written)
*bytes_written = res ? 0 : len;
return res;
}
static int _usbd_get_ep1_in_bytes_written()
{
if (_usbd_get_ep_status(USB_EP_BULK_IN) != USB_EP_STATUS_IDLE)
return 0;
else
return (usbdaemon->ep_bytes_requested[USB_EP_BULK_IN] - (usbdaemon->qhs[USB_EP_BULK_IN].token >> 16));
}
int usb_device_ep1_in_writing_finish(u32 *pending_bytes, u32 sync_timeout)
{
usb_ep_status_t ep_status;
do
{
ep_status = _usbd_get_ep1_status(USB_DIR_IN);
if ((ep_status == USB_EP_STATUS_IDLE) || (ep_status == USB_EP_STATUS_DISABLED))
break;
usbd_handle_ep0_ctrl_setup();
}
while ((ep_status == USB_EP_STATUS_ACTIVE) || (ep_status == USB_EP_STATUS_STALLED));
*pending_bytes = _usbd_get_ep1_in_bytes_written();
if (ep_status == USB_EP_STATUS_IDLE)
return USB_RES_OK;
else if (ep_status == USB_EP_STATUS_DISABLED)
return USB2_ERROR_XFER_EP_DISABLED;
usb_device_stall_ep1_bulk_out();
return USB_ERROR_XFER_ERROR;
}
bool usb_device_get_suspended()
{
bool suspended = (usbd_otg->regs->portsc1 & USB2D_PORTSC1_SUSP) == USB2D_PORTSC1_SUSP;
return suspended;
}
bool usb_device_get_port_in_sleep()
{
// Windows heuristic: Forces port into suspend, sleep and J-State.
return (usbd_otg->regs->portsc1) == 0x885;
}
int usb_device_class_send_max_lun(u8 max_lun)
{
// Timeout if get MAX_LUN request doesn't happen in 10s.
u32 timer = get_tmr_ms() + 10000;
usbd_otg->max_lun = max_lun;
while (!usbd_otg->max_lun_set)
{
usbd_handle_ep0_ctrl_setup();
if (timer < get_tmr_ms() || btn_read_vol() == (BTN_VOL_UP | BTN_VOL_DOWN))
return USB_ERROR_USER_ABORT;
}
return USB_RES_OK;
}
int usb_device_class_send_hid_report()
{
// Timeout if get GET_HID_REPORT request doesn't happen in 10s.
u32 timer = get_tmr_ms() + 10000;
// Wait for request and transfer start.
while (!usbd_otg->hid_report_sent)
{
usbd_handle_ep0_ctrl_setup();
if (timer < get_tmr_ms() || btn_read_vol() == (BTN_VOL_UP | BTN_VOL_DOWN))
return USB_ERROR_USER_ABORT;
}
return USB_RES_OK;
}
void usb_device_get_ops(usb_ops_t *ops)
{
ops->usbd_flush_endpoint = usbd_flush_endpoint;
ops->usbd_set_ep_stall = usbd_set_ep_stall;
ops->usbd_handle_ep0_ctrl_setup = usbd_handle_ep0_ctrl_setup;
ops->usbd_end = usbd_end;
ops->usb_device_init = usb_device_init;
ops->usb_device_enumerate = usb_device_enumerate;
ops->usb_device_class_send_max_lun = usb_device_class_send_max_lun;
ops->usb_device_class_send_hid_report = usb_device_class_send_hid_report;
ops->usb_device_get_suspended = usb_device_get_suspended;
ops->usb_device_get_port_in_sleep = usb_device_get_port_in_sleep;
ops->usb_device_ep1_out_read = usb_device_ep1_out_read;
ops->usb_device_ep1_out_read_big = usb_device_ep1_out_read_big;
ops->usb_device_ep1_out_reading_finish = usb_device_ep1_out_reading_finish;
ops->usb_device_ep1_in_write = usb_device_ep1_in_write;
ops->usb_device_ep1_in_writing_finish = usb_device_ep1_in_writing_finish;
}