#include "usbd_xinput.h" #include "usbd_ctlreq.h" #include static uint8_t USBD_XINPUT_Init(USBD_HandleTypeDef *pdev, uint8_t cfgidx); static uint8_t USBD_XINPUT_DeInit(USBD_HandleTypeDef *pdev, uint8_t cfgidx); static uint8_t USBD_XINPUT_Setup(USBD_HandleTypeDef *pdev, USBD_SetupReqTypedef *req); static uint8_t USBD_XINPUT_DataIn(USBD_HandleTypeDef *pdev, uint8_t epnum); static uint8_t USBD_XINPUT_DataOut(USBD_HandleTypeDef *pdev, uint8_t epnum); static uint8_t *USBD_XINPUT_GetCfgDesc(uint16_t *length); static uint8_t *USBD_XINPUT_GetDeviceQualifierDesc(uint16_t *length); #if (USBD_SUPPORT_USER_STRING_DESC == 1U) static uint8_t *USBD_XINPUT_GetUserString(USBD_HandleTypeDef *pdev, uint8_t index, uint16_t *length); #endif static uint8_t xinputOutBuffer[XINPUT_EP_SIZE]; static uint8_t xinputSetupOutBuffer[XINPUT_EP_SIZE]; static uint8_t xinputInBuffer[XINPUT_REPORT_SIZE]; static volatile uint8_t rumbleLeft; static volatile uint8_t rumbleRight; #if (USBD_SUPPORT_USER_STRING_DESC == 1U) static uint8_t xinputStringBuffer[32]; #endif USBD_ClassTypeDef USBD_XINPUT = { USBD_XINPUT_Init, USBD_XINPUT_DeInit, USBD_XINPUT_Setup, NULL, NULL, USBD_XINPUT_DataIn, USBD_XINPUT_DataOut, NULL, NULL, NULL, USBD_XINPUT_GetCfgDesc, USBD_XINPUT_GetCfgDesc, USBD_XINPUT_GetCfgDesc, USBD_XINPUT_GetDeviceQualifierDesc, #if (USBD_SUPPORT_USER_STRING_DESC == 1U) USBD_XINPUT_GetUserString, #endif }; /* Original three-interface layout. Only IF0 implements device behavior. */ __ALIGN_BEGIN static uint8_t USBD_XINPUT_CfgDesc[USB_XINPUT_CONFIG_DESC_SIZ] __ALIGN_END = { 0x09, USB_DESC_TYPE_CONFIGURATION, LOBYTE(USB_XINPUT_CONFIG_DESC_SIZ), HIBYTE(USB_XINPUT_CONFIG_DESC_SIZ), 0x03, /* bNumInterfaces */ 0x01, /* bConfigurationValue */ 0x00, /* iConfiguration */ 0x80, /* Bus powered */ 0x32, /* 100 mA */ 0x09, USB_DESC_TYPE_INTERFACE, 0x00, /* bInterfaceNumber */ 0x00, /* bAlternateSetting */ 0x02, /* bNumEndpoints */ 0xFF, /* Vendor specific */ 0x5D, /* XUSB */ 0x01, /* Xbox 360 wired controller */ 0x00, /* iInterface */ /* XUSB class-specific descriptor from the working controller firmware. */ 0x11, 0x21, 0x10, 0x01, 0x01, 0x25, 0x81, 0x14, 0x03, 0x03, 0x03, 0x04, 0x13, 0x02, 0x08, 0x03, 0x03, 0x07, USB_DESC_TYPE_ENDPOINT, XINPUT_EPIN_ADDR, 0x03, /* Interrupt */ LOBYTE(XINPUT_EP_SIZE), HIBYTE(XINPUT_EP_SIZE), 0x01, 0x07, USB_DESC_TYPE_ENDPOINT, XINPUT_EPOUT_ADDR, 0x03, /* Interrupt */ LOBYTE(XINPUT_EP_SIZE), HIBYTE(XINPUT_EP_SIZE), 0x01, /* IF1: descriptor-compatible Setup HID; data path intentionally stalled. */ 0x09, USB_DESC_TYPE_INTERFACE, 0x01, 0x00, 0x02, 0x03, 0x00, 0x00, 0x06, 0x09, 0x21, 0x11, 0x01, 0x00, 0x01, 0x22, 0x22, 0x00, 0x07, USB_DESC_TYPE_ENDPOINT, XINPUT_SETUP_EPIN_ADDR, 0x03, LOBYTE(XINPUT_EP_SIZE), HIBYTE(XINPUT_EP_SIZE), 0x01, 0x07, USB_DESC_TYPE_ENDPOINT, XINPUT_SETUP_EPOUT_ADDR, 0x03, LOBYTE(XINPUT_EP_SIZE), HIBYTE(XINPUT_EP_SIZE), 0x01, /* IF2: descriptor-compatible Raw HID; data path intentionally stalled. */ 0x09, USB_DESC_TYPE_INTERFACE, 0x02, 0x00, 0x01, 0x03, 0x00, 0x00, 0x07, 0x09, 0x21, 0x11, 0x01, 0x00, 0x01, 0x22, 0x15, 0x00, 0x07, USB_DESC_TYPE_ENDPOINT, XINPUT_RAW_EPIN_ADDR, 0x03, LOBYTE(XINPUT_EP_SIZE), HIBYTE(XINPUT_EP_SIZE), 0x01, }; __ALIGN_BEGIN static uint8_t xinputSetupReportDescriptor[] __ALIGN_END = { 0x06, 0x00, 0xFF, 0x09, 0x01, 0xA1, 0x01, 0x09, 0x10, 0x15, 0x00, 0x26, 0xFF, 0x00, 0x75, 0x08, 0x95, 0x1A, 0x81, 0x02, 0x09, 0x11, 0x15, 0x00, 0x26, 0xFF, 0x00, 0x75, 0x08, 0x95, 0x1B, 0x91, 0x02, 0xC0 }; __ALIGN_BEGIN static uint8_t xinputRawReportDescriptor[] __ALIGN_END = { 0x06, 0x00, 0xFF, 0x09, 0x02, 0xA1, 0x01, 0x09, 0x20, 0x15, 0x00, 0x26, 0xFF, 0x00, 0x75, 0x08, 0x95, 0x18, 0x81, 0x02, 0xC0 }; _Static_assert(sizeof(xinputSetupReportDescriptor) == 34U, "Setup report descriptor length changed"); _Static_assert(sizeof(xinputRawReportDescriptor) == 21U, "Raw report descriptor length changed"); __ALIGN_BEGIN static uint8_t USBD_XINPUT_DeviceQualifierDesc[USB_LEN_DEV_QUALIFIER_DESC] __ALIGN_END = { USB_LEN_DEV_QUALIFIER_DESC, USB_DESC_TYPE_DEVICE_QUALIFIER, 0x00, 0x02, 0x00, 0x00, 0x00, USB_MAX_EP0_SIZE, 0x01, 0x00, }; static uint8_t USBD_XINPUT_Init(USBD_HandleTypeDef *pdev, uint8_t cfgidx) { USBD_XINPUT_HandleTypeDef *handle; UNUSED(cfgidx); USBD_LL_OpenEP(pdev, XINPUT_EPIN_ADDR, USBD_EP_TYPE_INTR, XINPUT_EP_SIZE); pdev->ep_in[XINPUT_EPIN_ADDR & 0x0FU].is_used = 1U; USBD_LL_OpenEP(pdev, XINPUT_EPOUT_ADDR, USBD_EP_TYPE_INTR, XINPUT_EP_SIZE); pdev->ep_out[XINPUT_EPOUT_ADDR & 0x0FU].is_used = 1U; USBD_LL_OpenEP(pdev, XINPUT_SETUP_EPIN_ADDR, USBD_EP_TYPE_INTR, XINPUT_EP_SIZE); pdev->ep_in[XINPUT_SETUP_EPIN_ADDR & 0x0FU].is_used = 1U; USBD_LL_OpenEP(pdev, XINPUT_SETUP_EPOUT_ADDR, USBD_EP_TYPE_INTR, XINPUT_EP_SIZE); pdev->ep_out[XINPUT_SETUP_EPOUT_ADDR & 0x0FU].is_used = 1U; USBD_LL_OpenEP(pdev, XINPUT_RAW_EPIN_ADDR, USBD_EP_TYPE_INTR, XINPUT_EP_SIZE); pdev->ep_in[XINPUT_RAW_EPIN_ADDR & 0x0FU].is_used = 1U; pdev->pClassData = USBD_malloc(sizeof(USBD_XINPUT_HandleTypeDef)); if (pdev->pClassData == NULL) { return USBD_FAIL; } handle = (USBD_XINPUT_HandleTypeDef *)pdev->pClassData; memset(handle, 0, sizeof(*handle)); handle->state = XINPUT_IDLE; rumbleLeft = 0U; rumbleRight = 0U; USBD_LL_PrepareReceive(pdev, XINPUT_EPOUT_ADDR, xinputOutBuffer, XINPUT_EP_SIZE); /* Stub HID data paths: unused IN endpoints naturally answer NAK. Setup OUT * packets are accepted and discarded so Windows can start both interfaces. */ USBD_LL_PrepareReceive(pdev, XINPUT_SETUP_EPOUT_ADDR, xinputSetupOutBuffer, XINPUT_EP_SIZE); return USBD_OK; } static uint8_t USBD_XINPUT_DeInit(USBD_HandleTypeDef *pdev, uint8_t cfgidx) { UNUSED(cfgidx); USBD_LL_CloseEP(pdev, XINPUT_EPIN_ADDR); pdev->ep_in[XINPUT_EPIN_ADDR & 0x0FU].is_used = 0U; USBD_LL_CloseEP(pdev, XINPUT_EPOUT_ADDR); pdev->ep_out[XINPUT_EPOUT_ADDR & 0x0FU].is_used = 0U; USBD_LL_CloseEP(pdev, XINPUT_SETUP_EPIN_ADDR); pdev->ep_in[XINPUT_SETUP_EPIN_ADDR & 0x0FU].is_used = 0U; USBD_LL_CloseEP(pdev, XINPUT_SETUP_EPOUT_ADDR); pdev->ep_out[XINPUT_SETUP_EPOUT_ADDR & 0x0FU].is_used = 0U; USBD_LL_CloseEP(pdev, XINPUT_RAW_EPIN_ADDR); pdev->ep_in[XINPUT_RAW_EPIN_ADDR & 0x0FU].is_used = 0U; if (pdev->pClassData != NULL) { USBD_free(pdev->pClassData); pdev->pClassData = NULL; } return USBD_OK; } static uint8_t USBD_XINPUT_Setup(USBD_HandleTypeDef *pdev, USBD_SetupReqTypedef *req) { static uint8_t capability20[20] = { 0x00, 0x14, 0xF7, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0xFF, 0xC0, 0xFF, 0xC0, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; static uint8_t capability8[8] = { 0x00, 0x08, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00 }; static uint8_t status4[4] = { 0x28, 0x00, 0x00, 0x00 }; USBD_XINPUT_HandleTypeDef *handle = (USBD_XINPUT_HandleTypeDef *)pdev->pClassData; uint16_t statusInfo = 0U; uint8_t interface = (uint8_t)req->wIndex; uint8_t *descriptor = NULL; uint16_t descriptorLength = 0U; if ((req->bmRequest == 0xC1U) && (req->bRequest == 0x01U) && (req->wValue == 0x0100U) && (req->wIndex == 0x0000U) && (req->wLength == sizeof(capability20))) { USBD_CtlSendData(pdev, capability20, sizeof(capability20)); return USBD_OK; } if ((req->bmRequest == 0xC1U) && (req->bRequest == 0x01U) && (req->wValue == 0x0000U) && (req->wIndex == 0x0000U) && (req->wLength == sizeof(capability8))) { USBD_CtlSendData(pdev, capability8, sizeof(capability8)); return USBD_OK; } if ((req->bmRequest == 0xC0U) && (req->bRequest == 0x01U) && (req->wValue == 0x0000U) && (req->wIndex == 0x0000U) && (req->wLength == sizeof(status4))) { USBD_CtlSendData(pdev, status4, sizeof(status4)); return USBD_OK; } /* No Microsoft OS descriptors or compatible-ID vendor request exists. */ if ((req->bmRequest & USB_REQ_TYPE_MASK) == USB_REQ_TYPE_CLASS) { USBD_CtlError(pdev, req); return USBD_FAIL; } if (((req->bmRequest & USB_REQ_TYPE_MASK) == USB_REQ_TYPE_STANDARD) && (interface <= 2U)) { switch (req->bRequest) { case USB_REQ_GET_STATUS: USBD_CtlSendData(pdev, (uint8_t *)&statusInfo, sizeof(statusInfo)); return USBD_OK; case USB_REQ_GET_INTERFACE: if (handle == NULL) { break; } USBD_CtlSendData(pdev, &handle->AltSetting[interface], 1U); return USBD_OK; case USB_REQ_SET_INTERFACE: if (handle == NULL) { break; } if (req->wValue == 0U) { handle->AltSetting[interface] = 0U; return USBD_OK; } break; case USB_REQ_GET_DESCRIPTOR: if ((interface == 1U) && ((req->wValue >> 8) == 0x22U)) { descriptor = xinputSetupReportDescriptor; descriptorLength = sizeof(xinputSetupReportDescriptor); } else if ((interface == 2U) && ((req->wValue >> 8) == 0x22U)) { descriptor = xinputRawReportDescriptor; descriptorLength = sizeof(xinputRawReportDescriptor); } else if (((interface == 1U) || (interface == 2U)) && ((req->wValue >> 8) == 0x21U)) { descriptor = &USBD_XINPUT_CfgDesc[(interface == 1U) ? 58U : 90U]; descriptorLength = 9U; } if (descriptor != NULL) { USBD_CtlSendData(pdev, descriptor, MIN(descriptorLength, req->wLength)); return USBD_OK; } break; default: break; } } USBD_CtlError(pdev, req); return USBD_FAIL; } uint8_t USBD_XINPUT_SendReport(USBD_HandleTypeDef *pdev, uint8_t *report, uint16_t len) { USBD_XINPUT_HandleTypeDef *handle = (USBD_XINPUT_HandleTypeDef *)pdev->pClassData; if ((pdev->dev_state == USBD_STATE_CONFIGURED) && (handle != NULL) && (handle->state == XINPUT_IDLE) && (len == XINPUT_REPORT_SIZE)) { handle->state = XINPUT_BUSY; memcpy(xinputInBuffer, report, XINPUT_REPORT_SIZE); USBD_LL_Transmit(pdev, XINPUT_EPIN_ADDR, xinputInBuffer, len); } return USBD_OK; } static uint8_t USBD_XINPUT_DataIn(USBD_HandleTypeDef *pdev, uint8_t epnum) { if ((epnum == (XINPUT_EPIN_ADDR & 0x0FU)) && (pdev->pClassData != NULL)) { ((USBD_XINPUT_HandleTypeDef *)pdev->pClassData)->state = XINPUT_IDLE; return USBD_OK; } return USBD_FAIL; } static uint8_t USBD_XINPUT_DataOut(USBD_HandleTypeDef *pdev, uint8_t epnum) { uint32_t length; if (epnum == (XINPUT_SETUP_EPOUT_ADDR & 0x0FU)) { /* No setup protocol is implemented. Discard data and keep the endpoint * alive rather than halting the whole HID interface. */ USBD_LL_PrepareReceive(pdev, XINPUT_SETUP_EPOUT_ADDR, xinputSetupOutBuffer, XINPUT_EP_SIZE); return USBD_OK; } if (epnum != (XINPUT_EPOUT_ADDR & 0x0FU)) { return USBD_FAIL; } length = USBD_LL_GetRxDataSize(pdev, XINPUT_EPOUT_ADDR); if ((length == 8U) && (xinputOutBuffer[0] == 0x00U) && (xinputOutBuffer[1] == 0x08U)) { rumbleLeft = xinputOutBuffer[3]; rumbleRight = xinputOutBuffer[4]; } USBD_LL_PrepareReceive(pdev, XINPUT_EPOUT_ADDR, xinputOutBuffer, XINPUT_EP_SIZE); return USBD_OK; } uint8_t USBD_XINPUT_GetRumbleLeft(void) { return rumbleLeft; } uint8_t USBD_XINPUT_GetRumbleRight(void) { return rumbleRight; } static uint8_t *USBD_XINPUT_GetCfgDesc(uint16_t *length) { *length = sizeof(USBD_XINPUT_CfgDesc); return USBD_XINPUT_CfgDesc; } static uint8_t *USBD_XINPUT_GetDeviceQualifierDesc(uint16_t *length) { *length = sizeof(USBD_XINPUT_DeviceQualifierDesc); return USBD_XINPUT_DeviceQualifierDesc; } #if (USBD_SUPPORT_USER_STRING_DESC == 1U) static uint8_t *USBD_XINPUT_GetUserString(USBD_HandleTypeDef *pdev, uint8_t index, uint16_t *length) { const char *text; UNUSED(pdev); if (index == 0x06U) { text = "Setup"; } else if (index == 0x07U) { text = "Raw"; } else { *length = 0U; return NULL; } USBD_GetString((uint8_t *)text, xinputStringBuffer, length); return xinputStringBuffer; } #endif