/*++ Copyright (c) 1998 - 1999 Microsoft Corporation Module Name: hidjoy.c Abstract: This module contains routines Generate the HID report and configure the joystick. Environment: Kernel mode @@BEGIN_DDKSPLIT Author: Eliyas Yakub (Mar, 11, 1997) Revision History: Updated by Eliyas on Feb 5 1998 @@END_DDKSPLIT --*/ #include "hidgame.h" #ifdef ALLOC_PRAGMA #pragma alloc_text (INIT, HGM_DriverInit) #pragma alloc_text (PAGE, HGM_SetupButtons) #pragma alloc_text (PAGE, HGM_MapAxesFromDevExt) #pragma alloc_text (PAGE, HGM_GenerateReport) #pragma alloc_text (PAGE, HGM_JoystickConfig) #pragma alloc_text (PAGE, HGM_InitAnalog) /* Sample only functions */ #ifdef CHANGE_DEVICE #pragma alloc_text (PAGE, HGM_ChangeHandler) #pragma alloc_text (PAGE, HGM_DeviceChanged) #endif /* CHANGE_DEVICE */ #endif /* * A few look up tables to translate the JOY_HWS_* flags into axis masks. * These flags allow any axis to be polled on any of the four axis bits in * the gameport. For example, the X axis on a standard joystick is found on * bit 0 (LSB) and the Y axis is on bit 1; however many steering wheel/pedal * controllers have X on bit 0 but Y on bit 2. Although very few of these * combinations are known to be used, supporting all the flags only causes a * little extra work on setup. For each axis, there are three flags, one for * each of the possible non-standard bit masks. Since it is possible that * more than one of these may be set the invalid combinations are marked so * that they can be refused. */ #define NA ( 0x80 ) /* * Short versions of bit masks for axes */ #define X1 AXIS_X #define Y1 AXIS_Y #define X2 AXIS_R #define Y2 AXIS_Z /* * Per axis flag masks and look up tables. * In each case, combinations with more than one bit set are invalid */ #define XMAPBITS (JOY_HWS_XISJ2Y | JOY_HWS_XISJ2X | JOY_HWS_XISJ1Y) /* * 0 0 0 0001 * 0 0 1 0010 * 0 1 0 0100 * 1 0 0 1000 */ static const unsigned char XLU[8] = { X1,Y1,X2,NA,Y2,NA,NA,NA }; #define XMAPSHFT 7 #define YMAPBITS (JOY_HWS_YISJ2Y | JOY_HWS_YISJ2X | JOY_HWS_YISJ1X) /* 0 0 0 0010 * 0 0 1 0001 * 0 1 0 0100 * 1 0 0 1000 */ static const unsigned char YLU[8] = { Y1,X1,X2,NA,Y2,NA,NA,NA }; #define YMAPSHFT 10 #define RMAPBITS (JOY_HWS_RISJ2Y | JOY_HWS_RISJ1X | JOY_HWS_RISJ1Y) /* 0 0 0 0100 * 0 0 1 0010 * 0 1 0 0001 * 1 0 0 1000 */ static const unsigned char RLU[8] = { X2,Y1,X1,NA,Y2,NA,NA,NA }; #define RMAPSHFT 20 #define ZMAPBITS (JOY_HWS_ZISJ2X | JOY_HWS_ZISJ1X | JOY_HWS_ZISJ1Y) /* 0 0 0 1000 * 0 0 1 0010 * 0 1 0 0001 * 1 0 0 0100 */ static const unsigned char ZLU[8] = { Y2,Y1,X1,NA,X2,NA,NA,NA }; #define ZMAPSHFT 13 #define POVMAPBITS (JOY_HWS_POVISJ2X | JOY_HWS_POVISJ1X | JOY_HWS_POVISJ1Y) /* * POV is the same as Z but with a larger shift */ #define POVMAPSHFT 16 #undef X1 #undef Y1 #undef X2 #undef Y2 /* * This translates from an axis bitmask to an axis value index. The elements * used should be as follows (X marks unsed) { X, 0, 1, X, 2, X, X, X, 3 }. */ static const unsigned char cAxisIndexTable[9] = { 0, 0, 1, 0, 2, 0, 0, 0, 3 }; typedef enum _POV1 { P1_NULL = 0x80, P1_0, P1_90, P1_180, P1_270 } POV1; typedef enum _POV2 { P2_NULL = 0xc0, P2_0, P2_90, P2_180, P2_270 } POV2; #define POV_MASK ((unsigned char)(~(P1_NULL | P2_NULL))) /* * Look up tables for button combos * Buttons are zero based so use P1_NULL for a zero input so we don't have to * special case it as a do nothing button. * The 7th Button can be mapped either from it's unique combination or as * foreward on a second POV being read as buttons 7 - 10. */ static const unsigned char c1PComboLU[] = { P1_NULL,0, 1, P1_270, 2, 4, 8, P1_180, 3, 5, 7, P1_90, 9, 6, 6, P1_0 }; static const unsigned char c2PComboLU[] = { P1_NULL,0, 1, P1_270, 2, 4, P2_180, P1_180, 3, 5, P2_90, P1_90, P2_270, 6, P2_0, P1_0 }; /***************************************************************************** * * @doc EXTERNAL * * @func NTSTATUS | HGM_DriverInit | * * Perform global initialization. * This is called from DriverEntry. Try to initialize a CPU * specific timer but if it fails set up default * * @rvalue STATUS_SUCCESS | success * @rvalue STATUS_UNSUCCESSFUL | not success * *****************************************************************************/ NTSTATUS EXTERNAL HGM_DriverInit( void ) { NTSTATUS ntStatus = STATUS_SUCCESS; if( !HGM_CPUCounterInit() ) { LARGE_INTEGER QPCFrequency; KeQueryPerformanceCounter( &QPCFrequency ); if( ( QPCFrequency.HighPart == 0 ) && ( QPCFrequency.LowPart <= 10000 ) ) { ntStatus = STATUS_UNSUCCESSFUL; HGM_DBGPRINT(FILE_HIDJOY | HGM_ERROR,\ ("QPC at %I64u Hz is unusable", QPCFrequency.QuadPart )); } else { Global.CounterScale = CALCULATE_SCALE( QPCFrequency.QuadPart ); Global.ReadCounter = (COUNTER_FUNCTION)&KeQueryPerformanceCounter; HGM_DBGPRINT(FILE_HIDJOY | HGM_BABBLE,\ ("QPC at %I64u Hz used with scale %d", QPCFrequency.QuadPart, Global.CounterScale )); } } return ntStatus; } /***************************************************************************** * * @doc EXTERNAL * * @func NTSTATUS | HGM_SetupButtons | * * Use the flags in the DeviceExtension to check and set up buttons. * This is called both from HGM_JoystickConfig to validate the * configuration and HGM_GenerateReport to prepare for polling. * * @parm IN OUT PDEVICE_EXTENSION | DeviceExtension | * * Pointer to the minidriver device extension * * @rvalue STATUS_SUCCESS | success * @rvalue STATUS_DEVICE_CONFIGURATION_ERROR | The configuration is invalid * *****************************************************************************/ NTSTATUS INTERNAL HGM_SetupButtons ( IN OUT PDEVICE_EXTENSION DeviceExtension ) { NTSTATUS ntStatus = STATUS_SUCCESS; if( DeviceExtension->fSiblingFound ) { if( DeviceExtension->nButtons > 2 ) { ntStatus = STATUS_DEVICE_CONFIGURATION_ERROR; HGM_DBGPRINT( FILE_HIDJOY | HGM_ERROR,\ ("HGM_SetupButtons: failing config of sibling device with %u buttons",\ DeviceExtension->nButtons)); } if( DeviceExtension->HidGameOemData.OemData[1].joy_hws_dwFlags & JOY_HWS_POVISBUTTONCOMBOS ) { ntStatus = STATUS_DEVICE_CONFIGURATION_ERROR; HGM_DBGPRINT( FILE_HIDJOY | HGM_ERROR,\ ("HGM_SetupButtons: failing config of sibling device with combo buttons" )); } } else { if( DeviceExtension->HidGameOemData.OemData[0].joy_hws_dwFlags & JOY_HWS_POVISBUTTONCOMBOS ) { if( DeviceExtension->nButtons > MAX_BUTTONS ) { ntStatus = STATUS_DEVICE_CONFIGURATION_ERROR; HGM_DBGPRINT( FILE_HIDJOY | HGM_ERROR,\ ("HGM_SetupButtons: failing config of button combo device with %u buttons",\ DeviceExtension->nButtons)); } } else { if( DeviceExtension->nButtons > 4 ) { if( DeviceExtension->resistiveInputMask & AXIS_R ) { ntStatus = STATUS_DEVICE_CONFIGURATION_ERROR; HGM_DBGPRINT( FILE_HIDJOY | HGM_ERROR,\ ("HGM_SetupButtons: failing config of device with R axis and %u buttons",\ DeviceExtension->nButtons)); } else { /* * 5th button always read from R axis. * Set the inital on/off boundary low */ DeviceExtension->resistiveInputMask |= AXIS_R; DeviceExtension->button5limit = 2; } if( DeviceExtension->nButtons > 5 ) { if( DeviceExtension->resistiveInputMask & AXIS_Z ) { ntStatus = STATUS_DEVICE_CONFIGURATION_ERROR; HGM_DBGPRINT( FILE_HIDJOY | HGM_ERROR,\ ("HGM_SetupButtons: failing config of device with Z axis and %u buttons",\ DeviceExtension->nButtons)); } else { /* * 6th button always read from Z axis. * Set the inital on/off boundary low */ DeviceExtension->resistiveInputMask |= AXIS_Z; DeviceExtension->button6limit = 2; } if( DeviceExtension->nButtons > 6 ) { ntStatus = STATUS_DEVICE_CONFIGURATION_ERROR; HGM_DBGPRINT( FILE_HIDJOY | HGM_ERROR,\ ("HGM_SetupButtons: failing config of device with %u buttons",\ DeviceExtension->nButtons)); } } } } } return( ntStatus ); } /* HGM_SetupButtons */ /***************************************************************************** * * @doc EXTERNAL * * @func NTSTATUS | HGM_MapAxesFromDevExt | * * Use the flags in the DeviceExtension to generate mappings for each * axis. * This is called both from HGM_JoystickConfig to validate the * configuration and HGM_GenerateReport to use the axis maps. * * * @parm IN OUT PDEVICE_EXTENSION | DeviceExtension | * * Pointer to the minidriver device extension * * @rvalue STATUS_SUCCESS | success * @rvalue STATUS_DEVICE_CONFIGURATION_ERROR | The configuration is invalid * *****************************************************************************/ NTSTATUS EXTERNAL HGM_MapAxesFromDevExt ( IN OUT PDEVICE_EXTENSION DeviceExtension ) { NTSTATUS ntStatus; ULONG dwFlags; int nAxis; UCHAR AxisMask; ntStatus = STATUS_SUCCESS; dwFlags = DeviceExtension->HidGameOemData.OemData[(DeviceExtension->fSiblingFound!=0)].joy_hws_dwFlags; HGM_DBGPRINT( FILE_HIDJOY | HGM_BABBLE2,\ ("HGM_MapAxesFromDevExt: - - - dwFlags=0x%x - - -", dwFlags)); #define XIS (0) #define YIS (1) #define ZIS (2) #define RIS (3) /* * Check X and Y last as Z, R and POV must not overlap * The are no flags to indicate the presence of X or Y so if they * overlap, this indicates that they are not used, */ DeviceExtension->resistiveInputMask = 0; for( nAxis=MAX_AXES; nAxis>=0; nAxis-- ) { DeviceExtension->AxisMap[nAxis] = INVALID_INDEX; } nAxis = 0; DeviceExtension->povMap = INVALID_INDEX; if( dwFlags & JOY_HWS_HASZ ) { AxisMask = ZLU[(dwFlags & ZMAPBITS) >> ZMAPSHFT]; if( AxisMask >= NA ) { HGM_DBGPRINT( FILE_HIDJOY | HGM_ERROR,\ ("HGM_MapAxesFromDevExt: Z axis mapping error dwFlags=0x%x",\ dwFlags)); ntStatus = STATUS_DEVICE_CONFIGURATION_ERROR; } else { nAxis = 1; DeviceExtension->resistiveInputMask = AxisMask; DeviceExtension->AxisMap[ZIS] = cAxisIndexTable[AxisMask]; } } if( dwFlags & JOY_HWS_HASR ) { AxisMask = RLU[(dwFlags & RMAPBITS) >> RMAPSHFT]; if( AxisMask >= NA ) { HGM_DBGPRINT( FILE_HIDJOY | HGM_ERROR,\ ("HGM_MapAxesFromDevExt: R axis mapping error dwFlags=0x%x",\ dwFlags)); ntStatus = STATUS_DEVICE_CONFIGURATION_ERROR; } else { if( DeviceExtension->resistiveInputMask & AxisMask ) { HGM_DBGPRINT( FILE_HIDJOY | HGM_ERROR, \ ("HGM_MapAxesFromDevExt: R axis mapped to same as Z axis")); ntStatus = STATUS_DEVICE_CONFIGURATION_ERROR ; } else { nAxis++; DeviceExtension->resistiveInputMask |= AxisMask; DeviceExtension->AxisMap[RIS] = cAxisIndexTable[AxisMask]; } } } if( dwFlags & JOY_HWS_HASPOV ) { switch( dwFlags & ( JOY_HWS_POVISPOLL | JOY_HWS_POVISBUTTONCOMBOS ) ) { case 0: HGM_DBGPRINT(FILE_HIDJOY | HGM_ERROR,\ ("HGM_MapAxesFromDevExt: POV is not polled or button combo")); ntStatus = STATUS_DEVICE_CONFIGURATION_ERROR; break; case JOY_HWS_POVISBUTTONCOMBOS: break; case JOY_HWS_POVISPOLL: AxisMask = ZLU[(dwFlags & POVMAPBITS) >> POVMAPSHFT]; if( AxisMask >= NA ) { HGM_DBGPRINT( FILE_HIDJOY | HGM_ERROR,\ ("HGM_MapAxesFromDevExt: POV axis mapping error dwFlags=0x%x",\ dwFlags)); ntStatus = STATUS_DEVICE_CONFIGURATION_ERROR ; } else { if( DeviceExtension->resistiveInputMask & AxisMask ) { HGM_DBGPRINT( FILE_HIDJOY | HGM_ERROR,\ ("HGM_MapAxesFromDevExt: POV axis mapped to same as Z or R axis") ); ntStatus = STATUS_DEVICE_CONFIGURATION_ERROR ; } else { DeviceExtension->resistiveInputMask |= AxisMask; DeviceExtension->povMap = cAxisIndexTable[AxisMask]; } } break; case JOY_HWS_POVISPOLL | JOY_HWS_POVISBUTTONCOMBOS: HGM_DBGPRINT(FILE_HIDJOY | HGM_ERROR,\ ("HGM_MapAxesFromDevExt: POV reports button combo and polled")); ntStatus = STATUS_DEVICE_CONFIGURATION_ERROR; break; } } else if( dwFlags & ( JOY_HWS_POVISPOLL | JOY_HWS_POVISBUTTONCOMBOS ) ) { HGM_DBGPRINT(FILE_HIDJOY | HGM_ERROR,\ ("HGM_MapAxesFromDevExt: non-existant POV is polled or button combo")); ntStatus = STATUS_DEVICE_CONFIGURATION_ERROR ; } AxisMask = XLU[( dwFlags & XMAPBITS ) >> XMAPSHFT]; if( AxisMask >= NA ) { HGM_DBGPRINT( FILE_HIDJOY | HGM_ERROR,\ ("HGM_MapAxesFromDevExt: X axis mapping error dwFlags=0x%x",\ dwFlags)); ntStatus = STATUS_DEVICE_CONFIGURATION_ERROR ; } else { if( DeviceExtension->resistiveInputMask & AxisMask ) { HGM_DBGPRINT( FILE_HIDJOY | HGM_WARN,\ ("HGM_MapAxesFromDevExt: X axis mapped to same as another axis") ); } else { nAxis++; DeviceExtension->resistiveInputMask |= AxisMask; DeviceExtension->AxisMap[XIS] = cAxisIndexTable[AxisMask]; } } AxisMask = YLU[( dwFlags & YMAPBITS ) >> YMAPSHFT]; if( AxisMask >= NA ) { HGM_DBGPRINT( FILE_HIDJOY | HGM_ERROR,\ ("HGM_MapAxesFromDevExt: Y axis mapping error dwFlags=0x%x",\ dwFlags)); ntStatus = STATUS_DEVICE_CONFIGURATION_ERROR ; } else { if( DeviceExtension->resistiveInputMask & AxisMask ) { HGM_DBGPRINT( FILE_HIDJOY | HGM_WARN,\ ("HGM_MapAxesFromDevExt: Y axis mapped to same as another axis") ); } else { nAxis++; DeviceExtension->resistiveInputMask |= AxisMask; DeviceExtension->AxisMap[YIS] = cAxisIndexTable[AxisMask]; } } #undef XIS #undef YIS #undef ZIS #undef RIS #undef NA /* * Don't fail for this if CHANGE_DEVICE is defined because an exposed * sibling will always have an nAxis of zero. */ #ifdef CHANGE_DEVICE if( DeviceExtension->nAxes ) { #endif /* CHANGE_DEVICE */ if( nAxis != DeviceExtension->nAxes ) { ntStatus = STATUS_DEVICE_CONFIGURATION_ERROR ; HGM_DBGPRINT(FILE_HIDJOY | HGM_ERROR,\ ("HGM_MapAxesFromDevExt: nAxis(%d) != DeviceExtension->nAxes(%d)", \ nAxis, (int) DeviceExtension->nAxes)); } #ifdef CHANGE_DEVICE } else { /* * This must be an exposed sibling so store the calculated nAxis and * a nButton just to look different. */ DeviceExtension->nAxes = (USHORT)nAxis; DeviceExtension->nButtons = MAX_BUTTONS; } #endif /* CHANGE_DEVICE */ HGM_DBGPRINT( FILE_HIDJOY | HGM_BABBLE,\ ("HGM_MapAxesFromDevExt: uResistiveInputMask=0x%x",\ DeviceExtension->resistiveInputMask) ); if( NT_SUCCESS(ntStatus) ) { ntStatus = HGM_SetupButtons( DeviceExtension ); } return( ntStatus ); } /* HGM_MapAxesFromDevExt */ /***************************************************************************** * * @doc EXTERNAL * * @func NTSTATUS | HGM_GenerateReport | * * Generates a hid report descriptor for a n-axis, m-button joystick, * depending on number of buttons and joy_hws_flags field. * * @parm IN PDEVICE_OBJECT | DeviceObject | * * Pointer to the device object * * @parm IN OUT UCHAR * | rgGameReport[MAXBYTES_GAME_REPORT] | * * Array that receives the HID report descriptor * * @parm OUT PUSHORT | pCbReport | * * Address of a short integer that receives size of * HID report descriptor. * * @rvalue STATUS_SUCCESS | success * @rvalue STATUS_BUFFER_TOO_SMALL | Need more memory for HID descriptor * *****************************************************************************/ NTSTATUS INTERNAL HGM_GenerateReport ( IN PDEVICE_OBJECT DeviceObject, OUT UCHAR rgGameReport[MAXBYTES_GAME_REPORT], OUT PUSHORT pCbReport ) { NTSTATUS ntStatus; PDEVICE_EXTENSION DeviceExtension; UCHAR *pucReport; int Idx; int UsageIdx; ULONG dwFlags; int InitialAxisMappings[MAX_AXES]; typedef struct _USAGES { UCHAR UsagePage; UCHAR Usage; } USAGES, *PUSAGE; typedef struct _JOYCLASSPARAMS { UCHAR TopLevelUsage; USAGES Usages[MAX_AXES]; } JOYCLASSPARAMS, *PJOYCLASSPARAMS; PJOYCLASSPARAMS pJoyParams; /* * Canned parameters for devices * The top-level usage must be either HID_USAGE_GENERIC_JOYSTICK or * HID_USAGE_GENERIC_GAMEPAD in order for the device to be treated as a * game controller. * The poll limits are specified in uSecs so the value stored here is 1000000/x */ JOYCLASSPARAMS JoystickParams = { HID_USAGE_GENERIC_JOYSTICK, { { HID_USAGE_PAGE_GENERIC, HID_USAGE_GENERIC_X} , { HID_USAGE_PAGE_GENERIC, HID_USAGE_GENERIC_Y} , { HID_USAGE_PAGE_SIMULATION, HID_USAGE_SIMULATION_THROTTLE} , { HID_USAGE_PAGE_SIMULATION, HID_USAGE_SIMULATION_RUDDER} } }; JOYCLASSPARAMS GamepadParams = { HID_USAGE_GENERIC_GAMEPAD, { { HID_USAGE_PAGE_GENERIC, HID_USAGE_GENERIC_X} , { HID_USAGE_PAGE_GENERIC, HID_USAGE_GENERIC_Y} , { HID_USAGE_PAGE_SIMULATION, HID_USAGE_SIMULATION_THROTTLE} , { HID_USAGE_PAGE_SIMULATION, HID_USAGE_SIMULATION_RUDDER} } }; JOYCLASSPARAMS CarCtrlParams = { HID_USAGE_GENERIC_JOYSTICK, { { HID_USAGE_PAGE_GENERIC, HID_USAGE_GENERIC_X} , { HID_USAGE_PAGE_GENERIC, HID_USAGE_GENERIC_Y} , { HID_USAGE_PAGE_SIMULATION, HID_USAGE_SIMULATION_THROTTLE} , { HID_USAGE_PAGE_SIMULATION, HID_USAGE_SIMULATION_RUDDER} } }; JOYCLASSPARAMS FlightYokeParams = { HID_USAGE_GENERIC_JOYSTICK, { { HID_USAGE_PAGE_GENERIC, HID_USAGE_GENERIC_X} , { HID_USAGE_PAGE_GENERIC, HID_USAGE_GENERIC_Y} , { HID_USAGE_PAGE_SIMULATION, HID_USAGE_SIMULATION_THROTTLE} , { HID_USAGE_PAGE_SIMULATION, HID_USAGE_SIMULATION_RUDDER} } }; PAGED_CODE(); HGM_DBGPRINT( FILE_HIDJOY | HGM_FENTRY,\ ("HGM_GenerateReport(ucIn=0x%x,DeviceObject=0x%x)",\ rgGameReport, DeviceObject) ); /* * Get a pointer to the device extension */ DeviceExtension = GET_MINIDRIVER_DEVICE_EXTENSION(DeviceObject); /* * Although the axes have already been validated and mapped in * HGM_JoystickConfig this function destroys the mapping when it compacts * the axes towards the start of the descriptor report. Since this * function will be called once to find the descriptor length and then * again to read the report, the mappings are regenerated again each * time through. Although this results in the parameters being * interpreted three times (for validation, descriptor size and * descriptor content) it avoids the possibility of a discrepancy in * implementation of separate functions. */ ntStatus = HGM_MapAxesFromDevExt( DeviceExtension ); ASSERTMSG( "HGM_GenerateReport:", ntStatus == STATUS_SUCCESS ); pucReport = rgGameReport; dwFlags = DeviceExtension->HidGameOemData.OemData[(DeviceExtension->fSiblingFound!=0)].joy_hws_dwFlags; /* * What manner of beast have we ? */ if( dwFlags & JOY_HWS_ISGAMEPAD ) { pJoyParams = &GamepadParams; } else if( dwFlags & JOY_HWS_ISYOKE ) { pJoyParams = &FlightYokeParams; } else if( dwFlags & JOY_HWS_ISCARCTRL ) { pJoyParams = &CarCtrlParams; } else { pJoyParams = &JoystickParams; } #define NEXT_BYTE( pReport, Data ) \ ASSERTMSG( "HGM_GenerateReport:", pReport+sizeof(UCHAR)-rgGameReport < MAXBYTES_GAME_REPORT ); \ *pReport++ = Data; #define NEXT_LONG( pReport, Data ) \ ASSERTMSG( "HGM_GenerateReport:", pReport+sizeof(ULONG)-rgGameReport < MAXBYTES_GAME_REPORT); \ *(((LONG UNALIGNED*)(pReport))++) = Data; #define ITEM_DEFAULT 0x00 /* Data, Array, Absolute, No Wrap, Linear, Preferred State, Has no NULL */ #define ITEM_VARIABLE 0x02 /* as ITEM_DEFAULT but value is a variable, not an array */ #define ITEM_HASNULL 0x40 /* as ITEM_DEFAULT but values out of range are considered NULL */ #define ITEM_ANALOG_AXIS ITEM_VARIABLE #define ITEM_DIGITAL_POV (ITEM_VARIABLE|ITEM_HASNULL) #define ITEM_BUTTON ITEM_VARIABLE #define ITEM_PADDING 0x01 /* Constant (nothing else applies) */ /* USAGE_PAGE (Generic Desktop) */ NEXT_BYTE(pucReport, HIDP_GLOBAL_USAGE_PAGE_1); NEXT_BYTE(pucReport, HID_USAGE_PAGE_GENERIC); /* USAGE (Joystick | GamePad ) */ NEXT_BYTE(pucReport, HIDP_LOCAL_USAGE_1); NEXT_BYTE(pucReport, pJoyParams->TopLevelUsage); /* Logical Min is the smallest value that could be produced by a poll */ NEXT_BYTE(pucReport, HIDP_GLOBAL_LOG_MIN_4); NEXT_LONG(pucReport, 0 ); /* Logical Max is the largest value that could be produced by a poll */ NEXT_BYTE(pucReport, HIDP_GLOBAL_LOG_MAX_4); NEXT_LONG(pucReport, AXIS_FULL_SCALE ); /* Start a Linked collection */ /* * Since this is a generic driver we know knothing about the physical * distribution of controls on the device so we put everything in a * single collection. If, for instance, we knew that some buttons were * on the base and some on the stick we could better describe them by * reporting them in separate collections. */ NEXT_BYTE(pucReport, HIDP_MAIN_COLLECTION); NEXT_BYTE(pucReport, 0x0 ); /* Define one axis at a time */ NEXT_BYTE(pucReport, HIDP_GLOBAL_REPORT_COUNT_1); NEXT_BYTE(pucReport, 0x1); /* Each axis is a 32 bits value */ NEXT_BYTE(pucReport, HIDP_GLOBAL_REPORT_SIZE); NEXT_BYTE(pucReport, 8 * sizeof(ULONG) ); /* * Do the axis * Although HID could cope with the "active" axes being mixed with the * dummy ones, it makes life simpler to move them to the start. * Pass through all the axis maps generated by HGM_JoystickConfig * and map all the active ones into the descriptor, copying the usages * appropriate for the type of device. * Since a polled POV is nothing more than a different interpretation * of axis data, this is added after any axes. */ C_ASSERT( sizeof( InitialAxisMappings ) == sizeof( DeviceExtension->AxisMap ) ); RtlCopyMemory( InitialAxisMappings, DeviceExtension->AxisMap, sizeof( InitialAxisMappings ) ); Idx = 0; for( UsageIdx = 0; UsageIdx < MAX_AXES; UsageIdx++ ) { if( InitialAxisMappings[UsageIdx] >= INVALID_INDEX ) { continue; } DeviceExtension->AxisMap[Idx] = InitialAxisMappings[UsageIdx]; NEXT_BYTE(pucReport, HIDP_LOCAL_USAGE_4); NEXT_BYTE(pucReport, pJoyParams->Usages[UsageIdx].Usage); NEXT_BYTE(pucReport, 0x0); NEXT_BYTE(pucReport, pJoyParams->Usages[UsageIdx].UsagePage); NEXT_BYTE(pucReport, 0x0); /* Data Field */ NEXT_BYTE(pucReport, HIDP_MAIN_INPUT_1); NEXT_BYTE(pucReport, ITEM_ANALOG_AXIS); HGM_DBGPRINT( FILE_HIDJOY | HGM_GEN_REPORT, \ ("HGM_GenerateReport:Idx=%d, UsageIdx=%d, Mapping=%d, Usage=%02x, Page=%02x",\ Idx, UsageIdx, DeviceExtension->AxisMap[UsageIdx], \ pJoyParams->Usages[UsageIdx].Usage, pJoyParams->Usages[UsageIdx].UsagePage ) ) ; Idx++; } if( dwFlags & JOY_HWS_POVISPOLL ) { /* * A polled POV is just the same as an axis. * Note, we have already checked that there is an axis for use as the POV. * Also, this type of POV can be distinguished from a digital POV by it's * lack of a NULL value. */ NEXT_BYTE(pucReport, HIDP_LOCAL_USAGE_4); NEXT_BYTE(pucReport, HID_USAGE_GENERIC_HATSWITCH); NEXT_BYTE(pucReport, 0x0); NEXT_BYTE(pucReport, HID_USAGE_PAGE_GENERIC); NEXT_BYTE(pucReport, 0x0); /* Data Field */ NEXT_BYTE(pucReport, HIDP_MAIN_INPUT_1); NEXT_BYTE(pucReport, ITEM_ANALOG_AXIS); HGM_DBGPRINT( FILE_HIDJOY | HGM_GEN_REPORT, \ ("HGM_GenerateReport:Idx=%d, Set to polled POV", Idx ) ) ; Idx++; } /* * Now fill in any remaining axis values as dummys */ while( Idx < MAX_AXES ) { /* Constant Field */ NEXT_BYTE(pucReport, HIDP_MAIN_INPUT_1); NEXT_BYTE(pucReport, ITEM_PADDING); HGM_DBGPRINT( FILE_HIDJOY | HGM_GEN_REPORT, \ ("HGM_GenerateReport:Idx=%d, Set to constant field", Idx ) ) ; Idx++; } /* * Now move on to the byte sized fields */ if( dwFlags & JOY_HWS_POVISBUTTONCOMBOS ) { /* * Redefine the logical and physical ranges from now on * A digital POV has a NULL value (a value outside the logical range) * when the POV is centered. To make life easier call the NULL value * zero, so the logical range is from 1 to 4. /* Logical Min */ NEXT_BYTE(pucReport, HIDP_GLOBAL_LOG_MIN_1); NEXT_BYTE(pucReport, 1 ); /* Logical Max */ NEXT_BYTE(pucReport, HIDP_GLOBAL_LOG_MAX_1); NEXT_BYTE(pucReport, 4 ); /* * report for digital POV is 3 bits data plus 5 constant bits to fill * the byte. */ NEXT_BYTE(pucReport, HIDP_LOCAL_USAGE_4); NEXT_BYTE(pucReport, HID_USAGE_GENERIC_HATSWITCH); NEXT_BYTE(pucReport, 0x0); NEXT_BYTE(pucReport, HID_USAGE_PAGE_GENERIC); NEXT_BYTE(pucReport, 0x0); /* Data Field */ NEXT_BYTE(pucReport, HIDP_GLOBAL_REPORT_SIZE); NEXT_BYTE(pucReport, 0x3); NEXT_BYTE(pucReport, HIDP_MAIN_INPUT_1); NEXT_BYTE(pucReport, ITEM_DIGITAL_POV); /* top 5 bits constant */ NEXT_BYTE(pucReport, HIDP_GLOBAL_REPORT_SIZE); NEXT_BYTE(pucReport, 0x5); NEXT_BYTE(pucReport, HIDP_MAIN_INPUT_1); NEXT_BYTE(pucReport, ITEM_PADDING); HGM_DBGPRINT( FILE_HIDJOY | HGM_GEN_REPORT, \ ("HGM_GenerateReport:First button combo POV is on" ) ) ; if( dwFlags & JOY_HWS_HASPOV2 ) { NEXT_BYTE(pucReport, HIDP_LOCAL_USAGE_4); NEXT_BYTE(pucReport, HID_USAGE_GENERIC_HATSWITCH); NEXT_BYTE(pucReport, 0x0); NEXT_BYTE(pucReport, HID_USAGE_PAGE_GENERIC); NEXT_BYTE(pucReport, 0x0); /* Data Field */ NEXT_BYTE(pucReport, HIDP_GLOBAL_REPORT_SIZE); NEXT_BYTE(pucReport, 0x3); NEXT_BYTE(pucReport, HIDP_MAIN_INPUT_1); NEXT_BYTE(pucReport, ITEM_DIGITAL_POV); /* top 5 bits constant */ NEXT_BYTE(pucReport, HIDP_GLOBAL_REPORT_SIZE); NEXT_BYTE(pucReport, 0x5); NEXT_BYTE(pucReport, HIDP_MAIN_INPUT_1); NEXT_BYTE(pucReport, ITEM_PADDING); HGM_DBGPRINT( FILE_HIDJOY | HGM_GEN_REPORT, \ ("HGM_GenerateReport:Second button combo POV is on" ) ) ; } else { /* 8 bits of constant data instead of second POV */ NEXT_BYTE(pucReport, HIDP_GLOBAL_REPORT_SIZE); NEXT_BYTE(pucReport, 0x8); /* Constant Field */ NEXT_BYTE(pucReport, HIDP_MAIN_INPUT_1); NEXT_BYTE(pucReport, ITEM_PADDING); HGM_DBGPRINT( FILE_HIDJOY | HGM_GEN_REPORT, \ ("HGM_GenerateReport:No second button combo POV" ) ) ; } } else { /* 16 bits of constant data instead of button combo POVs */ NEXT_BYTE(pucReport, HIDP_GLOBAL_REPORT_SIZE); NEXT_BYTE(pucReport, 0x10); /* Constant Field */ NEXT_BYTE(pucReport, HIDP_MAIN_INPUT_1); NEXT_BYTE(pucReport, ITEM_PADDING); HGM_DBGPRINT( FILE_HIDJOY | HGM_GEN_REPORT, \ ("HGM_GenerateReport:Button combo POV are off" ) ) ; } /* * Now the buttons */ for( Idx = 0x0; Idx < DeviceExtension->nButtons; Idx++ ) { /* Report size is 1 bit for button */ NEXT_BYTE(pucReport, HIDP_GLOBAL_REPORT_SIZE); NEXT_BYTE(pucReport, 0x1); NEXT_BYTE(pucReport, HIDP_LOCAL_USAGE_4); NEXT_BYTE(pucReport, (UCHAR)(Idx + 1) ); NEXT_BYTE(pucReport, 0x0); NEXT_BYTE(pucReport, HID_USAGE_PAGE_BUTTON); NEXT_BYTE(pucReport, 0x0); /* Data field */ NEXT_BYTE(pucReport, HIDP_MAIN_INPUT_1); NEXT_BYTE(pucReport, ITEM_BUTTON); /* 7 bits of constant data */ NEXT_BYTE(pucReport, HIDP_GLOBAL_REPORT_SIZE); NEXT_BYTE(pucReport, 0x7); NEXT_BYTE(pucReport, HIDP_MAIN_INPUT_1); NEXT_BYTE(pucReport, ITEM_PADDING); HGM_DBGPRINT( FILE_HIDJOY | HGM_GEN_REPORT, \ ("HGM_GenerateReport:Button %u on",Idx ) ) ; } if( Idx < MAX_BUTTONS ) { /* Constant report for 8 * unused buttons bits */ NEXT_BYTE(pucReport, HIDP_GLOBAL_REPORT_SIZE); NEXT_BYTE(pucReport, (UCHAR)((MAX_BUTTONS-Idx)*8) ); /* Constant Field */ NEXT_BYTE(pucReport, HIDP_MAIN_INPUT_1); NEXT_BYTE(pucReport, ITEM_PADDING); HGM_DBGPRINT( FILE_HIDJOY | HGM_GEN_REPORT, \ ("HGM_GenerateReport:Last %u buttons off",MAX_BUTTONS-Idx ) ) ; } /* End of collection, We're done ! */ NEXT_BYTE(pucReport, HIDP_MAIN_ENDCOLLECTION); #undef NEXT_BYTE #undef NEXT_LONG if( pucReport - rgGameReport > MAXBYTES_GAME_REPORT) { ntStatus = STATUS_BUFFER_TOO_SMALL; *pCbReport = 0x0; RtlZeroMemory(rgGameReport, sizeof(rgGameReport)); } else { *pCbReport = (USHORT) (pucReport - rgGameReport); ntStatus = STATUS_SUCCESS; } HGM_DBGPRINT( FILE_HIDJOY | HGM_GEN_REPORT,\ ("HGM_GenerateReport: ReportSize=0x%x",\ *pCbReport) ); HGM_EXITPROC(FILE_HIDJOY | HGM_FEXIT_STATUSOK, "HGM_GenerateReport", ntStatus); return ( ntStatus ); } /* HGM_GenerateReport */ /***************************************************************************** * * @doc EXTERNAL * * @func NTSTATUS | HGM_JoystickConfig | * * Check that the configuration is valid whilst there is still time * to refuse it. * HGM_GenerateReport uses the results generated here if the * settings are OK. * * @parm IN PDEVICE_OBJECT | DeviceObject | * * Pointer to the device object * * @rvalue STATUS_SUCCESS | success * @rvalue STATUS_DEVICE_CONFIGURATION_ERROR | Invalid configuration specified * *****************************************************************************/ NTSTATUS INTERNAL HGM_JoystickConfig ( IN PDEVICE_OBJECT DeviceObject ) { PDEVICE_EXTENSION DeviceExtension; NTSTATUS ntStatus; int Idx; PAGED_CODE(); HGM_DBGPRINT( FILE_HIDJOY | HGM_FENTRY,\ ("HGM_JoystickConfig(DeviceObject=0x%x)",\ DeviceObject) ); /* * Get a pointer to the device extension */ DeviceExtension = GET_MINIDRIVER_DEVICE_EXTENSION(DeviceObject); ntStatus = HGM_MapAxesFromDevExt( DeviceExtension ); if( DeviceExtension->ReadAccessorDigital ) { DeviceExtension->ScaledTimeout = AXIS_TIMEOUT; } else { /* * Calculate time thresholds for analog device */ if( ( DeviceExtension->HidGameOemData.OemData[0].Timeout < ANALOG_POLL_TIMEOUT_MIN ) ||( DeviceExtension->HidGameOemData.OemData[0].Timeout > ANALOG_POLL_TIMEOUT_MAX ) ) { HGM_DBGPRINT(FILE_HIDJOY | HGM_BABBLE,\ ("Ignoring out of range timeout: %u uSecs",\ DeviceExtension->HidGameOemData.OemData[0].Timeout)); DeviceExtension->ScaledTimeout = (ULONG)( ( (ULONGLONG)ANALOG_POLL_TIMEOUT_DFT * (ULONGLONG)(AXIS_FULL_SCALE<ScaledTimeout = (ULONG)( ( (ULONGLONG)DeviceExtension->HidGameOemData.OemData[0].Timeout * (ULONGLONG)(AXIS_FULL_SCALE<ScaledTimeout)); /* * Use one quarter of the minimum poll timeout as a starting value * for the time between two polls which will be considered to have * been interrupted. */ DeviceExtension->ScaledThreshold = (ULONG)( ( (ULONGLONG)ANALOG_POLL_TIMEOUT_MIN * (ULONGLONG)AXIS_FULL_SCALE ) / (ULONGLONG)ANALOG_POLL_TIMEOUT_MAX )>>2; } /* * Set initial values of LastGoodAxis so that the device will not show * up as present until we get at least one valid poll. */ for( Idx = MAX_AXES; Idx >= 0; Idx-- ) { DeviceExtension->LastGoodAxis[Idx] = AXIS_TIMEOUT; } HGM_EXITPROC(FILE_HIDJOY | HGM_FEXIT_STATUSOK, "HGM_JoystickConfig", ntStatus); return ntStatus; } /* HGM_JoystickConfig */ /***************************************************************************** * * @doc EXTERNAL * * @func NTSTATUS | HGM_InitAnalog | * * Check that the configuration is valid whilst there is still time * to refuse it. * Detect and validate sibling relationships and call * HGM_JoystickConfig for the rest of the work. * * @parm IN PDEVICE_OBJECT | DeviceObject | * * Pointer to the device object * * @rvalue STATUS_SUCCESS | success * @rvalue STATUS_DEVICE_CONFIGURATION_ERROR | Invalid configuration specified * *****************************************************************************/ /* * Disable warning for variable used before set as it is hard for a compiler * to see that the use of DeviceExtension_Sibling is gated by a flag which * can only be set after DeviceExtension_Sibling is initialized. */ #pragma warning( disable:4701 ) NTSTATUS EXTERNAL HGM_InitAnalog ( IN PDEVICE_OBJECT DeviceObject ) { NTSTATUS ntStatus; PDEVICE_EXTENSION DeviceExtension; PDEVICE_EXTENSION DeviceExtension_Sibling; PLIST_ENTRY pEntry; #define ARE_WE_RELATED(_x_, _y_) \ ( \ (_x_)->GameContext == (_y_)->GameContext && \ (_x_)->WriteAccessor == (_y_)->WriteAccessor && \ (_x_)->ReadAccessor == (_y_)->ReadAccessor \ ) PAGED_CODE (); /* * Get a pointer to the device extension */ DeviceExtension = GET_MINIDRIVER_DEVICE_EXTENSION(DeviceObject); /* * No modifications to the Global List while we are looking at it */ ExAcquireFastMutex (&Global.Mutex); /* * For two joysticks interface two fdos are created to service them * but physically they both share the same port. * For the second sibling certain extra rules must be applied so we * search our list of devices for another device using the same port * and if we find one mark this one as a sibling. */ for(pEntry = Global.DeviceListHead.Flink; pEntry != &Global.DeviceListHead; pEntry = pEntry->Flink) { /* * Obtain the device Extension of the Sibling */ DeviceExtension_Sibling = CONTAINING_RECORD(pEntry, DEVICE_EXTENSION, Link); if( DeviceExtension_Sibling != DeviceExtension && TRUE == ARE_WE_RELATED(DeviceExtension, DeviceExtension_Sibling) && TRUE == DeviceExtension_Sibling->fStarted ) { #ifdef CHANGE_DEVICE if( DeviceExtension_Sibling->fReplaced ) { HGM_DBGPRINT(FILE_HIDJOY | HGM_BABBLE, ("Outgoing Sibling found (0x%x)", DeviceExtension_Sibling)); } else { #endif /* CHANGE_DEVICE */ HGM_DBGPRINT(FILE_HIDJOY | HGM_BABBLE, ("Sibling found (0x%x)", DeviceExtension_Sibling)); DeviceExtension->fSiblingFound = TRUE; #ifdef CHANGE_DEVICE } #endif /* CHANGE_DEVICE */ break; } } /* * We are done, release the Mutex */ ExReleaseFastMutex (&Global.Mutex); /* * check the axis and button configuration for the joystick */ ntStatus = HGM_JoystickConfig(DeviceObject); if( NT_SUCCESS( ntStatus ) ) { /* * Make sure that sibling axes are not overlapped */ if( DeviceExtension->fSiblingFound && (DeviceExtension_Sibling->resistiveInputMask & DeviceExtension->resistiveInputMask) != 0x0 ) { HGM_DBGPRINT(FILE_HIDJOY |HGM_ERROR,\ ("HGM_InitDevice: OverLapping Resources ResisitiveInputMask(0x%x) Sibling(0x%x)",\ DeviceExtension->resistiveInputMask,DeviceExtension_Sibling->resistiveInputMask )); ntStatus = STATUS_DEVICE_CONFIGURATION_ERROR; } } else { HGM_DBGPRINT(FILE_HIDJOY | HGM_ERROR,\ ("HGM_InitDevice: JoystickConfig Failed")); } return( ntStatus ); } /* HGM_InitAnalog */ /* * Change device sample only code */ #ifdef CHANGE_DEVICE /***************************************************************************** * * @doc EXTERNAL * * @func VOID | HGM_ChangeHandler | * * Use IOCTL_GAMEENUM_EXPOSE_SIBLING and IOCTL_GAMEENUM_REMOVE_SELF * to change the attributes of the device. * * @parm IN PDEVICE_OBJECT | DeviceObject | * * Pointer to the device object * * @parm PIO_WORKITEM | WorkItem | * * The work item that this call is being processed under. * *****************************************************************************/ VOID HGM_ChangeHandler ( IN PDEVICE_OBJECT DeviceObject, PIO_WORKITEM WorkItem ) { NTSTATUS ntStatus = STATUS_SUCCESS; PDEVICE_EXTENSION DeviceExtension; KEVENT IoctlCompleteEvent; IO_STATUS_BLOCK IoStatus; PIO_STACK_LOCATION irpStack, nextStack; PIRP pIrp; PVOID SiblingHandle; GAMEENUM_EXPOSE_SIBLING ExposeSibling; PAGED_CODE (); /* * Get a pointer to the device extension */ DeviceExtension = GET_MINIDRIVER_DEVICE_EXTENSION(DeviceObject); HGM_DBGPRINT(FILE_HIDJOY | HGM_FENTRY,\ ("HGM_ChangeHandler(DeviceExtension=0x%x)", DeviceExtension)); KeInitializeEvent(&IoctlCompleteEvent, NotificationEvent, FALSE); pIrp = IoBuildDeviceIoControlRequest ( IOCTL_GAMEENUM_EXPOSE_SIBLING, DeviceExtension->NextDeviceObject, &ExposeSibling, sizeof( ExposeSibling ), &ExposeSibling, sizeof( ExposeSibling ), TRUE, &IoctlCompleteEvent, &IoStatus); if( pIrp ) { /* * For demonstration purposes only, we don't actually change the * device, we just re-expose the same one. If the device really * needs to be changed, this would be signalled either by a * change in the OemData on the newly exposed device or by using * a specific HardwareID string. * Note the nAxis and nButton fields will always be zero for an * exposed sibling. */ RtlZeroMemory( &ExposeSibling, sizeof( ExposeSibling ) ); ExposeSibling.Size = sizeof( ExposeSibling ); ExposeSibling.HardwareHandle = &SiblingHandle; C_ASSERT( sizeof( ExposeSibling.OemData ) == sizeof( DeviceExtension->HidGameOemData.Game_Oem_Data ) ); RtlCopyMemory(ExposeSibling.OemData, DeviceExtension->HidGameOemData.Game_Oem_Data, sizeof(ExposeSibling.OemData)); ASSERT( ExposeSibling.UnitID == 0 ); /* * Setting a NULL pointer causes the HardwareID of this sibling to be used */ ExposeSibling.HardwareIDs = NULL; /* * issue a synchronous request to GameEnum to expose this new sibling */ ntStatus = IoCallDriver( DeviceExtension->NextDeviceObject, pIrp ); if( ntStatus == STATUS_PENDING ) { ntStatus = KeWaitForSingleObject (&IoctlCompleteEvent, Executive, KernelMode, FALSE, NULL); } if( NT_SUCCESS(ntStatus) ) { /* * All went well so remove self */ HGM_DBGPRINT(FILE_HIDJOY | HGM_BABBLE, ("Sibling exposed!")); pIrp = IoBuildDeviceIoControlRequest ( IOCTL_GAMEENUM_REMOVE_SELF, DeviceExtension->NextDeviceObject, NULL, 0, NULL, 0, TRUE, &IoctlCompleteEvent, &IoStatus); if( pIrp ) { /* * issue a synchronous request to GameEnum to remove self */ ntStatus = IoCallDriver( DeviceExtension->NextDeviceObject, pIrp ); if( ntStatus == STATUS_PENDING ) { ntStatus = KeWaitForSingleObject( &IoctlCompleteEvent, Executive, KernelMode, FALSE, NULL ); } if( NT_SUCCESS(ntStatus) ) { /* * All done */ HGM_DBGPRINT(FILE_HIDJOY | HGM_BABBLE, ("Removed self!")); } else { /* * Something bad happened but there's little we can do */ HGM_DBGPRINT(FILE_HIDJOY | HGM_ERROR,\ ("Failed to remove self with GameEnum error: 0x%08x", \ ntStatus)); } } else { ntStatus = STATUS_NO_MEMORY; HGM_DBGPRINT(FILE_HIDJOY | HGM_ERROR, \ ("Failed to create IRP for remove self") ); } } else { /* * Something bad happened so reset the flag and carry on */ DeviceExtension->fReplaced = FALSE; HGM_DBGPRINT(FILE_HIDJOY | HGM_WARN,\ ("Failed to expose sibling with GameEnum error: 0x%08x", ntStatus)); } } else { ntStatus = STATUS_NO_MEMORY; DeviceExtension->fReplaced = FALSE; HGM_DBGPRINT(FILE_HIDJOY | HGM_ERROR, \ ("Failed to create IRP for expose sibling") ); } /* * The work is done, so free the resources */ IoFreeWorkItem( WorkItem ); /* * We've finished touching the DeviceExtension now. */ HGM_DecRequestCount( DeviceExtension ); HGM_EXITPROC(FILE_HIDJOY|HGM_FEXIT_STATUSOK, "HGM_ChangeHandler", ntStatus); return; } /* HGM_ChangeHandler */ /***************************************************************************** * * @doc EXTERNAL * * @func VOID | HGM_DeviceChanged | * * Start the process of changing the device attributes by stashing * away all the data needed and then initializing and queuing a work * item to call the IOCTL at the required PASSIVE_LEVEL. * * @parm IN PDEVICE_OBJECT | DeviceObject | * * Pointer to the device object * * @parm IN OUT PDEVICE_EXTENSION | DeviceExtension | * * Pointer to the mini-driver device extension. * *****************************************************************************/ VOID HGM_DeviceChanged ( IN PDEVICE_OBJECT DeviceObject, IN OUT PDEVICE_EXTENSION DeviceExtension ) { NTSTATUS ntStatus; PIO_WORKITEM WorkItem; /* * Since the work item will use the device extension, bump the usage * count up one in case anyone tries to remove the device between * now and when the work item gets to run. If that fails, forget it. */ ntStatus = HGM_IncRequestCount( DeviceExtension ); if( NT_SUCCESS(ntStatus) ) { WorkItem = IoAllocateWorkItem( DeviceObject ); if( WorkItem ) { DeviceExtension->fReplaced = TRUE; IoQueueWorkItem( WorkItem, (PIO_WORKITEM_ROUTINE)HGM_ChangeHandler, DelayedWorkQueue, WorkItem ); } else { HGM_DecRequestCount( DeviceExtension ); HGM_DBGPRINT(FILE_HIDJOY | HGM_WARN, ("Failed to allocate work item to change device") ); } } else { HGM_DBGPRINT(FILE_HIDJOY | HGM_WARN, ("Failed to change device as device is being removed") ); } } /* HGM_DeviceChanged */ #endif /* CHANGE_DEVICE */ /***************************************************************************** * * @doc EXTERNAL * * @func VOID | HGM_Game2HID | * * Process the data returned from polling the gameport into values * and buttons for returning to HID. * The meaning of the data is interpreted according to the * characteristics of the device described in the hardware settings * flags. * * @parm IN PDEVICE_OBJECT | DeviceObject | * * Pointer to the device object * * @parm IN PDEVICE_EXTENSION | DeviceExtension | * * Pointer to the mini-driver device extension. * * @parm IN OUT PUHIDGAME_INPUT_DATA | pHIDData | * * Pointer to the buffer into which the HID report should be written. * This buffer must be assumed to be unaligned. * *****************************************************************************/ VOID HGM_Game2HID ( #ifdef CHANGE_DEVICE IN PDEVICE_OBJECT DeviceObject, #endif /* CHANGE_DEVICE */ IN PDEVICE_EXTENSION DeviceExtension, IN OUT PUHIDGAME_INPUT_DATA pHIDData ) { LONG Idx; /* * Use a local buffer to assemble the report as the real buffer may not * be aligned. */ HIDGAME_INPUT_DATA LocalBuffer; RtlZeroMemory( &LocalBuffer, sizeof( LocalBuffer ) ); /* * Remap axis */ for(Idx = 0x0; Idx < DeviceExtension->nAxes; Idx++ ) { LocalBuffer.Axis[Idx] = DeviceExtension->LastGoodAxis[DeviceExtension->AxisMap[Idx]]; } /* * Copy buttons and remap any POVs */ if( DeviceExtension->fSiblingFound ) { /* * Simplest case, 2nd half poll must be 2A 2B */ LocalBuffer.Button[0] = DeviceExtension->LastGoodButton[2]; LocalBuffer.Button[1] = DeviceExtension->LastGoodButton[3]; } else if( DeviceExtension->HidGameOemData.OemData[0].joy_hws_dwFlags & JOY_HWS_POVISBUTTONCOMBOS ) { UCHAR Buttons = 0; for( Idx = 3; Idx >=0; Idx-- ) { Buttons <<= 1; Buttons = (UCHAR)(Buttons + (UCHAR)(DeviceExtension->LastGoodButton[Idx] >> BUTTON_BIT)); } if( DeviceExtension->HidGameOemData.OemData[0].joy_hws_dwFlags & JOY_HWS_HASPOV2 ) { Idx = c2PComboLU[Buttons]; } else { Idx = c1PComboLU[Buttons]; } if( Idx >= P1_NULL ) { if( Idx < P2_NULL ) { LocalBuffer.hatswitch[0] = (UCHAR)(Idx & POV_MASK); } else { LocalBuffer.hatswitch[1] = (UCHAR)(Idx & POV_MASK); } } else { #ifdef CHANGE_DEVICE if( ( Idx >= DeviceExtension->nButtons ) && ( !DeviceExtension->fReplaced ) ) { /* * If a higher button was pressed than expected, use * remove_self/expose_sibling to change expectations. */ HGM_DeviceChanged( DeviceObject, DeviceExtension ); } #endif /* CHANGE_DEVICE */ LocalBuffer.Button[Idx] = BUTTON_ON; } } else { if( DeviceExtension->HidGameOemData.OemData[0].joy_hws_dwFlags & JOY_HWS_POVISPOLL ) { /* * Following the axis mapping loop, Idx is one larger than * DeviceExtension->nAxes which is the correct index for a * polled POV. */ LocalBuffer.Axis[Idx] = DeviceExtension->LastGoodAxis[DeviceExtension->povMap]; } /* * Check buttons on R and Z axes */ if( DeviceExtension->nButtons > 5 ) { if( DeviceExtension->LastGoodAxis[3] > DeviceExtension->button6limit ) { /* * New max found so button is off */ HGM_DBGPRINT( FILE_HIDJOY | HGM_BABBLE2, \ ("HGM_Game2HID: Changing button 6 limit from %u to %u", \ DeviceExtension->button6limit, DeviceExtension->LastGoodAxis[3] ) ) ; DeviceExtension->button6limit = DeviceExtension->LastGoodAxis[3]; } else if( DeviceExtension->LastGoodAxis[3] < (DeviceExtension->button6limit>>1) ) { LocalBuffer.Button[5] = BUTTON_ON; } } if( DeviceExtension->nButtons > 4 ) { Idx = 4; if( DeviceExtension->LastGoodAxis[2] > DeviceExtension->button5limit ) { /* * New max found so button is off */ HGM_DBGPRINT( FILE_HIDJOY | HGM_BABBLE2, \ ("HGM_Game2HID: Changing button 5 limit from %u to %u", \ DeviceExtension->button5limit, DeviceExtension->LastGoodAxis[2] ) ) ; DeviceExtension->button5limit = DeviceExtension->LastGoodAxis[2]; } else if( DeviceExtension->LastGoodAxis[2] < (DeviceExtension->button5limit>>1) ) { LocalBuffer.Button[4] = BUTTON_ON; } } else { Idx = DeviceExtension->nButtons; } /* * Copy all standard buttons */ while( Idx-- ) { LocalBuffer.Button[Idx] = DeviceExtension->LastGoodButton[Idx]; } } C_ASSERT( sizeof( *pHIDData ) == sizeof( LocalBuffer ) ); RtlCopyMemory( pHIDData, &LocalBuffer, sizeof( LocalBuffer ) ); HGM_DBGPRINT( FILE_HIDJOY | HGM_BABBLE2, \ ("HGM_Game2HID: Axes: X: %08x Y: %08x Z: %08x R: %08x", \ LocalBuffer.Axis[0], LocalBuffer.Axis[1], LocalBuffer.Axis[2], LocalBuffer.Axis[3] ) ) ; HGM_DBGPRINT( FILE_HIDJOY | HGM_BABBLE2, \ ("HGM_Game2HID: P1: %d P2: %d Buttons %d, %d, %d, %d, %d, %d, %d, %d, %d, %d", \ LocalBuffer.hatswitch[0], LocalBuffer.hatswitch[1], \ LocalBuffer.Button[0], LocalBuffer.Button[1], LocalBuffer.Button[2], LocalBuffer.Button[3], LocalBuffer.Button[4], \ LocalBuffer.Button[5], LocalBuffer.Button[6], LocalBuffer.Button[7], LocalBuffer.Button[8], LocalBuffer.Button[9] ) ) ; } /* HGM_Game2HID */