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