//************************************************************************** // // SWGAMPAD.C -- Xena Gaming Project // // Version 3.XX // // Copyright (c) 1997 Microsoft Corporation. All rights reserved. // // @doc // @module SWGAMPAD.C | Gameport mini-driver for GamePads //************************************************************************** #ifndef SAITEK #include "msgame.h" //--------------------------------------------------------------------------- // Definitions //--------------------------------------------------------------------------- #define DEVICENAME "SWGAMPAD" #define DEVICE_PID 0x0003 #define HARDWARE_ID L"Gameport\\SideWindergamepad\0\0" // // Packet Constants // #define GAME_PACKET_SIZE 32 #define GAME_PACKET_BUTTONS 10 #define GAME_Y_UP_BIT 0x01 #define GAME_Y_DOWN_BIT 0x02 #define GAME_Y_BITS (GAME_Y_UP_BIT|GAME_Y_DOWN_BIT) #define GAME_X_LEFT_BIT 0x04 #define GAME_X_RIGHT_BIT 0x08 #define GAME_X_BITS (GAME_X_LEFT_BIT|GAME_X_RIGHT_BIT) #define GAME_BUTTON_BITS 0x3ff0 // // Id Definitions // #define GAME_ID_STRING "H0003" // // Timing Constants // #define PACKET_START_TIMEOUT 500 #define PACKET_LOWHIGH_TIMEOUT 75 #define PACKET_HIGHLOW_TIMEOUT 150 #define PACKET_INTERRUPT_DELAY 45 #define ID_START_TIMEOUT 500 #define ID_LOWHIGH_TIMEOUT 75 #define ID_HIGHLOW_TIMEOUT 150 #define MAX_CLOCK_DUTY_CYCLE 50 #define MAX_STD_SCLKS 150 // // Joystick Extents // #define EXTENTS_X_MIN 1 #define EXTENTS_X_MID 0x80 #define EXTENTS_X_MAX 0xff #define EXTENTS_Y_MIN 1 #define EXTENTS_Y_MID 0x80 #define EXTENTS_Y_MAX 0xff //--------------------------------------------------------------------------- // Types //--------------------------------------------------------------------------- typedef struct { // @struct SWGAMPAD_ID | GamePad Id String #pragma pack(1) UCHAR OpenParen; // @field Open parentheses UCHAR EisaId[5]; // @field Eisa bus Id USHORT Version[3]; // @field Firmware version UCHAR CloseParen; // @field Close parentheses UCHAR Reserved[22]; // @field Reserved #pragma pack() } SWGAMPAD_ID, *PSWGAMPAD_ID; //--------------------------------------------------------------------------- // Procedures //--------------------------------------------------------------------------- static VOID SWGAMPAD_Calibrate (PGAMEPORT PortInfo); static BOOLEAN SWGAMPAD_GoAnalog (PPACKETINFO Packet1, PPACKETINFO Packet2); static BOOLEAN SWGAMPAD_ReadId (PPACKETINFO DataPacket, PPACKETINFO IdPacket); static BOOLEAN SWGAMPAD_GetId (PPACKETINFO IdPacket); static NTSTATUS SWGAMPAD_ReadData (PPACKETINFO DataPacket); static BOOLEAN SWGAMPAD_Read1Wide (PPACKETINFO DataPacket); static BOOLEAN SWGAMPAD_Read3Wide (PPACKETINFO DataPacket); static BOOLEAN SWGAMPAD_ValidateData (PPACKETINFO DataPacket); static VOID SWGAMPAD_ProcessData (ULONG UnitId, USHORT Data[], PDEVICE_PACKET Report); //--------------------------------------------------------------------------- // Services //--------------------------------------------------------------------------- static NTSTATUS SWGAMPAD_DriverEntry (VOID); static NTSTATUS SWGAMPAD_ConnectDevice (PGAMEPORT PortInfo); static NTSTATUS SWGAMPAD_StartDevice (PGAMEPORT PortInfo); static NTSTATUS SWGAMPAD_ReadReport (PGAMEPORT PortInfo, PDEVICE_PACKET Report); static NTSTATUS SWGAMPAD_StopDevice (PGAMEPORT PortInfo, BOOLEAN TouchHardware); //--------------------------------------------------------------------------- // Alloc_text pragma to specify routines that can be paged out. //--------------------------------------------------------------------------- #ifdef ALLOC_PRAGMA #pragma alloc_text (INIT, SWGAMPAD_DriverEntry) #endif //--------------------------------------------------------------------------- // Private Data //--------------------------------------------------------------------------- // // HID Descriptors // static UCHAR ReportDescriptor[] = { HIDP_GLOBAL_USAGE_PAGE_1, HID_USAGE_PAGE_GENERIC, // USAGE_PAGE (Generic Desktop) HIDP_LOCAL_USAGE_1, HID_USAGE_GENERIC_JOYSTICK,// USAGE (Joystick) HIDP_MAIN_COLLECTION, HIDP_MAIN_COLLECTION_APP, // COLLECTION (Application) //id HIDP_GLOBAL_REPORT_SIZE, 0x20, // REPORT_SIZE (20) HIDP_GLOBAL_REPORT_COUNT_1,0x01, // REPORT_COUNT (1) HIDP_MAIN_INPUT_1, 0x01, // INPUT (Cnst,Ary,Abs) //do_other HIDP_GLOBAL_REPORT_SIZE, 0x20, // REPORT_SIZE (20) HIDP_GLOBAL_REPORT_COUNT_1,0x01, // REPORT_COUNT (1) HIDP_MAIN_INPUT_1, 0x01, // INPUT (Cnst,Ary,Abs) //dwX / dwY HIDP_LOCAL_USAGE_1, HID_USAGE_GENERIC_POINTER, // USAGE (Pointer) HIDP_MAIN_COLLECTION, HIDP_MAIN_COLLECTION_LINK, // COLLECTION (Linked) HIDP_GLOBAL_LOG_MIN_1, 0x01, // LOGICAL_MINIMUM (1) HIDP_GLOBAL_LOG_MAX_4, 0xFF, 0x00, 0x00, 0x00, // LOGICAL_MAXIMUM (255) HIDP_GLOBAL_PHY_MIN_1, 0x01, // PHYSICAL_MINIMUM (1) HIDP_GLOBAL_PHY_MAX_4, 0xFF, 0x00, 0x00, 0x00, // PHYSICAL_MAXIMUM (255) HIDP_GLOBAL_UNIT_2, 0x00, 0x00, // UNIT (None) HIDP_GLOBAL_REPORT_COUNT_1,0x01, // REPORT_COUNT (1) HIDP_GLOBAL_REPORT_SIZE, 0x20, // REPORT_SIZE (32) HIDP_LOCAL_USAGE_1, HID_USAGE_GENERIC_X, // USAGE (X) HIDP_MAIN_INPUT_1, 0x02, // INPUT (Data,Var,Abs) HIDP_LOCAL_USAGE_1, HID_USAGE_GENERIC_Y, // USAGE (Y) HIDP_MAIN_INPUT_1, 0x02, // INPUT (Data,Var,Abs) HIDP_MAIN_ENDCOLLECTION, // END_COLLECTION //dwZ HIDP_GLOBAL_REPORT_SIZE, 0x20, // REPORT_SIZE (32) HIDP_GLOBAL_REPORT_COUNT_1,0x01, // REPORT_COUNT (1) HIDP_MAIN_INPUT_1, 0x01, // INPUT (Cnst,Ary,Abs) //dwR HIDP_GLOBAL_REPORT_SIZE, 0x20, // REPORT_SIZE (32) HIDP_GLOBAL_REPORT_COUNT_1,0x01, // REPORT_COUNT (1) HIDP_MAIN_INPUT_1, 0x01, // INPUT (Cnst,Ary,Abs) //dwU HIDP_GLOBAL_REPORT_SIZE, 0x20, // REPORT_SIZE (20) HIDP_GLOBAL_REPORT_COUNT_1,0x01, // REPORT_COUNT (1) HIDP_MAIN_INPUT_1, 0x01, // INPUT (Cnst,Ary,Abs) //dwV HIDP_GLOBAL_REPORT_SIZE, 0x20, // REPORT_SIZE (20) HIDP_GLOBAL_REPORT_COUNT_1,0x01, // REPORT_COUNT (1) HIDP_MAIN_INPUT_1, 0x01, // INPUT (Cnst,Ary,Abs) //dwPOV HIDP_GLOBAL_REPORT_SIZE, 0x20, // REPORT_SIZE (32) HIDP_GLOBAL_REPORT_COUNT_1,0x01, // REPORT_COUNT (1) HIDP_MAIN_INPUT_1, 0x01, // INPUT (Cnst,Ary,Abs) //dwButtons HIDP_GLOBAL_USAGE_PAGE_1, HID_USAGE_PAGE_BUTTON, // USAGE_PAGE (Button) HIDP_LOCAL_USAGE_MIN_1, 0x01, // USAGE_MINIMUM (Button 1) HIDP_LOCAL_USAGE_MAX_1, 0x0A, // USAGE_MAXIMUM (Button 10) HIDP_GLOBAL_LOG_MIN_1, 0x00, // LOGICAL_MINIMUM (0) HIDP_GLOBAL_LOG_MAX_1, 0x01, // LOGICAL_MAXIMUM (1) HIDP_GLOBAL_PHY_MIN_1, 0x00, // PHYSICAL_MINIMUM (0) HIDP_GLOBAL_PHY_MAX_1, 0x01, // PHYSICAL_MAXIMUM (1) HIDP_GLOBAL_UNIT_2, 0x00, 0x00, // UNIT (None) HIDP_GLOBAL_REPORT_SIZE, 0x01, // REPORT_SIZE (1) HIDP_GLOBAL_REPORT_COUNT_1,0x20, // REPORT_COUNT (32) HIDP_MAIN_INPUT_1, 0x02, // INPUT (Data,Var,Abs) //dwButtonNumber HIDP_GLOBAL_REPORT_SIZE, 0x20, // REPORT_SIZE (20) HIDP_GLOBAL_REPORT_COUNT_1,0x01, // REPORT_COUNT (1) HIDP_MAIN_INPUT_1, 0x01, // INPUT (Cnst,Ary,Abs) //END OF COLLECTION HIDP_MAIN_ENDCOLLECTION // END_COLLECTION }; static HID_DESCRIPTOR DeviceDescriptor = { sizeof (HID_DESCRIPTOR), HID_HID_DESCRIPTOR_TYPE, MSGAME_HID_VERSION, MSGAME_HID_COUNTRY, MSGAME_HID_DESCRIPTORS, {HID_REPORT_DESCRIPTOR_TYPE, sizeof(ReportDescriptor)} }; // // Raw Data Buffer // static USHORT RawData[GAME_PACKET_SIZE/sizeof(USHORT)] = { 0 }; // // Raw Id Buffer // static SWGAMPAD_ID RawId = { 0 }; // // Timing Variables // static DEVICE_VALUES Delays = { PACKET_START_TIMEOUT, PACKET_HIGHLOW_TIMEOUT, PACKET_LOWHIGH_TIMEOUT, ID_START_TIMEOUT, ID_HIGHLOW_TIMEOUT, ID_LOWHIGH_TIMEOUT, PACKET_INTERRUPT_DELAY, MAX_CLOCK_DUTY_CYCLE, 0,0,0,0 // No status packet used }; // // Data Packet Info // static PACKETINFO DataInfo = { sizeof (PACKETINFO), // Size of structure DEVICENAME, // Name of device MSGAME_TRANSACT_NONE, // Transaction type IMODE_DIGITAL_STD, // Interface mode GAME_SPEED_100K, // Transmission speed ERROR_SUCCESS, // Last internal error result {0}, // Game port info FLAG_WAIT_FOR_CLOCK, // Packet acquisition mode 1, // Number of packets received 0, // Last valid acquisition time stamp 0, // Number of clocks sampled 0, // Number of B4 line transitions (std mode only) 0, // Start timeout period (in samples) 0, // Clock High to Low timeout period (in samples) 0, // Clock Low to High timeout period (in samples) 0, // Interrupt Timeout period 0, // Maximum clock duty cycle 0, // Number of Packet Failures 0, // Number of Packet Attempts sizeof (RawData), // Size of raw data buffer RawData // Pointer to Raw data }; // // ID Packet Info // static PACKETINFO IdInfo = { sizeof (PACKETINFO), // Size of structure DEVICENAME, // Name of device MSGAME_TRANSACT_NONE, // Transaction type IMODE_DIGITAL_STD, // Interface mode GAME_SPEED_100K, // Transmission speed ERROR_SUCCESS, // Last internal error result {0}, // Game port info FLAG_START_CLOCK_LOW, // Packet acquisition mode 1, // Number of packets received 0, // Last valid acquisition time stamp 0, // Number of clocks sampled 0, // Number of B4 line transitions (std mode only) 0, // Start timeout period (in samples) 0, // Clock High to Low timeout period (in samples) 0, // Clock Low to High timeout period (in samples) 0, // Interrupt Timeout period 0, // Maximum clock duty cycle 0, // Number of Packet Failures 0, // Number of Packet Attempts sizeof (RawId), // Size of raw id buffer &RawId // Pointer to Raw data }; // // Services Table // static DRIVERSERVICES Services = { SWGAMPAD_DriverEntry, // DriverEntry SWGAMPAD_ConnectDevice, // ConnectDevice SWGAMPAD_StartDevice, // StartDevice SWGAMPAD_ReadReport, // ReadReport SWGAMPAD_StopDevice, // StopDevice NULL // GetFeature }; // // Last Valid Data // static USHORT ValidData[GAME_PACKET_SIZE/sizeof(USHORT)] = { GAME_BUTTON_BITS, GAME_BUTTON_BITS, GAME_BUTTON_BITS, GAME_BUTTON_BITS }; // // Interrupt Flags // static UCHAR InterruptFlags = 0; // // Hardware ID String // static WCHAR HardwareId[] = HARDWARE_ID; //--------------------------------------------------------------------------- // Public Data //--------------------------------------------------------------------------- public DEVICEINFO GamePadInfo = { &Services, // Service table NULL, // Sibling device list &DeviceDescriptor, // Device descriptor data ReportDescriptor, // Report descriptor data sizeof(ReportDescriptor), // Report descriptor size 0, // Number of devices detected 0, // Number of devices started 0, // Number of devices pending DEVICENAME, // Name of device DETECT_NORMAL, // Detection order FALSE, // Analog device flag DEVICE_PID, // Hid device identifier HardwareId // PnP hardware identifier }; //--------------------------------------------------------------------------- // @func Reads registry timing values and calibrates them // @parm PGAMEPORT | PortInfo | Gameport parameters // @rdesc Returns nothing // @comm Private function //--------------------------------------------------------------------------- VOID SWGAMPAD_Calibrate (PGAMEPORT PortInfo) { MsGamePrint((DBG_INFORM,"SWGAMPAD: SWGAMPAD_Calibrate Enter\n")); // // Convert timing values to counts // DataInfo.StartTimeout = TIMER_CalibratePort (PortInfo, Delays.PacketStartTimeout); MsGamePrint((DBG_VERBOSE, "SWGAMPAD: DataInfo.StartTimeout = %ld\n", DataInfo.StartTimeout)); DataInfo.LowHighTimeout = TIMER_CalibratePort (PortInfo, Delays.PacketLowHighTimeout); MsGamePrint((DBG_VERBOSE, "SWGAMPAD: DataInfo.LowHighTimeout = %ld\n", DataInfo.LowHighTimeout)); DataInfo.HighLowTimeout = TIMER_CalibratePort (PortInfo, Delays.PacketHighLowTimeout); MsGamePrint((DBG_VERBOSE, "SWGAMPAD: DataInfo.HighLowTimeout = %ld\n", DataInfo.HighLowTimeout)); IdInfo.StartTimeout = TIMER_CalibratePort (PortInfo, Delays.IdStartTimeout); MsGamePrint((DBG_VERBOSE, "SWGAMPAD: IdInfo.StartTimeout = %ld\n", IdInfo.StartTimeout)); IdInfo.LowHighTimeout = TIMER_CalibratePort (PortInfo, Delays.IdLowHighTimeout); MsGamePrint((DBG_VERBOSE, "SWGAMPAD: IdInfo.LowHighTimeout=%ld\n", IdInfo.LowHighTimeout)); IdInfo.HighLowTimeout = TIMER_CalibratePort (PortInfo, Delays.IdHighLowTimeout); MsGamePrint((DBG_VERBOSE, "SWGAMPAD: IdInfo.HighLowTimeout=%ld\n", IdInfo.HighLowTimeout)); DataInfo.ClockDutyCycle = TIMER_CalibratePort (PortInfo, Delays.MaxClockDutyCycle); MsGamePrint((DBG_VERBOSE, "SWGAMPAD: DataInfo.ClockDutyCycle = %ld\n", DataInfo.ClockDutyCycle)); IdInfo.ClockDutyCycle = TIMER_CalibratePort (PortInfo, Delays.MaxClockDutyCycle); MsGamePrint((DBG_VERBOSE, "SWGAMPAD: IdInfo.ClockDutyCycle = %ld\n", IdInfo.ClockDutyCycle)); DataInfo.InterruptDelay = Delays.InterruptDelay; MsGamePrint((DBG_VERBOSE, "SWGAMPAD: DataInfo.InterruptDelay = %ld\n", DataInfo.InterruptDelay)); IdInfo.InterruptDelay = Delays.InterruptDelay; MsGamePrint((DBG_VERBOSE, "SWGAMPAD: IdInfo.InterruptDelay = %ld\n", IdInfo.InterruptDelay)); } //--------------------------------------------------------------------------- // @func Puts Gamepads into analog mode // @parm PPACKETINFO | Packet1 | Data packet // @parm PPACKETINFO | Packet2 | Id packet // @rdesc True if successful, False otherwise // @comm Private function //--------------------------------------------------------------------------- BOOLEAN SWGAMPAD_GoAnalog (PPACKETINFO Packet1, PPACKETINFO Packet2) { LONG Result = ERROR_SUCCESS; PGAMEPORT PortInfo = &Packet1->PortInfo; MsGamePrint ((DBG_INFORM, "SWGAMPAD_ResetDevice enter\n")); if (!PORTIO_AcquirePort (PortInfo)) return (FALSE); PORTIO_MaskInterrupts (); InterruptFlags = INTERRUPT_AFTER_PACKET; Packet1->B4Transitions = 0; Packet1->ClocksSampled = 0; PORTIO_Write (PortInfo, 0); if (!(PORTIO_Read (PortInfo) & XA_BIT_MASK)) { Result = ERROR_XA_TIMEOUT; } else { if (Packet1->Mode == IMODE_DIGITAL_ENH) SWGAMPAD_Read3Wide (Packet1); else SWGAMPAD_Read1Wide (Packet1); Packet2->B4Transitions = 0; Packet2->ClocksSampled = 0; if (Packet2->Mode == IMODE_DIGITAL_ENH) { SWGAMPAD_Read3Wide (Packet2); Result = Packet2->LastError; } else SWGAMPAD_Read1Wide (Packet2); } Packet1->B4Transitions = 0; Packet1->ClocksSampled = 0; Packet2->B4Transitions = 0; Packet2->ClocksSampled = 0; InterruptFlags = 0; DataInfo.LastError = Result; DataInfo.Transaction = MSGAME_TRANSACT_GOANALOG; PORTIO_UnMaskInterrupts (); PORTIO_ReleasePort (PortInfo); if (Result == ERROR_SUCCESS) DataInfo.Mode = IdInfo.Mode = IMODE_ANALOG; else MsGamePrint ((DBG_SEVERE, "SWGAMPAD_ResetDevice (GoAnalog) Failed\n")); MSGAME_PostTransaction (&DataInfo); return (!Result); } //--------------------------------------------------------------------------- // @func Reads device id string from port // @parm PPACKETINFO | DataPacket | Data packet parameters // @parm PPACKETINFO | IdPacket | ID packet parameters // @rdesc True if successful, False otherwise // @comm Private function //--------------------------------------------------------------------------- BOOLEAN SWGAMPAD_ReadId (PPACKETINFO DataPacket, PPACKETINFO IdPacket) { LONG Result = ERROR_SUCCESS; PGAMEPORT PortInfo = &DataPacket->PortInfo; MsGamePrint ((DBG_INFORM, "SWGAMPAD_ReadId enter\n")); if (!PORTIO_AcquirePort (PortInfo)) return (FALSE); PORTIO_MaskInterrupts (); InterruptFlags = INTERRUPT_AFTER_PACKET; DataPacket->B4Transitions = 0; DataPacket->ClocksSampled = 0; PORTIO_Write (PortInfo, 0); if (!(PORTIO_Read (PortInfo) & XA_BIT_MASK)) { Result = ERROR_XA_TIMEOUT; } else { if (DataPacket->Mode == IMODE_DIGITAL_ENH) SWGAMPAD_Read3Wide (DataPacket); else SWGAMPAD_Read1Wide (DataPacket); InterruptFlags = 0; IdPacket->B4Transitions = 0; IdPacket->ClocksSampled = 0; SWGAMPAD_Read1Wide (IdPacket); Result = IdPacket->LastError; } IdPacket->LastError = Result; IdPacket->Transaction = MSGAME_TRANSACT_ID; PORTIO_UnMaskInterrupts (); PORTIO_ReleasePort (PortInfo); if (Result != ERROR_SUCCESS) MsGamePrint ((DBG_SEVERE, "SWGAMPAD_GetId Failed\n")); MSGAME_PostTransaction (IdPacket); return (!Result); } //--------------------------------------------------------------------------- // @func Reads and validates device id string // @parm PPACKETINFO | IdPacket | ID Packet parameters // @rdesc True if successful, False otherwise // @comm Private function //--------------------------------------------------------------------------- BOOLEAN SWGAMPAD_GetId (PPACKETINFO IdPacket) { BOOLEAN Result = FALSE; MsGamePrint ((DBG_INFORM, "SWGAMPAD_GetId enter\n")); IdPacket->Attempts++; if (SWGAMPAD_ReadId (&DataInfo, IdPacket)) { ULONG i; PUSHORT p; PSWGAMPAD_ID pId; // // Remove parity bit and convert to words // p = IdPacket->Data; for (i = 0; i < 5; i++, p++) *p = ((*p<<1) & 0x7f00) | (*p & 0x7f); // // Check Id String // pId = (PSWGAMPAD_ID)IdPacket->Data; if (!strncmp (pId->EisaId, GAME_ID_STRING, strlen(GAME_ID_STRING))) { if (IdPacket->B4Transitions > 10) { DataInfo.Mode = IdInfo.Mode = IMODE_DIGITAL_ENH; } else { SWGAMPAD_GoAnalog (&DataInfo, &IdInfo); DataInfo.Mode = IdInfo.Mode = IMODE_DIGITAL_STD; } Result = TRUE; } else MsGamePrint ((DBG_SEVERE, "SWGAMPAD_GetId - Id string did not match = 0x%X\n", (ULONG)(*(PULONG)&pId->EisaId))); } if (!Result) IdPacket->Failures++; return (Result); } //--------------------------------------------------------------------------- // @func Reads 1 wide data packet from gameport // @parm PPACKETINFO | DataPacket| Data packet parameters // @rdesc True if successful, False otherwise // @comm Private function //--------------------------------------------------------------------------- #if _MSC_FULL_VER >= 13008827 && defined(_M_IX86) #pragma warning(disable:4731) // EBP modified with inline asm #endif BOOLEAN SWGAMPAD_Read1Wide (PPACKETINFO DataPacket) { LONG Result; // MsGamePrint ((DBG_VERBOSE, "SWGAMPAD_Read1Wide enter\n")); __asm { push edi push esi push ebp mov edi, DataPacket mov esi, (PPACKETINFO [edi]).Data lea edx, (PPACKETINFO [edi]).PortInfo mov ebx, 10000h xor ebp, ebp xor eax, eax test (PPACKETINFO [edi]).Acquisition, FLAG_START_CLOCK_LOW jnz Std_StartClockLow ; make sure clock is "high" before sampling clocks... mov ecx, (PPACKETINFO [edi]).StartTimeout Std_StartClockHigh: push edx ; read byte from gameport call PORTIO_Read test al, CLOCK_BIT_MASK ; Q: Start of Packet ? jz Std_StartHighToLow ; Y: jump dec ecx jnz Std_StartClockHigh ; else keep looping mov eax, ERROR_LOWCLOCKSTART jmp PacketDone ; Time out error. Std_StartHighToLow: mov ecx, (PPACKETINFO [edi]).StartTimeout Std_StartHighToLow_1: push edx ; read byte from gameport call PORTIO_Read test al, CLOCK_BIT_MASK ; Q: clock = 0 jz Std_LowToHigh ; Y: jump. dec ecx jnz Std_StartHighToLow_1 ; else see if we timed out mov eax, ERROR_CLOCKFALLING jmp PacketDone ; Time out error. Std_StartClockLow: ; wait for clock to transition to "high" (sample immediately) mov ecx, (PPACKETINFO [edi]).StartTimeout Std_StartClockLow_1: push edx ; read byte from gameport call PORTIO_Read test al, CLOCK_BIT_MASK ; Q: Clock went high ? jnz CollectData ; Y: jump (sample data) dec ecx jnz Std_StartClockLow_1 ; else keep looping mov eax, ERROR_CLOCKRISING jmp PacketDone ; Time out error. Std_CheckClkState: push edx ; read byte from gameport call PORTIO_Read test al, CLOCK_BIT_MASK jz Std_LowToHigh ;Std_HighToLow: mov ecx, (PPACKETINFO [edi]).HighLowTimeout Std_HighToLow_1: test al, CLOCK_BIT_MASK ; Q: clock = 0 jz Std_LowToHigh ; Y: jump. push edx ; read byte from gameport call PORTIO_Read dec ecx jnz Std_HighToLow_1 ; else see if we timed out mov eax, ERROR_CLOCKFALLING jmp PacketDone ; Time out error. Std_LowToHigh: mov ecx, (PPACKETINFO [edi]).LowHighTimeout Std_LowToHigh_1: test al, CLOCK_BIT_MASK ; Q: clock high ? jnz CollectData ; Y: jump. (get data) push edx ; read byte from gameport call PORTIO_Read dec ecx ; else see if we timed out jnz Std_LowToHigh_1 jmp Std_TestInterrupt CollectData: inc ebp cmp ebp, MAX_STD_SCLKS jg Std_BufferOverFlow xor ah, al test ah, DATA2_BIT_MASK ; Q: Data 2 is toggled ? jz CollectData_1 ; N: jump. inc (PPACKETINFO [edi]).B4Transitions ; Y: increment Data 2 count. CollectData_1: mov ah, al shr al, 6 ; put data into carry rcr bx, 1 ; and then in data counter add ebx, 10000h ; inc mini packet clk counter test ebx, 100000h ; Q: done mini packet ? jz Std_CheckClkState ; N: jump. shr bx, 1 ; right align mov word ptr [esi], bx ; move mini packet into buffer add esi, 2 ; advance data pointer mov ebx, 10000h ; init mini-packet counter jmp Std_CheckClkState ; go look for more clocks. Std_TestInterrupt: test InterruptFlags, INTERRUPT_AFTER_PACKET; Q: Interrupt packet ? jnz Std_IntPacket ; Y: jump. mov eax, ERROR_SUCCESS jmp PacketDone Std_IntPacket: mov ecx, 700 Std_IntPacket_1: push edx ; read byte from gameport call PORTIO_Read test al, INTXA_BIT_MASK jz Std_IntPacket_2 loop Std_IntPacket_1 mov eax, ERROR_XA_TIMEOUT jmp PacketDone Std_IntPacket_2: cmp ecx, 700 je Std_IntOut mov ecx, (PPACKETINFO [edi]).InterruptDelay Std_IntPacket_3: push edx ; read byte from gameport call PORTIO_Read test al, al xchg al, ah dec ecx jnz Std_IntPacket_3 Std_IntOut: push 0 ; write byte to gameport push edx call PORTIO_Write mov eax, ERROR_SUCCESS jmp PacketDone Std_BufferOverFlow: mov eax, ERROR_CLOCKOVERFLOW PacketDone: mov (PPACKETINFO [edi]).ClocksSampled, ebp pop ebp pop esi pop edi mov Result, eax } DataPacket->LastError = Result; #if (DBG==1) switch (Result) { case ERROR_LOWCLOCKSTART: MsGamePrint ((DBG_SEVERE, "SWGAMPAD_Read1Wide - TimeOut@LowClockStart, Clk=%ld\n", DataPacket->ClocksSampled)); break; case ERROR_HIGHCLOCKSTART: MsGamePrint ((DBG_SEVERE, "SWGAMPAD_Read1Wide - TimeOut@HighClockStart, Clk=%ld\n", DataPacket->ClocksSampled)); break; case ERROR_CLOCKFALLING: MsGamePrint ((DBG_SEVERE, "SWGAMPAD_Read1Wide - TimeOut@ClockFalling, Clk=%ld\n", DataPacket->ClocksSampled)); break; case ERROR_CLOCKRISING: MsGamePrint ((DBG_SEVERE, "SWGAMPAD_Read1Wide - TimeOut@ClockRising, Clk=%ld\n", DataPacket->ClocksSampled)); break; } #endif return (!Result); } //--------------------------------------------------------------------------- // @func Reads 3 wide data packet from gameport // @parm PPACKETINFO | DataPacket| Data packet parameters // @rdesc True if successful, False otherwise // @comm Private function //--------------------------------------------------------------------------- BOOLEAN SWGAMPAD_Read3Wide (PPACKETINFO DataPacket) { LONG Result; // MsGamePrint ((DBG_VERBOSE, "SWGAMPAD_Read3Wide enter\n")); __asm { push edi push esi push ebp mov edi, DataPacket mov esi, (PPACKETINFO [edi]).Data lea edx, (PPACKETINFO [edi]).PortInfo xor eax, eax xor ebx, ebx xor ebp, ebp ;StartEnhancedMode: test (PPACKETINFO [edi]).Acquisition, FLAG_START_CLOCK_LOW jnz Enh_LowToHigh ; make sure clock is "high" before sampling clocks... mov ecx, (PPACKETINFO [edi]).StartTimeout StartEnhancedMode_1: push edx ; read byte from gameport call PORTIO_Read test al, CLOCK_BIT_MASK ; Q: Start of Packet ? jnz Enh_StartHighToLow ; Y: jump dec ecx jnz StartEnhancedMode_1 ; else keep looping mov eax, ERROR_LOWCLOCKSTART jmp Enh_PacketDone ; Time out error. Enh_StartHighToLow: mov ecx, (PPACKETINFO [edi]).StartTimeout Enh_StartHighToLow_1: push edx ; read byte from gameport call PORTIO_Read test al, CLOCK_BIT_MASK ; Q: clock = 0 jz Enh_LowToHigh ; Y: jump. dec ecx jnz Enh_StartHighToLow_1 ; else see if we timed out mov eax, ERROR_HIGHCLOCKSTART jmp Enh_PacketDone ; Time out error. ;Enh_StartClockLow: ; wait for clock to transition to "high" (sample immediately) mov ecx, (PPACKETINFO [edi]).StartTimeout Enh_StartClockLow_1: push edx ; read byte from gameport call PORTIO_Read test al, CLOCK_BIT_MASK ; Q: Clock went high ? jnz Enh_CollectData ; Y: jump (sample data) dec ecx jnz Enh_StartClockLow_1 ; else keep looping mov eax, ERROR_CLOCKFALLING jmp Enh_PacketDone ; Time out error. Enh_CheckClkState: push edx ; read byte from gameport call PORTIO_Read test al, CLOCK_BIT_MASK jz Enh_LowToHigh ; Wait for clock to transition from high to low. ;Enh_HighToLow: mov ecx, (PPACKETINFO [edi]).HighLowTimeout Enh_HighToLow_1: test al, CLOCK_BIT_MASK ; Q: Clock Low ? jz Enh_LowToHigh ; Y: jump. push edx ; read byte from gameport call PORTIO_Read dec ecx jnz Enh_HighToLow_1 ; if !Timeout continue looping. mov eax, ERROR_LOWCLOCKSTART jmp Enh_PacketDone ; Time out error. ; Wait for clock to transition from low to high. Enh_LowToHigh: mov ecx, (PPACKETINFO [edi]).LowHighTimeout Enh_LowToHigh_1: test al, CLOCK_BIT_MASK ; Q: Clock = 1 ? jnz Enh_CollectData ; Y: jump. push edx ; read byte from gameport call PORTIO_Read dec ecx jnz Enh_LowToHigh_1 ; else continue looping. jmp Enh_TestInterrupt Enh_CollectData: inc ebp ; inc. total clocks sampled test ebp, 40h jnz Enh_BufferOverflow shr al, 5 ; move data to lower 3 bits shrd ebx, eax, 3 ; shift data into ebx. add ebp, 10000h ; inc hiword of ebp mov eax, ebp shr eax, 16 ; set ax = hiword of ebp cmp al, 5 ; Q: mini-packet done ? jne Enh_CheckClkState ; N: jump. shr ebx, 17 mov word ptr [esi],bx add esi, 2 and ebp, 0ffffh ; zero out hiword of ebp jmp Enh_CheckClkState Enh_TestInterrupt: test InterruptFlags, INTERRUPT_AFTER_PACKET ; Q: Interrupt packet ? jz Enh_PacketOK ; N: jump. ; Wait for XA line to be cleared before we can fire interrupt. mov ecx, 700 Enh_Interrupt: push edx ; read byte from gameport call PORTIO_Read test al, INTXA_BIT_MASK jz Enh_Interrupt_1 loop Enh_Interrupt mov eax, ERROR_XA_TIMEOUT jmp Enh_PacketDone Enh_Interrupt_1: mov ecx, (PPACKETINFO [edi]).InterruptDelay Enh_Interrupt_2: push edx ; read byte from gameport call PORTIO_Read test al, al dec ecx jnz Enh_Interrupt_2 push 0 ; write byte to gameport push edx call PORTIO_Write Enh_PacketOK: and ebp, 0ffffh mov (PPACKETINFO [edi]).ClocksSampled, ebp mov eax, ERROR_SUCCESS jmp Enh_PacketDone Enh_BufferOverflow: mov eax, ERROR_CLOCKOVERFLOW Enh_PacketDone: pop ebp pop esi pop edi mov Result, eax } DataPacket->LastError = Result; #if (DBG==1) switch (Result) { case ERROR_LOWCLOCKSTART: MsGamePrint ((DBG_SEVERE, "SWGAMPAD_Read3Wide - TimeOut@LowClockStart, Clk=%ld\n", DataPacket->ClocksSampled)); break; case ERROR_HIGHCLOCKSTART: MsGamePrint ((DBG_SEVERE, "SWGAMPAD_Read3Wide - TimeOut@HighClockStart, Clk=%ld\n", DataPacket->ClocksSampled)); break; case ERROR_CLOCKFALLING: MsGamePrint ((DBG_SEVERE, "SWGAMPAD_Read3Wide - TimeOut@ClockFalling, Clk=%ld\n", DataPacket->ClocksSampled)); break; case ERROR_CLOCKRISING: MsGamePrint ((DBG_SEVERE, "SWGAMPAD_Read3Wide - TimeOut@ClockRising, Clk=%ld\n", DataPacket->ClocksSampled)); break; } #endif return (!Result); } //--------------------------------------------------------------------------- // @func Reads data packet from gameport depending on mode // @parm PPACKETINFO | DataPacket| Data packet parameters // @rdesc Returns NT status code // @comm Private function //--------------------------------------------------------------------------- NTSTATUS SWGAMPAD_ReadData (PPACKETINFO DataPacket) { BOOLEAN Result = FALSE; PGAMEPORT PortInfo = &DataPacket->PortInfo; MsGamePrint ((DBG_VERBOSE, "SWGAMPAD_ReadData enter\n")); if (!PORTIO_AcquirePort (PortInfo)) return (STATUS_DEVICE_BUSY); PORTIO_MaskInterrupts (); DataPacket->ClocksSampled = 0; DataPacket->B4Transitions = 0; InterruptFlags = 0; switch (DataPacket->Mode) { case IMODE_DIGITAL_STD: PORTIO_Write (PortInfo, 0); Result = SWGAMPAD_Read1Wide (DataPacket); break; case IMODE_DIGITAL_ENH: PORTIO_Write (PortInfo, 0); Result = SWGAMPAD_Read3Wide (DataPacket); break; default: MsGamePrint ((DBG_SEVERE, "SWGAMPAD_ReadData - unknown interface\n")); break; } DataPacket->TimeStamp = TIMER_GetTickCount (); DataPacket->Transaction = MSGAME_TRANSACT_DATA; PORTIO_UnMaskInterrupts (); PORTIO_ReleasePort (PortInfo); MSGAME_PostTransaction (DataPacket); if (!Result) return (STATUS_DEVICE_NOT_CONNECTED); return (STATUS_SUCCESS); } //--------------------------------------------------------------------------- // @func Validates raw packet information // @parm PPACKETINFO | DataPacket| Data packet parameters // @rdesc True if successful, False otherwise // @comm Private function //--------------------------------------------------------------------------- BOOLEAN SWGAMPAD_ValidateData (PPACKETINFO DataPacket) { BOOLEAN Result = FALSE; PVOID Data = DataPacket->Data; ULONG Packets = DataPacket->NumPackets; ULONG Clocks = DataPacket->ClocksSampled; MsGamePrint ((DBG_VERBOSE, "SWGAMPAD_ValidateData enter\n")); if ((Clocks % 5) || (Clocks > 20)) { MsGamePrint ((DBG_SEVERE, "SWGAMPAD_ValidateData - wrong number of clocks = %lu\n", Clocks)); return (Result); } __asm { mov esi, Data mov ecx, Packets ValidateLoop: mov ax, [esi] xor al, ah jpo ValidateDone add esi, 2 loop ValidateLoop mov Result, TRUE ValidateDone: } return (Result); } //--------------------------------------------------------------------------- // @func Converts raw packet information to HID report // @parm ULONG | UnitId | UnitId for this device // @parm USHORT[] | Data | Pointer to raw data buffer // @parm PDEVICE_PACKET | Report | Pointer to device packet // @rdesc Returns nothing // @comm Private function //--------------------------------------------------------------------------- VOID SWGAMPAD_ProcessData (ULONG UnitId, USHORT Data[], PDEVICE_PACKET Report) { ULONG B1; MsGamePrint ((DBG_VERBOSE, "SWGAMPAD_ProcessData enter\n")); // // Process X Axis // switch (Data[UnitId] & GAME_X_BITS) { case GAME_X_LEFT_BIT: Report->dwX = EXTENTS_X_MIN; break; case GAME_X_RIGHT_BIT: Report->dwX = EXTENTS_X_MAX; break; default: Report->dwX = EXTENTS_X_MID; break; } // // Process Y Axis // switch (Data[UnitId] & GAME_Y_BITS) { case GAME_Y_DOWN_BIT: Report->dwY = EXTENTS_Y_MIN; break; case GAME_Y_UP_BIT: Report->dwY = EXTENTS_Y_MAX; break; default: Report->dwY = EXTENTS_Y_MID; break; } // // Process Buttons // Report->dwButtons = ~((Data[UnitId] & GAME_BUTTON_BITS) >> 4); Report->dwButtons &= ((1L << GAME_PACKET_BUTTONS) - 1); Report->dwButtonNumber = 0; for (B1 = 1; B1 <= GAME_PACKET_BUTTONS; B1++) if (Report->dwButtons & (1L << (B1-1))) { Report->dwButtonNumber = B1; break; } } //--------------------------------------------------------------------------- // @func Driver entry point for device // @rdesc Returns NT status code // @comm Private function //--------------------------------------------------------------------------- NTSTATUS SWGAMPAD_DriverEntry (VOID) { MsGamePrint((DBG_INFORM,"SWGAMPAD: SWGAMPAD_DriverEntry Enter\n")); // // Read timing values from registry // MSGAME_ReadRegistry (DEVICENAME, &Delays); return (STATUS_SUCCESS); } //--------------------------------------------------------------------------- // @func Establishes connection to device by detection // @parm PGAMEPORT | PortInfo | Gameport parameters // @rdesc Returns NT Status code // @comm Private function //--------------------------------------------------------------------------- NTSTATUS SWGAMPAD_ConnectDevice (PGAMEPORT PortInfo) { NTSTATUS ntStatus; ULONG i = MAX_CONNECT_ATTEMPTS; MsGamePrint ((DBG_INFORM, "SWGAMPAD_ConnectDevice enter\n")); DataInfo.PortInfo = IdInfo.PortInfo = *PortInfo; // // Convert registry timing values // SWGAMPAD_Calibrate (PortInfo); // // Reset to "known" state // MsGamePrint ((DBG_CONTROL, "SWGAMPAD_ConnectDevice - resetting device\n")); if (!SWGAMPAD_GoAnalog (&DataInfo, &IdInfo)) MsGamePrint ((DBG_CONTROL, "SWGAMPAD_ConnectDevice - unable to go Analog\n")); else do { // // SWGAMPAD Connection method (try these steps twice) // TIMER_DelayMicroSecs (TIMER_GetDelay(ONE_MILLI_SEC)); // // Get the ID string. // MsGamePrint ((DBG_CONTROL, "SWGAMPAD_ConnectDevice - getting ID string\n")); if (!SWGAMPAD_GetId (&IdInfo)) continue; // // Mark device found and return // if (!GamePadInfo.NumDevices) GamePadInfo.NumDevices = 1; return (STATUS_SUCCESS); } while (--i); // // Return error // GamePadInfo.NumDevices = 0; return (STATUS_DEVICE_NOT_CONNECTED); } //--------------------------------------------------------------------------- // @func Reads and converts HID packet for this device // @parm PGAMEPORT | PortInfo | Gameport parameters // @parm PUCHAR | Report | Output buffer for report // @parm ULONG | MaxSize | Size of buffer for report // @parm PULONG | Copied | Bytes copied to buffer for report // @rdesc Returns Returns NT status code // @comm Private function //--------------------------------------------------------------------------- NTSTATUS SWGAMPAD_ReadReport (PGAMEPORT PortInfo, PDEVICE_PACKET Report) { NTSTATUS ntStatus = STATUS_SUCCESS; MsGamePrint ((DBG_VERBOSE, "SWGAMPAD_ReadReport enter\n")); // // Log number of attempts // DataInfo.Attempts++; // // Set up default data to process // memcpy (DataInfo.Data, ValidData, sizeof (ValidData)); // // Check for collision // if (DEVICE_IsCollision (&DataInfo)) { MsGamePrint ((DBG_INFORM, "SWGAMPAD_ReadReport - port collision\n")); ntStatus = STATUS_DEVICE_BUSY; goto ReadReportExit; } // // Get a packet and check for errors // ntStatus = SWGAMPAD_ReadData (&DataInfo); if (!NT_SUCCESS(ntStatus)) { if (ntStatus != STATUS_DEVICE_BUSY) { DataInfo.Failures++; ntStatus = STATUS_DEVICE_NOT_CONNECTED; MsGamePrint ((DBG_SEVERE, "SWGAMPAD_ReadReport - invalid packet\n")); } else { MsGamePrint ((DBG_CONTROL, "SWGAMPAD_ReadReport - Port busy or in use\n")); } } else { if (DataInfo.Mode == IMODE_DIGITAL_ENH) DataInfo.NumPackets = DataInfo.ClocksSampled / 5; else DataInfo.NumPackets = DataInfo.ClocksSampled / 15; if (DataInfo.NumPackets == 0) DataInfo.NumPackets = 1; else if (DataInfo.NumPackets > 4) DataInfo.NumPackets = 4; if (!SWGAMPAD_ValidateData (&DataInfo)) { DataInfo.Failures++; ntStatus = STATUS_DEVICE_NOT_CONNECTED; MsGamePrint ((DBG_SEVERE, "SWGAMPAD_ReadReport - invalid packet\n")); } else memcpy (ValidData, DataInfo.Data, sizeof (ValidData)); } // --------------- ReadReportExit: // --------------- if (NT_SUCCESS(ntStatus)) GamePadInfo.NumDevices = DataInfo.NumPackets; if (GET_DEVICE_UNIT (PortInfo) < GamePadInfo.NumDevices) SWGAMPAD_ProcessData (GET_DEVICE_UNIT (PortInfo), ValidData, Report); else ntStatus = STATUS_DEVICE_NOT_CONNECTED; return (ntStatus); } //--------------------------------------------------------------------------- // @func Device handler for Pnp Start Device // @parm PGAMEPORT | PortInfo | Gameport parameters // @rdesc Returns NT status code // @comm Private function //--------------------------------------------------------------------------- NTSTATUS SWGAMPAD_StartDevice (PGAMEPORT PortInfo) { MsGamePrint ((DBG_INFORM, "SWGAMPAD_StartDevice enter\n")); UNREFERENCED_PARAMETER (PortInfo); return (STATUS_SUCCESS); } //--------------------------------------------------------------------------- // @func Device handler for Pnp Stop Device // @parm PGAMEPORT | PortInfo | Gameport parameters // @rdesc Returns NT status code // @comm Private function //--------------------------------------------------------------------------- NTSTATUS SWGAMPAD_StopDevice (PGAMEPORT PortInfo, BOOLEAN TouchHardware) { MsGamePrint ((DBG_INFORM, "SWGAMPAD_StopDevice enter\n")); UNREFERENCED_PARAMETER (PortInfo); UNREFERENCED_PARAMETER (TouchHardware); return (STATUS_SUCCESS); } //************************************************************************** #endif // SAITEK //**************************************************************************