diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 35c363bc..e3d99159 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,6 +5,7 @@ lib_xua Change Log ---------- * ADDED: UAC1 HID support with simulated Voice Command detection reported every 10 seconds + * ADDED: Support for USB HID Set Idle request 0.2.0 ----- diff --git a/lib_xua/src/core/endpoint0/descriptor_defs.h b/lib_xua/src/core/endpoint0/descriptor_defs.h index b348d6f1..1f29d58e 100644 --- a/lib_xua/src/core/endpoint0/descriptor_defs.h +++ b/lib_xua/src/core/endpoint0/descriptor_defs.h @@ -3,6 +3,11 @@ #ifndef __DESCRIPTOR_DEFS_H__ #define __DESCRIPTOR_DEFS_H__ +/* + Include xua.h to pick up the #defines of NUM_USB_CHAN_IN and NUM_USB_CHAN_OUT. + */ +#include "xua.h" + #if (NUM_USB_CHAN_IN > 0) && (NUM_USB_CHAN_OUT > 0) #define AUDIO_INTERFACE_COUNT 3 #elif (NUM_USB_CHAN_IN > 0) || (NUM_USB_CHAN_OUT > 0) @@ -60,4 +65,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_endpoint0.c b/lib_xua/src/core/endpoint0/xua_endpoint0.c index c59886c6..7b42909b 100755 --- a/lib_xua/src/core/endpoint0/xua_endpoint0.c +++ b/lib_xua/src/core/endpoint0/xua_endpoint0.c @@ -60,7 +60,7 @@ extern void device_reboot(void); #endif -#if HID_CONTROLS +#if( 0 < HID_CONTROLS ) #include "xua_hid.h" #endif @@ -993,7 +993,7 @@ void XUA_Endpoint0_lite_loop(XUD_Result_t result, USB_SetupPacket_t sp, chanend } } #endif -#if HID_CONTROLS +#if( 0 < HID_CONTROLS ) if (interfaceNum == INTERFACE_NUMBER_HID) { result = HidInterfaceClassRequests(ep0_out, ep0_in, &sp); 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..bdd5aef3 100644 --- a/lib_xua/src/hid/hid.xc +++ b/lib_xua/src/hid/hid.xc @@ -1,26 +1,221 @@ #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; +#if( 0 < HID_CONTROLS ) +#define MS_IN_TICKS 100000U -unsigned HidIsSetIdleSilenced(void) +static unsigned s_hidIdleActive = 0U; +static unsigned s_hidCurrentPeriod = ENDPOINT_INT_INTERVAL_IN_HID * MS_IN_TICKS; +static unsigned s_hidIndefiniteDuration = 0U; +static unsigned s_hidNextReportTime = 0U; +static unsigned s_hidReportTime = 0U; + +static unsigned HidCalcNewReportTime( const unsigned currentPeriod, const unsigned reportTime, const unsigned reportToSetIdleInterval, const unsigned newPeriod ); +static unsigned HidCalcReportToSetIdleInterval( const unsigned reportTime ); +static unsigned HidFindSetIdleActivationPoint( const unsigned currentPeriod, const unsigned timeWithinPeriod ); +static XUD_Result_t HidProcessSetIdleRequest( XUD_ep c_ep0_out, XUD_ep c_ep0_in, USB_SetupPacket_t &sp ); +static unsigned HidTimeDiff( const unsigned earlierTime, const unsigned laterTime ); + +void HidCalcNextReportTime( void ) { - return hidSetIdle; + s_hidNextReportTime = s_hidReportTime + s_hidCurrentPeriod; } -XUD_Result_t HidInterfaceClassRequests(XUD_ep c_ep0_out, XUD_ep c_ep0_in, - USB_SetupPacket_t &sp) +void HidCaptureReportTime( void ) { - 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; + 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: + result = HidProcessSetIdleRequest( c_ep0_out, c_ep0_in, sp ); + 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 = ( s_hidIndefiniteDuration || ( timeafter( s_hidNextReportTime, currentTime ))); + } + + return isSilenced; +} + +/** + * \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] newPeriod -- 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 newPeriod ) +{ + 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 + newPeriod; + } + + return nextReportTime; +} + +/** + * \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 ) +{ + 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 Process a Set Idle request + * + * \param[in] c_ep0_out -- the channel that carries data from Endpoint 0 + * \param[in] c_ep0_in -- the channel that carries data for Endpoint 0 + * \param[in] sp -- a structure containing the Set Idle data + * + * \return An XUD status value + */ +static XUD_Result_t HidProcessSetIdleRequest( XUD_ep c_ep0_out, XUD_ep c_ep0_in, USB_SetupPacket_t &sp ) +{ + XUD_Result_t result = XUD_RES_ERR; + + /* + 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. + */ + uint16_t duration = ( sp.wValue & 0xFF00 ) >> 6; // Transform from units of 4ms into units of 1ms. + uint8_t reportId = sp.wValue & 0x00FF; + uint16_t interfaceNum = sp.wIndex; + + /* + 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 ) && ( INTERFACE_NUMBER_HID == interfaceNum )) { + s_hidIdleActive = (( 0U == duration ) || ( ENDPOINT_INT_INTERVAL_IN_HID < duration )); + + 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; + s_hidIndefiniteDuration = ( 0U == duration ); + } else { + s_hidCurrentPeriod = ENDPOINT_INT_INTERVAL_IN_HID * MS_IN_TICKS; + s_hidIndefiniteDuration = 0U; } - return XUD_RES_ERR; + + result = XUD_DoSetRequestStatus( c_ep0_in ); + } + + return result; } + +/** + * \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 ( earlierTime < laterTime ) ? laterTime - earlierTime : UINT_MAX - earlierTime + laterTime; +} + +#endif /* ( 0 < HID_CONTROLS ) */ diff --git a/lib_xua/src/hid/xua_hid.h b/lib_xua/src/hid/xua_hid.h index df89dc55..ab8b2e05 100644 --- a/lib_xua/src/hid/xua_hid.h +++ b/lib_xua/src/hid/xua_hid.h @@ -3,7 +3,52 @@ #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)); +/** + * \brief Calculate the next time to respond with a HID Report. + * + * If the USB Host has previously sent a valid HID Set_Idle request with + * a duration of zero or greater than the default reporting interval, + * the device sends HID Reports periodically or when the value of the + * payload has changed. + * + * This function calculates the time for sending the next periodic + * HID Report. + */ +void HidCalcNextReportTime( void ); -unsigned HidIsSetIdleSilenced(void); +/** + * \brief Capture the time of sending the current HID Report. + * + * If the USB Host has previously sent a valid HID Set_Idle request with + * a duration of zero or greater than the default reporting interval, + * the device sends HID Reports periodically or when the value of the + * payload has changed. + * + * This function captures the time when the HID Report was sent so that + * a subsequent call to HidCalNextReportTime() can calculate the time + * to send the next periodic HID Report. + */ +void HidCaptureReportTime( void ); + +XUD_Result_t HidInterfaceClassRequests( + XUD_ep c_ep0_out, + XUD_ep c_ep0_in, + REFERENCE_PARAM( USB_SetupPacket_t, sp )); + +/** + * \brief Indicate whether to send a HID Report based on elapsed time. + * + * If the USB Host has previously sent a valid HID Set_Idle request with + * a duration of zero or greater than the default reporting interval, + * the device sends HID Reports periodically or when the value of the + * payload has changed. + * + * This function monitors the passage of time and reports to the caller + * whether or not the time to send the next periodic HID Report has + * elapsed. + * + * \return A Boolean value indicating whether or not to send the HID Report. + * \retval 1 -- Do not send the HID Report + * \retval 0 -- Send the HID Report + */ +unsigned HidIsSetIdleSilenced( void );