BindingFlags.Public needed here as Exception.HResult is now public in .NET 4.5. This...
[mono.git] / mcs / class / System / System.IO.Ports / WinSerialStream.cs
1 //
2 // System.IO.Ports.WinSerialStream.cs
3 //
4 // Authors:
5 //      Carlos Alberto Cortez (calberto.cortez@gmail.com)
6 //
7 // (c) Copyright 2006 Novell, Inc. (http://www.novell.com)
8 //
9
10 using System;
11 using System.Text;
12 using System.IO;
13 using System.Runtime.InteropServices;
14 using System.Threading;
15 using System.ComponentModel;
16
17 #if NET_2_0
18
19 namespace System.IO.Ports
20 {
21         class WinSerialStream : Stream, ISerialStream, IDisposable
22         {
23                 // Windows API Constants
24                 const uint GenericRead = 0x80000000;
25                 const uint GenericWrite = 0x40000000;
26                 const uint OpenExisting = 3;
27                 const uint FileFlagOverlapped = 0x40000000;
28                 const uint PurgeRxClear = 0x0004;
29                 const uint PurgeTxClear = 0x0008;
30                 const uint WinInfiniteTimeout = 0xFFFFFFFF;
31                 const uint FileIOPending = 997;
32
33                 // Signal constants
34                 const uint SetRts = 3;
35                 const uint ClearRts = 4;
36                 const uint SetDtr = 5;
37                 const uint ClearDtr = 6;
38                 const uint SetBreak = 8;
39                 const uint ClearBreak = 9;
40                 const uint CtsOn = 0x0010;
41                 const uint DsrOn = 0x0020;
42                 const uint RsldOn = 0x0080;
43
44                 // Event constants
45                 const uint EvRxChar = 0x0001;
46                 const uint EvCts = 0x0008;
47                 const uint EvDsr = 0x0010;
48                 const uint EvRlsd = 0x0020;
49                 const uint EvBreak = 0x0040;
50                 const uint EvErr = 0x0080;
51                 const uint EvRing = 0x0100;
52
53                 int handle;
54                 int read_timeout;
55                 int write_timeout;
56                 bool disposed;
57                 IntPtr write_overlapped;
58                 IntPtr read_overlapped;
59                 ManualResetEvent read_event;
60                 ManualResetEvent write_event;
61                 Timeouts timeouts;
62
63                 [DllImport("kernel32", SetLastError = true)]
64                 static extern int CreateFile(string port_name, uint desired_access,
65                                 uint share_mode, uint security_attrs, uint creation, uint flags,
66                                 uint template);
67
68                 [DllImport("kernel32", SetLastError = true)]
69                 static extern bool SetupComm(int handle, int read_buffer_size, int write_buffer_size);
70
71                 [DllImport("kernel32", SetLastError = true)]
72                 static extern bool PurgeComm(int handle, uint flags);
73
74                 [DllImport("kernel32", SetLastError = true)]
75                 static extern bool SetCommTimeouts(int handle, Timeouts timeouts);
76
77                 public WinSerialStream (string port_name, int baud_rate, int data_bits, Parity parity, StopBits sb,
78                                 bool dtr_enable, bool rts_enable, Handshake hs, int read_timeout, int write_timeout,
79                                 int read_buffer_size, int write_buffer_size)
80                 {
81                         handle = CreateFile (port_name != null && !port_name.StartsWith(@"\\.\")
82                                         ? @"\\.\" + port_name : port_name,
83                                         GenericRead | GenericWrite, 0, 0, OpenExisting,
84                                         FileFlagOverlapped, 0);
85
86                         if (handle == -1)
87                                 ReportIOError (port_name);
88
89                         // Set port low level attributes
90                         SetAttributes (baud_rate, parity, data_bits, sb, hs);
91
92                         // Clean buffers and set sizes
93                         if (!PurgeComm (handle, PurgeRxClear | PurgeTxClear) ||
94                                         !SetupComm (handle, read_buffer_size, write_buffer_size))
95                                 ReportIOError (null);
96
97                         // Set timeouts
98                         this.read_timeout = read_timeout;
99                         this.write_timeout = write_timeout;
100                         timeouts = new Timeouts (read_timeout, write_timeout);
101                         if (!SetCommTimeouts(handle, timeouts))
102                                 ReportIOError (null);
103
104                         /// Set DTR and RTS
105                         SetSignal(SerialSignal.Dtr, dtr_enable);
106
107                         if (hs != Handshake.RequestToSend &&
108                                         hs != Handshake.RequestToSendXOnXOff)
109                                 SetSignal(SerialSignal.Rts, rts_enable);
110
111                         // Init overlapped structures
112                         NativeOverlapped wo = new NativeOverlapped ();
113                         write_event = new ManualResetEvent (false);
114 #if NET_2_0
115                         wo.EventHandle = write_event.Handle;
116 #else
117                         wo.EventHandle = (int) write_event.Handle;
118 #endif
119                         write_overlapped = Marshal.AllocHGlobal (Marshal.SizeOf (typeof (NativeOverlapped)));
120                         Marshal.StructureToPtr (wo, write_overlapped, true);
121
122                         NativeOverlapped ro = new NativeOverlapped ();
123                         read_event = new ManualResetEvent (false);
124 #if NET_2_0
125                         ro.EventHandle = read_event.Handle;
126 #else
127                         ro.EventHandle = (int) read_event.Handle;
128 #endif
129                         read_overlapped = Marshal.AllocHGlobal (Marshal.SizeOf (typeof (NativeOverlapped)));
130                         Marshal.StructureToPtr (ro, read_overlapped, true);
131                 }
132
133                 public override bool CanRead {
134                         get {
135                                 return true;
136                         }
137                 }
138
139                 public override bool CanSeek {
140                         get {
141                                 return false;
142                         }
143                 }
144
145                 public override bool CanTimeout {
146                         get {
147                                 return true;
148                         }
149                 }
150
151                 public override bool CanWrite {
152                         get {
153                                 return true;
154                         }
155                 }
156
157                 public override int ReadTimeout {
158                         get {
159                                 return read_timeout;
160                         }
161                         set {
162                                 if (value < 0 && value != SerialPort.InfiniteTimeout)
163                                         throw new ArgumentOutOfRangeException ("value");
164
165                                 timeouts.SetValues (value, write_timeout);
166                                 if (!SetCommTimeouts (handle, timeouts))
167                                         ReportIOError (null);
168
169                                 read_timeout = value;
170                         }
171                 }
172
173                 public override int WriteTimeout {
174                         get {
175                                 return write_timeout;
176                         }
177                         set
178                         {
179                                 if (value < 0 && value != SerialPort.InfiniteTimeout)
180                                         throw new ArgumentOutOfRangeException ("value");
181
182                                 timeouts.SetValues (read_timeout, value);
183                                 if (!SetCommTimeouts (handle, timeouts))
184                                         ReportIOError (null);
185
186                                 write_timeout = value;
187                         }
188                 }
189
190                 public override long Length {
191                         get {
192                                 throw new NotSupportedException ();
193                         }
194                 }
195
196                 public override long Position {
197                         get {
198                                 throw new NotSupportedException ();
199                         }
200                         set {
201                                 throw new NotSupportedException ();
202                         }
203                 }
204
205                 [DllImport("kernel32", SetLastError = true)]
206                 static extern bool CloseHandle (int handle);
207
208                 protected override void Dispose (bool disposing)
209                 {
210                         if (disposed)
211                                 return;
212
213                         disposed = true;
214                         CloseHandle (handle);
215                         Marshal.FreeHGlobal (write_overlapped);
216                         Marshal.FreeHGlobal (read_overlapped);
217                 }
218
219                 void IDisposable.Dispose ()
220                 {
221                         Dispose (true);
222                         GC.SuppressFinalize (this);
223                 }
224
225                 public override void Close ()
226                 {
227                         ((IDisposable)this).Dispose ();
228                 }
229
230                 ~WinSerialStream ()
231                 {
232                         Dispose (false);
233                 }
234
235                 public override void Flush ()
236                 {
237                         CheckDisposed ();
238                         // No dothing by now
239                 }
240
241                 public override long Seek (long offset, SeekOrigin origin)
242                 {
243                         throw new NotSupportedException();
244                 }
245
246                 public override void SetLength (long value)
247                 {
248                         throw new NotSupportedException();
249                 }
250
251 #if !TARGET_JVM
252                 [DllImport("kernel32", SetLastError = true)]
253                         static extern unsafe bool ReadFile (int handle, byte* buffer, int bytes_to_read,
254                                         out int bytes_read, IntPtr overlapped);
255
256                 [DllImport("kernel32", SetLastError = true)]
257                         static extern unsafe bool GetOverlappedResult (int handle, IntPtr overlapped,
258                                         ref int bytes_transfered, bool wait);
259 #endif
260
261                 public override int Read ([In, Out] byte [] buffer, int offset, int count)
262                 {
263                         CheckDisposed ();
264                         if (buffer == null)
265                                 throw new ArgumentNullException ("buffer");
266                         if (offset < 0 || count < 0)
267                                 throw new ArgumentOutOfRangeException ("offset or count less than zero.");
268
269                         if (buffer.Length - offset < count )
270                                 throw new ArgumentException ("offset+count",
271                                                               "The size of the buffer is less than offset + count.");
272
273                         int bytes_read;
274
275                         unsafe {
276                                 fixed (byte* ptr = buffer) {
277                                         if (ReadFile (handle, ptr + offset, count, out bytes_read, read_overlapped))
278                                                 return bytes_read;
279                                 
280                                         // Test for overlapped behavior
281                                         if (Marshal.GetLastWin32Error () != FileIOPending)
282                                                 ReportIOError (null);
283                                 
284                                         if (!GetOverlappedResult (handle, read_overlapped, ref bytes_read, true))
285                                                 ReportIOError (null);
286                         
287                                 }
288                         }
289
290                         if (bytes_read == 0)
291                                 throw new TimeoutException (); // We didn't get any byte
292
293                         return bytes_read;
294                 }
295
296 #if !TARGET_JVM
297                 [DllImport("kernel32", SetLastError = true)]
298                 static extern unsafe bool WriteFile (int handle, byte* buffer, int bytes_to_write,
299                                 out int bytes_written, IntPtr overlapped);
300 #endif
301
302                 public override void Write (byte [] buffer, int offset, int count)
303                 {
304                         CheckDisposed ();
305                         if (buffer == null)
306                                 throw new ArgumentNullException ("buffer");
307
308                         if (offset < 0 || count < 0)
309                                 throw new ArgumentOutOfRangeException ();
310
311                         if (buffer.Length - offset < count)
312                                 throw new ArgumentException ("offset+count",
313                                                              "The size of the buffer is less than offset + count.");
314
315                         int bytes_written = 0;
316
317                         unsafe {
318                                 fixed (byte* ptr = buffer) {
319                                         if (WriteFile (handle, ptr + offset, count, out bytes_written, write_overlapped))
320                                                 return;
321                                         if (Marshal.GetLastWin32Error() != FileIOPending)
322                                                 ReportIOError (null);
323                                         
324                                         if (!GetOverlappedResult(handle, write_overlapped, ref bytes_written, true))
325                                                 ReportIOError (null);
326                                 }
327                         }
328
329                         // If the operation timed out, then
330                         // we transfered less bytes than the requested ones
331                         if (bytes_written < count)
332                                 throw new TimeoutException ();
333                 }
334
335                 [DllImport("kernel32", SetLastError = true)]
336                 static extern bool GetCommState (int handle, [Out] DCB dcb);
337
338                 [DllImport ("kernel32", SetLastError=true)]
339                 static extern bool SetCommState (int handle, DCB dcb);
340
341                 public void SetAttributes (int baud_rate, Parity parity, int data_bits, StopBits bits, Handshake hs)
342                 {
343                         DCB dcb = new DCB ();
344                         if (!GetCommState (handle, dcb))
345                                 ReportIOError (null);
346
347                         dcb.SetValues (baud_rate, parity, data_bits, bits, hs);
348                         if (!SetCommState (handle, dcb))
349                                 ReportIOError (null);
350                 }
351
352                 void ReportIOError(string optional_arg)
353                 {
354                         int error = Marshal.GetLastWin32Error ();
355                         string message;
356                         switch (error) {
357                                 case 2:
358                                 case 3:
359                                         message = "The port `" + optional_arg + "' does not exist.";
360                                         break;
361                                 case 87:
362                                         message = "Parameter is incorrect.";
363                                         break;
364                                 default:
365                                         // As fallback, we show the win32 error
366                                         message = new Win32Exception ().Message;
367                                         break;
368                         }
369
370                         throw new IOException (message);
371                 }
372
373                 void CheckDisposed ()
374                 {
375                         if (disposed)
376                                 throw new ObjectDisposedException (GetType ().FullName);
377                 }
378
379                 // ISerialStream members
380                 public void DiscardInBuffer ()
381                 {
382                         if (!PurgeComm (handle, PurgeRxClear))
383                                 ReportIOError (null);
384                 }
385
386                 public void DiscardOutBuffer ()
387                 {
388                         if (!PurgeComm (handle, PurgeRxClear))
389                                 ReportIOError (null);
390                 }
391
392                 [DllImport ("kernel32", SetLastError=true)]
393                 static extern bool ClearCommError (int handle, out uint errors, out CommStat stat);
394
395                 public int BytesToRead {
396                         get {
397                                 uint errors;
398                                 CommStat stat;
399                                 if (!ClearCommError (handle, out errors, out stat))
400                                         ReportIOError (null);
401
402                                 return (int)stat.BytesIn;
403                         }
404                 }
405
406                 public int BytesToWrite {
407                         get {
408                                 uint errors;
409                                 CommStat stat;
410                                 if (!ClearCommError (handle, out errors, out stat))
411                                         ReportIOError (null);
412
413                                 return (int)stat.BytesOut;
414                         }
415                 }
416
417                 [DllImport ("kernel32", SetLastError=true)]
418                 static extern bool GetCommModemStatus (int handle, out uint flags);
419
420                 public SerialSignal GetSignals ()
421                 {
422                         uint flags;
423                         if (!GetCommModemStatus (handle, out flags))
424                                 ReportIOError (null);
425
426                         SerialSignal signals = SerialSignal.None;
427                         if ((flags & RsldOn) != 0)
428                                 signals |= SerialSignal.Cd;
429                         if ((flags & CtsOn) != 0)
430                                 signals |= SerialSignal.Cts;
431                         if ((flags & DsrOn) != 0)
432                                 signals |= SerialSignal.Dsr;
433
434                         return signals;
435                 }
436                 
437                 [DllImport ("kernel32", SetLastError=true)]
438                 static extern bool EscapeCommFunction (int handle, uint flags);
439
440                 public void SetSignal (SerialSignal signal, bool value)
441                 {
442                         if (signal != SerialSignal.Rts && signal != SerialSignal.Dtr)
443                                 throw new Exception ("Wrong internal value");
444
445                         uint flag;
446                         if (signal == SerialSignal.Rts)
447                                 if (value)
448                                         flag = SetRts;
449                                 else
450                                         flag = ClearRts;
451                         else
452                                 if (value)
453                                         flag = SetDtr;
454                                 else
455                                         flag = ClearDtr;
456
457                         if (!EscapeCommFunction (handle, flag))
458                                 ReportIOError (null);
459                 }
460
461                 public void SetBreakState (bool value)
462                 {
463                         if (!EscapeCommFunction (handle, value ? SetBreak : ClearBreak))
464                                 ReportIOError (null);
465                 }
466
467         }
468         
469         [StructLayout (LayoutKind.Sequential)]
470         class DCB
471         {
472                 public int dcb_length;
473                 public int baud_rate;
474                 public int flags;
475                 public short w_reserved;
476                 public short xon_lim;
477                 public short xoff_lim;
478                 public byte byte_size;
479                 public byte parity;
480                 public byte stop_bits;
481                 public byte xon_char;
482                 public byte xoff_char;
483                 public byte error_char;
484                 public byte eof_char;
485                 public byte evt_char;
486                 public short w_reserved1;
487
488                 // flags:
489                 //const int fBinary = 0x0001;
490                 //const int fParity = 0x0002;
491                 const int fOutxCtsFlow = 0x0004;
492                 //const int fOutxDsrFlow1 = 0x0008;
493                 //const int fOutxDsrFlow2 = 0x0010;
494                 //const int fDtrControl = 0x00020;
495                 //const int fDsrSensitivity = 0x0040;
496                 //const int fTXContinueOnXoff = 0x0080;
497                 const int fOutX = 0x0100;
498                 const int fInX = 0x0200;
499                 //const int fErrorChar = 0x0400;
500                 //const int fNull = 0x0800;
501                 //const int fRtsControl1 = 0x1000;
502                 const int fRtsControl2 = 0x2000;
503                 //const int fAbortOnError = 0x4000;
504
505                 public void SetValues (int baud_rate, Parity parity, int byte_size, StopBits sb, Handshake hs)
506                 {
507                         switch (sb) {
508                                 case StopBits.One:
509                                         stop_bits = 0;
510                                         break;
511                                 case StopBits.OnePointFive:
512                                         stop_bits = 1;
513                                         break;
514                                 case StopBits.Two:
515                                         stop_bits = 2;
516                                         break;
517                                 default: // Shouldn't happen
518                                         break;
519                         }
520
521                         this.baud_rate = baud_rate;
522                         this.parity = (byte)parity;
523                         this.byte_size = (byte)byte_size;
524
525                         // Clear Handshake flags
526                         flags &= ~(fOutxCtsFlow | fOutX | fInX | fRtsControl2);
527
528                         // Set Handshake flags
529                         switch (hs)
530                         {
531                                 case Handshake.None:
532                                         break;
533                                 case Handshake.XOnXOff:
534                                         flags |= fOutX | fInX;
535                                         break;
536                                 case Handshake.RequestToSend:
537                                         flags |= fOutxCtsFlow | fRtsControl2;
538                                         break;
539                                 case Handshake.RequestToSendXOnXOff:
540                                         flags |= fOutxCtsFlow | fOutX | fInX | fRtsControl2;
541                                         break;
542                                 default: // Shouldn't happen
543                                         break;
544                         }
545                 }
546         }
547         
548         [StructLayout (LayoutKind.Sequential)]
549         class Timeouts
550         {
551                 public uint ReadIntervalTimeout;
552                 public uint ReadTotalTimeoutMultiplier;
553                 public uint ReadTotalTimeoutConstant;
554                 public uint WriteTotalTimeoutMultiplier;
555                 public uint WriteTotalTimeoutConstant;
556
557                 public const uint MaxDWord = 0xFFFFFFFF;
558
559                 public Timeouts (int read_timeout, int write_timeout)
560                 {
561                         SetValues (read_timeout, write_timeout);
562                 }
563
564                 public void SetValues (int read_timeout, int write_timeout)
565                 {
566                         // FIXME: The windows api docs are not very clear about read timeouts,
567                         // and we have to simulate infinite with a big value (uint.MaxValue - 1)
568                         ReadIntervalTimeout = MaxDWord;
569                         ReadTotalTimeoutMultiplier = MaxDWord;
570                         ReadTotalTimeoutConstant = (read_timeout == -1 ? MaxDWord - 1 : (uint) read_timeout);
571
572                         WriteTotalTimeoutMultiplier = 0;
573                         WriteTotalTimeoutConstant = (write_timeout == -1 ? MaxDWord : (uint) write_timeout);
574                 }
575
576         }
577
578         [StructLayout (LayoutKind.Sequential)]
579         struct CommStat
580         {
581                 public uint flags;
582                 public uint BytesIn;
583                 public uint BytesOut;
584         }
585 }
586
587 #endif
588