diff --git a/lib_xua/src/core/endpoint0/descriptor_defs.h b/lib_xua/src/core/endpoint0/descriptor_defs.h index b348d6f1..53ba28ed 100644 --- a/lib_xua/src/core/endpoint0/descriptor_defs.h +++ b/lib_xua/src/core/endpoint0/descriptor_defs.h @@ -60,4 +60,8 @@ enum USBInterfaceNumber INTERFACE_COUNT /* End marker */ }; +#if( 0 < HID_CONTROLS ) +#define ENDPOINT_INT_INTERVAL_IN_HID 0x08 +#endif + #endif diff --git a/lib_xua/src/core/endpoint0/xua_ep0_descriptors.h b/lib_xua/src/core/endpoint0/xua_ep0_descriptors.h index 0c2c3eb5..066319f4 100644 --- a/lib_xua/src/core/endpoint0/xua_ep0_descriptors.h +++ b/lib_xua/src/core/endpoint0/xua_ep0_descriptors.h @@ -2184,7 +2184,7 @@ USB_Config_Descriptor_Audio2_t cfgDesc_Audio2= ENDPOINT_ADDRESS_IN_HID, /* 2 bEndpointAddress */ 3, /* 3 bmAttributes (INTERRUPT) */ 64, /* 4 wMaxPacketSize */ - 8, /* 6 bInterval */ + ENDPOINT_INT_INTERVAL_IN_HID, /* 6 bInterval */ } #endif @@ -2862,7 +2862,7 @@ unsigned char cfgDesc_Audio1[] = 0x03, /* 3 bmAttributes (INTERRUPT) */ 0x40, /* 4 wMaxPacketSize */ 0x00, /* 5 wMaxPacketSize */ - 0x08, /* 6 bInterval */ + ENDPOINT_INT_INTERVAL_IN_HID, /* 6 bInterval */ #endif }; diff --git a/lib_xua/src/hid/hid.xc b/lib_xua/src/hid/hid.xc index fd7cb32b..1e92e283 100644 --- a/lib_xua/src/hid/hid.xc +++ b/lib_xua/src/hid/hid.xc @@ -1,26 +1,192 @@ #include -#include "xud.h" +#include "descriptor_defs.h" #include "hid.h" +#include "xud.h" #include "xud_std_requests.h" #include "xua_hid.h" -static unsigned hidSetIdle = 0; +#define MS_IN_TICKS 100000U -unsigned HidIsSetIdleSilenced(void) +static unsigned s_hidIdleActive = 0; +static unsigned s_hidCurrentPeriod = ENDPOINT_INT_INTERVAL_IN_HID * MS_IN_TICKS; +static unsigned s_hidNextReportTime = 0; +static unsigned s_hidReportTime = 0; + +/** + * \brief Calculate the difference between two points in time + * + * This function calculates the difference between two two points in time. + * It always returns a positive value even if the timer used to obtain the two + * time measurements has wrapped around. + * + * \warning If time values have been obtained from a timer that has wrapped + * more than once in between the two measurements, this function returns an + * incorrect value. + * + * \param[in] earlierTime -- A value from a timer + * \param[in] laterTime -- A value from a timer taken after \a earlierTime + * + * \return The interval between the two points in time + */ +static unsigned HidTimeDiff( const unsigned earlierTime, const unsigned laterTime ) { - return hidSetIdle; + return ( earlierTime < laterTime ) ? laterTime - earlierTime : UINT_MAX - earlierTime + laterTime; } -XUD_Result_t HidInterfaceClassRequests(XUD_ep c_ep0_out, XUD_ep c_ep0_in, - USB_SetupPacket_t &sp) +/** + * \brief Calculate the time interval between the most recent HID Report and a subsequent Set Idle Request + * + * \warning For this function to produce an accurate interval measument, it must be called without delay + * upon receiving a Set Idle Request from the USB Host. + * + * \param[in] reportTime -- The time at which the last HID Report was sent + * + * \return The time interval between receiving the Set Idle Request and sending the most recent HID Report + */ +static unsigned HidCalcReportToSetIdleInterval( const unsigned reportTime ) { - switch (sp.bRequest) { - case HID_SET_IDLE: - printstr("HID_SET_IDLE\n"); - hidSetIdle = 1; // TODO implement duration - return XUD_DoSetRequestStatus(c_ep0_in); - default: - break; - } - return XUD_RES_ERR; + timer tmr; + unsigned setIdleTime; + + tmr :> setIdleTime; + unsigned result = HidTimeDiff( reportTime, setIdleTime ); + return result; +} + +/** + * \brief Indicate if activation of the Set Idle Request happens at the previous or next HID Report + * + * Section 7.2.4 Set_Idle Request of the USB Device Class Definition for Human Interface + * Devices (HID) Version 1.11 makes two statements about the activation point for starting the + * duration of the request: + * - 'A new request will be executed as if it were issued immediately after the last report, if + * the new request is received at least 4 milliseconds before the end of the currently executing + * period.' + * - 'If the new request is received within 4 milliseconds of the end of the current period, then + * the new request will have no effect until after the report.' + * + * \param[in] currentPeriod -- The duration of the current period + * \param[in] timeWithinPeriod -- The current point in time relative to the current period + * + * \return A Boolean indicating where the activation of the Set Idle Request Duration occurs. + * \retval 1 -- Activate immediately after the next HID Report + * \retval 0 -- Activate immediately after the previous HID Report + */ +static unsigned HidFindSetIdleActivationPoint( const unsigned currentPeriod, const unsigned timeWithinPeriod ) +{ + unsigned result = (( currentPeriod - timeWithinPeriod ) < ( 4U * MS_IN_TICKS )) ? 1 : 0; + + return result; +} + +/** + * \brief Calculate the timer value for sending the next HID Report. + * + * With regard to Section 7.2.4 Set_Idle Request of the USB Device Class Definition for Human + * Interface Devices (HID) Version 1.11, I've interpreted 'currently executing period' and + * 'current period' to mean the previously established Set Idle duration if one has been + * established or the polling interval from the HID Report Descriptor if a Set Idle duration + * has not been established. + * + * \param[in] currentPeriod -- The duration of the current period in timer ticks + * \param[in] reportTime -- The time at which the last HID Report was sent + * \param[in] reportToSetIdleInterval -- The time interval between receiving the Set Idle Request + * and sending the most recent HID Report + * \param[in] nextPeriod -- The new period value in timer ticks + * + * \return The time at which the next HID Report should be sent + */ +static unsigned HidCalcNewReportTime( const unsigned currentPeriod, const unsigned reportTime, const unsigned reportToSetIdleInterval, const unsigned nextPeriod ) +{ + unsigned nextReportTime = 0; + + if( HidFindSetIdleActivationPoint( currentPeriod, reportToSetIdleInterval )) { + /* Activate immediately after sending the next HID Report */ + nextReportTime = reportTime + currentPeriod; + } else { + /* Activate immediately after sending the most recent HID Report */ + nextReportTime = reportTime + nextPeriod; + } + + return nextReportTime; +} + +void HidCalcNextReportTime( void ) +{ + s_hidNextReportTime = s_hidReportTime + s_hidCurrentPeriod; +} + +void HidCaptureReportTime( void ) +{ + timer tmr; + tmr :> s_hidReportTime; +} + +XUD_Result_t HidInterfaceClassRequests( + XUD_ep c_ep0_out, + XUD_ep c_ep0_in, + USB_SetupPacket_t &sp ) +{ + XUD_Result_t result = XUD_RES_ERR; + + switch ( sp.bRequest ) { + case HID_SET_IDLE: + printstr("HID_SET_IDLE\n"); + /* + The Set Idle request wValue field contains two sub-fields: + - Duration in the MSB; and + - Report ID in the LSB. + + The Duration field specifies how long the USB Device responds with NAK provided the HID data hasn't changed. + Zero means indefinitely. + The value is in units of 4ms. + + The Report ID identifies the HID report that the USB Host wishes to silence. + + The Set Idle request xIndex field contains the interface number. + */ + uint8_t duration = ( sp.wValue & 0xFF00 ) >> 6; // Transform into units of 1ms. + uint8_t reportId = sp.wValue & 0x00FF; + uint16_t interfaceNum = ( sp.wIndex & 0xFF00 ) >> 8; + + /* + As long as our HID Report Descriptor does not include a Report ID, any Report ID value other than zero + indicates an error by the USB Host (see xua_ep0_descriptors.h for the definition of the HID + Report Descriptor). + + Any Interface value other than INTERFACE_NUMBER_HID indicates an error by the USB Host. + */ + if(( 0U == reportId ) && ( interfaceNum == INTERFACE_NUMBER_HID )) { + s_hidIdleActive = !(( 0 < duration ) && ( duration < ENDPOINT_INT_INTERVAL_IN_HID )); + + if( s_hidIdleActive ) { + unsigned reportToSetIdleInterval = HidCalcReportToSetIdleInterval( s_hidReportTime ); + s_hidNextReportTime = HidCalcNewReportTime( s_hidCurrentPeriod, s_hidReportTime, reportToSetIdleInterval, duration * MS_IN_TICKS ); + s_hidCurrentPeriod = duration * MS_IN_TICKS; + } else { + s_hidCurrentPeriod = ENDPOINT_INT_INTERVAL_IN_HID * MS_IN_TICKS; + } + + result = XUD_DoSetRequestStatus( c_ep0_in ); + } + break; + + default: + break; + } + + return result; +} + +unsigned HidIsSetIdleSilenced( void ) +{ + unsigned isSilenced = s_hidIdleActive; + + if( s_hidIdleActive ) { + unsigned currentTime; + asm volatile( "gettime %0" : "=r" ( currentTime )); // Use inline assembly to access the time without creating a side-effect + isSilenced = timeafter( s_hidNextReportTime, currentTime ); + } + + return isSilenced; } diff --git a/lib_xua/src/hid/xua_hid.h b/lib_xua/src/hid/xua_hid.h index df89dc55..c3dfcc66 100644 --- a/lib_xua/src/hid/xua_hid.h +++ b/lib_xua/src/hid/xua_hid.h @@ -3,7 +3,13 @@ #include "xud.h" #include "xud_std_requests.h" -XUD_Result_t HidInterfaceClassRequests(XUD_ep c_ep0_out, XUD_ep c_ep0_in, - REFERENCE_PARAM(USB_SetupPacket_t, sp)); +void HidCalcNextReportTime( void ); -unsigned HidIsSetIdleSilenced(void); +void HidCaptureReportTime( void ); + +XUD_Result_t HidInterfaceClassRequests( + XUD_ep c_ep0_out, + XUD_ep c_ep0_in, + REFERENCE_PARAM( USB_SetupPacket_t, sp )); + +unsigned HidIsSetIdleSilenced( void );