Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

544 lines
22 KiB

  1. /*---
  2. Copyright (c) 1998 - 1999 Microsoft Corporation
  3. Module Name:
  4. poll.c
  5. Abstract: This module contains the routines to poll an analog gameport device
  6. Environment:
  7. Kernel mode
  8. @@BEGIN_DDKSPLIT
  9. Author:
  10. Eliyas Yakub (Mar, 11, 1997)
  11. Revision History:
  12. Updated by Eliyas on Feb 5 1998
  13. MarcAnd 02-Jul-98 Quick tidy for DDK
  14. MarcAnd 04-Oct-98 Re-org
  15. @@END_DDKSPLIT
  16. --*/
  17. #include "hidgame.h"
  18. /*****************************************************************************
  19. *
  20. * @doc EXTERNAL
  21. *
  22. * @func NTSTATUS | HidAnalogPoll |
  23. *
  24. * Polling routine for analog joysticks.
  25. * <nl>Polls the analog device for position and button information.
  26. * The position information in analog devices is conveyed by the
  27. * duration of a pulse width. Each axis occupies one bit position.
  28. * The read operation is started by writing a value to the joystick
  29. * io address. Immediately thereafter we begin examing the values
  30. * returned and the elapsed time.
  31. *
  32. * This sort of device has a few limitations:
  33. *
  34. * First, button information is not latched by the device, so if a
  35. * button press which occurrs in between polls it will be lost.
  36. * There is really no way to prevent this short of devoting
  37. * the entire cpu to polling. In reality this does not cause a problem.
  38. *
  39. * Second, since it is necessary to measure the duration of the axis pulse,
  40. * the most accurate results would be obtained using the smallest possible
  41. * sense loop and no interruptions of this loop.
  42. * The typical range of pulse lengths is from around 10 uSecs to 1500 uSecs
  43. * but depending on the joystick and gameport, this could extend to at least
  44. * 8000 uSecs. Keeping interrupts disabled for this length of time causes
  45. * many problems, like modems losing connections to sound break ups.
  46. *
  47. * Third, because each iteration of the poll loop requires an port read, the
  48. * speed of the loop is largely constrained by the speed of the IO bus.
  49. * This also means that when there is contention for the IO bus, the loop
  50. * will be slowed down. IO contention is usually caused by DMAs (or FDMAs)
  51. * which result in a significant slow down.
  52. *
  53. * Forth, because of the previous two problems, the poll loop may be slowed
  54. * down or interrupted at any time so an external time source is needed to
  55. * measure the pulse width for each axis. The only cross-platform high
  56. * resolution timer is the read with KeQueryPerformanceCounter.
  57. * Unfortunately the implementation of this often uses a 1.18MHz 8253 timer
  58. * which requires 3 IO accesses to read, compounding the third problem and
  59. * even then, the result may need to be reread if the counters were in the
  60. * wrong state. Current CPUs have on board counters that can be used to
  61. * provide very accurate timing and more recent HAL implementations tend to
  62. * use these to implement KeQueryPerformanceCounter so this will be a problem
  63. * on less systems as time goes on. In the majority of cases, a poor
  64. * KeQueryPerformanceCounter implementation is made irrelevant by testing
  65. * for the availability of a CPU time stamp counter on Intel architechtures
  66. * and using it directly if it is available.
  67. *
  68. * The algorithm implemented here is not the most obvious but works as
  69. * follows:
  70. *
  71. * Once started, the axes read a value of one until the completion of their
  72. * pulse. The axes are the four lower bits in the byte read from the port.
  73. * The state of the axes in each iteration of the poll loop is therefore
  74. * represented as a value between 0 and 15. The important time for each
  75. * axis is the time at which it changes from 1 to 0. This is done by using
  76. * the value representing the state of the axes to index an array into which
  77. * time values are stored. For each axis, the duration of its pulse width is
  78. * the latest time stored in the array at an index with the bit for that axis
  79. * set. However since interrupts can occur at any time, it is not possible
  80. * to simultaneously read the port value and record that time in an atomic
  81. * operation the in each iteration, the current time is stored in two arrays,
  82. * one using the index before the time was recorded and the other using the
  83. * index after the time was recorded.
  84. * Once all the axes being monitored have become 0, or a timeout value is
  85. * reached, the data left in the arrays is analysed to find the best
  86. * estimate for the transition time for each axis. If the times before and
  87. * after the transition differ by too much, it is judged that an interrupt
  88. * must have occured so the last known good axis value is returned unless
  89. * that falls outside the range in which it is known that the transition
  90. * occured.
  91. *
  92. * This routine cannot be pageable as HID can make reads at dispatch-level.
  93. *
  94. * @parm IN PDEVICE_EXTENSION | DeviceExtension |
  95. *
  96. * Pointer to the device extension.
  97. *
  98. * @parm IN UCHAR | resisitiveInputMask |
  99. *
  100. * Mask that describes the axes lines that are to be polled
  101. *
  102. * @parm IN BOOLEAN | fApproximate |
  103. *
  104. * Boolean value indicating if it is OK to approximate some
  105. * value of the current axis state with the last axis state
  106. * if polling was not successful (we took an interrput during polling)
  107. *
  108. * @parm IN OUT ULONG | Axis[MAX_AXES] |
  109. *
  110. * The state of the axes. On entry the last axis state is passed
  111. * into this routine. If the fApproximate flag is turned on, we can
  112. * make use of the last axis state to "guess" the current axis state.
  113. *
  114. * @parm OUT UCHAR | Button[PORT_BUTTONS]|
  115. *
  116. * Receives the state of the buttons. 0x0 specifies the button is not
  117. * pressed and 0x1 indicates an armed button state.
  118. *
  119. * @rvalue STATUS_SUCCESS | success
  120. * @rvalue STATUS_DEVICE_NOT_CONNECTED | Device Failed to Quiesce
  121. * ( not connected ) This is a failure code.
  122. * @rvalue STATUS_TIMEOUT | Could not determine exact transition time for
  123. * one or more axis. This is a success code.
  124. *
  125. *****************************************************************************/
  126. /*
  127. * Tell a compiler that we "a" won't use any aliasing and "t" want fast code
  128. */
  129. #pragma optimize( "at", on )
  130. /*
  131. * Disable warning for variable used before set as it is hard for a compiler
  132. * to see that TimeNow is always initialized before it is used.
  133. */
  134. #pragma warning( disable:4701 )
  135. NTSTATUS INTERNAL
  136. HGM_AnalogPoll
  137. (
  138. IN PDEVICE_EXTENSION DeviceExtension,
  139. IN UCHAR resistiveInputMask,
  140. IN BOOLEAN fApproximate,
  141. IN OUT ULONG Axis[MAX_AXES],
  142. OUT UCHAR Button[PORT_BUTTONS]
  143. )
  144. {
  145. ULONG BeforeTimes[MAX_AXES*MAX_AXES];
  146. ULONG AfterTimes[MAX_AXES*MAX_AXES];
  147. ULONGLONG CounterFreq;
  148. PUCHAR GameContext;
  149. NTSTATUS ntStatus = STATUS_SUCCESS;
  150. /*
  151. * To improve compiler optimization, we cast the ReadAccessor function to
  152. * return a ULONG instead of a UCHAR. This means that the result must
  153. * always be masked before use but this would be done anyway to remove
  154. * the parts of the UCHAR we are not interested in.
  155. */
  156. typedef ULONG (*PHIDGAME_READPORT) ( PVOID GameContext );
  157. PHIDGAME_READPORT ReadPort;
  158. ULONG portLast, portMask;
  159. HGM_DBGPRINT( FILE_POLL | HGM_FENTRY, \
  160. ("HGM_AnalogPoll DeviceExtension=0x%x, resistiveInputMask=0x%x",\
  161. DeviceExtension, resistiveInputMask ));
  162. portMask = (ULONG)(resistiveInputMask & 0xf);
  163. /*
  164. * Initialize Times to recognizable value
  165. */
  166. memset( (PVOID)BeforeTimes, 0, sizeof( BeforeTimes ) );
  167. memset( (PVOID)AfterTimes, 0, sizeof( AfterTimes ) );
  168. /*
  169. * Find where our port and data area are, and related parameters
  170. */
  171. GameContext = DeviceExtension->GameContext;
  172. ReadPort = (PHIDGAME_READPORT)(*DeviceExtension->ReadAccessor);
  173. /*
  174. * get the buttons (not forgetting that the top 3 bytes are garbage)
  175. */
  176. portLast = ReadPort(GameContext);
  177. Button[0] = (UCHAR)(( portLast & 0x10 ) == 0x0);
  178. Button[1] = (UCHAR)(( portLast & 0x20 ) == 0x0);
  179. Button[2] = (UCHAR)(( portLast & 0x40 ) == 0x0);
  180. Button[3] = (UCHAR)(( portLast & 0x80 ) == 0x0);
  181. portLast = portMask;
  182. /*
  183. * Start the pots
  184. * (any debug output from here until the completion of the
  185. * while( portLast ) loop will destroy the axis data)
  186. */
  187. (*DeviceExtension->WriteAccessor)(GameContext, JOY_START_TIMERS);
  188. /*
  189. * Keep reading until all the pots we care about are zero or we time out
  190. */
  191. {
  192. ULONG TimeNow;
  193. ULONG TimeStart;
  194. ULONG TimeOut = DeviceExtension->ScaledTimeout/Global.CounterScale;
  195. ULONG portVal = portMask;
  196. TimeStart = Global.ReadCounter(NULL).LowPart;
  197. while( portLast )
  198. {
  199. TimeNow = Global.ReadCounter(NULL).LowPart - TimeStart;
  200. AfterTimes[portLast] = TimeNow;
  201. portLast = portVal;
  202. portVal = ReadPort(GameContext) & portMask;
  203. BeforeTimes[portVal] = TimeNow;
  204. if( TimeNow >= TimeOut ) break;
  205. }
  206. if( portLast && ( TimeNow >= TimeOut ) )
  207. {
  208. HGM_DBGPRINT( FILE_POLL | HGM_BABBLE, \
  209. ("HGM_AnalogPoll: TimeNow: 0x%08x TimeOut: 0x%08x", TimeNow, TimeOut ) );
  210. }
  211. }
  212. {
  213. LONG axisIdx;
  214. for( axisIdx = 3; axisIdx>=0; axisIdx-- )
  215. {
  216. ULONG axisMask;
  217. axisMask = 1 << axisIdx;
  218. if( axisMask & portMask )
  219. {
  220. if( axisMask & portLast )
  221. {
  222. /*
  223. * Whether or not a hit was taken, this axis did not
  224. * quiesce. So update the axis time so that next poll
  225. * the last value will be a timeout in case a hit is
  226. * taken over both the transition and the timeout.
  227. */
  228. Axis[axisIdx] = AXIS_TIMEOUT;
  229. ntStatus = STATUS_DEVICE_NOT_CONNECTED;
  230. HGM_DBGPRINT( FILE_POLL | HGM_WARN, \
  231. ("HGM_AnalogPoll: axis %x still set at timeout", axisMask ) );
  232. }
  233. else
  234. {
  235. ULONG timeIdx;
  236. ULONG beforeThresholdTime;
  237. ULONG afterThresholdTime;
  238. ULONG delta;
  239. afterThresholdTime = beforeThresholdTime = 0;
  240. for( timeIdx = axisMask; timeIdx<= portMask; timeIdx=(timeIdx+1) | axisMask )
  241. {
  242. if( BeforeTimes[timeIdx] > beforeThresholdTime )
  243. {
  244. beforeThresholdTime = BeforeTimes[timeIdx];
  245. afterThresholdTime = AfterTimes[timeIdx];
  246. }
  247. }
  248. /*
  249. * Convert the CPU specific timing values into 'wall clock'
  250. * values so that they can be compared with the previous
  251. * poll values and so that the range will be dependent on
  252. * the gamecard/joystick characteristics, not the CPU
  253. * and counter implementation.
  254. * Use a ULONGLONG temp to avoid overflow.
  255. */
  256. {
  257. ULONGLONG u64Temp;
  258. u64Temp = beforeThresholdTime * Global.CounterScale;
  259. beforeThresholdTime = (ULONG)(u64Temp >> SCALE_SHIFT);
  260. u64Temp = afterThresholdTime * Global.CounterScale;
  261. afterThresholdTime = (ULONG)(u64Temp >> SCALE_SHIFT);
  262. }
  263. delta = afterThresholdTime - beforeThresholdTime;
  264. if( delta > DeviceExtension->ScaledThreshold )
  265. {
  266. /*
  267. * We took an unacceptable hit so only change the value
  268. * if we know the last value is no longer correct
  269. * Since the real time is somewhere between the before and
  270. * after, take the value closer to the last value.
  271. */
  272. if( fApproximate )
  273. {
  274. /*
  275. * Be careful not to turn a failure into a success
  276. */
  277. if( NT_SUCCESS(ntStatus) )
  278. {
  279. ntStatus = STATUS_TIMEOUT;
  280. }
  281. }
  282. else
  283. {
  284. ntStatus = STATUS_DEVICE_NOT_CONNECTED;
  285. }
  286. if( Axis[axisIdx] >= AXIS_FULL_SCALE )
  287. {
  288. /*
  289. * The previous poll was a timeout
  290. */
  291. if( afterThresholdTime < AXIS_FULL_SCALE )
  292. {
  293. /*
  294. * This poll is not a timeout so split the
  295. * difference since there is nothing else
  296. * to use for an estimate.
  297. * Since these values are scaled, it would
  298. * be perfectly legitimate for their sum to
  299. * be greater than 32 bits.
  300. */
  301. Axis[axisIdx] = (beforeThresholdTime>>1)
  302. + (afterThresholdTime>>1);
  303. HGM_DBGPRINT( FILE_POLL | HGM_BABBLE2, \
  304. ("HGM_AnalogPoll:Axis=%d, using glitch average %04x",\
  305. axisIdx, Axis[axisIdx] ) ) ;
  306. }
  307. else
  308. {
  309. /*
  310. * Since the previous poll was a timeout and
  311. * there is no evidence that this is not, call
  312. * this a timeout.
  313. */
  314. ntStatus = STATUS_DEVICE_NOT_CONNECTED;
  315. HGM_DBGPRINT( FILE_POLL | HGM_BABBLE2, \
  316. ("HGM_AnalogPoll:Axis=%d, repeating timeout on glitch",\
  317. axisIdx ) ) ;
  318. }
  319. }
  320. else if( beforeThresholdTime > Axis[axisIdx] )
  321. {
  322. Axis[axisIdx] = beforeThresholdTime;
  323. HGM_DBGPRINT( FILE_POLL | HGM_BABBLE2, \
  324. ("HGM_AnalogPoll:Axis=%d, using smaller glitch limit %04x",\
  325. axisIdx, Axis[axisIdx] ) ) ;
  326. }
  327. else if( afterThresholdTime < Axis[axisIdx] )
  328. {
  329. Axis[axisIdx] = afterThresholdTime;
  330. HGM_DBGPRINT( FILE_POLL | HGM_BABBLE2, \
  331. ("HGM_AnalogPoll:Axis=%d, using larger glitch limit %04x",\
  332. axisIdx, Axis[axisIdx] ) ) ;
  333. }
  334. else
  335. {
  336. HGM_DBGPRINT( FILE_POLL | HGM_BABBLE2, \
  337. ("HGM_AnalogPoll:Axis=%d, repeating previous on glitch %04x",\
  338. axisIdx, Axis[axisIdx] ) ) ;
  339. }
  340. }
  341. else
  342. {
  343. if( (delta <<= 1) < DeviceExtension->ScaledThreshold )
  344. {
  345. HGM_DBGPRINT( FILE_POLL | HGM_BABBLE2, \
  346. ("HGM_AnalogPoll: Updating ScaledThreshold from %d to %d",\
  347. DeviceExtension->ScaledThreshold, delta ) ) ;
  348. /*
  349. * Fastest change yet, update
  350. */
  351. DeviceExtension->ScaledThreshold = delta;
  352. }
  353. /*
  354. * It is possible that afterThresholdTime is greater
  355. * than the timeout limit but since the purpose of
  356. * the timeout is to prevent excessive sampling of
  357. * the gameport, the success or failure of an
  358. * uninterrupted poll around this limit is not
  359. * important.
  360. * Since these values are scaled, it would be
  361. * perfectly legitimate for their sum to be greater
  362. * than 32 bits and the 1 bit of lost resolution is
  363. * utterly negligable.
  364. */
  365. Axis[axisIdx] = (beforeThresholdTime>>1)
  366. + (afterThresholdTime>>1);
  367. }
  368. }
  369. }
  370. }
  371. }
  372. HGM_DBGPRINT( FILE_POLL | HGM_BABBLE2, \
  373. ("HGM_AnalogPoll:X=%d, Y=%d, R=%d, Z=%d Buttons=%d,%d,%d,%d",\
  374. Axis[0], Axis[1], Axis[2], Axis[3],\
  375. Button[0],Button[1],Button[2],Button[3] ) ) ;
  376. HGM_EXITPROC(FILE_POLL|HGM_FEXIT, "HGM_AnalogPoll", ntStatus);
  377. return ntStatus;
  378. } /* HGM_AnalogPoll */
  379. #pragma warning( default:4701 )
  380. #pragma optimize( "", on )
  381. /*****************************************************************************
  382. *
  383. * @doc EXTERNAL
  384. *
  385. * @func NTSTATUS | HGM_UpdateLatestPollData |
  386. *
  387. * Do whatever polling is required and possible to update the
  388. * LastGoodAxis and LastGoodButton arrays in the DeviceExtension.
  389. * Handles synchronization and non-fatal errors.
  390. * <nl>This routine cannot be pageable as HID can make reads at
  391. * dispatch-level.
  392. *
  393. * @parm IN OUT PDEVICE_EXTENSION | DeviceExtension |
  394. *
  395. * Pointer to the device extension containing the data to be updated
  396. * and the functions and to use.
  397. *
  398. * @rvalue STATUS_SUCCESS | success
  399. * @rvalue STATUS_DEVICE_CONFIGURATION_ERROR | Invalid configuration specified
  400. *
  401. *****************************************************************************/
  402. #define APPROXIMATE_FAILS TRUE
  403. NTSTATUS
  404. HGM_UpdateLatestPollData
  405. (
  406. IN OUT PDEVICE_EXTENSION DeviceExtension
  407. )
  408. {
  409. NTSTATUS ntStatus;
  410. KIRQL oldIrql;
  411. LONG axisIdx;
  412. /*
  413. * Acquire the global spinlock
  414. * Read / Writes are made at dispatch level.
  415. */
  416. KeAcquireSpinLock(&Global.SpinLock, &oldIrql );
  417. /*
  418. * First gain exclusive access to the hardware
  419. */
  420. ntStatus = (*DeviceExtension->AcquirePort)( DeviceExtension->PortContext );
  421. if( NT_SUCCESS(ntStatus) )
  422. {
  423. /*
  424. * If it's available, let the hardware do the work
  425. */
  426. if( DeviceExtension->ReadAccessorDigital )
  427. {
  428. ntStatus = (*DeviceExtension->ReadAccessorDigital)(DeviceExtension->GameContext,
  429. DeviceExtension->resistiveInputMask,
  430. APPROXIMATE_FAILS,
  431. &DeviceExtension->LastGoodAxis[0],
  432. &DeviceExtension->LastGoodButton[0]);
  433. }
  434. else
  435. {
  436. ntStatus = HGM_AnalogPoll(DeviceExtension,
  437. DeviceExtension->resistiveInputMask,
  438. APPROXIMATE_FAILS,
  439. &DeviceExtension->LastGoodAxis[0],
  440. &DeviceExtension->LastGoodButton[0]);
  441. }
  442. /*
  443. * Either way, release the hardware ASAP
  444. */
  445. (*DeviceExtension->ReleasePort)( DeviceExtension->PortContext );
  446. }
  447. /*
  448. * Release the global spinlock and return to previous IRQL
  449. */
  450. KeReleaseSpinLock(&Global.SpinLock, oldIrql);
  451. if( ( ntStatus == STATUS_DEVICE_BUSY ) && APPROXIMATE_FAILS )
  452. {
  453. /*
  454. * Clashed trying to access the gameport. So work with the same
  455. * data as last time unless all failures must be reported or the
  456. * last data was a failure for these axes.
  457. */
  458. for( axisIdx=3; axisIdx>=0; axisIdx-- )
  459. {
  460. if( ( ( 1 << axisIdx ) & DeviceExtension->resistiveInputMask )
  461. &&( DeviceExtension->LastGoodAxis[axisIdx]
  462. >= DeviceExtension->ScaledTimeout ) )
  463. {
  464. break;
  465. }
  466. }
  467. if( axisIdx<0 )
  468. {
  469. ntStatus = STATUS_TIMEOUT;
  470. }
  471. }
  472. if( !NT_SUCCESS( ntStatus ) )
  473. {
  474. HGM_DBGPRINT(FILE_IOCTL | HGM_WARN,\
  475. ("HGM_UpdateLatestPollData Failed 0x%x", ntStatus));
  476. }
  477. return( ntStatus );
  478. } /* HGM_UpdateLatestPollData */