From 86659bddbe585f9fd829e22ab70aea3d0fe8ea52 Mon Sep 17 00:00:00 2001 From: Tomas Weinfurt Date: Sun, 8 Jul 2018 23:04:05 -0700 Subject: [PATCH 1/4] minimal support for serial port on Linux --- src/System.IO.Ports/src/Configurations.props | 1 + .../src/Interop/Unix/Interop.Termios.cs | 2 +- .../src/System.IO.Ports.csproj | 50 +- .../src/System/IO/Ports/SerialPort.Linux.cs | 42 ++ .../src/System/IO/Ports/SerialPort.OSX.cs | 39 ++ .../src/System/IO/Ports/SerialStream.Unix.cs | 579 ++++++++++++++++++ .../tests/Configurations.props | 3 +- .../tests/SerialPort/RtsEnable.cs | 1 + .../tests/Support/PortHelper.cs | 6 + .../tests/System.IO.Ports.Tests.csproj | 10 +- 10 files changed, 721 insertions(+), 12 deletions(-) create mode 100644 src/System.IO.Ports/src/System/IO/Ports/SerialPort.Linux.cs create mode 100644 src/System.IO.Ports/src/System/IO/Ports/SerialPort.OSX.cs create mode 100644 src/System.IO.Ports/src/System/IO/Ports/SerialStream.Unix.cs diff --git a/src/System.IO.Ports/src/Configurations.props b/src/System.IO.Ports/src/Configurations.props index b611284f7732..9848e9415b3b 100644 --- a/src/System.IO.Ports/src/Configurations.props +++ b/src/System.IO.Ports/src/Configurations.props @@ -3,6 +3,7 @@ netstandard-Windows_NT; + netstandard-Linux; netstandard; netfx; diff --git a/src/System.IO.Ports/src/Interop/Unix/Interop.Termios.cs b/src/System.IO.Ports/src/Interop/Unix/Interop.Termios.cs index 1e65aca21897..29743b9a6ceb 100644 --- a/src/System.IO.Ports/src/Interop/Unix/Interop.Termios.cs +++ b/src/System.IO.Ports/src/Interop/Unix/Interop.Termios.cs @@ -42,7 +42,7 @@ internal enum Queue internal static extern int TermiosGetSpeed(SafeFileHandle handle); [DllImport(Libraries.IOPortsNative, EntryPoint = "SystemIoPortsNative_TermiosAvailableBytes", SetLastError = true)] - internal static extern int TermiosGetAvailableBytes(SafeFileHandle handle, int input); + internal static extern int TermiosGetAvailableBytes(SafeFileHandle handle, Queue input); [DllImport(Libraries.IOPortsNative, EntryPoint = "SystemIoPortsNative_TermiosDiscard", SetLastError = true)] internal static extern int TermiosDiscard(SafeFileHandle handle, Queue input); diff --git a/src/System.IO.Ports/src/System.IO.Ports.csproj b/src/System.IO.Ports/src/System.IO.Ports.csproj index fd5a81a75c82..999ccd5230e5 100644 --- a/src/System.IO.Ports/src/System.IO.Ports.csproj +++ b/src/System.IO.Ports/src/System.IO.Ports.csproj @@ -3,15 +3,14 @@ true {187503F4-BEF9-4369-A1B2-E3DC5D564E4E} true - SR.PlatformNotSupported_IOPorts + SR.PlatformNotSupported_IOPorts $(DefineConstants);NOSPAN true - netfx-Debug;netfx-Release;netstandard-Debug;netstandard-Release;netstandard-Windows_NT-Debug;netstandard-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release + netfx-Debug;netfx-Release;netstandard-Debug;netstandard-Release;netstandard-Windows_NT-Debug;netstandard-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;netstandard-Linux-Debug;netstandard-Linux-Release - + - @@ -24,8 +23,11 @@ - + + + + Common\Interop\Windows\kernel32\Interop.DCB.cs @@ -139,6 +141,27 @@ + + + + + + + Common\Interop\Unix\Interop.Libraries.cs + + + Common\Interop\Unix\Interop.Errors.cs + + + Interop\Unix\System.Native\Interop.Read.cs + + + Interop\Unix\System.Native\Interop.Write.cs + + + Interop\Unix\System.Native\Interop.Poll.cs + + @@ -161,4 +184,19 @@ - \ No newline at end of file + + + + + + + + + + + + + + + + diff --git a/src/System.IO.Ports/src/System/IO/Ports/SerialPort.Linux.cs b/src/System.IO.Ports/src/System/IO/Ports/SerialPort.Linux.cs new file mode 100644 index 000000000000..714f9974baed --- /dev/null +++ b/src/System.IO.Ports/src/System/IO/Ports/SerialPort.Linux.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.ComponentModel; +using System.Collections.Generic; +using System.IO; + +namespace System.IO.Ports +{ + public partial class SerialPort : Component + { + public static string[] GetPortNames() + { + const string sysDir = "/sys/class/tty"; + + if (Directory.Exists(sysDir)) + { + // /sys is mounted. Let's explore tty class and pick active nodes. + List ports = new List(); + DirectoryInfo di = new DirectoryInfo(@"/sys/class/tty"); + var entries = di.EnumerateFileSystemInfos(@"*", SearchOption.TopDirectoryOnly); + foreach (var entry in entries) + { + if (Directory.Exists("/sys/bus/usb-serial/devices/" + entry.Name) || File.Exists(entry.FullName + "/device/id")) + { + ports.Add("/dev/" + entry.Name); + } + } + + return ports.ToArray(); + } + else + { + // Fallback to scanning /dev. That may have more devices then needed as well as + // This can miss usb or devices with non-standard name. + return Directory.GetFiles("/dev", "ttyS*"); + } + } + } +} diff --git a/src/System.IO.Ports/src/System/IO/Ports/SerialPort.OSX.cs b/src/System.IO.Ports/src/System/IO/Ports/SerialPort.OSX.cs new file mode 100644 index 000000000000..dcd2a47eb1c2 --- /dev/null +++ b/src/System.IO.Ports/src/System/IO/Ports/SerialPort.OSX.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.ComponentModel; +using System.Collections.Generic; +using System.IO; + +namespace System.IO.Ports +{ + public partial class SerialPort : Component + { + public static string[] GetPortNames() + { + List ports = new List(); + + foreach (string name in Directory.GetFiles("/dev", "tty.*")) + { + // GetFiles can return unexpected results because of 8.3 matching. + // Like /dev/tty + if (name.StartsWith("/dev/tty.")) + { + ports.Add(name); + } + } + + foreach (string name in Directory.GetFiles("/dev", "cu.*")) + { + if (name.StartsWith("/dev/cu.")) + { + ports.Add(name); + } + } + + return ports.ToArray(); + } + } +} diff --git a/src/System.IO.Ports/src/System/IO/Ports/SerialStream.Unix.cs b/src/System.IO.Ports/src/System/IO/Ports/SerialStream.Unix.cs new file mode 100644 index 000000000000..1230b88b040d --- /dev/null +++ b/src/System.IO.Ports/src/System/IO/Ports/SerialStream.Unix.cs @@ -0,0 +1,579 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Win32.SafeHandles; +using System.Collections; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.IO.Ports; + +namespace System.IO.Ports +{ + internal sealed partial class SerialStream : Stream + { + private const int MaxDataBits = 8; + private const int MinDataBits = 5; + + private string _portName; + private int _baudRate; + private StopBits _stopBits; + private Handshake _handshake; + private Parity _parity; + private int _dataBits = 8; + private bool _inBreak = false; + private SafeFileHandle _handle = null; + private bool _rtsEnable = false; + private int _readTimeout = 0; + private int _writeTimeout = 0; + + // three different events, also wrapped by SerialPort. + internal event SerialDataReceivedEventHandler DataReceived; // called when one character is received. + internal event SerialPinChangedEventHandler PinChanged; // called when any of the pin/ring-related triggers occurs + internal event SerialErrorReceivedEventHandler ErrorReceived; // called when any runtime error occurs on the port (frame, overrun, parity, etc.) + + // ----SECTION: inherited properties from Stream class ------------* + + // These six properties are required for SerialStream to inherit from the abstract Stream class. + // Note four of them are always true or false, and two of them throw exceptions, so these + // are not usefully queried by applications which know they have a SerialStream, etc... + + public override int ReadTimeout + { + get { return _readTimeout; } + set { _readTimeout = value; } + } + + public override int WriteTimeout + { + get { return _writeTimeout; } + set { _writeTimeout = value; } + } + + public override bool CanRead + { + get { return (_handle != null); } + } + + public override bool CanSeek + { + get { return false; } + } + + public override bool CanTimeout + { + get { return (_handle != null); } + } + + public override bool CanWrite + { + get { return (_handle != null); } + } + + public override long Length + { + get { throw new NotSupportedException(SR.NotSupported_UnseekableStream); } + } + + public override long Position + { + get { throw new NotSupportedException(SR.NotSupported_UnseekableStream); } + set { throw new NotSupportedException(SR.NotSupported_UnseekableStream); } + } + + internal int BaudRate + { + set + { + if (value != _baudRate) + { + if (value <= 0 || value > 230400) + { + throw new ArgumentOutOfRangeException(nameof(BaudRate), SR.ArgumentOutOfRange_NeedPosNum); + } + + if (Interop.Termios.TermiosSetSpeed(_handle, value) < 0) + { + throw new IOException(); + } + + _baudRate = value; + } + } + + get + { + return Interop.Termios.TermiosGetSpeed(_handle); + } + } + + public bool BreakState + { + get { return _inBreak; } + set + { + throw new System.PlatformNotSupportedException(SR.PlatformNotSupported_IOPorts); + } + } + + internal int BytesToWrite + { + get { return Interop.Termios.TermiosGetAvailableBytes(_handle, Interop.Termios.Queue.SendQueue); } + } + + internal int BytesToRead + { + get { return Interop.Termios.TermiosGetAvailableBytes(_handle, Interop.Termios.Queue.ReceiveQueue); } + } + + internal bool CDHolding + { + get + { + int status = Interop.Termios.TermiosGetSignal(_handle, Interop.Termios.Signals.SignalDcd); + if (status < 0) + { + throw new IOException(); + } + + return status == 1; + } + } + + internal bool CtsHolding + { + get + { + int status = Interop.Termios.TermiosGetSignal(_handle, Interop.Termios.Signals.SignalCts); + if (status < 0) + { + throw new IOException(); + } + + return status == 1; + } + } + + internal bool DsrHolding + { + get + { + int status = Interop.Termios.TermiosGetSignal(_handle, Interop.Termios.Signals.SignalDsr); + if (status < 0) + { + throw new IOException(); + } + + return status == 1; + } + } + + internal bool DtrEnable + { + get + { + int status = Interop.Termios.TermiosGetSignal(_handle, Interop.Termios.Signals.SignalDtr); + if (status < 0) + { + throw new IOException(); + } + + return status == 1; + } + + set + { + if (Interop.Termios.TermiosGetSignal(_handle, Interop.Termios.Signals.SignalDtr, value ? 1 : 0) != 0) + { + throw new IOException(); + } + } + } + + internal bool RtsEnable + { + get + { + int status = Interop.Termios.TermiosGetSignal(_handle, Interop.Termios.Signals.SignalRts); + if (status < 0) + { + throw new IOException(); + } + + return status == 1; + } + + set + { + if (Interop.Termios.TermiosGetSignal(_handle, Interop.Termios.Signals.SignalRts, value ? 1 : 0) != 0) + { + throw new IOException(); + } + } + } + + internal Handshake Handshake + { + set + { + Debug.Assert(!(value < Handshake.None || value > Handshake.RequestToSendXOnXOff), + "An invalid value was passed to Handshake"); + + if (value != _handshake) + { + _handshake = value; + if (Interop.Termios.TermiosReset(_handle, _baudRate, _dataBits, _stopBits, _parity, _handshake) != 0) + { + throw new ArgumentException(); + } + } + } + } + + internal int DataBits + { + set + { + Debug.Assert(!(value < MinDataBits || value > MaxDataBits), "An invalid value was passed to DataBits"); + if (value != _dataBits) + { + _dataBits = value; + if (Interop.Termios.TermiosReset(_handle, _baudRate, _dataBits, _stopBits, _parity, _handshake) != 0) + { + throw new ArgumentException(); + } + } + } + } + + internal Parity Parity + { + set + { + Debug.Assert(!(value < Parity.None || value > Parity.Space), "An invalid value was passed to Parity"); + + if (value != _parity) + { + _parity = value; + if (Interop.Termios.TermiosReset(_handle, _baudRate, _dataBits, _stopBits, _parity, _handshake) != 0) + { + throw new ArgumentException(); + } + } + } + } + + internal StopBits StopBits + { + set + { + Debug.Assert(!(value < StopBits.One || value > StopBits.OnePointFive), "An invalid value was passed to StopBits"); + if (value != _stopBits) + { + _stopBits = value; + if (Interop.Termios.TermiosReset(_handle, _baudRate, _dataBits, _stopBits, _parity, _handshake) != 0) + { + throw new ArgumentException(); + } + } + } + } + + internal bool DiscardNull + { + set + { + // Ignore. + } + } + + internal byte ParityReplace + { + set + { + // Ignore. + } + } + + internal void DiscardInBuffer() + { + if (_handle == null) throw new ObjectDisposedException(SR.Port_not_open); + // This may or may not work depending on hardware. + Interop.Termios.TermiosDiscard(_handle, Interop.Termios.Queue.ReceiveQueue); + } + + internal void DiscardOutBuffer() + { + if (_handle == null) throw new ObjectDisposedException(SR.Port_not_open); + // This may or may not work depending on hardware. + Interop.Termios.TermiosDiscard(_handle, Interop.Termios.Queue.SendQueue); + } + + internal void SetBufferSizes(int readBufferSize, int writeBufferSize) + { + if (_handle == null) throw new IOException(); + + // Ignore for now. + } + + internal bool IsOpen => _handle != null; + + + // Flush dumps the contents of the serial driver's internal read and write buffers. + // We actually expose the functionality for each, but fulfilling Stream's contract + // requires a Flush() method. Fails if handle closed. + // Note: Serial driver's write buffer is *already* attempting to write it, so we can only wait until it finishes. + public override void Flush() + { + if (_handle == null) throw new ObjectDisposedException(SR.Port_not_open); + Interop.Termios.TermiosDiscard(_handle, Interop.Termios.Queue.AllQueues); + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException(SR.NotSupported_UnseekableStream); + } + + public override void SetLength(long value) + { + throw new NotSupportedException(SR.NotSupported_UnseekableStream); + } + + public override int ReadByte() + { + return ReadByte(ReadTimeout); + } + + internal unsafe int ReadByte(int timeout) + { + if (_handle == null) throw new IOException(); + + if (timeout > 0) + { + Interop.Sys.Poll(_handle, Interop.Sys.PollEvents.POLLIN | Interop.Sys.PollEvents.POLLERR, timeout, out Interop.Sys.PollEvents events); + if ((events & (Interop.Sys.PollEvents.POLLERR | Interop.Sys.PollEvents.POLLNVAL)) != 0) + { + throw new IOException(); + } + + if ((events & Interop.Sys.PollEvents.POLLIN) == 0) + { + throw new TimeoutException(); + } + } + + byte* tempBuf = stackalloc byte[1]; + + int numBytes = Interop.Sys.Read(_handle, tempBuf, 1); + if (numBytes != 1) + { + throw new IOException(); + } + + return tempBuf[0]; + } + + public override int Read(byte[] array, int offset, int count) + { + return Read(array, offset, count, ReadTimeout); + } + + internal unsafe int Read(byte[] array, int offset, int count, int timeout) + { + if (array == null) + throw new ArgumentNullException(nameof(array)); + if (offset < 0) + throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNumRequired); + if (count < 0) + throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNumRequired); + if (array.Length - offset < count) + throw new ArgumentException(SR.Argument_InvalidOffLen); + if (count == 0) return 0; // return immediately if no bytes requested; no need for overhead. + + Interop.Sys.PollEvents events = Interop.Sys.PollEvents.POLLNONE; + + if (timeout > 0) + { + Interop.Sys.Poll(_handle, Interop.Sys.PollEvents.POLLIN | Interop.Sys.PollEvents.POLLERR, timeout,out events); + + if ((events & (Interop.Sys.PollEvents.POLLERR | Interop.Sys.PollEvents.POLLNVAL)) != 0) + { + throw new IOException(); + } + + if ( (events & Interop.Sys.PollEvents.POLLIN) == 0) + { + throw new TimeoutException(); + } + } + + int numBytes; + fixed (byte* bufPtr = array) + { + numBytes = Interop.Sys.Read(_handle, bufPtr + offset, count); + } + + if (numBytes == 0) + throw new TimeoutException(); + + return numBytes; + } + + public override void Write(byte[] array, int offset, int count) + { + Write(array, offset, count, WriteTimeout); + } + + internal unsafe void Write(byte[] array, int offset, int count, int timeout) + { + if (_inBreak) + throw new InvalidOperationException(SR.In_Break_State); + if (array == null) + throw new ArgumentNullException(nameof(array)); + if (offset < 0) + throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedPosNum); + if (count < 0) + throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedPosNum); + if (count == 0) return; // no need to expend overhead in creating asyncResult, etc. + if (array.Length - offset < count) + throw new ArgumentException(SR.Argument_InvalidOffLen); + Debug.Assert(timeout == SerialPort.InfiniteTimeout || timeout >= 0, "Serial Stream Write - write timeout is " + timeout); + + // check for open handle, though the port is always supposed to be open + if (_handle == null) throw new IOException(); //InternalResources.FileNotOpen(); + + int numBytes = 0; + + while (count > 0) + { + Interop.Sys.PollEvents events = Interop.Sys.PollEvents.POLLNONE; + if (timeout > 0) + { + Interop.Sys.Poll(_handle, Interop.Sys.PollEvents.POLLOUT | Interop.Sys.PollEvents.POLLERR, timeout,out events); + + if ((events & (Interop.Sys.PollEvents.POLLERR | Interop.Sys.PollEvents.POLLNVAL)) != 0) + { + throw new IOException(); + } + + if ( (events & Interop.Sys.PollEvents.POLLOUT) == 0) + { + throw new TimeoutException(SR.Write_timed_out); + } + } + + fixed (byte* bufPtr = array) + { + numBytes = Interop.Sys.Write(_handle, bufPtr + offset, count); + } + + if (numBytes == -1) + { + throw new IOException(); + } + + if (numBytes == 0) + { + throw new TimeoutException(SR.Write_timed_out); + } + count -= numBytes; + offset += numBytes; + } + } + + internal SafeFileHandle OpenPort(string portName) + { + SafeFileHandle handle = Interop.Serial.SerialPortOpen(portName); + + return handle; + } + + // this method is used by SerialPort upon SerialStream's creation + internal SerialStream(string portName, int baudRate, Parity parity, int dataBits, StopBits stopBits, int readTimeout, int writeTimeout, Handshake handshake, + bool dtrEnable, bool rtsEnable, bool discardNull, byte parityReplace) + { + + if (portName == null) + { + throw new ArgumentException(SR.Arg_InvalidSerialPort, nameof(portName)); + } + + // Error checking done in SerialPort. + + SafeFileHandle tempHandle = OpenPort(portName); + + if (tempHandle.IsInvalid) + { + throw new ArgumentException(SR.Arg_InvalidSerialPort, nameof(portName)); + } + + try + { + _handle = tempHandle; + // set properties of the stream that exist as members in SerialStream + _portName = portName; + _handshake = handshake; + _parity = parity; + _readTimeout = readTimeout; + _writeTimeout = writeTimeout; + _baudRate = baudRate; + _stopBits = stopBits; + _dataBits = dataBits; + _parity = parity; + + if (Interop.Termios.TermiosReset(_handle, _baudRate, _dataBits, _stopBits, _parity, _handshake) != 0) + { + throw new ArgumentException(); + } + + DtrEnable = dtrEnable; + // query and cache the initial RtsEnable value + // so that set_RtsEnable can do the (value != rtsEnable) optimization + //_rtsEnable = (GetDcbFlag(NativeMethods.FRTSCONTROL) == NativeMethods.RTS_CONTROL_ENABLE); + _rtsEnable = RtsEnable; + + BaudRate = baudRate; + + // now set this.RtsEnable to the specified value. + // Handshake takes precedence, this will be a nop if + // handshake is either RequestToSend or RequestToSendXOnXOff + if ((handshake != Handshake.RequestToSend && handshake != Handshake.RequestToSendXOnXOff)) + { + RtsEnable = rtsEnable; + } + + PinChanged = null; + ErrorReceived = null; + DataReceived = null; + } + catch + { + // if there are any exceptions after the call to CreateFile, we need to be sure to close the + // handle before we let them continue up. + tempHandle.Close(); + _handle = null; + throw; + } + } + + ~SerialStream() + { + Dispose(false); + } + + protected override void Dispose(bool disposing) + { + // Signal the other side that we're closing. Should do regardless of whether we've called + // Close() or not Dispose() + if (_handle != null && !_handle.IsInvalid) + { + _handle.Close(); + _handle = null; + if (PinChanged != null || ErrorReceived != null || DataReceived != null) + { + } + base.Dispose(disposing); + } + } + } +} diff --git a/src/System.IO.Ports/tests/Configurations.props b/src/System.IO.Ports/tests/Configurations.props index 1e5845b0b616..17e83be7494e 100644 --- a/src/System.IO.Ports/tests/Configurations.props +++ b/src/System.IO.Ports/tests/Configurations.props @@ -3,7 +3,8 @@ netstandard-Windows_NT; + netstandard-Linux; netfx; - \ No newline at end of file + diff --git a/src/System.IO.Ports/tests/SerialPort/RtsEnable.cs b/src/System.IO.Ports/tests/SerialPort/RtsEnable.cs index c0aee56a2fcc..230eef1518e0 100644 --- a/src/System.IO.Ports/tests/SerialPort/RtsEnable.cs +++ b/src/System.IO.Ports/tests/SerialPort/RtsEnable.cs @@ -196,6 +196,7 @@ public void RtsEnable_Get_Handshake_RequestToSend() } [ConditionalFact(nameof(HasOneSerialPort))] + [PlatformSpecific(~TestPlatforms.AnyUnix)] public void RtsEnable_Get_Handshake_RequestToSendXOnXOff() { using (SerialPort com1 = new SerialPort(TCSupport.LocalMachineSerialInfo.FirstAvailablePortName)) diff --git a/src/System.IO.Ports/tests/Support/PortHelper.cs b/src/System.IO.Ports/tests/Support/PortHelper.cs index fce77e6916ad..d562606ac30c 100644 --- a/src/System.IO.Ports/tests/Support/PortHelper.cs +++ b/src/System.IO.Ports/tests/Support/PortHelper.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO.Ports; using System.Runtime.InteropServices; using System.Text.RegularExpressions; @@ -20,6 +21,11 @@ public class PortHelper public static string[] GetPorts() { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return SerialPort.GetPortNames(); + } + if (PlatformDetection.IsUap) { return new [] { "COM3", "COM4", "COM5", "COM6", "COM7" }; // we are waiting for a Win32 new QueryDosDevice API since the current doesn't work for Uap https://github.com/dotnet/corefx/issues/21156 diff --git a/src/System.IO.Ports/tests/System.IO.Ports.Tests.csproj b/src/System.IO.Ports/tests/System.IO.Ports.Tests.csproj index 043fda7ce748..5e5dc4137d79 100644 --- a/src/System.IO.Ports/tests/System.IO.Ports.Tests.csproj +++ b/src/System.IO.Ports/tests/System.IO.Ports.Tests.csproj @@ -28,7 +28,6 @@ - @@ -41,12 +40,10 @@ - - @@ -114,7 +111,12 @@ + + + + + - \ No newline at end of file + From 854bb5c95b1674df779002f5dc500c6cf717832d Mon Sep 17 00:00:00 2001 From: Tomas Weinfurt Date: Wed, 18 Jul 2018 22:36:42 -0700 Subject: [PATCH 2/4] feedback from review --- src/System.IO.Ports/src/System.IO.Ports.csproj | 2 +- .../src/System/IO/Ports/SerialPort.Linux.cs | 13 +++++++------ .../Ports/{SerialPort.cs => SerialPort.Windows.cs} | 0 .../src/System/IO/Ports/SerialStream.Unix.cs | 12 +++++------- 4 files changed, 13 insertions(+), 14 deletions(-) rename src/System.IO.Ports/src/System/IO/Ports/{SerialPort.cs => SerialPort.Windows.cs} (100%) diff --git a/src/System.IO.Ports/src/System.IO.Ports.csproj b/src/System.IO.Ports/src/System.IO.Ports.csproj index 999ccd5230e5..43a11a806ba4 100644 --- a/src/System.IO.Ports/src/System.IO.Ports.csproj +++ b/src/System.IO.Ports/src/System.IO.Ports.csproj @@ -22,7 +22,7 @@ - + diff --git a/src/System.IO.Ports/src/System/IO/Ports/SerialPort.Linux.cs b/src/System.IO.Ports/src/System/IO/Ports/SerialPort.Linux.cs index 714f9974baed..c137c24ea8e6 100644 --- a/src/System.IO.Ports/src/System/IO/Ports/SerialPort.Linux.cs +++ b/src/System.IO.Ports/src/System/IO/Ports/SerialPort.Linux.cs @@ -13,17 +13,18 @@ public partial class SerialPort : Component { public static string[] GetPortNames() { - const string sysDir = "/sys/class/tty"; + const string sysTtyDir = "/sys/class/tty"; + const string sysUsbDir = "/sys/bus/usb-serial/devices/"; - if (Directory.Exists(sysDir)) + if (Directory.Exists(sysTtyDir)) { // /sys is mounted. Let's explore tty class and pick active nodes. List ports = new List(); - DirectoryInfo di = new DirectoryInfo(@"/sys/class/tty"); + DirectoryInfo di = new DirectoryInfo(sysTtyDir); var entries = di.EnumerateFileSystemInfos(@"*", SearchOption.TopDirectoryOnly); foreach (var entry in entries) { - if (Directory.Exists("/sys/bus/usb-serial/devices/" + entry.Name) || File.Exists(entry.FullName + "/device/id")) + if (Directory.Exists(sysUsbDir + entry.Name) || File.Exists(entry.FullName + "/device/id")) { ports.Add("/dev/" + entry.Name); } @@ -33,8 +34,8 @@ public static string[] GetPortNames() } else { - // Fallback to scanning /dev. That may have more devices then needed as well as - // This can miss usb or devices with non-standard name. + // Fallback to scanning /dev. That may have more devices then needed. + // This can also miss usb or serial devices with non-standard name. return Directory.GetFiles("/dev", "ttyS*"); } } diff --git a/src/System.IO.Ports/src/System/IO/Ports/SerialPort.cs b/src/System.IO.Ports/src/System/IO/Ports/SerialPort.Windows.cs similarity index 100% rename from src/System.IO.Ports/src/System/IO/Ports/SerialPort.cs rename to src/System.IO.Ports/src/System/IO/Ports/SerialPort.Windows.cs diff --git a/src/System.IO.Ports/src/System/IO/Ports/SerialStream.Unix.cs b/src/System.IO.Ports/src/System/IO/Ports/SerialStream.Unix.cs index 1230b88b040d..999486143c58 100644 --- a/src/System.IO.Ports/src/System/IO/Ports/SerialStream.Unix.cs +++ b/src/System.IO.Ports/src/System/IO/Ports/SerialStream.Unix.cs @@ -221,11 +221,11 @@ internal Handshake Handshake if (value != _handshake) { - _handshake = value; if (Interop.Termios.TermiosReset(_handle, _baudRate, _dataBits, _stopBits, _parity, _handshake) != 0) { throw new ArgumentException(); } + _handshake = value; } } } @@ -311,7 +311,7 @@ internal void DiscardOutBuffer() internal void SetBufferSizes(int readBufferSize, int writeBufferSize) { - if (_handle == null) throw new IOException(); + if (_handle == null) throw new ObjectDisposedException(SR.Port_not_open); // Ignore for now. } @@ -346,8 +346,7 @@ public override int ReadByte() internal unsafe int ReadByte(int timeout) { - if (_handle == null) throw new IOException(); - + if (_handle == null) throw new ObjectDisposedException(SR.Port_not_open); if (timeout > 0) { Interop.Sys.Poll(_handle, Interop.Sys.PollEvents.POLLIN | Interop.Sys.PollEvents.POLLERR, timeout, out Interop.Sys.PollEvents events); @@ -390,10 +389,9 @@ internal unsafe int Read(byte[] array, int offset, int count, int timeout) throw new ArgumentException(SR.Argument_InvalidOffLen); if (count == 0) return 0; // return immediately if no bytes requested; no need for overhead. - Interop.Sys.PollEvents events = Interop.Sys.PollEvents.POLLNONE; - if (timeout > 0) { + Interop.Sys.PollEvents events = Interop.Sys.PollEvents.POLLNONE; Interop.Sys.Poll(_handle, Interop.Sys.PollEvents.POLLIN | Interop.Sys.PollEvents.POLLERR, timeout,out events); if ((events & (Interop.Sys.PollEvents.POLLERR | Interop.Sys.PollEvents.POLLNVAL)) != 0) @@ -446,9 +444,9 @@ internal unsafe void Write(byte[] array, int offset, int count, int timeout) while (count > 0) { - Interop.Sys.PollEvents events = Interop.Sys.PollEvents.POLLNONE; if (timeout > 0) { + Interop.Sys.PollEvents events = Interop.Sys.PollEvents.POLLNONE; Interop.Sys.Poll(_handle, Interop.Sys.PollEvents.POLLOUT | Interop.Sys.PollEvents.POLLERR, timeout,out events); if ((events & (Interop.Sys.PollEvents.POLLERR | Interop.Sys.PollEvents.POLLNVAL)) != 0) From ea9751a3e054f3f99cad82d71898a76e306aa80e Mon Sep 17 00:00:00 2001 From: Tomas Weinfurt Date: Wed, 18 Jul 2018 22:40:16 -0700 Subject: [PATCH 3/4] rename SerialStream -> SerialStream.Windows --- .../System/IO/Ports/{SerialStream.cs => SerialStream.Windows.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/System.IO.Ports/src/System/IO/Ports/{SerialStream.cs => SerialStream.Windows.cs} (100%) diff --git a/src/System.IO.Ports/src/System/IO/Ports/SerialStream.cs b/src/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs similarity index 100% rename from src/System.IO.Ports/src/System/IO/Ports/SerialStream.cs rename to src/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs From 1c79103cdf69f54fc819665ce61287a15ca72f23 Mon Sep 17 00:00:00 2001 From: Tomas Weinfurt Date: Thu, 6 Sep 2018 21:41:07 -0700 Subject: [PATCH 4/4] some fixes and improvements --- .../src/System.IO.Ports.csproj | 5 +- .../src/System/IO/Ports/SerialStream.Unix.cs | 80 ++++++++++--------- 2 files changed, 47 insertions(+), 38 deletions(-) diff --git a/src/System.IO.Ports/src/System.IO.Ports.csproj b/src/System.IO.Ports/src/System.IO.Ports.csproj index 43a11a806ba4..2a101597d164 100644 --- a/src/System.IO.Ports/src/System.IO.Ports.csproj +++ b/src/System.IO.Ports/src/System.IO.Ports.csproj @@ -27,7 +27,7 @@ - + Common\Interop\Windows\kernel32\Interop.DCB.cs @@ -161,6 +161,9 @@ Interop\Unix\System.Native\Interop.Poll.cs + + Interop\Unix\System.Native\Interop.Shutdown.cs + diff --git a/src/System.IO.Ports/src/System/IO/Ports/SerialStream.Unix.cs b/src/System.IO.Ports/src/System/IO/Ports/SerialStream.Unix.cs index 999486143c58..63009b2ecf71 100644 --- a/src/System.IO.Ports/src/System/IO/Ports/SerialStream.Unix.cs +++ b/src/System.IO.Ports/src/System/IO/Ports/SerialStream.Unix.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Runtime.InteropServices; using System.IO.Ports; +using System.Net.Sockets; namespace System.IO.Ports { @@ -26,6 +27,7 @@ internal sealed partial class SerialStream : Stream private bool _rtsEnable = false; private int _readTimeout = 0; private int _writeTimeout = 0; + private byte[] _tempBuf = new byte[1]; // three different events, also wrapped by SerialPort. internal event SerialDataReceivedEventHandler DataReceived; // called when one character is received. @@ -41,13 +43,29 @@ internal sealed partial class SerialStream : Stream public override int ReadTimeout { get { return _readTimeout; } - set { _readTimeout = value; } + set + { + if (value < 0 && value != SerialPort.InfiniteTimeout) + throw new ArgumentOutOfRangeException(nameof(ReadTimeout), SR.ArgumentOutOfRange_Timeout); + if (_handle == null) { + throw new ObjectDisposedException(SR.Port_not_open); + } + _readTimeout = value; + } } public override int WriteTimeout { get { return _writeTimeout; } - set { _writeTimeout = value; } + set + { + if (value < 0 && value != SerialPort.InfiniteTimeout) + throw new ArgumentOutOfRangeException(nameof(ReadTimeout), SR.ArgumentOutOfRange_Timeout); + if (_handle == null) { + throw new ObjectDisposedException(SR.Port_not_open); + } + _writeTimeout = value; + } } public override bool CanRead @@ -112,7 +130,13 @@ public bool BreakState get { return _inBreak; } set { - throw new System.PlatformNotSupportedException(SR.PlatformNotSupported_IOPorts); + if (value) + { + // Unlike Windows, there is no infinite break and positive value is platform dependent. + // As best guess, send break with default duration. + Interop.Termios.TermiosSendBreak(_handle, 0); + } + _inBreak = value; } } @@ -344,32 +368,10 @@ public override int ReadByte() return ReadByte(ReadTimeout); } - internal unsafe int ReadByte(int timeout) + internal int ReadByte(int timeout) { - if (_handle == null) throw new ObjectDisposedException(SR.Port_not_open); - if (timeout > 0) - { - Interop.Sys.Poll(_handle, Interop.Sys.PollEvents.POLLIN | Interop.Sys.PollEvents.POLLERR, timeout, out Interop.Sys.PollEvents events); - if ((events & (Interop.Sys.PollEvents.POLLERR | Interop.Sys.PollEvents.POLLNVAL)) != 0) - { - throw new IOException(); - } - - if ((events & Interop.Sys.PollEvents.POLLIN) == 0) - { - throw new TimeoutException(); - } - } - - byte* tempBuf = stackalloc byte[1]; - - int numBytes = Interop.Sys.Read(_handle, tempBuf, 1); - if (numBytes != 1) - { - throw new IOException(); - } - - return tempBuf[0]; + Read(_tempBuf, 0, 1, timeout); + return _tempBuf[0]; } public override int Read(byte[] array, int offset, int count) @@ -379,6 +381,7 @@ public override int Read(byte[] array, int offset, int count) internal unsafe int Read(byte[] array, int offset, int count, int timeout) { + if (_handle == null) throw new ObjectDisposedException(SR.Port_not_open); if (array == null) throw new ArgumentNullException(nameof(array)); if (offset < 0) @@ -389,7 +392,7 @@ internal unsafe int Read(byte[] array, int offset, int count, int timeout) throw new ArgumentException(SR.Argument_InvalidOffLen); if (count == 0) return 0; // return immediately if no bytes requested; no need for overhead. - if (timeout > 0) + if (timeout != 0) { Interop.Sys.PollEvents events = Interop.Sys.PollEvents.POLLNONE; Interop.Sys.Poll(_handle, Interop.Sys.PollEvents.POLLIN | Interop.Sys.PollEvents.POLLERR, timeout,out events); @@ -411,8 +414,14 @@ internal unsafe int Read(byte[] array, int offset, int count, int timeout) numBytes = Interop.Sys.Read(_handle, bufPtr + offset, count); } - if (numBytes == 0) - throw new TimeoutException(); + if (numBytes < 0) + { + if (Interop.Error.EWOULDBLOCK == Interop.Sys.GetLastError()) + { + throw new TimeoutException(); + } + throw new IOException(); + } return numBytes; } @@ -435,10 +444,9 @@ internal unsafe void Write(byte[] array, int offset, int count, int timeout) if (count == 0) return; // no need to expend overhead in creating asyncResult, etc. if (array.Length - offset < count) throw new ArgumentException(SR.Argument_InvalidOffLen); - Debug.Assert(timeout == SerialPort.InfiniteTimeout || timeout >= 0, "Serial Stream Write - write timeout is " + timeout); // check for open handle, though the port is always supposed to be open - if (_handle == null) throw new IOException(); //InternalResources.FileNotOpen(); + if (_handle == null) throw new ObjectDisposedException(SR.Port_not_open); int numBytes = 0; @@ -539,10 +547,6 @@ internal SerialStream(string portName, int baudRate, Parity parity, int dataBits { RtsEnable = rtsEnable; } - - PinChanged = null; - ErrorReceived = null; - DataReceived = null; } catch { @@ -565,8 +569,10 @@ protected override void Dispose(bool disposing) // Close() or not Dispose() if (_handle != null && !_handle.IsInvalid) { + Interop.Sys.Shutdown(_handle, SocketShutdown.Both); _handle.Close(); _handle = null; + base.Dispose(disposing); if (PinChanged != null || ErrorReceived != null || DataReceived != null) { }