Leaked source code of windows server 2003
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.

543 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. PUCHAR GameContext;
  148. NTSTATUS ntStatus = STATUS_SUCCESS;
  149. /*
  150. * To improve compiler optimization, we cast the ReadAccessor function to
  151. * return a ULONG instead of a UCHAR. This means that the result must
  152. * always be masked before use but this would be done anyway to remove
  153. * the parts of the UCHAR we are not interested in.
  154. */
  155. typedef ULONG (*PHIDGAME_READPORT) ( PVOID GameContext );
  156. PHIDGAME_READPORT ReadPort;
  157. ULONG portLast, portMask;
  158. HGM_DBGPRINT( FILE_POLL | HGM_FENTRY, \
  159. ("HGM_AnalogPoll DeviceExtension=0x%x, resistiveInputMask=0x%x",\
  160. DeviceExtension, resistiveInputMask ));
  161. portMask = (ULONG)(resistiveInputMask & 0xf);
  162. /*
  163. * Initialize Times to recognizable values
  164. */
  165. RtlZeroMemory( (PVOID)BeforeTimes, sizeof( BeforeTimes ) );
  166. RtlZeroMemory( (PVOID)AfterTimes, sizeof( AfterTimes ) );
  167. /*
  168. * Find where our port and data area are, and related parameters
  169. */
  170. GameContext = DeviceExtension->GameContext;
  171. ReadPort = (PHIDGAME_READPORT)(*DeviceExtension->ReadAccessor);
  172. /*
  173. * get the buttons (not forgetting that the top 3 bytes are garbage)
  174. */
  175. portLast = ReadPort(GameContext);
  176. Button[0] = (UCHAR)(( portLast & 0x10 ) == 0x0);
  177. Button[1] = (UCHAR)(( portLast & 0x20 ) == 0x0);
  178. Button[2] = (UCHAR)(( portLast & 0x40 ) == 0x0);
  179. Button[3] = (UCHAR)(( portLast & 0x80 ) == 0x0);
  180. portLast = portMask;
  181. /*
  182. * Start the pots
  183. * (any debug output from here until the completion of the
  184. * while( portLast ) loop will destroy the axis data)
  185. */
  186. (*DeviceExtension->WriteAccessor)(GameContext, JOY_START_TIMERS);
  187. /*
  188. * Keep reading until all the pots we care about are zero or we time out
  189. */
  190. {
  191. ULONG TimeNow;
  192. ULONG TimeStart;
  193. ULONG TimeOut = DeviceExtension->ScaledTimeout/Global.CounterScale;
  194. ULONG portVal = portMask;
  195. TimeStart = Global.ReadCounter(NULL).LowPart;
  196. while( portLast )
  197. {
  198. TimeNow = Global.ReadCounter(NULL).LowPart - TimeStart;
  199. AfterTimes[portLast] = TimeNow;
  200. portLast = portVal;
  201. portVal = ReadPort(GameContext) & portMask;
  202. BeforeTimes[portVal] = TimeNow;
  203. if( TimeNow >= TimeOut ) break;
  204. }
  205. if( portLast && ( TimeNow >= TimeOut ) )
  206. {
  207. HGM_DBGPRINT( FILE_POLL | HGM_BABBLE, \
  208. ("HGM_AnalogPoll: TimeNow: 0x%08x TimeOut: 0x%08x", TimeNow, TimeOut ) );
  209. }
  210. }
  211. {
  212. LONG axisIdx;
  213. for( axisIdx = 3; axisIdx>=0; axisIdx-- )
  214. {
  215. ULONG axisMask;
  216. axisMask = 1 << axisIdx;
  217. if( axisMask & portMask )
  218. {
  219. if( axisMask & portLast )
  220. {
  221. /*
  222. * Whether or not a hit was taken, this axis did not
  223. * quiesce. So update the axis time so that next poll
  224. * the last value will be a timeout in case a hit is
  225. * taken over both the transition and the timeout.
  226. */
  227. Axis[axisIdx] = AXIS_TIMEOUT;
  228. ntStatus = STATUS_DEVICE_NOT_CONNECTED;
  229. HGM_DBGPRINT( FILE_POLL | HGM_WARN, \
  230. ("HGM_AnalogPoll: axis %x still set at timeout", axisMask ) );
  231. }
  232. else
  233. {
  234. ULONG timeIdx;
  235. ULONG beforeThresholdTime;
  236. ULONG afterThresholdTime;
  237. ULONG delta;
  238. afterThresholdTime = beforeThresholdTime = 0;
  239. for( timeIdx = axisMask; timeIdx<= portMask; timeIdx=(timeIdx+1) | axisMask )
  240. {
  241. if( BeforeTimes[timeIdx] > beforeThresholdTime )
  242. {
  243. beforeThresholdTime = BeforeTimes[timeIdx];
  244. afterThresholdTime = AfterTimes[timeIdx];
  245. }
  246. }
  247. /*
  248. * Convert the CPU specific timing values into 'wall clock'
  249. * values so that they can be compared with the previous
  250. * poll values and so that the range will be dependent on
  251. * the gamecard/joystick characteristics, not the CPU
  252. * and counter implementation.
  253. * Use a ULONGLONG temp to avoid overflow.
  254. */
  255. {
  256. ULONGLONG u64Temp;
  257. u64Temp = beforeThresholdTime * Global.CounterScale;
  258. beforeThresholdTime = (ULONG)(u64Temp >> SCALE_SHIFT);
  259. u64Temp = afterThresholdTime * Global.CounterScale;
  260. afterThresholdTime = (ULONG)(u64Temp >> SCALE_SHIFT);
  261. }
  262. delta = afterThresholdTime - beforeThresholdTime;
  263. if( delta > DeviceExtension->ScaledThreshold )
  264. {
  265. /*
  266. * We took an unacceptable hit so only change the value
  267. * if we know the last value is no longer correct
  268. * Since the real time is somewhere between the before and
  269. * after, take the value closer to the last value.
  270. */
  271. if( fApproximate )
  272. {
  273. /*
  274. * Be careful not to turn a failure into a success
  275. */
  276. if( NT_SUCCESS(ntStatus) )
  277. {
  278. ntStatus = STATUS_TIMEOUT;
  279. }
  280. }
  281. else
  282. {
  283. ntStatus = STATUS_DEVICE_NOT_CONNECTED;
  284. }
  285. if( Axis[axisIdx] >= AXIS_FULL_SCALE )
  286. {
  287. /*
  288. * The previous poll was a timeout
  289. */
  290. if( afterThresholdTime < AXIS_FULL_SCALE )
  291. {
  292. /*
  293. * This poll is not a timeout so split the
  294. * difference since there is nothing else
  295. * to use for an estimate.
  296. * Since these values are scaled, it would
  297. * be perfectly legitimate for their sum to
  298. * be greater than 32 bits.
  299. */
  300. Axis[axisIdx] = (beforeThresholdTime>>1)
  301. + (afterThresholdTime>>1);
  302. HGM_DBGPRINT( FILE_POLL | HGM_BABBLE2, \
  303. ("HGM_AnalogPoll:Axis=%d, using glitch average %04x",\
  304. axisIdx, Axis[axisIdx] ) ) ;
  305. }
  306. else
  307. {
  308. /*
  309. * Since the previous poll was a timeout and
  310. * there is no evidence that this is not, call
  311. * this a timeout.
  312. */
  313. ntStatus = STATUS_DEVICE_NOT_CONNECTED;
  314. HGM_DBGPRINT( FILE_POLL | HGM_BABBLE2, \
  315. ("HGM_AnalogPoll:Axis=%d, repeating timeout on glitch",\
  316. axisIdx ) ) ;
  317. }
  318. }
  319. else if( beforeThresholdTime > Axis[axisIdx] )
  320. {
  321. Axis[axisIdx] = beforeThresholdTime;
  322. HGM_DBGPRINT( FILE_POLL | HGM_BABBLE2, \
  323. ("HGM_AnalogPoll:Axis=%d, using smaller glitch limit %04x",\
  324. axisIdx, Axis[axisIdx] ) ) ;
  325. }
  326. else if( afterThresholdTime < Axis[axisIdx] )
  327. {
  328. Axis[axisIdx] = afterThresholdTime;
  329. HGM_DBGPRINT( FILE_POLL | HGM_BABBLE2, \
  330. ("HGM_AnalogPoll:Axis=%d, using larger glitch limit %04x",\
  331. axisIdx, Axis[axisIdx] ) ) ;
  332. }
  333. else
  334. {
  335. HGM_DBGPRINT( FILE_POLL | HGM_BABBLE2, \
  336. ("HGM_AnalogPoll:Axis=%d, repeating previous on glitch %04x",\
  337. axisIdx, Axis[axisIdx] ) ) ;
  338. }
  339. }
  340. else
  341. {
  342. if( (delta <<= 1) < DeviceExtension->ScaledThreshold )
  343. {
  344. HGM_DBGPRINT( FILE_POLL | HGM_BABBLE2, \
  345. ("HGM_AnalogPoll: Updating ScaledThreshold from %d to %d",\
  346. DeviceExtension->ScaledThreshold, delta ) ) ;
  347. /*
  348. * Fastest change yet, update
  349. */
  350. DeviceExtension->ScaledThreshold = delta;
  351. }
  352. /*
  353. * It is possible that afterThresholdTime is greater
  354. * than the timeout limit but since the purpose of
  355. * the timeout is to prevent excessive sampling of
  356. * the gameport, the success or failure of an
  357. * uninterrupted poll around this limit is not
  358. * important.
  359. * Since these values are scaled, it would be
  360. * perfectly legitimate for their sum to be greater
  361. * than 32 bits and the 1 bit of lost resolution is
  362. * utterly negligable.
  363. */
  364. Axis[axisIdx] = (beforeThresholdTime>>1)
  365. + (afterThresholdTime>>1);
  366. }
  367. }
  368. }
  369. }
  370. }
  371. HGM_DBGPRINT( FILE_POLL | HGM_BABBLE2, \
  372. ("HGM_AnalogPoll:X=%d, Y=%d, R=%d, Z=%d Buttons=%d,%d,%d,%d",\
  373. Axis[0], Axis[1], Axis[2], Axis[3],\
  374. Button[0],Button[1],Button[2],Button[3] ) ) ;
  375. HGM_EXITPROC(FILE_POLL|HGM_FEXIT, "HGM_AnalogPoll", ntStatus);
  376. return ntStatus;
  377. } /* HGM_AnalogPoll */
  378. #pragma warning( default:4701 )
  379. #pragma optimize( "", on )
  380. /*****************************************************************************
  381. *
  382. * @doc EXTERNAL
  383. *
  384. * @func NTSTATUS | HGM_UpdateLatestPollData |
  385. *
  386. * Do whatever polling is required and possible to update the
  387. * LastGoodAxis and LastGoodButton arrays in the DeviceExtension.
  388. * Handles synchronization and non-fatal errors.
  389. * <nl>This routine cannot be pageable as HID can make reads at
  390. * dispatch-level.
  391. *
  392. * @parm IN OUT PDEVICE_EXTENSION | DeviceExtension |
  393. *
  394. * Pointer to the device extension containing the data to be updated
  395. * and the functions and to use.
  396. *
  397. * @rvalue STATUS_SUCCESS | success
  398. * @rvalue STATUS_DEVICE_CONFIGURATION_ERROR | Invalid configuration specified
  399. *
  400. *****************************************************************************/
  401. #define APPROXIMATE_FAILS TRUE
  402. NTSTATUS
  403. HGM_UpdateLatestPollData
  404. (
  405. IN OUT PDEVICE_EXTENSION DeviceExtension
  406. )
  407. {
  408. NTSTATUS ntStatus;
  409. KIRQL oldIrql;
  410. LONG axisIdx;
  411. /*
  412. * Acquire the global spinlock
  413. * Read / Writes are made at dispatch level.
  414. */
  415. KeAcquireSpinLock(&Global.SpinLock, &oldIrql );
  416. /*
  417. * First gain exclusive access to the hardware
  418. */
  419. ntStatus = (*DeviceExtension->AcquirePort)( DeviceExtension->PortContext );
  420. if( NT_SUCCESS(ntStatus) )
  421. {
  422. /*
  423. * If it's available, let the hardware do the work
  424. */
  425. if( DeviceExtension->ReadAccessorDigital )
  426. {
  427. ntStatus = (*DeviceExtension->ReadAccessorDigital)(DeviceExtension->GameContext,
  428. DeviceExtension->resistiveInputMask,
  429. APPROXIMATE_FAILS,
  430. &DeviceExtension->LastGoodAxis[0],
  431. &DeviceExtension->LastGoodButton[0]);
  432. }
  433. else
  434. {
  435. ntStatus = HGM_AnalogPoll(DeviceExtension,
  436. DeviceExtension->resistiveInputMask,
  437. APPROXIMATE_FAILS,
  438. &DeviceExtension->LastGoodAxis[0],
  439. &DeviceExtension->LastGoodButton[0]);
  440. }
  441. /*
  442. * Either way, release the hardware ASAP
  443. */
  444. (*DeviceExtension->ReleasePort)( DeviceExtension->PortContext );
  445. }
  446. /*
  447. * Release the global spinlock and return to previous IRQL
  448. */
  449. KeReleaseSpinLock(&Global.SpinLock, oldIrql);
  450. if( ( ntStatus == STATUS_DEVICE_BUSY ) && APPROXIMATE_FAILS )
  451. {
  452. /*
  453. * Clashed trying to access the gameport. So work with the same
  454. * data as last time unless all failures must be reported or the
  455. * last data was a failure for these axes.
  456. */
  457. for( axisIdx=3; axisIdx>=0; axisIdx-- )
  458. {
  459. if( ( ( 1 << axisIdx ) & DeviceExtension->resistiveInputMask )
  460. &&( DeviceExtension->LastGoodAxis[axisIdx]
  461. >= DeviceExtension->ScaledTimeout ) )
  462. {
  463. break;
  464. }
  465. }
  466. if( axisIdx<0 )
  467. {
  468. ntStatus = STATUS_TIMEOUT;
  469. }
  470. }
  471. if( !NT_SUCCESS( ntStatus ) )
  472. {
  473. HGM_DBGPRINT(FILE_IOCTL | HGM_WARN,\
  474. ("HGM_UpdateLatestPollData Failed 0x%x", ntStatus));
  475. }
  476. return( ntStatus );
  477. } /* HGM_UpdateLatestPollData */