From dfa96accc0df96de88b7f55baf55ebdd9d99d679 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C5=BD=C5=AFrek?= Date: Mon, 5 Jan 2026 16:24:56 +0100 Subject: [PATCH 01/10] Fix confusing baud bitmask (obtained from Win32) and actuall bitrate (obtained from user). --- .../System/IO/Ports/SerialStream.Windows.cs | 39 ++++++++++++++++--- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs b/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs index 158c3895489f61..5c7d2aef7c1116 100644 --- a/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs +++ b/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; +using System.Numerics; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; @@ -71,17 +72,18 @@ internal int BaudRate { set { - if (value <= 0 || (value > _commProp.dwMaxBaud && _commProp.dwMaxBaud > 0)) + int maxBaud = ConvertBaudBitMaskToBaudRate((uint)_commProp.dwMaxBaud); + if (value <= 0 || (value > maxBaud && maxBaud > 0)) { // if no upper bound on baud rate imposed by serial driver, note that argument must be positive - if (_commProp.dwMaxBaud == 0) + if (maxBaud == 0) { throw new ArgumentOutOfRangeException(nameof(BaudRate), SR.ArgumentOutOfRange_NeedPosNum); } else { // otherwise, we can present the bounds on the baud rate for this driver - throw new ArgumentOutOfRangeException(nameof(BaudRate), SR.Format(SR.ArgumentOutOfRange_Bounds_Lower_Upper, 0, _commProp.dwMaxBaud)); + throw new ArgumentOutOfRangeException(nameof(BaudRate), SR.Format(SR.ArgumentOutOfRange_Bounds_Lower_Upper, 0, maxBaud)); } } // Set only if it's different. Rollback to previous values if setting fails. @@ -623,8 +625,9 @@ internal SerialStream(string portName, int baudRate, Parity parity, int dataBits throw Win32Marshal.GetExceptionForWin32Error(errorCode, string.Empty); } - if (_commProp.dwMaxBaud != 0 && baudRate > _commProp.dwMaxBaud) - throw new ArgumentOutOfRangeException(nameof(baudRate), SR.Format(SR.Max_Baud, _commProp.dwMaxBaud)); + int maxBaud = ConvertBaudBitMaskToBaudRate((uint)_commProp.dwMaxBaud); + if (maxBaud != 0 && baudRate > maxBaud) + throw new ArgumentOutOfRangeException(nameof(baudRate), SR.Format(SR.Max_Baud, maxBaud)); _comStat = default; // create internal DCB structure, initialize according to Platform SDK @@ -1524,6 +1527,32 @@ private static unsafe void AsyncFSCallback(uint errorCode, uint numBytes, Native asyncResult._userCallback?.Invoke(asyncResult); } + private static int ConvertBaudBitMaskToBaudRate(uint baudBitMask) + { + const uint BAUD_USER = 0x10000000; + if (baudBitMask == 0 || baudBitMask == BAUD_USER) + { + return 0; + } + + if (BitOperations.PopCount(baudBitMask) != 1) + { + throw new ArgumentException("??? Not a valid baud bitmask"); + } + + // https://learn.microsoft.com/windows/win32/api/winbase/ns-winbase-commprop + // todo make static and const? + int[] bauds = { 75, 110, 135, 150, 300, 600, 1200, 1800, 2400, 4800, 7200, 9600, 14400, 19200, 38400, 56000, 57600, 115200, 128000 }; + + int index = BitOperations.LeadingZeroCount(baudBitMask); + + if (index >= bauds.Length) + { + throw new Exception("??? Unsupported baudrate bitmask"); + } + + return bauds[index]; + } // ----SECTION: internal classes --------* From ee7ad7979652e1bf55b650ecd4e7eaae8c00e307 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C5=BD=C5=AFrek?= Date: Mon, 12 Jan 2026 15:12:28 +0100 Subject: [PATCH 02/10] Allow working with buggy device drivers providing int instead of bitmask --- .../src/System/IO/Ports/SerialStream.Windows.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs b/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs index 5c7d2aef7c1116..d8b8246559c334 100644 --- a/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs +++ b/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs @@ -1537,7 +1537,8 @@ private static int ConvertBaudBitMaskToBaudRate(uint baudBitMask) if (BitOperations.PopCount(baudBitMask) != 1) { - throw new ArgumentException("??? Not a valid baud bitmask"); + //throw new ArgumentException("??? Not a valid baud bitmask"); + return (int)baudBitMask; } // https://learn.microsoft.com/windows/win32/api/winbase/ns-winbase-commprop @@ -1548,7 +1549,8 @@ private static int ConvertBaudBitMaskToBaudRate(uint baudBitMask) if (index >= bauds.Length) { - throw new Exception("??? Unsupported baudrate bitmask"); + // throw new Exception("??? Unsupported baudrate bitmask"); + return 0; } return bauds[index]; From 9098b94efc05094f0dd17247a0ab1d163a109302 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C5=BD=C5=AFrek?= Date: Mon, 12 Jan 2026 15:22:25 +0100 Subject: [PATCH 03/10] Add comment --- .../src/System/IO/Ports/SerialStream.Windows.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs b/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs index d8b8246559c334..9696e79d2da1ca 100644 --- a/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs +++ b/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs @@ -1527,6 +1527,7 @@ private static unsafe void AsyncFSCallback(uint errorCode, uint numBytes, Native asyncResult._userCallback?.Invoke(asyncResult); } + private static int ConvertBaudBitMaskToBaudRate(uint baudBitMask) { const uint BAUD_USER = 0x10000000; @@ -1535,6 +1536,10 @@ private static int ConvertBaudBitMaskToBaudRate(uint baudBitMask) return 0; } + // Windows passes value obtained from driver. According to docs, it should be single + // bit coresponding to supported max baudrate (bits up to baud 128K are dfined) or + // BAUD_USER if device support almost arbitrary baudrate. But some device drivers + // provide maximum baudrate value in decimal instead. if (BitOperations.PopCount(baudBitMask) != 1) { //throw new ArgumentException("??? Not a valid baud bitmask"); From 99796a4bf19c8ef14a4020ef7d1a948d4dbd2913 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C5=BD=C5=AFrek?= <188900745+mrek-msft@users.noreply.github.com> Date: Tue, 13 Jan 2026 11:50:12 +0100 Subject: [PATCH 04/10] Refactor and baud order fix --- .../System/IO/Ports/SerialStream.Windows.cs | 68 +++++++++---------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs b/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs index 9696e79d2da1ca..ff20bc370aedbf 100644 --- a/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs +++ b/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs @@ -1304,6 +1304,40 @@ internal void SetDcbFlag(int whichFlag, int setting) _dcb.Flags |= ((uint)setting); } + // Convert BAUD_XXX Win32 enum bit value to baudrate. Returns 0 if no limitation is present. + private static int ConvertMaxBaudBitMaskToBaudRate(int baudBitMask) + { + const uint BAUD_USER = 0x10000000; + if (baudBitMask == 0 || baudBitMask == BAUD_USER) + { + return 0; + } + + // Windows passes value obtained from driver. According to docs, it should be single + // bit coresponding to supported max baudrate (bits up to baud 128K are defined) or + // BAUD_USER if device support arbitrary baudrate. But some device drivers (for example, + // Silicon Labs USB to UART convertors) provides maximum baudrate value as decimal + // value instead. + if (BitOperations.PopCount((uint)baudBitMask) != 1) + { + return baudBitMask; + } + + // https://learn.microsoft.com/windows/win32/api/winbase/ns-winbase-commprop + // i-th value corespond to (1 << i) bitmask + ReadOnlySpan bauds = [75, 110, 135, 150, 300, 600, 1200, 1800, 2400, 4800, 7200, 9600, 14400, 19200, 38400, 56000, 128000, 115200, 57600]; + + int index = BitOperations.LeadingZeroCount((uint)baudBitMask); + + if (index >= bauds.Length) + { + // throw new Exception("??? Unsupported baudrate bitmask"); + return 0; + } + + return bauds[index]; + } + // ----SUBSECTION: internal methods supporting public read/write methods-------* private unsafe SerialStreamAsyncResult BeginReadCore(byte[] array, int offset, int numBytes, AsyncCallback userCallback, object stateObject) @@ -1527,40 +1561,6 @@ private static unsafe void AsyncFSCallback(uint errorCode, uint numBytes, Native asyncResult._userCallback?.Invoke(asyncResult); } - - private static int ConvertBaudBitMaskToBaudRate(uint baudBitMask) - { - const uint BAUD_USER = 0x10000000; - if (baudBitMask == 0 || baudBitMask == BAUD_USER) - { - return 0; - } - - // Windows passes value obtained from driver. According to docs, it should be single - // bit coresponding to supported max baudrate (bits up to baud 128K are dfined) or - // BAUD_USER if device support almost arbitrary baudrate. But some device drivers - // provide maximum baudrate value in decimal instead. - if (BitOperations.PopCount(baudBitMask) != 1) - { - //throw new ArgumentException("??? Not a valid baud bitmask"); - return (int)baudBitMask; - } - - // https://learn.microsoft.com/windows/win32/api/winbase/ns-winbase-commprop - // todo make static and const? - int[] bauds = { 75, 110, 135, 150, 300, 600, 1200, 1800, 2400, 4800, 7200, 9600, 14400, 19200, 38400, 56000, 57600, 115200, 128000 }; - - int index = BitOperations.LeadingZeroCount(baudBitMask); - - if (index >= bauds.Length) - { - // throw new Exception("??? Unsupported baudrate bitmask"); - return 0; - } - - return bauds[index]; - } - // ----SECTION: internal classes --------* internal sealed class EventLoopRunner From 487ee326561ccfae9e1c62cdf6b0d20d920876bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C5=BD=C5=AFrek?= <188900745+mrek-msft@users.noreply.github.com> Date: Tue, 13 Jan 2026 11:54:50 +0100 Subject: [PATCH 05/10] Fix bad refactor and undo whitespace change. --- .../src/System/IO/Ports/SerialStream.Windows.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs b/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs index ff20bc370aedbf..6811f625754777 100644 --- a/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs +++ b/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs @@ -72,7 +72,7 @@ internal int BaudRate { set { - int maxBaud = ConvertBaudBitMaskToBaudRate((uint)_commProp.dwMaxBaud); + int maxBaud = ConvertMaxBaudBitMaskToBaudRate(_commProp.dwMaxBaud); if (value <= 0 || (value > maxBaud && maxBaud > 0)) { // if no upper bound on baud rate imposed by serial driver, note that argument must be positive @@ -625,7 +625,7 @@ internal SerialStream(string portName, int baudRate, Parity parity, int dataBits throw Win32Marshal.GetExceptionForWin32Error(errorCode, string.Empty); } - int maxBaud = ConvertBaudBitMaskToBaudRate((uint)_commProp.dwMaxBaud); + int maxBaud = ConvertMaxBaudBitMaskToBaudRate(_commProp.dwMaxBaud); if (maxBaud != 0 && baudRate > maxBaud) throw new ArgumentOutOfRangeException(nameof(baudRate), SR.Format(SR.Max_Baud, maxBaud)); @@ -1561,6 +1561,7 @@ private static unsafe void AsyncFSCallback(uint errorCode, uint numBytes, Native asyncResult._userCallback?.Invoke(asyncResult); } + // ----SECTION: internal classes --------* internal sealed class EventLoopRunner From 1d405d12b0550876ea95d26de647fe10b23db96d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C5=BD=C5=AFrek?= <188900745+mrek-msft@users.noreply.github.com> Date: Tue, 13 Jan 2026 11:57:13 +0100 Subject: [PATCH 06/10] cleanup --- .../System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs b/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs index 6811f625754777..3af74c758f31bd 100644 --- a/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs +++ b/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs @@ -1329,9 +1329,9 @@ private static int ConvertMaxBaudBitMaskToBaudRate(int baudBitMask) int index = BitOperations.LeadingZeroCount((uint)baudBitMask); + // bit which has not defined macro if (index >= bauds.Length) { - // throw new Exception("??? Unsupported baudrate bitmask"); return 0; } From b4657db9bf7cb502f543ff9f9aa086863791c448 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C5=BD=C5=AFrek?= <188900745+mrek-msft@users.noreply.github.com> Date: Tue, 13 Jan 2026 12:01:38 +0100 Subject: [PATCH 07/10] Improving comments --- .../src/System/IO/Ports/SerialStream.Windows.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs b/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs index 3af74c758f31bd..8e7d912b8a66cd 100644 --- a/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs +++ b/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs @@ -1317,7 +1317,9 @@ private static int ConvertMaxBaudBitMaskToBaudRate(int baudBitMask) // bit coresponding to supported max baudrate (bits up to baud 128K are defined) or // BAUD_USER if device support arbitrary baudrate. But some device drivers (for example, // Silicon Labs USB to UART convertors) provides maximum baudrate value as decimal - // value instead. + // value instead. Because no common baudrate is power of 2, we assume that when we get + // single bit (power of two) it is bitmask, and if we get more bits set it is baudrate + // encoded as decimal. if (BitOperations.PopCount((uint)baudBitMask) != 1) { return baudBitMask; @@ -1329,7 +1331,8 @@ private static int ConvertMaxBaudBitMaskToBaudRate(int baudBitMask) int index = BitOperations.LeadingZeroCount((uint)baudBitMask); - // bit which has not defined macro + // bit which has not defined macro. Rather enforce no limitation and give a try rather + // then restricting usage of such device. if (index >= bauds.Length) { return 0; From 1a6955b3f5167145940a045845afeb87d92debdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C5=BD=C5=AFrek?= <188900745+mrek-msft@users.noreply.github.com> Date: Tue, 13 Jan 2026 12:31:30 +0100 Subject: [PATCH 08/10] Fix wrong direction of searching for bit in bitmask. --- .../System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs b/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs index 8e7d912b8a66cd..4e04bfa1f922d6 100644 --- a/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs +++ b/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs @@ -1329,7 +1329,7 @@ private static int ConvertMaxBaudBitMaskToBaudRate(int baudBitMask) // i-th value corespond to (1 << i) bitmask ReadOnlySpan bauds = [75, 110, 135, 150, 300, 600, 1200, 1800, 2400, 4800, 7200, 9600, 14400, 19200, 38400, 56000, 128000, 115200, 57600]; - int index = BitOperations.LeadingZeroCount((uint)baudBitMask); + int index = BitOperations.TrailingZeroCount((uint)baudBitMask); // bit which has not defined macro. Rather enforce no limitation and give a try rather // then restricting usage of such device. From 977f60e614b96a7486026dcb921313cb1a03662b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C5=BD=C5=AFrek?= <188900745+mrek-msft@users.noreply.github.com> Date: Mon, 19 Jan 2026 21:41:32 +0100 Subject: [PATCH 09/10] Improve comments as proposed by Copilot --- .../src/System/IO/Ports/SerialStream.Windows.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs b/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs index 4e04bfa1f922d6..8e6ae9da50d714 100644 --- a/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs +++ b/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs @@ -1314,7 +1314,7 @@ private static int ConvertMaxBaudBitMaskToBaudRate(int baudBitMask) } // Windows passes value obtained from driver. According to docs, it should be single - // bit coresponding to supported max baudrate (bits up to baud 128K are defined) or + // bit corresponding to supported max baudrate (bits up to baud 128K are defined) or // BAUD_USER if device support arbitrary baudrate. But some device drivers (for example, // Silicon Labs USB to UART convertors) provides maximum baudrate value as decimal // value instead. Because no common baudrate is power of 2, we assume that when we get @@ -1326,13 +1326,13 @@ private static int ConvertMaxBaudBitMaskToBaudRate(int baudBitMask) } // https://learn.microsoft.com/windows/win32/api/winbase/ns-winbase-commprop - // i-th value corespond to (1 << i) bitmask + // i-th value correspond to (1 << i) bitmask ReadOnlySpan bauds = [75, 110, 135, 150, 300, 600, 1200, 1800, 2400, 4800, 7200, 9600, 14400, 19200, 38400, 56000, 128000, 115200, 57600]; int index = BitOperations.TrailingZeroCount((uint)baudBitMask); - // bit which has not defined macro. Rather enforce no limitation and give a try rather - // then restricting usage of such device. + // Bit for which no macro is defined. Rather than restricting usage of such a device, + // enforce no limitation and give it a try. if (index >= bauds.Length) { return 0; From e7fa07e4972cc76d5de76b5d8cc733064dcaca46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C5=BD=C5=AFrek?= <188900745+mrek-msft@users.noreply.github.com> Date: Mon, 19 Jan 2026 21:45:37 +0100 Subject: [PATCH 10/10] Explicitely handle negative max baudrate --- .../System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs b/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs index 8e6ae9da50d714..609c06e984b43c 100644 --- a/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs +++ b/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs @@ -1308,7 +1308,7 @@ internal void SetDcbFlag(int whichFlag, int setting) private static int ConvertMaxBaudBitMaskToBaudRate(int baudBitMask) { const uint BAUD_USER = 0x10000000; - if (baudBitMask == 0 || baudBitMask == BAUD_USER) + if (baudBitMask <= 0 || baudBitMask == BAUD_USER) { return 0; }