LinuxHidStream.cs 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. #region License
  2. /* Copyright 2012 James F. Bellinger <http://www.zer7.com/software/hidsharp>
  3. Permission to use, copy, modify, and/or distribute this software for any
  4. purpose with or without fee is hereby granted, provided that the above
  5. copyright notice and this permission notice appear in all copies.
  6. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  7. WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  8. MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  9. ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  10. WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  11. ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  12. OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
  13. #endregion
  14. using System;
  15. using System.Collections.Generic;
  16. using System.IO;
  17. using System.Linq;
  18. using System.Runtime.InteropServices;
  19. using System.Threading;
  20. namespace HidSharp.Platform.Linux
  21. {
  22. class LinuxHidStream : HidStream
  23. {
  24. Queue<byte[]> _inputQueue;
  25. Queue<CommonOutputReport> _outputQueue;
  26. LinuxHidDevice _device;
  27. int _handle;
  28. Thread _readThread, _writeThread;
  29. volatile bool _shutdown;
  30. internal LinuxHidStream()
  31. {
  32. _inputQueue = new Queue<byte[]>();
  33. _outputQueue = new Queue<CommonOutputReport>();
  34. _handle = -1;
  35. _readThread = new Thread(ReadThread);
  36. _readThread.IsBackground = true;
  37. _writeThread = new Thread(WriteThread);
  38. _writeThread.IsBackground = true;
  39. }
  40. static int DeviceHandleFromPath(string path)
  41. {
  42. IntPtr udev = NativeMethods.udev_new();
  43. if (IntPtr.Zero != udev)
  44. {
  45. try
  46. {
  47. IntPtr device = NativeMethods.udev_device_new_from_syspath(udev, path);
  48. if (IntPtr.Zero != device)
  49. {
  50. try
  51. {
  52. string devnode = NativeMethods.udev_device_get_devnode(device);
  53. if (devnode != null)
  54. {
  55. int handle = NativeMethods.retry(() => NativeMethods.open
  56. (devnode, NativeMethods.oflag.RDWR | NativeMethods.oflag.NONBLOCK));
  57. if (handle < 0)
  58. {
  59. var error = (NativeMethods.error)Marshal.GetLastWin32Error();
  60. if (error == NativeMethods.error.EACCES)
  61. {
  62. throw new UnauthorizedAccessException("Not permitted to open HID class device at " + devnode + ".");
  63. }
  64. else
  65. {
  66. throw new IOException("Unable to open HID class device (" + error.ToString() + ").");
  67. }
  68. }
  69. return handle;
  70. }
  71. }
  72. finally
  73. {
  74. NativeMethods.udev_device_unref(device);
  75. }
  76. }
  77. }
  78. finally
  79. {
  80. NativeMethods.udev_unref(udev);
  81. }
  82. }
  83. throw new FileNotFoundException("HID class device not found.");
  84. }
  85. internal void Init(string path, LinuxHidDevice device)
  86. {
  87. int handle;
  88. handle = DeviceHandleFromPath(path);
  89. _device = device;
  90. _handle = handle;
  91. HandleInitAndOpen();
  92. _readThread.Start();
  93. _writeThread.Start();
  94. }
  95. protected override void Dispose(bool disposing)
  96. {
  97. base.Dispose(disposing);
  98. if (!HandleClose()) { return; }
  99. _shutdown = true;
  100. try { lock (_inputQueue) { Monitor.PulseAll(_inputQueue); } } catch { }
  101. try { lock (_outputQueue) { Monitor.PulseAll(_outputQueue); } } catch { }
  102. try { _readThread.Join(); } catch { }
  103. try { _writeThread.Join(); } catch { }
  104. HandleRelease();
  105. }
  106. internal override void HandleFree()
  107. {
  108. NativeMethods.retry(() => NativeMethods.close(_handle)); _handle = -1;
  109. }
  110. unsafe void ReadThread()
  111. {
  112. if (!HandleAcquire()) { return; }
  113. try
  114. {
  115. lock (_inputQueue)
  116. {
  117. while (true)
  118. {
  119. var fds = new NativeMethods.pollfd[1];
  120. fds[0].fd = _handle;
  121. fds[0].events = NativeMethods.pollev.IN;
  122. while (!_shutdown)
  123. {
  124. tryReadAgain:
  125. int ret;
  126. Monitor.Exit(_inputQueue);
  127. try { ret = NativeMethods.retry(() => NativeMethods.poll(fds, (IntPtr)1, 250)); }
  128. finally { Monitor.Enter(_inputQueue); }
  129. if (ret != 1) { continue; }
  130. if (0 != (fds[0].revents & (NativeMethods.pollev.ERR | NativeMethods.pollev.HUP))) { break; }
  131. if (0 != (fds[0].revents & NativeMethods.pollev.IN))
  132. {
  133. // Linux doesn't provide a Report ID if the device doesn't use one.
  134. int inputLength = _device.MaxInputReportLength;
  135. if (inputLength > 0 && !_device.ReportsUseID) { inputLength--; }
  136. byte[] inputReport = new byte[inputLength];
  137. fixed (byte* inputBytes = inputReport)
  138. {
  139. var inputBytesPtr = (IntPtr)inputBytes;
  140. IntPtr length = NativeMethods.retry(() => NativeMethods.read
  141. (_handle, inputBytesPtr, (IntPtr)inputReport.Length));
  142. if ((long)length < 0)
  143. {
  144. var error = (NativeMethods.error)Marshal.GetLastWin32Error();
  145. if (error != NativeMethods.error.EAGAIN) { break; }
  146. goto tryReadAgain;
  147. }
  148. Array.Resize(ref inputReport, (int)length); // No Report ID? First byte becomes Report ID 0.
  149. if (!_device.ReportsUseID) { inputReport = new byte[1].Concat(inputReport).ToArray(); }
  150. _inputQueue.Enqueue(inputReport); Monitor.PulseAll(_inputQueue);
  151. }
  152. }
  153. }
  154. while (!_shutdown && _inputQueue.Count == 0) { Monitor.Wait(_inputQueue); }
  155. if (_shutdown) { break; }
  156. _inputQueue.Dequeue();
  157. }
  158. }
  159. }
  160. finally
  161. {
  162. HandleRelease();
  163. }
  164. }
  165. public override int Read(byte[] buffer, int offset, int count)
  166. {
  167. return CommonRead(buffer, offset, count, _inputQueue);
  168. }
  169. public override void GetFeature(byte[] buffer, int offset, int count)
  170. {
  171. throw new NotSupportedException(); // TODO
  172. }
  173. unsafe void WriteThread()
  174. {
  175. if (!HandleAcquire()) { return; }
  176. try
  177. {
  178. lock (_outputQueue)
  179. {
  180. while (true)
  181. {
  182. while (!_shutdown && _outputQueue.Count == 0) { Monitor.Wait(_outputQueue); }
  183. if (_shutdown) { break; }
  184. CommonOutputReport outputReport = _outputQueue.Peek();
  185. // Linux doesn't expect a Report ID if the device doesn't use one.
  186. byte[] outputBytesRaw = outputReport.Bytes;
  187. if (!_device.ReportsUseID && outputBytesRaw.Length > 0) { outputBytesRaw = outputBytesRaw.Skip(1).ToArray(); }
  188. try
  189. {
  190. fixed (byte* outputBytes = outputBytesRaw)
  191. {
  192. // hidraw is apparently blocking for output, even when O_NONBLOCK is used.
  193. // See for yourself at drivers/hid/hidraw.c...
  194. IntPtr length;
  195. Monitor.Exit(_outputQueue);
  196. try
  197. {
  198. var outputBytesPtr = (IntPtr)outputBytes;
  199. length = NativeMethods.retry(() => NativeMethods.write
  200. (_handle, outputBytesPtr, (IntPtr)outputBytesRaw.Length));
  201. if ((long)length == outputBytesRaw.Length) { outputReport.DoneOK = true; }
  202. }
  203. finally
  204. {
  205. Monitor.Enter(_outputQueue);
  206. }
  207. }
  208. }
  209. finally
  210. {
  211. _outputQueue.Dequeue();
  212. outputReport.Done = true;
  213. Monitor.PulseAll(_outputQueue);
  214. }
  215. }
  216. }
  217. }
  218. finally
  219. {
  220. HandleRelease();
  221. }
  222. }
  223. public override void Write(byte[] buffer, int offset, int count)
  224. {
  225. CommonWrite(buffer, offset, count, _outputQueue, false, _device.MaxOutputReportLength);
  226. }
  227. public override void SetFeature(byte[] buffer, int offset, int count)
  228. {
  229. throw new NotSupportedException(); // TODO
  230. }
  231. public override HidDevice Device
  232. {
  233. get { return _device; }
  234. }
  235. }
  236. }