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