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