From 7078ea9f323a1247547b6255742426400782baa3 Mon Sep 17 00:00:00 2001 From: Kristian Sloth Lauszus Date: Thu, 9 Nov 2017 18:56:40 +0100 Subject: [PATCH] Extract the endpoints from the Xbox One controller instead of hardcoding them Also cleaned up the code a bit Fixes #305 --- XBOXONE.cpp | 154 ++++++++++++++++++++++++++++++++++------------------ XBOXONE.h | 62 +++++++++++++++------ 2 files changed, 147 insertions(+), 69 deletions(-) diff --git a/XBOXONE.cpp b/XBOXONE.cpp index 9c5b388f..2c9cb392 100644 --- a/XBOXONE.cpp +++ b/XBOXONE.cpp @@ -27,8 +27,11 @@ XBOXONE::XBOXONE(USB *p) : pUsb(p), // pointer to USB class instance - mandatory bAddress(0), // device address - mandatory +bNumEP(1), // If config descriptor needs to be parsed +qNextPollTime(0), // Reset NextPollTime +pollInterval(0), bPollEnable(false) { // don't start polling before dongle is connected - for(uint8_t i = 0; i < XBOX_MAX_ENDPOINTS; i++) { + for(uint8_t i = 0; i < XBOX_ONE_MAX_ENDPOINTS; i++) { epInfo[i].epAddr = 0; epInfo[i].maxPktSize = (i) ? 0 : 8; epInfo[i].bmSndToggle = 0; @@ -46,8 +49,8 @@ uint8_t XBOXONE::Init(uint8_t parent, uint8_t port, bool lowspeed) { uint8_t rcode; UsbDevice *p = NULL; EpInfo *oldep_ptr = NULL; - uint16_t PID; - uint16_t VID; + uint16_t PID, VID; + uint8_t num_of_conf; // Number of configurations // get memory address of USB device address pool AddressPool &addrPool = pUsb->GetAddressPool(); @@ -142,31 +145,30 @@ uint8_t XBOXONE::Init(uint8_t parent, uint8_t port, bool lowspeed) { if(rcode) goto FailSetDevTblEntry; - /* The application will work in reduced host mode, so we can save program and data - memory space. After verifying the VID we will use known values for the - configuration values for device, interface, endpoints and HID for the XBOXONE Controllers */ + num_of_conf = udd->bNumConfigurations; // Number of configurations - /* Initialize data structures for endpoints of device */ - epInfo[ XBOX_OUTPUT_PIPE ].epAddr = 0x01; // XBOX one output endpoint - epInfo[ XBOX_OUTPUT_PIPE ].epAttribs = USB_TRANSFER_TYPE_INTERRUPT; - epInfo[ XBOX_OUTPUT_PIPE ].bmNakPower = USB_NAK_NOWAIT; // Only poll once for interrupt endpoints - epInfo[ XBOX_OUTPUT_PIPE ].maxPktSize = EP_MAXPKTSIZE; - epInfo[ XBOX_OUTPUT_PIPE ].bmSndToggle = 0; - epInfo[ XBOX_OUTPUT_PIPE ].bmRcvToggle = 0; - epInfo[ XBOX_INPUT_PIPE ].epAddr = 0x01; // XBOX one input endpoint - epInfo[ XBOX_INPUT_PIPE ].epAttribs = USB_TRANSFER_TYPE_INTERRUPT; - epInfo[ XBOX_INPUT_PIPE ].bmNakPower = USB_NAK_NOWAIT; // Only poll once for interrupt endpoints - epInfo[ XBOX_INPUT_PIPE ].maxPktSize = EP_MAXPKTSIZE; - epInfo[ XBOX_INPUT_PIPE ].bmSndToggle = 0; - epInfo[ XBOX_INPUT_PIPE ].bmRcvToggle = 0; + USBTRACE2("NC:", num_of_conf); - rcode = pUsb->setEpInfoEntry(bAddress, 3, epInfo); + // Check if attached device is a Xbox One controller and fill endpoint data structure + for(uint8_t i = 0; i < num_of_conf; i++) { + ConfigDescParser<0, 0, 0, 0> confDescrParser(this); // Allow all devices, as we have already verified that it is a Xbox One controller from the VID and PID + rcode = pUsb->getConfDescr(bAddress, 0, i, &confDescrParser); + if(rcode) // Check error code + goto FailGetConfDescr; + if(bNumEP >= XBOX_ONE_MAX_ENDPOINTS) // All endpoints extracted + break; + } + + if(bNumEP < XBOX_ONE_MAX_ENDPOINTS) + goto FailUnknownDevice; + + rcode = pUsb->setEpInfoEntry(bAddress, bNumEP, epInfo); if(rcode) goto FailSetDevTblEntry; delay(200); // Give time for address change - rcode = pUsb->setConf(bAddress, epInfo[ XBOX_CONTROL_PIPE ].epAddr, 1); + rcode = pUsb->setConf(bAddress, epInfo[ XBOX_ONE_CONTROL_PIPE ].epAddr, bConfNum); if(rcode) goto FailSetConfDescr; @@ -176,7 +178,8 @@ uint8_t XBOXONE::Init(uint8_t parent, uint8_t port, bool lowspeed) { delay(200); // let things settle - // initialize the controller for input + // Initialize the controller for input + uint8_t writeBuf[5]; writeBuf[0] = 0x05; writeBuf[1] = 0x20; writeBuf[2] = 0x00; @@ -204,6 +207,12 @@ FailSetDevTblEntry: goto Fail; #endif +FailGetConfDescr: +#ifdef DEBUG_USB_HOST + NotifyFailGetConfDescr(); + goto Fail; +#endif + FailSetConfDescr: #ifdef DEBUG_USB_HOST NotifyFailSetConfDescr(); @@ -225,11 +234,53 @@ Fail: return rcode; } +/* Extracts endpoint information from config descriptor */ +void XBOXONE::EndpointXtract(uint8_t conf, uint8_t iface, uint8_t alt, uint8_t proto, const USB_ENDPOINT_DESCRIPTOR *pep) { + bConfNum = conf; + uint8_t index; + + if((pep->bmAttributes & bmUSB_TRANSFER_TYPE) == USB_TRANSFER_TYPE_INTERRUPT) { // Interrupt endpoint + index = (pep->bEndpointAddress & 0x80) == 0x80 ? XBOX_ONE_INPUT_PIPE : XBOX_ONE_OUTPUT_PIPE; // Set the endpoint index + } else + return; + + // Fill the rest of endpoint data structure + epInfo[index].epAddr = (pep->bEndpointAddress & 0x0F); + epInfo[index].maxPktSize = (uint8_t)pep->wMaxPacketSize; +#ifdef EXTRADEBUG + PrintEndpointDescriptor(pep); +#endif + if(pollInterval < pep->bInterval) // Set the polling interval as the largest polling interval obtained from endpoints + pollInterval = pep->bInterval; + bNumEP++; +} + +void XBOXONE::PrintEndpointDescriptor(const USB_ENDPOINT_DESCRIPTOR* ep_ptr) { +#ifdef EXTRADEBUG + Notify(PSTR("\r\nEndpoint descriptor:"), 0x80); + Notify(PSTR("\r\nLength:\t\t"), 0x80); + D_PrintHex (ep_ptr->bLength, 0x80); + Notify(PSTR("\r\nType:\t\t"), 0x80); + D_PrintHex (ep_ptr->bDescriptorType, 0x80); + Notify(PSTR("\r\nAddress:\t"), 0x80); + D_PrintHex (ep_ptr->bEndpointAddress, 0x80); + Notify(PSTR("\r\nAttributes:\t"), 0x80); + D_PrintHex (ep_ptr->bmAttributes, 0x80); + Notify(PSTR("\r\nMaxPktSize:\t"), 0x80); + D_PrintHex (ep_ptr->wMaxPacketSize, 0x80); + Notify(PSTR("\r\nPoll Intrv:\t"), 0x80); + D_PrintHex (ep_ptr->bInterval, 0x80); +#endif +} + /* Performs a cleanup after failed Init() attempt */ uint8_t XBOXONE::Release() { XboxOneConnected = false; pUsb->GetAddressPool().FreeAddress(bAddress); - bAddress = 0; + bAddress = 0; // Clear device address + bNumEP = 1; // Must have to be reset to 1 + qNextPollTime = 0; // Reset next poll time + pollInterval = 0; bPollEnable = false; #ifdef DEBUG_USB_HOST Notify(PSTR("\r\nXbox One Controller Disconnected\r\n"), 0x80); @@ -238,28 +289,36 @@ uint8_t XBOXONE::Release() { } uint8_t XBOXONE::Poll() { + uint8_t rcode = 0; + if(!bPollEnable) return 0; - uint16_t BUFFER_SIZE = EP_MAXPKTSIZE; - uint8_t rcode = pUsb->inTransfer(bAddress, epInfo[ XBOX_INPUT_PIPE ].epAddr, &BUFFER_SIZE, readBuf); - if (!rcode) { - readReport(); -#ifdef PRINTREPORT - printReport(); // Uncomment "#define PRINTREPORT" to print the report send by the Xbox ONE Controller + + if((int32_t)((uint32_t)millis() - qNextPollTime) >= 0L) { // Do not poll if shorter than polling interval + qNextPollTime = (uint32_t)millis() + pollInterval; // Set new poll time + uint16_t length = (uint16_t)epInfo[ XBOX_ONE_INPUT_PIPE ].maxPktSize; // Read the maximum packet size from the endpoint + uint8_t rcode = pUsb->inTransfer(bAddress, epInfo[ XBOX_ONE_INPUT_PIPE ].epAddr, &length, readBuf, pollInterval); + if(!rcode) { + readReport(); +#ifdef PRINTREPORT // Uncomment "#define PRINTREPORT" to print the report send by the Xbox ONE Controller + for(uint8_t i = 0; i < length; i++) { + D_PrintHex (readBuf[i], 0x80); + Notify(PSTR(" "), 0x80); + } + Notify(PSTR("\r\n"), 0x80); #endif - } + } #ifdef DEBUG_USB_HOST - else if (rcode != 0x04) { // not a matter of no update to send - Notify(PSTR("\r\nXbox One Poll Failed, error code: "), 0x80); - NotifyFail(rcode); + else if(rcode != hrNAK) { // Not a matter of no update to send + Notify(PSTR("\r\nXbox One Poll Failed, error code: "), 0x80); + NotifyFail(rcode); } #endif - return rcode; + } + return rcode; } void XBOXONE::readReport() { - if(readBuf == NULL) - return; if(readBuf[0] == 0x07) { // The XBOX button has a separate message if(readBuf[4] == 1) @@ -302,25 +361,13 @@ void XBOXONE::readReport() { // Handle click detection for triggers if(triggerValue[0] != 0 && triggerValueOld[0] == 0) - L2Clicked = true; + L2Clicked = true; triggerValueOld[0] = triggerValue[0]; if(triggerValue[1] != 0 && triggerValueOld[1] == 0) - R2Clicked = true; + R2Clicked = true; triggerValueOld[1] = triggerValue[1]; } -void XBOXONE::printReport() { //Uncomment "#define PRINTREPORT" to print the report send by the Xbox ONE Controller -#ifdef PRINTREPORT - if(readBuf == NULL) - return; - for(uint8_t i = 0; i < XBOX_REPORT_BUFFER_SIZE; i++) { - D_PrintHex (readBuf[i], 0x80); - Notify(PSTR(" "), 0x80); - } - Notify(PSTR("\r\n"), 0x80); -#endif -} - uint16_t XBOXONE::getButtonPress(ButtonEnum b) { if(b == L2) // These are analog buttons return triggerValue[0]; @@ -345,7 +392,7 @@ bool XBOXONE::getButtonClick(ButtonEnum b) { } uint16_t button = pgm_read_word(&XBOX_BUTTONS[(uint8_t)b]); bool click = (ButtonClickState & button); - ButtonClickState &= ~button; // clear "click" event + ButtonClickState &= ~button; // Clear "click" event return click; } @@ -355,7 +402,7 @@ int16_t XBOXONE::getAnalogHat(AnalogHatEnum a) { /* Xbox Controller commands */ uint8_t XBOXONE::XboxCommand(uint8_t* data, uint16_t nbytes) { - uint8_t rcode = pUsb->outTransfer(bAddress, epInfo[ XBOX_OUTPUT_PIPE ].epAddr, nbytes, data); + uint8_t rcode = pUsb->outTransfer(bAddress, epInfo[ XBOX_ONE_OUTPUT_PIPE ].epAddr, nbytes, data); #ifdef DEBUG_USB_HOST Notify(PSTR("\r\nXboxCommand, Return: "), 0x80); D_PrintHex (rcode, 0x80); @@ -364,7 +411,8 @@ uint8_t XBOXONE::XboxCommand(uint8_t* data, uint16_t nbytes) { } void XBOXONE::onInit() { - // a short buzz to show the controller is active + // A short buzz to show the controller is active + uint8_t writeBuf[11]; writeBuf[0] = 0x09; writeBuf[1] = 0x08; writeBuf[2] = 0x00; diff --git a/XBOXONE.h b/XBOXONE.h index ab309d34..131a1fbd 100644 --- a/XBOXONE.h +++ b/XBOXONE.h @@ -26,15 +26,17 @@ #include "Usb.h" #include "xboxEnums.h" -/* Data Xbox ONE taken from descriptors */ -#define EP_MAXPKTSIZE 32 // max size for data via USB +/* Xbox One data taken from descriptors */ +#define XBOX_ONE_EP_MAXPKTSIZE 64 // Max size for data via USB /* Names we give to the 3 XboxONE pipes */ -#define XBOX_CONTROL_PIPE 0 -#define XBOX_OUTPUT_PIPE 1 -#define XBOX_INPUT_PIPE 2 +#define XBOX_ONE_CONTROL_PIPE 0 +#define XBOX_ONE_OUTPUT_PIPE 1 +#define XBOX_ONE_INPUT_PIPE 2 -// PID and VID of the different devices - see: https://github.com/torvalds/linux/blob/master/drivers/input/joystick/xpad.c +#define XBOX_ONE_MAX_ENDPOINTS 3 + +// PID and VID of the different versions of the controller - see: https://github.com/torvalds/linux/blob/master/drivers/input/joystick/xpad.c // Official controllers #define XBOX_VID1 0x045E // Microsoft Corporation @@ -59,12 +61,8 @@ #define XBOX_ONE_PID11 0x542A // Xbox ONE spectra #define XBOX_ONE_PID12 0x543A // PowerA Xbox One wired controller -#define XBOX_REPORT_BUFFER_SIZE 14 // Size of the input report buffer - -#define XBOX_MAX_ENDPOINTS 3 - /** This class implements support for a Xbox ONE controller connected via USB. */ -class XBOXONE : public USBDeviceConfig { +class XBOXONE : public USBDeviceConfig, public UsbConfigXtracter { public: /** * Constructor for the XBOXONE class. @@ -108,6 +106,14 @@ public: return bPollEnable; }; + /** + * Read the poll interval taken from the endpoint descriptors. + * @return The poll interval in ms. + */ + uint8_t readPollInterval() { + return pollInterval; + }; + /** * Used by the USB core to check what this driver support. * @param vid The device's VID. @@ -161,7 +167,32 @@ protected: /** Device address. */ uint8_t bAddress; /** Endpoint info structure. */ - EpInfo epInfo[XBOX_MAX_ENDPOINTS]; + EpInfo epInfo[XBOX_ONE_MAX_ENDPOINTS]; + + /** Configuration number. */ + uint8_t bConfNum; + /** Total number of endpoints in the configuration. */ + uint8_t bNumEP; + /** Next poll time based on poll interval taken from the USB descriptor. */ + uint32_t qNextPollTime; + + /** @name UsbConfigXtracter implementation */ + /** + * UsbConfigXtracter implementation, used to extract endpoint information. + * @param conf Configuration value. + * @param iface Interface number. + * @param alt Alternate setting. + * @param proto Interface Protocol. + * @param ep Endpoint Descriptor. + */ + void EndpointXtract(uint8_t conf, uint8_t iface, uint8_t alt, uint8_t proto, const USB_ENDPOINT_DESCRIPTOR *ep); + /**@}*/ + + /** + * Used to print the USB Endpoint Descriptor. + * @param ep_ptr Pointer to USB Endpoint Descriptor. + */ + void PrintEndpointDescriptor(const USB_ENDPOINT_DESCRIPTOR* ep_ptr); private: /** @@ -171,6 +202,7 @@ private: void onInit(); void (*pFuncOnInit)(void); // Pointer to function called in onInit() + uint8_t pollInterval; bool bPollEnable; /* Variables to store the buttons */ @@ -184,11 +216,9 @@ private: bool L2Clicked; // These buttons are analog, so we use we use these bools to check if they where clicked or not bool R2Clicked; - uint8_t readBuf[EP_MAXPKTSIZE]; // General purpose buffer for input data - uint8_t writeBuf[12]; // General purpose buffer for output data + uint8_t readBuf[XBOX_ONE_EP_MAXPKTSIZE]; // General purpose buffer for input data - void readReport(); // read incoming data - void printReport(); // print incoming date - Uncomment for debugging + void readReport(); // Used to read the incoming data /* Private commands */ uint8_t XboxCommand(uint8_t* data, uint16_t nbytes);