From 6a8c6507902cf5297aec8a9fc42f75582c358dea Mon Sep 17 00:00:00 2001 From: "SAVPC\\Sav" Date: Wed, 5 Nov 2025 15:18:38 +0700 Subject: [PATCH 01/39] FEAT - added serial Cdc Driver (tested on Atmel and GigaDevice) --- .../Drivers/CdcAcmSerialDriver.cs | 330 ++++++++++++++++++ UsbSerialForAndroid.Net/Enums/VendorIds.cs | 3 + .../Extensions/BufferExtensions.cs | 53 +++ .../UsbDeviceConnectionExtension.cs | 31 ++ UsbSerialForAndroid.Net/UsbDriverFactory.cs | 7 + 5 files changed, 424 insertions(+) create mode 100644 UsbSerialForAndroid.Net/Drivers/CdcAcmSerialDriver.cs create mode 100644 UsbSerialForAndroid.Net/Extensions/BufferExtensions.cs create mode 100644 UsbSerialForAndroid.Net/Extensions/UsbDeviceConnectionExtension.cs diff --git a/UsbSerialForAndroid.Net/Drivers/CdcAcmSerialDriver.cs b/UsbSerialForAndroid.Net/Drivers/CdcAcmSerialDriver.cs new file mode 100644 index 0000000..aa16a6f --- /dev/null +++ b/UsbSerialForAndroid.Net/Drivers/CdcAcmSerialDriver.cs @@ -0,0 +1,330 @@ +using Android.Hardware.Usb; +using Java.Nio; +using System; +using System.Buffers; +using System.IO; +using System.Runtime.Versioning; +using System.Threading; +using System.Threading.Tasks; +using UsbSerialForAndroid.Net.Enums; +using UsbSerialForAndroid.Net.Exceptions; +using UsbSerialForAndroid.Net.Extensions; + +namespace UsbSerialForAndroid.Net.Drivers +{ + [SupportedOSPlatform("android26.0")] + public class CdcAcmSerialDriver : UsbDriverBase + { + public const int UsbSubclassAcm = 2; + public const int UsbRecipInterface = 0x01; + public const int UsbRtAcm = UsbConstants.UsbTypeClass | UsbRecipInterface; + public const int SetLineCoding = 0x20; + public const int SetControlLineState = 0x22; + private int controlIndex; + private UsbInterface? controlInterface; + private UsbEndpoint? controlEndpoint; + public CdcAcmSerialDriver(UsbDevice usbDevice) : base(usbDevice) { } + public override void Open(int baudRate, byte dataBits, StopBits stopBits, Parity parity) + { + UsbDeviceConnection = UsbManager.OpenDevice(UsbDevice); + + if (UsbDevice.InterfaceCount == 1) + { + //device might be castrated ACM device, trying single interface logic + OpenSingleInterface(); + } + else + { + OpenInterface(); + } + SetParameters(baudRate, dataBits, stopBits, parity); + _usbReadRequest = new(); + _usbReadRequest.Initialize(UsbDeviceConnection, UsbEndpointRead); + _readBuf = ByteBuffer.Allocate(DefaultBufferLength); + } + private void OpenSingleInterface() + { + ArgumentNullException.ThrowIfNull(UsbDeviceConnection); + controlIndex = 0; + controlInterface = UsbInterface = UsbDevice.GetInterface(0); + if (!UsbDeviceConnection.ClaimInterface(UsbInterface, true)) + throw new Exception($"Could not claim interface {UsbInterfaceIndex}"); + + for (int i = 0; i < UsbInterface.EndpointCount; ++i) + { + var ep = UsbInterface.GetEndpoint(i); + if ((ep?.Direction == UsbAddressing.In) && (ep.Type == UsbAddressing.XferInterrupt)) + { + controlEndpoint = ep; + } + else if ((ep?.Direction == UsbAddressing.In) && (ep.Type == UsbAddressing.XferBulk)) + { + UsbEndpointRead = ep; + } + else if ((ep?.Direction == UsbAddressing.Out) && (ep.Type == UsbAddressing.XferBulk)) + { + UsbEndpointWrite = ep; + } + } + } + private int GetFirstInterfaceIdFromDescriptors() + { + ArgumentNullException.ThrowIfNull(UsbDeviceConnection); + var descriptors = UsbDeviceConnection.GetDescriptors(); + + if (descriptors.Count > 0 && + descriptors[0].Length == 18 && + descriptors[0][1] == 1 && // bDescriptorType + descriptors[0][4] == (byte)UsbClass.Misc && //bDeviceClass + descriptors[0][5] == 2 && // bDeviceSubClass + descriptors[0][6] == 1) + { + int port = -1; + for (int i = 1; i < descriptors.Count; i++) + { + if (descriptors[i].Length == 8 && + descriptors[i][1] == 0x0b && // bDescriptorType == IAD + descriptors[i][4] == (byte)UsbClass.Comm && // bFunctionClass == CDC + descriptors[i][5] == UsbSubclassAcm) + { + port++; + if (port == UsbInterfaceIndex && descriptors[i][3] == 2) + { + return descriptors[i][2]; + } + } + } + } + return -1; + } + + private void OpenInterface() + { + ArgumentNullException.ThrowIfNull(UsbDeviceConnection); + + controlInterface = UsbDevice.GetInterface(0); + if (!UsbDeviceConnection.ClaimInterface(controlInterface, true)) + throw new Exception("Could not claim control interface"); + + controlEndpoint = controlInterface.GetEndpoint(0); + UsbInterface = UsbDevice.GetInterface(1); + + if (!UsbDeviceConnection.ClaimInterface(UsbInterface, true)) + throw new Exception("Could not claim mDataInterface interface"); + + + for (int i = 0; i < UsbInterface.EndpointCount; i++) + { + var ep = UsbInterface.GetEndpoint(i); + if (ep?.Direction == UsbAddressing.In && ep.Type == UsbAddressing.XferBulk) + { + UsbEndpointRead = ep; + } + else if (ep?.Direction == UsbAddressing.Out && ep.Type == UsbAddressing.XferBulk) + { + UsbEndpointWrite = ep; + } + } + } + + private void OpenInterface1() + { + int id = GetFirstInterfaceIdFromDescriptors(); + if (id >= 0) + { + for (int i = 0; i < UsbDevice.InterfaceCount; i++) + { + var usbInterface = UsbDevice.GetInterface(i); + if (usbInterface.Id == id || usbInterface.Id == id + 1) + { + if (usbInterface.InterfaceClass == UsbClass.Comm && (int)usbInterface.InterfaceSubclass == UsbSubclassAcm) + { + controlIndex = usbInterface.Id; + controlInterface = usbInterface; + } + if (usbInterface.InterfaceClass == UsbClass.CdcData) + { + UsbInterface = usbInterface; + } + } + } + } + if (controlInterface == null || UsbInterface == null) + { + int controlInterfaceCount = 0; + int dataInterfaceCount = 0; + for (int i = 0; i < UsbDevice.InterfaceCount; i++) + { + var usbInterface = UsbDevice.GetInterface(i); + if (usbInterface.InterfaceClass == UsbClass.Comm && (int)usbInterface.InterfaceSubclass == UsbSubclassAcm) + { + if (controlInterfaceCount == UsbInterfaceIndex) + { + controlIndex = usbInterface.Id; + controlInterface = usbInterface; + } + controlInterfaceCount++; + } + if (usbInterface.InterfaceClass == UsbClass.CdcData) + { + if (dataInterfaceCount == UsbInterfaceIndex) + { + UsbInterface = usbInterface; + } + dataInterfaceCount++; + } + } + } + + ArgumentNullException.ThrowIfNull(controlInterface); + ArgumentNullException.ThrowIfNull(UsbDeviceConnection); + + if (!UsbDeviceConnection.ClaimInterface(controlInterface, true)) + throw new Exception("Could not claim control interface"); + + controlEndpoint = controlInterface.GetEndpoint(0); + if (controlEndpoint?.Direction != UsbAddressing.In || controlEndpoint?.Type != UsbAddressing.XferInterrupt) + throw new Exception("Invalid control endpoint"); + + ArgumentNullException.ThrowIfNull(UsbInterface); + + if (!UsbDeviceConnection.ClaimInterface(UsbInterface, true)) + throw new Exception("Could not claim data interface"); + + for (int i = 0; i < UsbInterface.EndpointCount; i++) + { + var ep = UsbInterface.GetEndpoint(i); + if (ep?.Direction == UsbAddressing.In && ep.Type == UsbAddressing.XferBulk) + { + UsbEndpointRead = ep; + } + else if (ep?.Direction == UsbAddressing.Out && ep.Type == UsbAddressing.XferBulk) + { + UsbEndpointWrite = ep; + } + } + } + private void SetParameters(int baudRate, byte dataBits, StopBits stopBits, Parity parity) + { + if (baudRate <= 0) + throw new ArgumentOutOfRangeException(nameof(baudRate), baudRate, "Invalid baud rate"); + + if (dataBits < 5 || dataBits > 8) + throw new ArgumentOutOfRangeException(nameof(dataBits), dataBits, "Invalid data bits"); + + byte stopBitsByte = stopBits switch + { + StopBits.One => 0, + StopBits.OnePointFive => 1, + StopBits.Two => 2, + _ => throw new ArgumentOutOfRangeException(nameof(stopBits), stopBits, "Invalid stop bits"), + }; + + byte parityBitesByte = parity switch + { + Parity.None => 0, + Parity.Odd => 1, + Parity.Even => 2, + Parity.Mark => 3, + Parity.Space => 4, + _ => throw new ArgumentOutOfRangeException(nameof(parity), parity, "Invalid parity"), + }; + + var buffer = new byte[]{ + (byte) ( baudRate & 0xff), + (byte) ((baudRate >> 8 ) & 0xff), + (byte) ((baudRate >> 16) & 0xff), + (byte) ((baudRate >> 24) & 0xff), + stopBitsByte, + parityBitesByte, + dataBits}; + + ArgumentNullException.ThrowIfNull(UsbDeviceConnection); + + int result = UsbDeviceConnection.ControlTransfer((UsbAddressing)UsbRtAcm, SetLineCoding, 0, controlIndex, buffer, buffer.Length, ControlTimeout); + if (result < 0) + throw new ControlTransferException("Set parameters failed", result, UsbRtAcm, SetLineCoding, 0, controlIndex, buffer, buffer.Length, ControlTimeout); + } + public override void Close() + { + _usbReadRequest?.Close(); + _usbReadRequest = null; + UsbDeviceConnection?.ReleaseInterface(controlInterface); + UsbDeviceConnection?.ReleaseInterface(UsbInterface); + UsbDeviceConnection?.Close(); + _readBuf?.Dispose(); + _readBuf = null; + } + public override void SetDtrEnabled(bool value) + { + DtrEnable = value; + SetDtrRts(); + } + public override void SetRtsEnabled(bool value) + { + RtsEnable = value; + SetDtrRts(); + } + private void SetDtrRts() + { + ArgumentNullException.ThrowIfNull(UsbDeviceConnection); + + int value = (RtsEnable ? 0x2 : 0) | (DtrEnable ? 0x1 : 0); + int result = UsbDeviceConnection.ControlTransfer((UsbAddressing)UsbRtAcm, SetControlLineState, value, controlIndex, null, 0, ControlTimeout); + if (result != 0) + throw new ControlTransferException("Set dtr rts failed", result, UsbRtAcm, SetControlLineState, value, controlIndex, null, 0, ControlTimeout); + } + + + UsbRequest? _usbReadRequest; + ByteBuffer? _readBuf; + public async Task ReadAsync(Memory mem, CancellationToken ct = default) + { + ArgumentNullException.ThrowIfNull(_readBuf); + ArgumentNullException.ThrowIfNull(_usbReadRequest); + ArgumentNullException.ThrowIfNull(UsbDeviceConnection); + if (!_usbReadRequest.Queue(_readBuf)) + throw new IOException("Error queueing request."); + using var crReg = ct.Register(() => _usbReadRequest?.Cancel()); + UsbRequest? response = await UsbDeviceConnection.RequestWaitAsync(ControlTimeout); + if (!ReferenceEquals(response, _usbReadRequest)) + throw new IOException("Wrong response"); + int nread = _readBuf.Position(); + if (nread > 0) + { + _readBuf.ToByteArray().AsMemory(0, nread).CopyTo(mem); + return nread; + } + return 0; + } + public override async Task ReadAsync() + { + var dest = ArrayPool.Shared.Rent(DefaultBufferLength); + try + { + var mem = dest.AsMemory(); + int len = await ReadAsync(mem); + return mem.Slice(0, len).ToArray(); + } + finally + { + ArrayPool.Shared.Return(dest); + } + } + public override byte[]? Read() + { + ArgumentNullException.ThrowIfNull(_readBuf); + ArgumentNullException.ThrowIfNull(_usbReadRequest); + ArgumentNullException.ThrowIfNull(UsbDeviceConnection); + if (!_usbReadRequest.Queue(_readBuf)) + throw new IOException("Error queueing request."); + UsbRequest? response = UsbDeviceConnection.RequestWait(ControlTimeout); + if (!ReferenceEquals(response, _usbReadRequest)) + throw new IOException("Wrong response"); + int nread = _readBuf.Position(); + if (nread > 0) + return _readBuf.ToByteArray().AsMemory(0, nread).ToArray(); + return default; + } + } +} diff --git a/UsbSerialForAndroid.Net/Enums/VendorIds.cs b/UsbSerialForAndroid.Net/Enums/VendorIds.cs index 28cc9b4..7241c0b 100644 --- a/UsbSerialForAndroid.Net/Enums/VendorIds.cs +++ b/UsbSerialForAndroid.Net/Enums/VendorIds.cs @@ -10,5 +10,8 @@ public enum VendorIds Prolific = 0x067B, QinHeng = 0x1A86, SiliconLabs = 0x10C4, + Arduino = 0x2341, + GigaDevice = 0x28E9, + Atmel = 0x03EB, } } diff --git a/UsbSerialForAndroid.Net/Extensions/BufferExtensions.cs b/UsbSerialForAndroid.Net/Extensions/BufferExtensions.cs new file mode 100644 index 0000000..6401939 --- /dev/null +++ b/UsbSerialForAndroid.Net/Extensions/BufferExtensions.cs @@ -0,0 +1,53 @@ +/* Copyright 2017 Tyler Technologies Inc. + * + * Project home page: https://github.com/anotherlab/xamarin-usb-serial-for-android + * Portions of this library are based on usb-serial-for-android (https://github.com/mik3y/usb-serial-for-android). + * Portions of this library are based on Xamarin USB Serial for Android (https://bitbucket.org/lusovu/xamarinusbserial). + */ + +using Android.Runtime; +using Java.Nio; + +namespace UsbSerialForAndroid.Net.Extensions +{ + /// + /// Work around for faulty JNI wrapping in Xamarin library. Fixes a bug + /// where binding for Java.Nio.ByteBuffer.Get(byte[], int, int) allocates a new temporary + /// Java byte array on every call + /// See https://bugzilla.xamarin.com/show_bug.cgi?id=31260 + /// and http://stackoverflow.com/questions/30268400/xamarin-implementation-of-bytebuffer-get-wrong + /// + public static class BufferExtensions + { + static nint _byteBufferClassRef; + static nint _byteBufferGetBii; + static nint _byteBufferGetArrayMethodRef; + + // init on first call + static BufferExtensions() + { + _byteBufferClassRef = JNIEnv.FindClass("java/nio/ByteBuffer"); + _byteBufferGetArrayMethodRef = JNIEnv.GetMethodID(_byteBufferClassRef, "array", "()[B"); + } + + public static ByteBuffer? Get(this ByteBuffer buffer, JavaArray dst, int dstOffset, int byteCount) + { + if (_byteBufferClassRef == nint.Zero) + _byteBufferClassRef = JNIEnv.FindClass("java/nio/ByteBuffer"); + if (_byteBufferGetBii == nint.Zero) + _byteBufferGetBii = JNIEnv.GetMethodID(_byteBufferClassRef, "get", "([BII)Ljava/nio/ByteBuffer;"); + + return Java.Lang.Object.GetObject( + JNIEnv.CallObjectMethod(buffer.Handle, _byteBufferGetBii, [new(dst), new(dstOffset), new(byteCount)]), + JniHandleOwnership.TransferLocalRef); + } + + public static byte[]? ToByteArray(this ByteBuffer buffer) + { + nint resultHandle = JNIEnv.CallObjectMethod(buffer.Handle, _byteBufferGetArrayMethodRef); + byte[]? result = JNIEnv.GetArray(resultHandle); + JNIEnv.DeleteLocalRef(resultHandle); + return result; + } + } +} \ No newline at end of file diff --git a/UsbSerialForAndroid.Net/Extensions/UsbDeviceConnectionExtension.cs b/UsbSerialForAndroid.Net/Extensions/UsbDeviceConnectionExtension.cs new file mode 100644 index 0000000..b5f7b3a --- /dev/null +++ b/UsbSerialForAndroid.Net/Extensions/UsbDeviceConnectionExtension.cs @@ -0,0 +1,31 @@ +using Android.Hardware.Usb; +using System; +using System.Collections.Generic; + +namespace UsbSerialForAndroid.Net.Extensions +{ + public static class UsbDeviceConnectionExtension + { + public static List GetDescriptors(this UsbDeviceConnection connection) + { + var descriptors = new List(); + var rawDescriptors = connection.GetRawDescriptors(); + if (rawDescriptors is null) return descriptors; + int pos = 0; + while (pos < rawDescriptors.Length) + { + int len = rawDescriptors[pos] & 0xFF; + if (len == 0) break; + + if (pos + len > rawDescriptors.Length) + len = rawDescriptors.Length - pos; + + byte[] descriptor = new byte[len]; + Array.Copy(rawDescriptors, pos, descriptor, 0, descriptor.Length); + descriptors.Add(descriptor); + pos += len; + } + return descriptors; + } + } +} diff --git a/UsbSerialForAndroid.Net/UsbDriverFactory.cs b/UsbSerialForAndroid.Net/UsbDriverFactory.cs index 5d8af4d..40a4c08 100644 --- a/UsbSerialForAndroid.Net/UsbDriverFactory.cs +++ b/UsbSerialForAndroid.Net/UsbDriverFactory.cs @@ -30,6 +30,9 @@ private static UsbDriverBase CreateUsbDriver(UsbDevice usbDevice) (int)VendorIds.Prolific => new ProlificSerialDriver(usbDevice), (int)VendorIds.QinHeng => new QinHengSerialDriver(usbDevice), (int)VendorIds.SiliconLabs => new SiliconLabsSerialDriver(usbDevice), + (int)VendorIds.Arduino => new CdcAcmSerialDriver(usbDevice), + (int)VendorIds.Atmel => new CdcAcmSerialDriver(usbDevice), + (int)VendorIds.GigaDevice => new CdcAcmSerialDriver(usbDevice), _ => throw new NotSupportedDriverException(usbDevice) }; } @@ -119,6 +122,10 @@ public static bool HasSupportedDriver(int vendorId, int productId) } break; } + case VendorIds.Arduino: + case VendorIds.Atmel: + case VendorIds.GigaDevice: + return true; } return false; } From 1eb4fdfa8e516caed89e1b78f80906e1c4ea16ea Mon Sep 17 00:00:00 2001 From: "SAVPC\\Sav" Date: Wed, 5 Nov 2025 16:52:05 +0700 Subject: [PATCH 02/39] FEAT - remove min android = 26 --- .../Drivers/CdcAcmSerialDriver.cs | 11 +++++----- .../UsbDeviceConnectionExtension.cs | 20 +++++++++++++++++++ .../Extensions/UsbRequestExtension.cs | 15 ++++++++++++++ 3 files changed, 40 insertions(+), 6 deletions(-) create mode 100644 UsbSerialForAndroid.Net/Extensions/UsbRequestExtension.cs diff --git a/UsbSerialForAndroid.Net/Drivers/CdcAcmSerialDriver.cs b/UsbSerialForAndroid.Net/Drivers/CdcAcmSerialDriver.cs index aa16a6f..4c0627f 100644 --- a/UsbSerialForAndroid.Net/Drivers/CdcAcmSerialDriver.cs +++ b/UsbSerialForAndroid.Net/Drivers/CdcAcmSerialDriver.cs @@ -3,16 +3,15 @@ using System; using System.Buffers; using System.IO; -using System.Runtime.Versioning; using System.Threading; using System.Threading.Tasks; using UsbSerialForAndroid.Net.Enums; using UsbSerialForAndroid.Net.Exceptions; using UsbSerialForAndroid.Net.Extensions; +using static Android.Media.Audiofx.DynamicsProcessing; namespace UsbSerialForAndroid.Net.Drivers { - [SupportedOSPlatform("android26.0")] public class CdcAcmSerialDriver : UsbDriverBase { public const int UsbSubclassAcm = 2; @@ -283,10 +282,10 @@ public async Task ReadAsync(Memory mem, CancellationToken ct = defaul ArgumentNullException.ThrowIfNull(_readBuf); ArgumentNullException.ThrowIfNull(_usbReadRequest); ArgumentNullException.ThrowIfNull(UsbDeviceConnection); - if (!_usbReadRequest.Queue(_readBuf)) + if (!_usbReadRequest.QueueReq(_readBuf)) throw new IOException("Error queueing request."); using var crReg = ct.Register(() => _usbReadRequest?.Cancel()); - UsbRequest? response = await UsbDeviceConnection.RequestWaitAsync(ControlTimeout); + UsbRequest? response = await UsbDeviceConnection.RequestWaitAsync(_usbReadRequest, ControlTimeout); if (!ReferenceEquals(response, _usbReadRequest)) throw new IOException("Wrong response"); int nread = _readBuf.Position(); @@ -316,9 +315,9 @@ public async Task ReadAsync(Memory mem, CancellationToken ct = defaul ArgumentNullException.ThrowIfNull(_readBuf); ArgumentNullException.ThrowIfNull(_usbReadRequest); ArgumentNullException.ThrowIfNull(UsbDeviceConnection); - if (!_usbReadRequest.Queue(_readBuf)) + if (!_usbReadRequest.QueueReq(_readBuf)) throw new IOException("Error queueing request."); - UsbRequest? response = UsbDeviceConnection.RequestWait(ControlTimeout); + UsbRequest? response = UsbDeviceConnection.RequestWait(_usbReadRequest, ControlTimeout); if (!ReferenceEquals(response, _usbReadRequest)) throw new IOException("Wrong response"); int nread = _readBuf.Position(); diff --git a/UsbSerialForAndroid.Net/Extensions/UsbDeviceConnectionExtension.cs b/UsbSerialForAndroid.Net/Extensions/UsbDeviceConnectionExtension.cs index b5f7b3a..7183c6a 100644 --- a/UsbSerialForAndroid.Net/Extensions/UsbDeviceConnectionExtension.cs +++ b/UsbSerialForAndroid.Net/Extensions/UsbDeviceConnectionExtension.cs @@ -1,6 +1,8 @@ using Android.Hardware.Usb; using System; using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; namespace UsbSerialForAndroid.Net.Extensions { @@ -27,5 +29,23 @@ public static List GetDescriptors(this UsbDeviceConnection connection) } return descriptors; } + + public static async Task RequestWaitAsync(this UsbDeviceConnection connection, UsbRequest req, int timeout) + { + if (OperatingSystem.IsAndroidVersionAtLeast(26)) + return await connection.RequestWaitAsync(timeout); + using var cts = new CancellationTokenSource(timeout); + using var crReg = cts.Token.Register(() => req?.Cancel()); + UsbRequest? ret = await connection.RequestWaitAsync(); + return ret; + } + public static UsbRequest? RequestWait(this UsbDeviceConnection connection, UsbRequest req, int timeout) + { + if (OperatingSystem.IsAndroidVersionAtLeast(26)) + return connection.RequestWait(timeout); + using var cts = new CancellationTokenSource(timeout); + using var crReg = cts.Token.Register(() => req?.Cancel()); + return connection.RequestWait(); + } } } diff --git a/UsbSerialForAndroid.Net/Extensions/UsbRequestExtension.cs b/UsbSerialForAndroid.Net/Extensions/UsbRequestExtension.cs new file mode 100644 index 0000000..2123c10 --- /dev/null +++ b/UsbSerialForAndroid.Net/Extensions/UsbRequestExtension.cs @@ -0,0 +1,15 @@ +using Android.Hardware.Usb; +using Java.Nio; +using System; + +namespace UsbSerialForAndroid.Net.Extensions; + +public static class UsbRequestExtension +{ + public static bool QueueReq(this UsbRequest req, ByteBuffer buffer) + { + if (OperatingSystem.IsAndroidVersionAtLeast(26)) + return req.Queue(buffer); + return req.Queue(buffer, buffer.Capacity()); + } +} From 03cf92e7592b01cd6b6fd84aae9a5cf537adcdb1 Mon Sep 17 00:00:00 2001 From: "HOMEPC\\alex3696" Date: Thu, 6 Nov 2025 00:39:14 +0700 Subject: [PATCH 03/39] fix - CH341A buffers data until a full endpoint-size packet (32 bytes) has been received unless bit 7 is set. fix - codestyle and constants --- .../Drivers/QinHengSerialDriver.cs | 122 +++++++----------- 1 file changed, 44 insertions(+), 78 deletions(-) diff --git a/UsbSerialForAndroid.Net/Drivers/QinHengSerialDriver.cs b/UsbSerialForAndroid.Net/Drivers/QinHengSerialDriver.cs index cca1db0..2b1c6be 100644 --- a/UsbSerialForAndroid.Net/Drivers/QinHengSerialDriver.cs +++ b/UsbSerialForAndroid.Net/Drivers/QinHengSerialDriver.cs @@ -21,6 +21,22 @@ public class QinHengSerialDriver : UsbDriverBase public const int SclDtr = 0x20; public const int SclRts = 0x40; + // info from linux + // https://github.com/torvalds/linux/tree/master/drivers/usb/serial + // https://github.com/nospam2000/ch341-baudrate-calculation/tree/master + public byte ChipVersion = 0; + public const byte CH341_REQ_READ_VERSION = 0x5F; + public const byte CH341_REQ_WRITE_REG = 0x9A; + public const byte CH341_REQ_READ_REG = 0x95; + public const byte CH341_REQ_SERIAL_INIT = 0xA1; + public const byte CH341_REQ_MODEM_CTRL = 0xA4; + + public const byte CH341_REG_DIVISOR = 0x13; + public const byte CH341_REG_PRESCALER = 0x12; + + public const byte CH341_REG_LCR2 = 0x25; + public const byte CH341_REG_LCR = 0x18; + public const int RequestTypeHostToDeviceIn = UsbConstants.UsbTypeVendor | (int)UsbAddressing.In; public const int RequestTypeHostToDeviceOut = UsbConstants.UsbTypeVendor | (int)UsbAddressing.Out; public QinHengSerialDriver(UsbDevice usbDevice) : base(usbDevice) { } @@ -74,53 +90,18 @@ public override void Open(int baudRate = DefaultBaudRate, byte dataBits = Defaul /// private void Initialize() { - int request = 0x5f; - int value = 0; - int index = 0; - var data = new int[] { -1 /* 0x27, 0x30 */, 0x00 }; - CheckState("init #1", request, value, data); - - request = 0xa1; - value = 0; - index = 0; - int ret = ControlOut(request, value, index); - if (ret < 0) - throw new ControlTransferException("init failed! #2", ret, RequestTypeHostToDeviceOut, request, value, index, null, 0, ControlTimeout); - + byte[] data = [0xFF /* 0x27, 0x30 */, 0x00]; + CheckState("init #1", CH341_REQ_READ_VERSION, 0, data); + ChipVersion = data[0]; + ControlOut("init #2", CH341_REQ_SERIAL_INIT, 0, 0); SetBaudRate(DefaultBaudRate); - - request = 0x95; - value = 0x2518; - data = new int[] { -1 /* 0x56, c3*/, 0x00 }; - CheckState("init #4", request, value, data); - - request = 0x9a; - value = 0x2518; - index = 0x0050; - ret = ControlOut(0x9a, 0x2518, 0x0050); - if (ret < 0) - throw new ControlTransferException("init failed! #5", ret, RequestTypeHostToDeviceOut, request, value, index, null, 0, ControlTimeout); - - request = 0x95; - value = 0x0706; - data = new int[] { -1 /*0xf?*/, -1 /*0xec,0xee*/}; - CheckState("init #6", request, value, data); - - request = 0xa1; - value = 0x501f; - index = 0xd90a; - ret = ControlOut(request, value, index); - if (ret < 0) - throw new ControlTransferException("init failed! #7", ret, RequestTypeHostToDeviceOut, request, value, index, null, 0, ControlTimeout); - + CheckState("init #4", CH341_REQ_READ_REG, 0x2518, [0xFF /* 0x56, c3*/, 0x00]); + ControlOut("init #5", CH341_REQ_WRITE_REG, 0x2518, 0x0050); + CheckState("init #6", CH341_REQ_READ_REG, 0x0706, [0xFF /*0xf?*/, 0xFF /*0xec,0xee*/]); + ControlOut("init #7", CH341_REQ_SERIAL_INIT, 0x501f, 0xd90a); SetBaudRate(DefaultBaudRate); - SetControlLines(); - - request = 0x95; - value = 0x0706; - data = new int[] { -1 /* 0x9f, 0xff*/, -1/*0xec,0xee*/ }; - CheckState("init #10", request, value, data); + CheckState("init #10", CH341_REQ_READ_REG, 0x0706, [0xFF /* 0x9f, 0xff*/, 0xFF/*0xec,0xee*/]); } /// /// Check the state of the device @@ -131,23 +112,20 @@ private void Initialize() /// /// /// - private void CheckState(string msg, int request, int value, int[] expected) + private void CheckState(string msg, int request, int value, byte[] expected) { byte[] buffer = new byte[expected.Length]; int ret = ControlIn(request, value, 0, buffer); if (ret < 0) throw new ControlTransferException($"Failed send cmd [{msg}]", ret, RequestTypeHostToDeviceIn, request, value, 0, buffer, buffer.Length, ControlTimeout); - if (ret != expected.Length) throw new ControlTransferException($"Expected {expected.Length} bytes, but get {ret} [{msg}]", ret, RequestTypeHostToDeviceIn, request, value, 0, buffer, buffer.Length, ControlTimeout); for (int i = 0; i < expected.Length; i++) { - if (expected[i] == -1) continue; - - int current = buffer[i] & 0xff; - if (expected[i] != current) - throw new Exception($"Expected 0x{expected[i]:X} bytes, but get 0x{current:X} [ {msg} ]"); + if (expected[i] != 0xFF && expected[i] != buffer[i]) + throw new Exception($"Expected 0x{expected[i]:X} bytes, but get 0x{buffer[i]:X} [ {msg} ]"); + expected[i] = buffer[i]; } } /// @@ -170,10 +148,12 @@ private int ControlIn(int request, int value, int index, byte[] buffer) /// /// /// - private int ControlOut(int request, int value, int index) + private void ControlOut(string msg, int request, int value, int index) { ArgumentNullException.ThrowIfNull(UsbDeviceConnection); - return UsbDeviceConnection.ControlTransfer((UsbAddressing)RequestTypeHostToDeviceOut, request, value, index, null, 0, ControlTimeout); + int ret = UsbDeviceConnection.ControlTransfer((UsbAddressing)RequestTypeHostToDeviceOut, request, value, index, null, 0, ControlTimeout); + if (0 > ret) + throw new ControlTransferException(msg, ret, RequestTypeHostToDeviceOut, request, value, index, null, 0, ControlTimeout); } /// /// Set the control lines @@ -181,12 +161,8 @@ private int ControlOut(int request, int value, int index) /// private void SetControlLines() { - const int request = 0xA4; int value = ~((DtrEnable ? SclDtr : 0) | (RtsEnable ? SclRts : 0)); - const int index = 0; - int ret = ControlOut(request, value, 0); - if (ret < 0) - throw new ControlTransferException("Failed to set control lines", ret, RequestTypeHostToDeviceOut, request, value, index, null, 0, ControlTimeout); + ControlOut("Failed to set control lines", CH341_REQ_MODEM_CTRL, value, 0); } /// /// Set the baud rate @@ -200,24 +176,17 @@ private void SetBaudRate(int baudRate) { if (baud[i * 3] == baudRate) { - const int request = 0x9a; - - const int value1 = 0x1312; - int index1 = baud[i * 3 + 1]; - int ret = ControlOut(request, value1, index1); - if (ret < 0) - throw new ControlTransferException("Error setting baud rate. #1", ret, RequestTypeHostToDeviceOut, request, value1, index1, null, 0, ControlTimeout); - - const int value2 = 0x0f2c; - int index2 = baud[i * 3 + 2]; - ret = ControlOut(request, value2, index2); - if (ret < 0) - throw new ControlTransferException("Error setting baud rate. #2", ret, RequestTypeHostToDeviceOut, request, value2, index2, null, 0, ControlTimeout); - + const int value1 = (CH341_REG_DIVISOR << 8) | CH341_REG_PRESCALER; + int index1 = baud[(i * 3) + 1]; + if (ChipVersion > 0x27) + index1 |= 0x80; // BIT(7) + ControlOut("Error setting baud rate. #1", CH341_REQ_WRITE_REG, value1, index1); + const int value2 = 0x0f2c; + int index2 = baud[(i * 3) + 2]; + ControlOut("Error setting baud rate. #2", CH341_REQ_WRITE_REG, value2, index2); return; } } - throw new Exception($"Baud rate {baudRate} currently not supported"); } /// @@ -266,11 +235,8 @@ private void SetParameter(int baudRate, byte dataBits, StopBits stopBits, Parity _ => throw new Exception($"Invalid stop bits: {stopBits}") }; - const int request = 0x9A; - const int value = 0x2518; - int ret = ControlOut(request, value, lcr); - if (ret < 0) - throw new ControlTransferException("Error setting control byte", ret, RequestTypeHostToDeviceOut, request, value, lcr, null, 0, ControlTimeout); + const int value = (CH341_REG_LCR2 << 8) | CH341_REG_LCR; + ControlOut("Error setting control byte", CH341_REQ_WRITE_REG, value, lcr); } /// /// Set DTR enabled From 17d23e80b72f05a43583e36262dee2a3e3e0622b Mon Sep 17 00:00:00 2001 From: "SAVPC\\Sav" Date: Thu, 6 Nov 2025 11:34:49 +0700 Subject: [PATCH 04/39] FEAT - add vendors (Atmel, GigaDevice, Arduino, Nrf) link to Cdc driver (Arduino - not tested) --- UsbSerialForAndroid.Net/Enums/VendorIds.cs | 2 ++ UsbSerialForAndroid.Net/UsbDriverDictionary.cs | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/UsbSerialForAndroid.Net/Enums/VendorIds.cs b/UsbSerialForAndroid.Net/Enums/VendorIds.cs index 7241c0b..4d8057d 100644 --- a/UsbSerialForAndroid.Net/Enums/VendorIds.cs +++ b/UsbSerialForAndroid.Net/Enums/VendorIds.cs @@ -13,5 +13,7 @@ public enum VendorIds Arduino = 0x2341, GigaDevice = 0x28E9, Atmel = 0x03EB, + Stm32 = 0x0483, + Nrf = 0x1915, } } diff --git a/UsbSerialForAndroid.Net/UsbDriverDictionary.cs b/UsbSerialForAndroid.Net/UsbDriverDictionary.cs index f8e7373..990dd09 100644 --- a/UsbSerialForAndroid.Net/UsbDriverDictionary.cs +++ b/UsbSerialForAndroid.Net/UsbDriverDictionary.cs @@ -40,6 +40,10 @@ public UsbDriverDictionary() Register((int)VendorIds.Prolific, (usbDevice) => new ProlificSerialDriver(usbDevice)); Register((int)VendorIds.QinHeng, (usbDevice) => new QinHengSerialDriver(usbDevice)); Register((int)VendorIds.SiliconLabs, (usbDevice) => new SiliconLabsSerialDriver(usbDevice)); + Register((int)VendorIds.Atmel, (usbDevice) => new CdcAcmSerialDriver(usbDevice)); + Register((int)VendorIds.GigaDevice, (usbDevice) => new CdcAcmSerialDriver(usbDevice)); + Register((int)VendorIds.Arduino, (usbDevice) => new CdcAcmSerialDriver(usbDevice)); + Register((int)VendorIds.Nrf, (usbDevice) => new CdcAcmSerialDriver(usbDevice)); } void Register(int VendorId, CreateUsbDriverFn? fn) { From 0e0097763cdf1a18ec3e43e37b6fef5e0c106e16 Mon Sep 17 00:00:00 2001 From: "SAVPC\\Sav" Date: Thu, 6 Nov 2025 17:26:19 +0700 Subject: [PATCH 05/39] FEAT - add native android async read and write --- .../Drivers/Base/UsbDriverBase.cs | 84 ++++++++++++++++--- .../Drivers/FtdiSerialDriver.cs | 1 + .../Drivers/ProlificSerialDriver.cs | 6 ++ .../Drivers/QinHengSerialDriver.cs | 1 + .../Drivers/SiliconLabsSerialDriver.cs | 1 + .../Extensions/BufferExtensions.cs | 53 ++++++++++++ .../UsbDeviceConnectionExtension.cs | 51 +++++++++++ .../Extensions/UsbRequestExtension.cs | 15 ++++ 8 files changed, 199 insertions(+), 13 deletions(-) create mode 100644 UsbSerialForAndroid.Net/Extensions/BufferExtensions.cs create mode 100644 UsbSerialForAndroid.Net/Extensions/UsbDeviceConnectionExtension.cs create mode 100644 UsbSerialForAndroid.Net/Extensions/UsbRequestExtension.cs diff --git a/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs b/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs index 376e874..cbde880 100644 --- a/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs +++ b/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs @@ -1,11 +1,15 @@ using Android.App; using Android.Content; using Android.Hardware.Usb; +using Java.Nio; using System; using System.Buffers; +using System.IO; +using System.Threading; using System.Threading.Tasks; using UsbSerialForAndroid.Net.Enums; using UsbSerialForAndroid.Net.Exceptions; +using UsbSerialForAndroid.Net.Extensions; namespace UsbSerialForAndroid.Net.Drivers { @@ -123,7 +127,18 @@ private static UsbManager GetUsbManager() /// public virtual void Close() { - UsbDeviceConnection?.Close(); + _usbReadRequest?.Close(); + _usbReadRequest = null; + _usbWriteRequest?.Close(); + _usbWriteRequest = null; + _readBuf?.Dispose(); + _readBuf = null; + + UsbEndpointRead?.Dispose(); UsbEndpointRead = null; + UsbEndpointWrite?.Dispose(); UsbEndpointWrite = null; + UsbDeviceConnection?.ReleaseInterface(UsbInterface); + UsbInterface?.Dispose(); UsbInterface = null; + UsbDeviceConnection?.Close(); UsbDeviceConnection = null; } /// /// sync write @@ -163,12 +178,9 @@ public virtual void Write(byte[] buffer) /// write data /// /// Write failed exception - public virtual async Task WriteAsync(byte[] buffer) + public virtual Task WriteAsync(byte[] buffer) { - ArgumentNullException.ThrowIfNull(UsbDeviceConnection); - int result = await UsbDeviceConnection.BulkTransferAsync(UsbEndpointWrite, buffer, 0, buffer.Length, WriteTimeout); - if (result < 0) - throw new BulkTransferException("Write failed", result, UsbEndpointWrite, buffer, 0, buffer.Length, WriteTimeout); + return WriteAsync(buffer, 0, buffer.Length); } /// /// async read @@ -176,18 +188,15 @@ public virtual async Task WriteAsync(byte[] buffer) /// The read data is returned after the read succeeds. Null data is returned after the read fails public virtual async Task ReadAsync() { - ArgumentNullException.ThrowIfNull(UsbDeviceConnection); - var buffer = ArrayPool.Shared.Rent(DefaultBufferLength); + var dest = ArrayPool.Shared.Rent(DefaultBufferLength); try { - int result = await UsbDeviceConnection.BulkTransferAsync(UsbEndpointRead, buffer, 0, DefaultBufferLength, ReadTimeout); - return result >= 0 - ? buffer.AsSpan().Slice(0, result).ToArray() - : default; + int len = await ReadAsync(dest, 0, dest.Length); + return dest.AsSpan(0, len).ToArray(); } finally { - ArrayPool.Shared.Return(buffer); + ArrayPool.Shared.Return(dest); } } /// @@ -223,5 +232,54 @@ public bool TestConnection() return false; } } + + protected void InitAsyncBuffers() + { + _usbReadRequest = new(); + _usbReadRequest.Initialize(UsbDeviceConnection, UsbEndpointRead); + _readBuf = ByteBuffer.Allocate(DefaultBufferLength); + _usbWriteRequest = new(); + _usbWriteRequest.Initialize(UsbDeviceConnection, UsbEndpointWrite); + } + // we need work around with _readBuf + // https://github.com/alex3696/UsbSerialForAndroid/blob/main/UsbSerialForAndroid/driver/CdcAcmSerialDriver.cs 300 + protected UsbRequest? _usbReadRequest; + protected ByteBuffer? _readBuf; + public virtual async Task ReadAsync(byte[] rbuf, int offset, int count, CancellationToken ct = default) + { + ArgumentNullException.ThrowIfNull(_readBuf); + ArgumentNullException.ThrowIfNull(_usbReadRequest); + ArgumentNullException.ThrowIfNull(UsbDeviceConnection); + var buf = _readBuf; + if (!_usbReadRequest.QueueReq(buf)) + throw new IOException("Error queueing request."); + using var crReg = ct.Register(() => _usbReadRequest?.Cancel()); + UsbRequest? response = await UsbDeviceConnection.RequestWaitAsync(_usbReadRequest, ControlTimeout); + if (!ReferenceEquals(response, _usbReadRequest)) + throw new IOException("Wrong response"); + int nread = buf.Position(); + if (nread > 0) + { + _readBuf.ToByteArray().AsSpan(0, nread).CopyTo(rbuf); + return nread; + } + return 0; + } + protected UsbRequest? _usbWriteRequest; + public virtual async Task WriteAsync(byte[] wbuf, int offset, int count, CancellationToken ct = default) + { + ArgumentNullException.ThrowIfNull(_usbWriteRequest); + ArgumentNullException.ThrowIfNull(UsbDeviceConnection); + using var buf = ByteBuffer.Wrap(wbuf, offset, count); + if (!_usbWriteRequest.QueueReq(buf)) + throw new IOException("Error queueing request."); + using var crReg = ct.Register(() => _usbWriteRequest?.Cancel()); + UsbRequest? response = await UsbDeviceConnection.RequestWaitAsync(_usbWriteRequest, ControlTimeout); + if (!ReferenceEquals(response, _usbWriteRequest)) + throw new IOException("Wrong response"); + int nwrite = buf.Position(); + return nwrite; + } + } } \ No newline at end of file diff --git a/UsbSerialForAndroid.Net/Drivers/FtdiSerialDriver.cs b/UsbSerialForAndroid.Net/Drivers/FtdiSerialDriver.cs index 1d9aa25..42f7c35 100644 --- a/UsbSerialForAndroid.Net/Drivers/FtdiSerialDriver.cs +++ b/UsbSerialForAndroid.Net/Drivers/FtdiSerialDriver.cs @@ -67,6 +67,7 @@ public override void Open(int baudRate = DefaultBaudRate, byte dataBits = Defaul || UsbDevice.InterfaceCount > 1;// FT2232C SetParameter(baudRate, dataBits, stopBits, parity); + InitAsyncBuffers(); } /// /// Reset the USB device diff --git a/UsbSerialForAndroid.Net/Drivers/ProlificSerialDriver.cs b/UsbSerialForAndroid.Net/Drivers/ProlificSerialDriver.cs index 1a0bafd..40c225c 100644 --- a/UsbSerialForAndroid.Net/Drivers/ProlificSerialDriver.cs +++ b/UsbSerialForAndroid.Net/Drivers/ProlificSerialDriver.cs @@ -127,6 +127,12 @@ public override void Open(int baudRate = DefaultBaudRate, byte dataBits = Defaul SetFlowControl(FlowControl); SetParameter(baudRate, dataBits, stopBits, parity); + InitAsyncBuffers(); + } + public override void Close() + { + UsbEndpointInterupt?.Dispose(); UsbEndpointInterupt = null; + base.Close(); } /// /// Set parameter diff --git a/UsbSerialForAndroid.Net/Drivers/QinHengSerialDriver.cs b/UsbSerialForAndroid.Net/Drivers/QinHengSerialDriver.cs index cca1db0..fb36c61 100644 --- a/UsbSerialForAndroid.Net/Drivers/QinHengSerialDriver.cs +++ b/UsbSerialForAndroid.Net/Drivers/QinHengSerialDriver.cs @@ -67,6 +67,7 @@ public override void Open(int baudRate = DefaultBaudRate, byte dataBits = Defaul Initialize(); SetParameter(baudRate, dataBits, stopBits, parity); + InitAsyncBuffers(); } /// /// Initialize the device diff --git a/UsbSerialForAndroid.Net/Drivers/SiliconLabsSerialDriver.cs b/UsbSerialForAndroid.Net/Drivers/SiliconLabsSerialDriver.cs index c148b16..73d0fa8 100644 --- a/UsbSerialForAndroid.Net/Drivers/SiliconLabsSerialDriver.cs +++ b/UsbSerialForAndroid.Net/Drivers/SiliconLabsSerialDriver.cs @@ -67,6 +67,7 @@ public override void Open(int baudRate = DefaultBaudRate, byte dataBits = Defaul SetConfigSingle(SilabserSetMhsRequestCode, McrAll | ControlDtrDisable | ControlRtsDisable); SetConfigSingle(SilabserSetBauddivRequestCode, BaudRateGenFreq / DefaultBaudRate); SetParameter(baudRate, dataBits, stopBits, parity); + InitAsyncBuffers(); } /// /// Set the UART enabled diff --git a/UsbSerialForAndroid.Net/Extensions/BufferExtensions.cs b/UsbSerialForAndroid.Net/Extensions/BufferExtensions.cs new file mode 100644 index 0000000..6401939 --- /dev/null +++ b/UsbSerialForAndroid.Net/Extensions/BufferExtensions.cs @@ -0,0 +1,53 @@ +/* Copyright 2017 Tyler Technologies Inc. + * + * Project home page: https://github.com/anotherlab/xamarin-usb-serial-for-android + * Portions of this library are based on usb-serial-for-android (https://github.com/mik3y/usb-serial-for-android). + * Portions of this library are based on Xamarin USB Serial for Android (https://bitbucket.org/lusovu/xamarinusbserial). + */ + +using Android.Runtime; +using Java.Nio; + +namespace UsbSerialForAndroid.Net.Extensions +{ + /// + /// Work around for faulty JNI wrapping in Xamarin library. Fixes a bug + /// where binding for Java.Nio.ByteBuffer.Get(byte[], int, int) allocates a new temporary + /// Java byte array on every call + /// See https://bugzilla.xamarin.com/show_bug.cgi?id=31260 + /// and http://stackoverflow.com/questions/30268400/xamarin-implementation-of-bytebuffer-get-wrong + /// + public static class BufferExtensions + { + static nint _byteBufferClassRef; + static nint _byteBufferGetBii; + static nint _byteBufferGetArrayMethodRef; + + // init on first call + static BufferExtensions() + { + _byteBufferClassRef = JNIEnv.FindClass("java/nio/ByteBuffer"); + _byteBufferGetArrayMethodRef = JNIEnv.GetMethodID(_byteBufferClassRef, "array", "()[B"); + } + + public static ByteBuffer? Get(this ByteBuffer buffer, JavaArray dst, int dstOffset, int byteCount) + { + if (_byteBufferClassRef == nint.Zero) + _byteBufferClassRef = JNIEnv.FindClass("java/nio/ByteBuffer"); + if (_byteBufferGetBii == nint.Zero) + _byteBufferGetBii = JNIEnv.GetMethodID(_byteBufferClassRef, "get", "([BII)Ljava/nio/ByteBuffer;"); + + return Java.Lang.Object.GetObject( + JNIEnv.CallObjectMethod(buffer.Handle, _byteBufferGetBii, [new(dst), new(dstOffset), new(byteCount)]), + JniHandleOwnership.TransferLocalRef); + } + + public static byte[]? ToByteArray(this ByteBuffer buffer) + { + nint resultHandle = JNIEnv.CallObjectMethod(buffer.Handle, _byteBufferGetArrayMethodRef); + byte[]? result = JNIEnv.GetArray(resultHandle); + JNIEnv.DeleteLocalRef(resultHandle); + return result; + } + } +} \ No newline at end of file diff --git a/UsbSerialForAndroid.Net/Extensions/UsbDeviceConnectionExtension.cs b/UsbSerialForAndroid.Net/Extensions/UsbDeviceConnectionExtension.cs new file mode 100644 index 0000000..7183c6a --- /dev/null +++ b/UsbSerialForAndroid.Net/Extensions/UsbDeviceConnectionExtension.cs @@ -0,0 +1,51 @@ +using Android.Hardware.Usb; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace UsbSerialForAndroid.Net.Extensions +{ + public static class UsbDeviceConnectionExtension + { + public static List GetDescriptors(this UsbDeviceConnection connection) + { + var descriptors = new List(); + var rawDescriptors = connection.GetRawDescriptors(); + if (rawDescriptors is null) return descriptors; + int pos = 0; + while (pos < rawDescriptors.Length) + { + int len = rawDescriptors[pos] & 0xFF; + if (len == 0) break; + + if (pos + len > rawDescriptors.Length) + len = rawDescriptors.Length - pos; + + byte[] descriptor = new byte[len]; + Array.Copy(rawDescriptors, pos, descriptor, 0, descriptor.Length); + descriptors.Add(descriptor); + pos += len; + } + return descriptors; + } + + public static async Task RequestWaitAsync(this UsbDeviceConnection connection, UsbRequest req, int timeout) + { + if (OperatingSystem.IsAndroidVersionAtLeast(26)) + return await connection.RequestWaitAsync(timeout); + using var cts = new CancellationTokenSource(timeout); + using var crReg = cts.Token.Register(() => req?.Cancel()); + UsbRequest? ret = await connection.RequestWaitAsync(); + return ret; + } + public static UsbRequest? RequestWait(this UsbDeviceConnection connection, UsbRequest req, int timeout) + { + if (OperatingSystem.IsAndroidVersionAtLeast(26)) + return connection.RequestWait(timeout); + using var cts = new CancellationTokenSource(timeout); + using var crReg = cts.Token.Register(() => req?.Cancel()); + return connection.RequestWait(); + } + } +} diff --git a/UsbSerialForAndroid.Net/Extensions/UsbRequestExtension.cs b/UsbSerialForAndroid.Net/Extensions/UsbRequestExtension.cs new file mode 100644 index 0000000..2123c10 --- /dev/null +++ b/UsbSerialForAndroid.Net/Extensions/UsbRequestExtension.cs @@ -0,0 +1,15 @@ +using Android.Hardware.Usb; +using Java.Nio; +using System; + +namespace UsbSerialForAndroid.Net.Extensions; + +public static class UsbRequestExtension +{ + public static bool QueueReq(this UsbRequest req, ByteBuffer buffer) + { + if (OperatingSystem.IsAndroidVersionAtLeast(26)) + return req.Queue(buffer); + return req.Queue(buffer, buffer.Capacity()); + } +} From 7fa1e2026f4071ccfe77f35724adbe952dc3de9f Mon Sep 17 00:00:00 2001 From: "SAVPC\\Sav" Date: Thu, 6 Nov 2025 17:42:32 +0700 Subject: [PATCH 06/39] FEAT - add native android async read and write to Cdc Driver and Ftdi --- .../Drivers/CdcAcmSerialDriver.cs | 69 ++----------------- .../Drivers/FtdiSerialDriver.cs | 22 +++--- 2 files changed, 18 insertions(+), 73 deletions(-) diff --git a/UsbSerialForAndroid.Net/Drivers/CdcAcmSerialDriver.cs b/UsbSerialForAndroid.Net/Drivers/CdcAcmSerialDriver.cs index 4c0627f..b527797 100644 --- a/UsbSerialForAndroid.Net/Drivers/CdcAcmSerialDriver.cs +++ b/UsbSerialForAndroid.Net/Drivers/CdcAcmSerialDriver.cs @@ -1,14 +1,10 @@ using Android.Hardware.Usb; -using Java.Nio; using System; using System.Buffers; -using System.IO; -using System.Threading; using System.Threading.Tasks; using UsbSerialForAndroid.Net.Enums; using UsbSerialForAndroid.Net.Exceptions; using UsbSerialForAndroid.Net.Extensions; -using static Android.Media.Audiofx.DynamicsProcessing; namespace UsbSerialForAndroid.Net.Drivers { @@ -37,9 +33,7 @@ public override void Open(int baudRate, byte dataBits, StopBits stopBits, Parity OpenInterface(); } SetParameters(baudRate, dataBits, stopBits, parity); - _usbReadRequest = new(); - _usbReadRequest.Initialize(UsbDeviceConnection, UsbEndpointRead); - _readBuf = ByteBuffer.Allocate(DefaultBufferLength); + InitAsyncBuffers(); } private void OpenSingleInterface() { @@ -246,13 +240,10 @@ private void SetParameters(int baudRate, byte dataBits, StopBits stopBits, Parit } public override void Close() { - _usbReadRequest?.Close(); - _usbReadRequest = null; + controlEndpoint?.Dispose(); controlEndpoint = null; UsbDeviceConnection?.ReleaseInterface(controlInterface); - UsbDeviceConnection?.ReleaseInterface(UsbInterface); - UsbDeviceConnection?.Close(); - _readBuf?.Dispose(); - _readBuf = null; + controlInterface?.Dispose(); controlInterface = null; + base.Close(); } public override void SetDtrEnabled(bool value) { @@ -273,57 +264,5 @@ private void SetDtrRts() if (result != 0) throw new ControlTransferException("Set dtr rts failed", result, UsbRtAcm, SetControlLineState, value, controlIndex, null, 0, ControlTimeout); } - - - UsbRequest? _usbReadRequest; - ByteBuffer? _readBuf; - public async Task ReadAsync(Memory mem, CancellationToken ct = default) - { - ArgumentNullException.ThrowIfNull(_readBuf); - ArgumentNullException.ThrowIfNull(_usbReadRequest); - ArgumentNullException.ThrowIfNull(UsbDeviceConnection); - if (!_usbReadRequest.QueueReq(_readBuf)) - throw new IOException("Error queueing request."); - using var crReg = ct.Register(() => _usbReadRequest?.Cancel()); - UsbRequest? response = await UsbDeviceConnection.RequestWaitAsync(_usbReadRequest, ControlTimeout); - if (!ReferenceEquals(response, _usbReadRequest)) - throw new IOException("Wrong response"); - int nread = _readBuf.Position(); - if (nread > 0) - { - _readBuf.ToByteArray().AsMemory(0, nread).CopyTo(mem); - return nread; - } - return 0; - } - public override async Task ReadAsync() - { - var dest = ArrayPool.Shared.Rent(DefaultBufferLength); - try - { - var mem = dest.AsMemory(); - int len = await ReadAsync(mem); - return mem.Slice(0, len).ToArray(); - } - finally - { - ArrayPool.Shared.Return(dest); - } - } - public override byte[]? Read() - { - ArgumentNullException.ThrowIfNull(_readBuf); - ArgumentNullException.ThrowIfNull(_usbReadRequest); - ArgumentNullException.ThrowIfNull(UsbDeviceConnection); - if (!_usbReadRequest.QueueReq(_readBuf)) - throw new IOException("Error queueing request."); - UsbRequest? response = UsbDeviceConnection.RequestWait(_usbReadRequest, ControlTimeout); - if (!ReferenceEquals(response, _usbReadRequest)) - throw new IOException("Wrong response"); - int nread = _readBuf.Position(); - if (nread > 0) - return _readBuf.ToByteArray().AsMemory(0, nread).ToArray(); - return default; - } } } diff --git a/UsbSerialForAndroid.Net/Drivers/FtdiSerialDriver.cs b/UsbSerialForAndroid.Net/Drivers/FtdiSerialDriver.cs index 1b1ec4d..1db509d 100644 --- a/UsbSerialForAndroid.Net/Drivers/FtdiSerialDriver.cs +++ b/UsbSerialForAndroid.Net/Drivers/FtdiSerialDriver.cs @@ -1,6 +1,7 @@ using Android.Hardware.Usb; using System; using System.Buffers; +using System.Threading; using System.Threading.Tasks; using UsbSerialForAndroid.Net.Enums; using UsbSerialForAndroid.Net.Exceptions; @@ -278,8 +279,8 @@ public void SetLatency(byte latency) ArgumentNullException.ThrowIfNull(UsbDeviceConnection); int config = latency; int index = UsbInterfaceIndex + 1; - int result = UsbDeviceConnection.ControlTransfer((UsbAddressing)RequestTypeHostToDevice, SetLatencyTimerRequest, config, index, - buffer: null, length: 0, timeout:ControlTimeout); + int result = UsbDeviceConnection.ControlTransfer((UsbAddressing)RequestTypeHostToDevice, SetLatencyTimerRequest, config, index, + buffer: null, length: 0, timeout: ControlTimeout); if (result < 0) throw new ControlTransferException("Set Latency Timer failed", result, RequestTypeHostToDevice, SetLatencyTimerRequest, config, index, null, 0, ControlTimeout); } @@ -305,13 +306,18 @@ public void SetLatency(byte latency) ArrayPool.Shared.Return(buffer); } } - /// - /// Asynchronous read the data - /// - /// - public override Task ReadAsync() + public override async Task ReadAsync(byte[] rbuf, int offset, int count, CancellationToken ct = default) { - return Task.FromResult(Read()); + var buffer = ArrayPool.Shared.Rent(DefaultBufferLength); + try + { + int len = await base.ReadAsync(buffer, 0, buffer.Length, ct); + return FilterBuf(buffer.AsSpan(len), rbuf.AsSpan(offset, count)); + } + finally + { + ArrayPool.Shared.Return(buffer); + } } private int FilterBuf(Span src, Span dst) { From 94ad46b445fce9b0d185485d0e3665539bf56977 Mon Sep 17 00:00:00 2001 From: "HOMEPC\\alex3696" Date: Thu, 6 Nov 2025 19:30:47 +0700 Subject: [PATCH 07/39] update ftri filter --- .../Drivers/FtdiSerialDriver.cs | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/UsbSerialForAndroid.Net/Drivers/FtdiSerialDriver.cs b/UsbSerialForAndroid.Net/Drivers/FtdiSerialDriver.cs index 2caad65..7a371eb 100644 --- a/UsbSerialForAndroid.Net/Drivers/FtdiSerialDriver.cs +++ b/UsbSerialForAndroid.Net/Drivers/FtdiSerialDriver.cs @@ -1,10 +1,12 @@ using Android.Hardware.Usb; using System; using System.Buffers; +using System.IO; using System.Threading; using System.Threading.Tasks; using UsbSerialForAndroid.Net.Enums; using UsbSerialForAndroid.Net.Exceptions; +using UsbSerialForAndroid.Net.Extensions; namespace UsbSerialForAndroid.Net.Drivers { @@ -308,16 +310,19 @@ public void SetLatency(byte latency) } public override async Task ReadAsync(byte[] rbuf, int offset, int count, CancellationToken ct = default) { - var buffer = ArrayPool.Shared.Rent(DefaultBufferLength); - try - { - int len = await base.ReadAsync(buffer, 0, buffer.Length, ct); - return FilterBuf(buffer.AsSpan(len), rbuf.AsSpan(offset, count)); - } - finally - { - ArrayPool.Shared.Return(buffer); - } + ArgumentNullException.ThrowIfNull(_readBuf); + ArgumentNullException.ThrowIfNull(_usbReadRequest); + ArgumentNullException.ThrowIfNull(UsbDeviceConnection); + if (!_usbReadRequest.QueueReq(_readBuf)) + throw new IOException("Error queueing request."); + using var crReg = ct.Register(() => _usbReadRequest?.Cancel()); + UsbRequest? response = await UsbDeviceConnection.RequestWaitAsync(_usbReadRequest, ControlTimeout); + if (!ReferenceEquals(response, _usbReadRequest)) + throw new IOException("Wrong response"); + int nread = _readBuf.Position(); + if (nread > 0) + return FilterBuf(_readBuf.ToByteArray().AsSpan(0, nread), rbuf.AsSpan(offset, count)); + return 0; } private int FilterBuf(Span src) { @@ -329,6 +334,16 @@ private int FilterBuf(Span src) int retLen = src.Length - (statusCount * 2); return retLen; } + private int FilterBuf(Span src, Span dst) + { + if (ReadHeaderLength >= src.Length) + return 0; + int statusCount = (src.Length + 63) / 64; + for (int i = 0; i < statusCount; i++) + src.Slice((i * 64) + 2, 62).CopyTo(src.Slice(i * 62)); + int retLen = src.Length - (statusCount * 2); + return retLen; + } /// /// Set the DTR enabled /// From b059595968ce3faf2e63c205922e665fd4a7ccaa Mon Sep 17 00:00:00 2001 From: "HOMEPC\\alex3696" Date: Thu, 6 Nov 2025 21:20:01 +0700 Subject: [PATCH 08/39] fix ftdi filter --- UsbSerialForAndroid.Net/Drivers/FtdiSerialDriver.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/UsbSerialForAndroid.Net/Drivers/FtdiSerialDriver.cs b/UsbSerialForAndroid.Net/Drivers/FtdiSerialDriver.cs index 7a371eb..a11fd68 100644 --- a/UsbSerialForAndroid.Net/Drivers/FtdiSerialDriver.cs +++ b/UsbSerialForAndroid.Net/Drivers/FtdiSerialDriver.cs @@ -313,6 +313,7 @@ public override async Task ReadAsync(byte[] rbuf, int offset, int count, Ca ArgumentNullException.ThrowIfNull(_readBuf); ArgumentNullException.ThrowIfNull(_usbReadRequest); ArgumentNullException.ThrowIfNull(UsbDeviceConnection); + _readBuf.Position(0); if (!_usbReadRequest.QueueReq(_readBuf)) throw new IOException("Error queueing request."); using var crReg = ct.Register(() => _usbReadRequest?.Cancel()); @@ -321,7 +322,8 @@ public override async Task ReadAsync(byte[] rbuf, int offset, int count, Ca throw new IOException("Wrong response"); int nread = _readBuf.Position(); if (nread > 0) - return FilterBuf(_readBuf.ToByteArray().AsSpan(0, nread), rbuf.AsSpan(offset, count)); + return FilterBuf(_readBuf.ToByteArray().AsSpan(0, nread), + rbuf.AsSpan(offset, rbuf.Length - offset)); return 0; } private int FilterBuf(Span src) @@ -340,7 +342,7 @@ private int FilterBuf(Span src, Span dst) return 0; int statusCount = (src.Length + 63) / 64; for (int i = 0; i < statusCount; i++) - src.Slice((i * 64) + 2, 62).CopyTo(src.Slice(i * 62)); + src.Slice((i * 64) + 2).CopyTo(dst.Slice(i * 62)); int retLen = src.Length - (statusCount * 2); return retLen; } From eb6d64f42d5137a11dd5abf91db753dac3968620 Mon Sep 17 00:00:00 2001 From: "HOMEPC\\alex3696" Date: Thu, 6 Nov 2025 21:26:02 +0700 Subject: [PATCH 09/39] fix reader --- UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs b/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs index cbde880..cb5df9d 100644 --- a/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs +++ b/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs @@ -250,17 +250,18 @@ public virtual async Task ReadAsync(byte[] rbuf, int offset, int count, Can ArgumentNullException.ThrowIfNull(_readBuf); ArgumentNullException.ThrowIfNull(_usbReadRequest); ArgumentNullException.ThrowIfNull(UsbDeviceConnection); - var buf = _readBuf; - if (!_usbReadRequest.QueueReq(buf)) + _readBuf.Position(0); + if (!_usbReadRequest.QueueReq(_readBuf)) throw new IOException("Error queueing request."); using var crReg = ct.Register(() => _usbReadRequest?.Cancel()); UsbRequest? response = await UsbDeviceConnection.RequestWaitAsync(_usbReadRequest, ControlTimeout); if (!ReferenceEquals(response, _usbReadRequest)) throw new IOException("Wrong response"); - int nread = buf.Position(); + int nread = _readBuf.Position(); if (nread > 0) { - _readBuf.ToByteArray().AsSpan(0, nread).CopyTo(rbuf); + _readBuf.ToByteArray().AsSpan(0, nread) + .CopyTo(rbuf.AsSpan(offset, count)); return nread; } return 0; From 8fde2e4e511ae66f4f550181a71a20832120c931 Mon Sep 17 00:00:00 2001 From: "HOMEPC\\alex3696" Date: Fri, 7 Nov 2025 00:09:01 +0700 Subject: [PATCH 10/39] fix reader --- .../Drivers/FtdiSerialDriver.cs | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/UsbSerialForAndroid.Net/Drivers/FtdiSerialDriver.cs b/UsbSerialForAndroid.Net/Drivers/FtdiSerialDriver.cs index a11fd68..93aacad 100644 --- a/UsbSerialForAndroid.Net/Drivers/FtdiSerialDriver.cs +++ b/UsbSerialForAndroid.Net/Drivers/FtdiSerialDriver.cs @@ -314,6 +314,8 @@ public override async Task ReadAsync(byte[] rbuf, int offset, int count, Ca ArgumentNullException.ThrowIfNull(_usbReadRequest); ArgumentNullException.ThrowIfNull(UsbDeviceConnection); _readBuf.Position(0); + // if limit less then 64 _readBuf.Position always 0 // TODO MOVE BUFFER + //_readBuf.Limit((count / 62 * 2) + 2 + count); // 65 = 2 + 62 + 2 + 3 = 69 if (!_usbReadRequest.QueueReq(_readBuf)) throw new IOException("Error queueing request."); using var crReg = ct.Register(() => _usbReadRequest?.Cancel()); @@ -322,28 +324,30 @@ public override async Task ReadAsync(byte[] rbuf, int offset, int count, Ca throw new IOException("Wrong response"); int nread = _readBuf.Position(); if (nread > 0) - return FilterBuf(_readBuf.ToByteArray().AsSpan(0, nread), - rbuf.AsSpan(offset, rbuf.Length - offset)); + return FilterBuf(_readBuf.ToByteArray().AsSpan(0, int.Min(nread, count)), + rbuf.AsSpan(offset, count)); return 0; } - private int FilterBuf(Span src) + static private int FilterBuf(Span src) { - if (ReadHeaderLength >= src.Length) - return 0; int statusCount = (src.Length + 63) / 64; for (int i = 0; i < statusCount; i++) src.Slice((i * 62) + 2).CopyTo(src.Slice(i * 62)); int retLen = src.Length - (statusCount * 2); return retLen; } - private int FilterBuf(Span src, Span dst) + static private int FilterBuf(Span src, Span dst) { - if (ReadHeaderLength >= src.Length) - return 0; - int statusCount = (src.Length + 63) / 64; - for (int i = 0; i < statusCount; i++) - src.Slice((i * 64) + 2).CopyTo(dst.Slice(i * 62)); - int retLen = src.Length - (statusCount * 2); + int curr; + int retLen = 0; + while (2 < src.Length) + { + curr = int.Min(64, src.Length); + src.Slice(2, curr - 2).CopyTo(dst); + dst = dst.Slice(curr - 2); + src = src.Slice(curr); + retLen += curr - 2; + } return retLen; } /// From 8b612d83e817266995dd0a74583c0e51868582d1 Mon Sep 17 00:00:00 2001 From: "HOMEPC\\alex3696" Date: Fri, 7 Nov 2025 00:16:27 +0700 Subject: [PATCH 11/39] fix reader min count --- UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs b/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs index cb5df9d..9044de6 100644 --- a/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs +++ b/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs @@ -260,7 +260,7 @@ public virtual async Task ReadAsync(byte[] rbuf, int offset, int count, Can int nread = _readBuf.Position(); if (nread > 0) { - _readBuf.ToByteArray().AsSpan(0, nread) + _readBuf.ToByteArray().AsSpan(0, int.Min(nread, count)) .CopyTo(rbuf.AsSpan(offset, count)); return nread; } From 30314b4d2bf3c22615824e5ef98cfef5fa739f33 Mon Sep 17 00:00:00 2001 From: "SAVPC\\Sav" Date: Fri, 7 Nov 2025 16:29:27 +0700 Subject: [PATCH 12/39] add FIFO buffer --- .../Drivers/Base/UsbDriverBase.cs | 66 ++++++++++++++----- UsbSerialForAndroid.Net/Helper/MemoryQueue.cs | 62 +++++++++++++++++ 2 files changed, 112 insertions(+), 16 deletions(-) create mode 100644 UsbSerialForAndroid.Net/Helper/MemoryQueue.cs diff --git a/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs b/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs index 9044de6..f5054fb 100644 --- a/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs +++ b/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs @@ -10,6 +10,7 @@ using UsbSerialForAndroid.Net.Enums; using UsbSerialForAndroid.Net.Exceptions; using UsbSerialForAndroid.Net.Extensions; +using UsbSerialForAndroid.Net.Helper; namespace UsbSerialForAndroid.Net.Drivers { @@ -131,8 +132,8 @@ public virtual void Close() _usbReadRequest = null; _usbWriteRequest?.Close(); _usbWriteRequest = null; - _readBuf?.Dispose(); - _readBuf = null; + _readBuf?.Dispose(); _readBuf = null; + _rsBuf = null; UsbEndpointRead?.Dispose(); UsbEndpointRead = null; UsbEndpointWrite?.Dispose(); UsbEndpointWrite = null; @@ -235,6 +236,7 @@ public bool TestConnection() protected void InitAsyncBuffers() { + _rsBuf = new(DefaultBufferLength); _usbReadRequest = new(); _usbReadRequest.Initialize(UsbDeviceConnection, UsbEndpointRead); _readBuf = ByteBuffer.Allocate(DefaultBufferLength); @@ -245,26 +247,58 @@ protected void InitAsyncBuffers() // https://github.com/alex3696/UsbSerialForAndroid/blob/main/UsbSerialForAndroid/driver/CdcAcmSerialDriver.cs 300 protected UsbRequest? _usbReadRequest; protected ByteBuffer? _readBuf; - public virtual async Task ReadAsync(byte[] rbuf, int offset, int count, CancellationToken ct = default) + protected MemoryQueue? _rsBuf; + + public virtual async Task ReadAsync(byte[] dstBuf, int offset, int count, CancellationToken ct = default) { + ArgumentNullException.ThrowIfNull(_rsBuf); ArgumentNullException.ThrowIfNull(_readBuf); ArgumentNullException.ThrowIfNull(_usbReadRequest); ArgumentNullException.ThrowIfNull(UsbDeviceConnection); - _readBuf.Position(0); - if (!_usbReadRequest.QueueReq(_readBuf)) - throw new IOException("Error queueing request."); - using var crReg = ct.Register(() => _usbReadRequest?.Cancel()); - UsbRequest? response = await UsbDeviceConnection.RequestWaitAsync(_usbReadRequest, ControlTimeout); - if (!ReferenceEquals(response, _usbReadRequest)) - throw new IOException("Wrong response"); - int nread = _readBuf.Position(); - if (nread > 0) + // if buffered data available, read it first + if (0 < _rsBuf.Length) + { + if (_rsBuf.Length > count) + { + _rsBuf.Read(dstBuf.AsSpan(offset, count)); + return count; + } + else + { + var ret = (int)_rsBuf.Length; + _rsBuf.Read(dstBuf.AsSpan(offset, ret)); + return ret; + } + } + // try read from port + var buf = _readBuf; + int nread = 0; + while (0 == nread) { - _readBuf.ToByteArray().AsSpan(0, int.Min(nread, count)) - .CopyTo(rbuf.AsSpan(offset, count)); - return nread; + ct.ThrowIfCancellationRequested(); + buf.Rewind(); + if (!_usbReadRequest.QueueReq(buf)) + throw new IOException("Error queueing request."); + using var crReg = ct.Register(() => _usbReadRequest?.Cancel()); + UsbRequest? response = await UsbDeviceConnection.RequestWaitAsync(_usbReadRequest, ControlTimeout); + ct.ThrowIfCancellationRequested(); + if (!ReferenceEquals(response, _usbReadRequest)) + throw new IOException("Wrong response"); + nread = buf.Position(); + if (0 < nread) + { + var data = buf.ToByteArray().AsSpan(0, nread); + if (nread > count) + { + _rsBuf.Write(data.Slice(count));//copy the rest to the buffer + nread = count; + } + data.Slice(0, nread).CopyTo(dstBuf.AsSpan(offset, count)); + } + else + nread = 0; } - return 0; + return nread; } protected UsbRequest? _usbWriteRequest; public virtual async Task WriteAsync(byte[] wbuf, int offset, int count, CancellationToken ct = default) diff --git a/UsbSerialForAndroid.Net/Helper/MemoryQueue.cs b/UsbSerialForAndroid.Net/Helper/MemoryQueue.cs new file mode 100644 index 0000000..1ac73a5 --- /dev/null +++ b/UsbSerialForAndroid.Net/Helper/MemoryQueue.cs @@ -0,0 +1,62 @@ +using System; + +namespace UsbSerialForAndroid.Net.Helper; + +/// +/// simple fifo +/// +public class MemoryQueue +{ + public MemoryQueue(int capacity = 256) + { + _mem = new byte[capacity]; + _r = 0; + _w = 0; + } + public uint Length => _w - _r; + public uint EmptySpace => (uint)_mem.Length - (_w - _r); + public void Write(Span src) + { + if (0 >= src.Length) + return; + var wempty = (uint)_mem.Length - _w; + if (src.Length > _r + wempty) + throw new OverflowException($"EmptySpace {EmptySpace}, data length is {src.Length}"); + if (src.Length > wempty) + MemMove(); + src.CopyTo(_mem.AsSpan((int)_w)); + _w += (uint)src.Length; + } + public void Read(Span dst) + { + if (dst.Length > _w - _r) + throw new ArgumentOutOfRangeException($"{dst.Length} is greater then data {_w - _r}"); + _mem.AsSpan((int)_r, dst.Length).CopyTo(dst); + _r += (uint)dst.Length; + } + public void Write(byte[] src, int soffset = 0, int slen = 0) + { + if (0 >= slen) + slen = src.Length - soffset; + Write(src.AsSpan(soffset, slen)); + } + public void Read(byte[] dst, int offset = 0, int len = 0) + { + if (0 >= len) + len = dst.Length - offset; + Read(dst.AsSpan(offset, len)); + } + + readonly byte[] _mem; + uint _r; + uint _w; + void MemMove() + { + if (0 < _r) + { + _w -= _r;//dataLen + Array.Copy(_mem, _r, _mem, 0, _w); + _r = 0; + } + } +} From b8c06dd5c0f19a445832c72ea69dcc735861aace Mon Sep 17 00:00:00 2001 From: "SAVPC\\Sav" Date: Fri, 7 Nov 2025 16:55:02 +0700 Subject: [PATCH 13/39] introduce delegate filter for in-place filter data --- UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs b/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs index f5054fb..7d1ef19 100644 --- a/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs +++ b/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs @@ -248,6 +248,8 @@ protected void InitAsyncBuffers() protected UsbRequest? _usbReadRequest; protected ByteBuffer? _readBuf; protected MemoryQueue? _rsBuf; + public FilterDataFn? FilterData; + public delegate Span FilterDataFn(Span src); public virtual async Task ReadAsync(byte[] dstBuf, int offset, int count, CancellationToken ct = default) { @@ -288,6 +290,11 @@ public virtual async Task ReadAsync(byte[] dstBuf, int offset, int count, C if (0 < nread) { var data = buf.ToByteArray().AsSpan(0, nread); + if (null != FilterData) + { + data = FilterData(data); + nread = data.Length; + } if (nread > count) { _rsBuf.Write(data.Slice(count));//copy the rest to the buffer From fe8afc95bd20ed033bfb6071d67c829e986dde03 Mon Sep 17 00:00:00 2001 From: "SAVPC\\Sav" Date: Fri, 7 Nov 2025 16:56:27 +0700 Subject: [PATCH 14/39] use filter for in-place filter data --- .../Drivers/FtdiSerialDriver.cs | 28 +++---------------- 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/UsbSerialForAndroid.Net/Drivers/FtdiSerialDriver.cs b/UsbSerialForAndroid.Net/Drivers/FtdiSerialDriver.cs index 93aacad..45767c5 100644 --- a/UsbSerialForAndroid.Net/Drivers/FtdiSerialDriver.cs +++ b/UsbSerialForAndroid.Net/Drivers/FtdiSerialDriver.cs @@ -78,6 +78,7 @@ public override void Open(int baudRate = DefaultBaudRate, byte dataBits = Defaul SetParameter(baudRate, dataBits, stopBits, parity); SetLatency(1); InitAsyncBuffers(); + FilterData = FilterBuf; } /// /// Reset the USB device @@ -300,41 +301,20 @@ public void SetLatency(byte latency) try { int len = UsbDeviceConnection.BulkTransfer(UsbEndpointRead, buffer, 0, DefaultBufferLength, ReadTimeout); - len = FilterBuf(buffer.AsSpan(0, len)); - return buffer.AsSpan(0, len).ToArray(); + return FilterBuf(buffer.AsSpan(0, len)).ToArray(); } finally { ArrayPool.Shared.Return(buffer); } } - public override async Task ReadAsync(byte[] rbuf, int offset, int count, CancellationToken ct = default) - { - ArgumentNullException.ThrowIfNull(_readBuf); - ArgumentNullException.ThrowIfNull(_usbReadRequest); - ArgumentNullException.ThrowIfNull(UsbDeviceConnection); - _readBuf.Position(0); - // if limit less then 64 _readBuf.Position always 0 // TODO MOVE BUFFER - //_readBuf.Limit((count / 62 * 2) + 2 + count); // 65 = 2 + 62 + 2 + 3 = 69 - if (!_usbReadRequest.QueueReq(_readBuf)) - throw new IOException("Error queueing request."); - using var crReg = ct.Register(() => _usbReadRequest?.Cancel()); - UsbRequest? response = await UsbDeviceConnection.RequestWaitAsync(_usbReadRequest, ControlTimeout); - if (!ReferenceEquals(response, _usbReadRequest)) - throw new IOException("Wrong response"); - int nread = _readBuf.Position(); - if (nread > 0) - return FilterBuf(_readBuf.ToByteArray().AsSpan(0, int.Min(nread, count)), - rbuf.AsSpan(offset, count)); - return 0; - } - static private int FilterBuf(Span src) + static private Span FilterBuf(Span src) { int statusCount = (src.Length + 63) / 64; for (int i = 0; i < statusCount; i++) src.Slice((i * 62) + 2).CopyTo(src.Slice(i * 62)); int retLen = src.Length - (statusCount * 2); - return retLen; + return src.Slice(0, retLen); } static private int FilterBuf(Span src, Span dst) { From 71814a29506d8b2fee1ffda8bb1a211e2ef4bef3 Mon Sep 17 00:00:00 2001 From: "SAVPC\\Sav" Date: Mon, 10 Nov 2025 14:34:18 +0700 Subject: [PATCH 15/39] FEAT - add FIFO buffer and tests --- UsbSerialForAndroid.Net/Helper/MemoryQueue.cs | 178 ++++++++++++++---- 1 file changed, 141 insertions(+), 37 deletions(-) diff --git a/UsbSerialForAndroid.Net/Helper/MemoryQueue.cs b/UsbSerialForAndroid.Net/Helper/MemoryQueue.cs index 1ac73a5..0fde71a 100644 --- a/UsbSerialForAndroid.Net/Helper/MemoryQueue.cs +++ b/UsbSerialForAndroid.Net/Helper/MemoryQueue.cs @@ -1,62 +1,166 @@ using System; +using System.Diagnostics; +using System.Text; namespace UsbSerialForAndroid.Net.Helper; +/* +begin - B +length - C + +// filled 0 Continuous +0 1 2 3 4 5 6 7 8 9 +. . . . . . . . . . + B + C=0 + +// filled 9 Continuous +0 1 2 3 4 5 6 7 8 9 +* * * * * * * * * . +B +C=9 + +// filled 10 Continuous +0 1 2 3 4 5 6 7 8 9 +* * * * * * * * * * +B +C=10 + +// filled 9 +0 1 2 3 4 5 6 7 8 9 +* * . * * * * * * * + B + C=9 + +// filled 10 Continuous +0 1 2 3 4 5 6 7 8 9 +* * * * * * * * * * + B + C=10 + +// filled 4 +0 1 2 3 4 5 6 7 8 9 +* * . . . . . . * * + B + C=4 + +// filled 4 Continuous +0 1 2 3 4 5 6 7 8 9 +. . * * * * . . . . + B + C = 4 +*/ + /// -/// simple fifo +/// simple fifo Queue /// +[DebuggerDisplay("{DebugString,nq}")] public class MemoryQueue { public MemoryQueue(int capacity = 256) { - _mem = new byte[capacity]; - _r = 0; - _w = 0; + _buf = new byte[capacity]; + } + public int Capacity => _buf.Length; + public int Length => _len; + public int EmptySpace => _buf.Length - _len; + public bool IsContinuous => _buf.Length >= _pos + _len; + + public int Free(int count) + { + count = int.Min(count, _len); + _pos += count; + if (_buf.Length <= _pos) + _pos -= _buf.Length; + _len -= count; + if (0 == _len)// reduce the number of copy calls + _pos = 0; + return count; + } + public void OverflowWrite(Span src) + { + if (src.Length >= _buf.Length) + src = src.Slice(src.Length - _buf.Length, _buf.Length); + if (EmptySpace < src.Length) + Free(src.Length - EmptySpace); + Write(src); } - public uint Length => _w - _r; - public uint EmptySpace => (uint)_mem.Length - (_w - _r); public void Write(Span src) { if (0 >= src.Length) return; - var wempty = (uint)_mem.Length - _w; - if (src.Length > _r + wempty) - throw new OverflowException($"EmptySpace {EmptySpace}, data length is {src.Length}"); - if (src.Length > wempty) - MemMove(); - src.CopyTo(_mem.AsSpan((int)_w)); - _w += (uint)src.Length; - } - public void Read(Span dst) - { - if (dst.Length > _w - _r) - throw new ArgumentOutOfRangeException($"{dst.Length} is greater then data {_w - _r}"); - _mem.AsSpan((int)_r, dst.Length).CopyTo(dst); - _r += (uint)dst.Length; + if (src.Length > EmptySpace) + throw new OverflowException($"EmptySpace {EmptySpace}, src length is {src.Length}"); + int tail = _pos + _len; + if (_buf.Length <= tail) + tail -= _buf.Length; + int wLen = int.Min(src.Length, _buf.Length - tail); + src.Slice(0, wLen).CopyTo(_buf.AsSpan(tail)); + _len += wLen; + if (src.Length > wLen) + { + src.Slice(wLen).CopyTo(_buf.AsSpan(0)); + _len += src.Length - wLen; + } } - public void Write(byte[] src, int soffset = 0, int slen = 0) + public int Read(Span dst) { - if (0 >= slen) - slen = src.Length - soffset; - Write(src.AsSpan(soffset, slen)); + int totalRead = int.Min(dst.Length, _len); + int rLen = int.Min(totalRead, _buf.Length - _pos); + _buf.AsSpan(_pos, rLen).CopyTo(dst); + if (totalRead > rLen) + _buf.AsSpan(0, totalRead - rLen).CopyTo(dst.Slice(rLen)); + _pos += totalRead; + if (_buf.Length <= _pos) + _pos -= _buf.Length; + _len -= totalRead; + if (0 == _len)// reduce the number of copy calls + _pos = 0; + return totalRead; } - public void Read(byte[] dst, int offset = 0, int len = 0) + public int TouchRead(Span dst) { - if (0 >= len) - len = dst.Length - offset; - Read(dst.AsSpan(offset, len)); + // use regular read but restore pointers + int pos = _pos; + int len = _len; + int ret = Read(dst); + _pos = pos; + _len = len; + return ret; } - - readonly byte[] _mem; - uint _r; - uint _w; - void MemMove() +#if DEBUG + public string DebugString { - if (0 < _r) + get { - _w -= _r;//dataLen - Array.Copy(_mem, _r, _mem, 0, _w); - _r = 0; + StringBuilder sb = new(Length * 4); + sb.Append('['); + int llen, rlen; + rlen = _pos + _len; + if (rlen > _buf.Length) // !IsContinuous + { + llen = rlen - _buf.Length; + rlen = _buf.Length; + } + else + llen = -1; + for (int i = 0; i < _buf.Length; i++) + { + sb.Append(i == _pos ? '+' : ' '); + if (i < llen || (i >= _pos && i < rlen)) + sb.AppendFormat("{0:x2} ", _buf[i]); + else + sb.Append("__ "); + } + sb.Append(']'); + return sb.ToString(); + //TouchRead(dst); + //return $"[{BitConverter.ToString(dst, 0, Length)}]"; } } +#endif + + readonly byte[] _buf; + int _pos = 0; + int _len = 0; } From 50576150df26e183351ebb835680bfbf58780d6c Mon Sep 17 00:00:00 2001 From: "SAVPC\\Sav" Date: Thu, 13 Nov 2025 15:48:36 +0700 Subject: [PATCH 16/39] introduce NetDirectByteBuffer - Implemented DirectByteBuffer with a single native buffer for NET and Java. No memory copying required --- .../Extensions/BufferExtensions.cs | 10 ++++ .../Extensions/UsbRequestExtension.cs | 11 ++-- .../Helper/NetDirectByteBuffer.cs | 50 +++++++++++++++++++ 3 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 UsbSerialForAndroid.Net/Helper/NetDirectByteBuffer.cs diff --git a/UsbSerialForAndroid.Net/Extensions/BufferExtensions.cs b/UsbSerialForAndroid.Net/Extensions/BufferExtensions.cs index 6401939..5aa856a 100644 --- a/UsbSerialForAndroid.Net/Extensions/BufferExtensions.cs +++ b/UsbSerialForAndroid.Net/Extensions/BufferExtensions.cs @@ -7,6 +7,8 @@ using Android.Runtime; using Java.Nio; +using System; +using System.Runtime.InteropServices; namespace UsbSerialForAndroid.Net.Extensions { @@ -49,5 +51,13 @@ static BufferExtensions() JNIEnv.DeleteLocalRef(resultHandle); return result; } + public static void CopyTo(this ByteBuffer buffer, int srcOffset, byte[] dstArr, int offset, int count) + { + ArgumentOutOfRangeException.ThrowIfGreaterThan(count, buffer.Capacity() - srcOffset); + nint srcPtr = buffer.GetDirectBufferAddress(); + ArgumentOutOfRangeException.ThrowIfZero(srcPtr); + srcPtr += srcOffset; + Marshal.Copy(srcPtr, dstArr, offset, count); + } } } \ No newline at end of file diff --git a/UsbSerialForAndroid.Net/Extensions/UsbRequestExtension.cs b/UsbSerialForAndroid.Net/Extensions/UsbRequestExtension.cs index 2123c10..5c63e77 100644 --- a/UsbSerialForAndroid.Net/Extensions/UsbRequestExtension.cs +++ b/UsbSerialForAndroid.Net/Extensions/UsbRequestExtension.cs @@ -1,15 +1,20 @@ using Android.Hardware.Usb; using Java.Nio; using System; +using System.IO; namespace UsbSerialForAndroid.Net.Extensions; public static class UsbRequestExtension { - public static bool QueueReq(this UsbRequest req, ByteBuffer buffer) + public static void QueueReq(this UsbRequest req, ByteBuffer buffer) { + bool isOk; if (OperatingSystem.IsAndroidVersionAtLeast(26)) - return req.Queue(buffer); - return req.Queue(buffer, buffer.Capacity()); + isOk = req.Queue(buffer); + else + isOk = req.Queue(buffer, buffer.Capacity()); + if (!isOk) + throw new IOException("Error queueing request."); } } diff --git a/UsbSerialForAndroid.Net/Helper/NetDirectByteBuffer.cs b/UsbSerialForAndroid.Net/Helper/NetDirectByteBuffer.cs new file mode 100644 index 0000000..6012311 --- /dev/null +++ b/UsbSerialForAndroid.Net/Helper/NetDirectByteBuffer.cs @@ -0,0 +1,50 @@ +using Android.Runtime; +using Java.Nio; +using System; +using System.Runtime.InteropServices; +using System.Threading; + +namespace UsbSerialForAndroid.Net.Helper; + +public class NetDirectByteBuffer : IDisposable +{ + public NetDirectByteBuffer(int capacity = 512) + { + NetBuffer = new byte[capacity]; + _handle = GCHandle.Alloc(NetBuffer, GCHandleType.Pinned); + IntPtr ndb = JNIEnv.NewDirectByteBuffer(_handle.AddrOfPinnedObject(), capacity); + ByteBuffer? jdb = Java.Lang.Object.GetObject(ndb, JniHandleOwnership.TransferLocalRef); + ArgumentNullException.ThrowIfNull(jdb); + JavaBuffer = jdb; + } + public readonly byte[] NetBuffer; + public readonly ByteBuffer JavaBuffer; + GCHandle _handle; + public static explicit operator ByteBuffer(NetDirectByteBuffer nb) => nb.JavaBuffer; + public static explicit operator byte[](NetDirectByteBuffer nb) => nb.NetBuffer; + + public Java.Nio.Buffer? Rewind() => JavaBuffer.Rewind(); + public int Position + { + get => JavaBuffer.Position(); + set => JavaBuffer.Position(value); + } + public bool IsDisposed => 0 != _isDisposed; + int _isDisposed = 0; + protected virtual void Dispose(bool disposing) + { + if (0 != Interlocked.Exchange(ref _isDisposed, 1)) + return; + if (disposing) + { + JavaBuffer.Dispose(); + _handle.Free(); + } + } + ~NetDirectByteBuffer() => Dispose(disposing: false); + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } +} From 1e3ad2d93aed380588828969c9a2a6dd6905b77d Mon Sep 17 00:00:00 2001 From: "SAVPC\\Sav" Date: Thu, 13 Nov 2025 15:51:02 +0700 Subject: [PATCH 17/39] FEAT - replace FIFO buffer with ConcurrentQueue --- .../Drivers/Base/UsbDriverBase.cs | 287 ++++++++++++++---- UsbSerialForAndroid.Net/Helper/MemoryQueue.cs | 166 ---------- 2 files changed, 228 insertions(+), 225 deletions(-) delete mode 100644 UsbSerialForAndroid.Net/Helper/MemoryQueue.cs diff --git a/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs b/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs index 7d1ef19..168e1fc 100644 --- a/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs +++ b/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs @@ -4,7 +4,8 @@ using Java.Nio; using System; using System.Buffers; -using System.IO; +using System.Collections.Concurrent; +using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using UsbSerialForAndroid.Net.Enums; @@ -126,14 +127,34 @@ private static UsbManager GetUsbManager() /// /// close the usb device /// - public virtual void Close() + public virtual async void Close() { + Debug.WriteLine($"[USBDRIVER]: Close started"); + _readerExit?.Cancel(); + + if (null != _dispatchTask) + await _dispatchTask; + if (null != _readTask) + await _readTask; + if (null != _filterTask) + await _filterTask; + + _filterTask = null; + _dispatchTask = null; + _readTask = null; + + _readerExit?.Dispose(); _readerExit = null; + var queue = Interlocked.Exchange(ref _emptyBuffers, new()); + foreach (var item in queue) + item.Dispose(); + queue = Interlocked.Exchange(ref _fillBuffers, new()); + foreach (var item in queue) + item.Dispose(); + _usbReadRequest?.Close(); _usbReadRequest = null; _usbWriteRequest?.Close(); _usbWriteRequest = null; - _readBuf?.Dispose(); _readBuf = null; - _rsBuf = null; UsbEndpointRead?.Dispose(); UsbEndpointRead = null; UsbEndpointWrite?.Dispose(); UsbEndpointWrite = null; @@ -234,91 +255,239 @@ public bool TestConnection() } } + public FilterDataFn? FilterData; + public delegate int FilterDataFn(Span src, Span dst); + + protected UsbRequest? _usbWriteRequest; + protected UsbRequest? _usbReadRequest; + protected TaskCompletionSource? _tcsRead; + protected TaskCompletionSource? _tcsWrite; + + protected TaskCompletionSource? _tcsFilterBufers; + protected TaskCompletionSource? _tcsFillBuf; + protected CancellationTokenSource? _readerExit; + protected ConcurrentQueue _emptyBuffers = new(); + protected ConcurrentQueue _fillBuffers = new(); + protected ConcurrentQueue _filterBufers = new(); + + protected Task? _dispatchTask; + protected Task? _readTask; + protected Task? _filterTask; protected void InitAsyncBuffers() { - _rsBuf = new(DefaultBufferLength); + Debug.WriteLine($"[USBDRIVER]: Init"); + ArgumentNullException.ThrowIfNull(UsbEndpointRead); _usbReadRequest = new(); _usbReadRequest.Initialize(UsbDeviceConnection, UsbEndpointRead); - _readBuf = ByteBuffer.Allocate(DefaultBufferLength); + _usbWriteRequest = new(); _usbWriteRequest.Initialize(UsbDeviceConnection, UsbEndpointWrite); + + int readBufLen = 512; + int readBufCount = DefaultBufferLength / readBufLen; + for (int i = 0; i < readBufCount; i++) + _emptyBuffers.Enqueue(new NetDirectByteBuffer(readBufLen)); + + _readerExit = new(); + _filterTask = Task.Run(() => ProcessFilterAsync(_readerExit.Token)); + _readTask = Task.Run(() => InternalUsbReadAsync(_readerExit.Token)); + _dispatchTask = Task.Run(() => UsbDispatchAsync(_readerExit.Token)); } - // we need work around with _readBuf - // https://github.com/alex3696/UsbSerialForAndroid/blob/main/UsbSerialForAndroid/driver/CdcAcmSerialDriver.cs 300 - protected UsbRequest? _usbReadRequest; - protected ByteBuffer? _readBuf; - protected MemoryQueue? _rsBuf; - public FilterDataFn? FilterData; - public delegate Span FilterDataFn(Span src); + protected virtual async Task ProcessFilterAsync(CancellationToken ct = default) + { + ArgumentNullException.ThrowIfNull(_fillBuffers); + ArgumentNullException.ThrowIfNull(_filterBufers); + ArgumentNullException.ThrowIfNull(_emptyBuffers); + ArgumentNullException.ThrowIfNull(FilterData); + NetDirectByteBuffer? buf = null; + try + { + while (!ct.IsCancellationRequested) + { + if (_filterBufers.TryDequeue(out buf)) + { + var data = buf.NetBuffer.AsSpan(0, buf.Position); + buf.Position = FilterData(data, data); + if (0 < buf.Position) + { + _fillBuffers.Enqueue(buf); + _tcsFillBuf?.TrySetResult(); + } + else + _emptyBuffers.Enqueue(buf); + } + else + { + TaskCompletionSource waitData = new(); + using (ct.Register(() => waitData.TrySetCanceled(ct))) + { + var task = waitData.Task; + Interlocked.Exchange(ref _tcsFilterBufers, waitData); + await task.WaitAsync(ct); + } + } + } + } + catch (Exception ex) + { + Debug.WriteLine(ex); + } + finally + { + if (null != buf) + _emptyBuffers.Enqueue(buf); + } + Debug.WriteLine($"[USBDRIVER]: exit ProcessFilter"); + } + protected virtual async Task UsbDispatchAsync(CancellationToken ct = default) + { + ArgumentNullException.ThrowIfNull(UsbDeviceConnection); + ArgumentNullException.ThrowIfNull(_usbReadRequest); + ArgumentNullException.ThrowIfNull(_usbWriteRequest); + try + { + while (!ct.IsCancellationRequested) + { + UsbRequest? response = await UsbDeviceConnection.RequestWaitAsync(); + ArgumentNullException.ThrowIfNull(response); - public virtual async Task ReadAsync(byte[] dstBuf, int offset, int count, CancellationToken ct = default) + if (ReferenceEquals(response, _usbReadRequest)) + { + //Debug.WriteLine($"[USBDRIVER]: _tcsRead"); + _tcsRead?.TrySetResult(response); + } + if (ReferenceEquals(response, _usbWriteRequest)) + { + //Debug.WriteLine($"[USBDRIVER]: _tcsWrite"); + _tcsWrite?.TrySetResult(response); + } + } + } + catch (Exception ex) + { + Debug.WriteLine(ex); + } + Debug.WriteLine($"[USBDRIVER]: exit UsbDispatchAsync"); + } + protected virtual async Task InternalUsbReadAsync(CancellationToken ct = default) { - ArgumentNullException.ThrowIfNull(_rsBuf); - ArgumentNullException.ThrowIfNull(_readBuf); + ArgumentNullException.ThrowIfNull(_emptyBuffers); + ArgumentNullException.ThrowIfNull(_fillBuffers); ArgumentNullException.ThrowIfNull(_usbReadRequest); ArgumentNullException.ThrowIfNull(UsbDeviceConnection); - // if buffered data available, read it first - if (0 < _rsBuf.Length) + var dataQueue = (null == FilterData) ? _fillBuffers : _filterBufers; + NetDirectByteBuffer? buf = null; + try { - if (_rsBuf.Length > count) + while (!_emptyBuffers.TryDequeue(out buf)) + ct.ThrowIfCancellationRequested(); + while (!ct.IsCancellationRequested) { - _rsBuf.Read(dstBuf.AsSpan(offset, count)); - return count; + buf.Rewind(); + using var crReg = ct.Register(() => _usbReadRequest?.Cancel()); + //Debug.WriteLine($"[USBDRIVER]: read requested"); + _tcsRead = new(); + _usbReadRequest.QueueReq((ByteBuffer)buf); + UsbRequest? response = await _tcsRead.Task.WaitAsync(ct); + //Debug.WriteLine($"[USBDRIVER]: read received"); + ct.ThrowIfCancellationRequested(); + if (0 < buf.Position) + { + while (!_emptyBuffers.IsEmpty) + { + ct.ThrowIfCancellationRequested(); + if (_emptyBuffers.TryDequeue(out var emptyBuf)) + { + dataQueue.Enqueue(buf); + var dataNotify = (null == FilterData) ? _tcsFillBuf : _tcsFilterBufers; + dataNotify?.TrySetResult(); + buf = emptyBuf; + break; + } + } + } } - else + } + catch (OperationCanceledException) + { + _usbReadRequest.Cancel(); + } + catch (Exception ex) + { + Debug.WriteLine($"[USBDRIVER]: read Error {ex}"); + } + finally + { + if (null != buf) + _emptyBuffers.Enqueue(buf); + } + Debug.WriteLine($"[USBDRIVER]: read exit"); + } + public virtual async Task ReadAsync(byte[] dstBuf, int offset, int count, CancellationToken ct = default) + { + int readed = 0; + NetDirectByteBuffer? buf = default; + + //try peek already buffered data, + //if there is none, do async wait and try peek again + while (!_fillBuffers.TryPeek(out buf)) + { + ct.ThrowIfCancellationRequested(); + TaskCompletionSource waitData = new(); + using (ct.Register(() => waitData.TrySetCanceled(ct))) { - var ret = (int)_rsBuf.Length; - _rsBuf.Read(dstBuf.AsSpan(offset, ret)); - return ret; + var task = waitData.Task; + Interlocked.Exchange(ref _tcsFillBuf, waitData); + await task.WaitAsync(ct); } } - // try read from port - var buf = _readBuf; - int nread = 0; - while (0 == nread) + // get buffered data, not more than the requested size + while (null != buf) { - ct.ThrowIfCancellationRequested(); - buf.Rewind(); - if (!_usbReadRequest.QueueReq(buf)) - throw new IOException("Error queueing request."); - using var crReg = ct.Register(() => _usbReadRequest?.Cancel()); - UsbRequest? response = await UsbDeviceConnection.RequestWaitAsync(_usbReadRequest, ControlTimeout); - ct.ThrowIfCancellationRequested(); - if (!ReferenceEquals(response, _usbReadRequest)) - throw new IOException("Wrong response"); - nread = buf.Position(); - if (0 < nread) + if (count < buf.Position) { - var data = buf.ToByteArray().AsSpan(0, nread); - if (null != FilterData) - { - data = FilterData(data); - nread = data.Length; - } - if (nread > count) + var qty = buf.Position; + var data = (byte[])buf; + data.AsSpan(0, count).CopyTo(dstBuf.AsSpan(offset)); + data.AsSpan(count, qty).CopyTo(data.AsSpan()); + buf.Position = qty - count; + break; + } + var r = buf.Position; + ((byte[])buf).AsSpan(0, r).CopyTo(dstBuf.AsSpan(offset)); + offset += r; + count -= r; + readed += r; + try + { + while (!_fillBuffers.TryDequeue(out buf)) + ct.ThrowIfCancellationRequested(); + } + finally + { + if (null != buf) { - _rsBuf.Write(data.Slice(count));//copy the rest to the buffer - nread = count; + _emptyBuffers.Enqueue(buf); + buf = null; } - data.Slice(0, nread).CopyTo(dstBuf.AsSpan(offset, count)); } - else - nread = 0; + while (false == _fillBuffers.IsEmpty && false == _fillBuffers.TryPeek(out buf)) + ct.ThrowIfCancellationRequested(); } - return nread; + return readed; + } - protected UsbRequest? _usbWriteRequest; public virtual async Task WriteAsync(byte[] wbuf, int offset, int count, CancellationToken ct = default) { ArgumentNullException.ThrowIfNull(_usbWriteRequest); ArgumentNullException.ThrowIfNull(UsbDeviceConnection); using var buf = ByteBuffer.Wrap(wbuf, offset, count); - if (!_usbWriteRequest.QueueReq(buf)) - throw new IOException("Error queueing request."); using var crReg = ct.Register(() => _usbWriteRequest?.Cancel()); - UsbRequest? response = await UsbDeviceConnection.RequestWaitAsync(_usbWriteRequest, ControlTimeout); - if (!ReferenceEquals(response, _usbWriteRequest)) - throw new IOException("Wrong response"); + _tcsWrite = new(); + //Debug.WriteLine($"[USBDRIVER]: write requested"); + _usbWriteRequest.QueueReq(buf); + UsbRequest response = await _tcsWrite.Task.WaitAsync(ct); + ct.ThrowIfCancellationRequested(); int nwrite = buf.Position(); return nwrite; } diff --git a/UsbSerialForAndroid.Net/Helper/MemoryQueue.cs b/UsbSerialForAndroid.Net/Helper/MemoryQueue.cs deleted file mode 100644 index 0fde71a..0000000 --- a/UsbSerialForAndroid.Net/Helper/MemoryQueue.cs +++ /dev/null @@ -1,166 +0,0 @@ -using System; -using System.Diagnostics; -using System.Text; - -namespace UsbSerialForAndroid.Net.Helper; - -/* -begin - B -length - C - -// filled 0 Continuous -0 1 2 3 4 5 6 7 8 9 -. . . . . . . . . . - B - C=0 - -// filled 9 Continuous -0 1 2 3 4 5 6 7 8 9 -* * * * * * * * * . -B -C=9 - -// filled 10 Continuous -0 1 2 3 4 5 6 7 8 9 -* * * * * * * * * * -B -C=10 - -// filled 9 -0 1 2 3 4 5 6 7 8 9 -* * . * * * * * * * - B - C=9 - -// filled 10 Continuous -0 1 2 3 4 5 6 7 8 9 -* * * * * * * * * * - B - C=10 - -// filled 4 -0 1 2 3 4 5 6 7 8 9 -* * . . . . . . * * - B - C=4 - -// filled 4 Continuous -0 1 2 3 4 5 6 7 8 9 -. . * * * * . . . . - B - C = 4 -*/ - -/// -/// simple fifo Queue -/// -[DebuggerDisplay("{DebugString,nq}")] -public class MemoryQueue -{ - public MemoryQueue(int capacity = 256) - { - _buf = new byte[capacity]; - } - public int Capacity => _buf.Length; - public int Length => _len; - public int EmptySpace => _buf.Length - _len; - public bool IsContinuous => _buf.Length >= _pos + _len; - - public int Free(int count) - { - count = int.Min(count, _len); - _pos += count; - if (_buf.Length <= _pos) - _pos -= _buf.Length; - _len -= count; - if (0 == _len)// reduce the number of copy calls - _pos = 0; - return count; - } - public void OverflowWrite(Span src) - { - if (src.Length >= _buf.Length) - src = src.Slice(src.Length - _buf.Length, _buf.Length); - if (EmptySpace < src.Length) - Free(src.Length - EmptySpace); - Write(src); - } - public void Write(Span src) - { - if (0 >= src.Length) - return; - if (src.Length > EmptySpace) - throw new OverflowException($"EmptySpace {EmptySpace}, src length is {src.Length}"); - int tail = _pos + _len; - if (_buf.Length <= tail) - tail -= _buf.Length; - int wLen = int.Min(src.Length, _buf.Length - tail); - src.Slice(0, wLen).CopyTo(_buf.AsSpan(tail)); - _len += wLen; - if (src.Length > wLen) - { - src.Slice(wLen).CopyTo(_buf.AsSpan(0)); - _len += src.Length - wLen; - } - } - public int Read(Span dst) - { - int totalRead = int.Min(dst.Length, _len); - int rLen = int.Min(totalRead, _buf.Length - _pos); - _buf.AsSpan(_pos, rLen).CopyTo(dst); - if (totalRead > rLen) - _buf.AsSpan(0, totalRead - rLen).CopyTo(dst.Slice(rLen)); - _pos += totalRead; - if (_buf.Length <= _pos) - _pos -= _buf.Length; - _len -= totalRead; - if (0 == _len)// reduce the number of copy calls - _pos = 0; - return totalRead; - } - public int TouchRead(Span dst) - { - // use regular read but restore pointers - int pos = _pos; - int len = _len; - int ret = Read(dst); - _pos = pos; - _len = len; - return ret; - } -#if DEBUG - public string DebugString - { - get - { - StringBuilder sb = new(Length * 4); - sb.Append('['); - int llen, rlen; - rlen = _pos + _len; - if (rlen > _buf.Length) // !IsContinuous - { - llen = rlen - _buf.Length; - rlen = _buf.Length; - } - else - llen = -1; - for (int i = 0; i < _buf.Length; i++) - { - sb.Append(i == _pos ? '+' : ' '); - if (i < llen || (i >= _pos && i < rlen)) - sb.AppendFormat("{0:x2} ", _buf[i]); - else - sb.Append("__ "); - } - sb.Append(']'); - return sb.ToString(); - //TouchRead(dst); - //return $"[{BitConverter.ToString(dst, 0, Length)}]"; - } - } -#endif - - readonly byte[] _buf; - int _pos = 0; - int _len = 0; -} From a158cb7431360f39f543349ce32c1d96ea9aac55 Mon Sep 17 00:00:00 2001 From: "SAVPC\\Sav" Date: Thu, 13 Nov 2025 16:32:28 +0700 Subject: [PATCH 18/39] Revert "Merge remote-tracking branch 'origin/MyMaster' into MyMaster" This reverts commit a6994d01ae71dc07eb50cd4e8b05732ff5ff2cb1, reversing changes made to f682814c9590d77e9bbebe90056b21aca9addf52. --- .../Drivers/SiliconLabsSerialDriver.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/UsbSerialForAndroid.Net/Drivers/SiliconLabsSerialDriver.cs b/UsbSerialForAndroid.Net/Drivers/SiliconLabsSerialDriver.cs index 7b3c81a..5f06a5e 100644 --- a/UsbSerialForAndroid.Net/Drivers/SiliconLabsSerialDriver.cs +++ b/UsbSerialForAndroid.Net/Drivers/SiliconLabsSerialDriver.cs @@ -18,6 +18,7 @@ public class SiliconLabsSerialDriver : UsbDriverBase public const int SilabserSetLineCtlRequestCode = 0x03; public const int SilabserSetBaudRate = 0x1E; + public const int UartDisable = 0x0000; public const int UartEnable = 0x0001; public const int BaudRateGenFreq = 0x384000; public const int McrAll = 0x0003; @@ -70,6 +71,15 @@ public override void Open(int baudRate = DefaultBaudRate, byte dataBits = Defaul InitAsyncBuffers(); } /// + /// close port + /// + public override void Close() + { + PurgeHwBuffers(true, true); + SetConfigSingle(SilabserIcfEnableRquestCode, UartDisable); + base.Close(); + } + /// /// Set the UART enabled /// /// @@ -202,5 +212,21 @@ public override void SetRtsEnabled(bool value) if (result != 0) throw new ControlTransferException("Set Rts failed", result, RequestTypeHostToDevice, SilabserSetMhsRequestCode, inValue, index, null, 0, ControlTimeout); } + + + public const int FLUSH_READ_CODE = 0x0a; + public const int FLUSH_WRITE_CODE = 0x05; + public const int SILABSER_FLUSH_REQUEST_CODE = 0x12; + // note: only working on some devices, on other devices ignored w/o error + public void PurgeHwBuffers(bool purgeWriteBuffers, bool purgeReadBuffers) + { + int value = (purgeReadBuffers ? FLUSH_READ_CODE : 0) + | (purgeWriteBuffers ? FLUSH_WRITE_CODE : 0); + + if (value != 0) + { + SetConfigSingle(SILABSER_FLUSH_REQUEST_CODE, value); + } + } } } \ No newline at end of file From 2156b0d54785812493e69ca52a5be3353f50c1cf Mon Sep 17 00:00:00 2001 From: "SAVPC\\Sav" Date: Thu, 13 Nov 2025 17:35:51 +0700 Subject: [PATCH 19/39] FIX - filter task start --- UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs b/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs index 168e1fc..aefcedb 100644 --- a/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs +++ b/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs @@ -289,19 +289,20 @@ protected void InitAsyncBuffers() _emptyBuffers.Enqueue(new NetDirectByteBuffer(readBufLen)); _readerExit = new(); - _filterTask = Task.Run(() => ProcessFilterAsync(_readerExit.Token)); + if (null != FilterData) + _filterTask = Task.Run(() => ProcessFilterAsync(_readerExit.Token)); _readTask = Task.Run(() => InternalUsbReadAsync(_readerExit.Token)); _dispatchTask = Task.Run(() => UsbDispatchAsync(_readerExit.Token)); } protected virtual async Task ProcessFilterAsync(CancellationToken ct = default) { - ArgumentNullException.ThrowIfNull(_fillBuffers); - ArgumentNullException.ThrowIfNull(_filterBufers); - ArgumentNullException.ThrowIfNull(_emptyBuffers); - ArgumentNullException.ThrowIfNull(FilterData); NetDirectByteBuffer? buf = null; try { + ArgumentNullException.ThrowIfNull(_fillBuffers); + ArgumentNullException.ThrowIfNull(_filterBufers); + ArgumentNullException.ThrowIfNull(_emptyBuffers); + ArgumentNullException.ThrowIfNull(FilterData); while (!ct.IsCancellationRequested) { if (_filterBufers.TryDequeue(out buf)) From 7c97cd04f370fe992cc83278d931f14b35f58971 Mon Sep 17 00:00:00 2001 From: "HOMEPC\\alex3696" Date: Thu, 13 Nov 2025 23:44:28 +0700 Subject: [PATCH 20/39] REFR - update Close and errors handle --- .../Drivers/Base/UsbDriverBase.cs | 115 ++++++++++-------- 1 file changed, 67 insertions(+), 48 deletions(-) diff --git a/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs b/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs index aefcedb..a16b4d0 100644 --- a/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs +++ b/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs @@ -5,6 +5,7 @@ using System; using System.Buffers; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; @@ -130,32 +131,7 @@ private static UsbManager GetUsbManager() public virtual async void Close() { Debug.WriteLine($"[USBDRIVER]: Close started"); - _readerExit?.Cancel(); - - if (null != _dispatchTask) - await _dispatchTask; - if (null != _readTask) - await _readTask; - if (null != _filterTask) - await _filterTask; - - _filterTask = null; - _dispatchTask = null; - _readTask = null; - - _readerExit?.Dispose(); _readerExit = null; - var queue = Interlocked.Exchange(ref _emptyBuffers, new()); - foreach (var item in queue) - item.Dispose(); - queue = Interlocked.Exchange(ref _fillBuffers, new()); - foreach (var item in queue) - item.Dispose(); - - _usbReadRequest?.Close(); - _usbReadRequest = null; - _usbWriteRequest?.Close(); - _usbWriteRequest = null; - + await DeinitBuffersAsync(); UsbEndpointRead?.Dispose(); UsbEndpointRead = null; UsbEndpointWrite?.Dispose(); UsbEndpointWrite = null; UsbDeviceConnection?.ReleaseInterface(UsbInterface); @@ -266,6 +242,7 @@ public bool TestConnection() protected TaskCompletionSource? _tcsFilterBufers; protected TaskCompletionSource? _tcsFillBuf; protected CancellationTokenSource? _readerExit; + protected List _allBuffers = []; protected ConcurrentQueue _emptyBuffers = new(); protected ConcurrentQueue _fillBuffers = new(); protected ConcurrentQueue _filterBufers = new(); @@ -276,32 +253,66 @@ public bool TestConnection() protected void InitAsyncBuffers() { Debug.WriteLine($"[USBDRIVER]: Init"); + ArgumentNullException.ThrowIfNull(UsbDeviceConnection); + ArgumentNullException.ThrowIfNull(UsbEndpointWrite); ArgumentNullException.ThrowIfNull(UsbEndpointRead); _usbReadRequest = new(); _usbReadRequest.Initialize(UsbDeviceConnection, UsbEndpointRead); - _usbWriteRequest = new(); _usbWriteRequest.Initialize(UsbDeviceConnection, UsbEndpointWrite); - int readBufLen = 512; int readBufCount = DefaultBufferLength / readBufLen; for (int i = 0; i < readBufCount; i++) - _emptyBuffers.Enqueue(new NetDirectByteBuffer(readBufLen)); - + { + var newBuf = new NetDirectByteBuffer(readBufLen); + _allBuffers.Add(newBuf); + _emptyBuffers.Enqueue(newBuf); + } _readerExit = new(); if (null != FilterData) _filterTask = Task.Run(() => ProcessFilterAsync(_readerExit.Token)); _readTask = Task.Run(() => InternalUsbReadAsync(_readerExit.Token)); _dispatchTask = Task.Run(() => UsbDispatchAsync(_readerExit.Token)); } + protected async Task DeinitBuffersAsync() + { + try + { + // cancel all tasks + _readerExit?.Cancel(); + Interlocked.Exchange(ref _readerExit, null)?.Dispose(); + // await exit all tasks + if (null != _dispatchTask) + await _dispatchTask; + if (null != _readTask) + await _readTask; + if (null != _filterTask) + await _filterTask; + // clear all tasks + _filterTask = null; + _dispatchTask = null; + _readTask = null; + // clear all buffers + _emptyBuffers.Clear(); + _fillBuffers.Clear(); + _filterBufers.Clear(); + foreach (var item in _allBuffers) + item.Dispose(); + _allBuffers.Clear(); + // clear requests + Interlocked.Exchange(ref _usbReadRequest, null)?.Dispose(); + Interlocked.Exchange(ref _usbWriteRequest, null)?.Dispose(); + } + catch (Exception ex) + { + Debug.WriteLine(ex); + } + } protected virtual async Task ProcessFilterAsync(CancellationToken ct = default) { NetDirectByteBuffer? buf = null; try { - ArgumentNullException.ThrowIfNull(_fillBuffers); - ArgumentNullException.ThrowIfNull(_filterBufers); - ArgumentNullException.ThrowIfNull(_emptyBuffers); ArgumentNullException.ThrowIfNull(FilterData); while (!ct.IsCancellationRequested) { @@ -325,6 +336,7 @@ protected virtual async Task ProcessFilterAsync(CancellationToken ct = default) var task = waitData.Task; Interlocked.Exchange(ref _tcsFilterBufers, waitData); await task.WaitAsync(ct); + Interlocked.Exchange(ref _tcsFilterBufers, null); } } } @@ -342,25 +354,29 @@ protected virtual async Task ProcessFilterAsync(CancellationToken ct = default) } protected virtual async Task UsbDispatchAsync(CancellationToken ct = default) { - ArgumentNullException.ThrowIfNull(UsbDeviceConnection); - ArgumentNullException.ThrowIfNull(_usbReadRequest); - ArgumentNullException.ThrowIfNull(_usbWriteRequest); try { + ArgumentNullException.ThrowIfNull(UsbDeviceConnection); while (!ct.IsCancellationRequested) { UsbRequest? response = await UsbDeviceConnection.RequestWaitAsync(); - ArgumentNullException.ThrowIfNull(response); - + // ArgumentNullException.ThrowIfNull(response); + if (null == response) + { + Debug.WriteLine($"[USBDRIVER]: WARN response is null"); + continue; + } if (ReferenceEquals(response, _usbReadRequest)) { //Debug.WriteLine($"[USBDRIVER]: _tcsRead"); _tcsRead?.TrySetResult(response); + continue; } if (ReferenceEquals(response, _usbWriteRequest)) { //Debug.WriteLine($"[USBDRIVER]: _tcsWrite"); _tcsWrite?.TrySetResult(response); + continue; } } } @@ -372,14 +388,13 @@ protected virtual async Task UsbDispatchAsync(CancellationToken ct = default) } protected virtual async Task InternalUsbReadAsync(CancellationToken ct = default) { - ArgumentNullException.ThrowIfNull(_emptyBuffers); - ArgumentNullException.ThrowIfNull(_fillBuffers); - ArgumentNullException.ThrowIfNull(_usbReadRequest); - ArgumentNullException.ThrowIfNull(UsbDeviceConnection); - var dataQueue = (null == FilterData) ? _fillBuffers : _filterBufers; + ConcurrentQueue dataQueue; NetDirectByteBuffer? buf = null; + NetDirectByteBuffer? emptyBuf = null; try { + ArgumentNullException.ThrowIfNull(_usbReadRequest); + dataQueue = (null == FilterData) ? _fillBuffers : _filterBufers; while (!_emptyBuffers.TryDequeue(out buf)) ct.ThrowIfCancellationRequested(); while (!ct.IsCancellationRequested) @@ -387,9 +402,10 @@ protected virtual async Task InternalUsbReadAsync(CancellationToken ct = default buf.Rewind(); using var crReg = ct.Register(() => _usbReadRequest?.Cancel()); //Debug.WriteLine($"[USBDRIVER]: read requested"); - _tcsRead = new(); + Interlocked.Exchange(ref _tcsRead, new()); _usbReadRequest.QueueReq((ByteBuffer)buf); UsbRequest? response = await _tcsRead.Task.WaitAsync(ct); + Interlocked.Exchange(ref _tcsRead, null); //Debug.WriteLine($"[USBDRIVER]: read received"); ct.ThrowIfCancellationRequested(); if (0 < buf.Position) @@ -397,7 +413,7 @@ protected virtual async Task InternalUsbReadAsync(CancellationToken ct = default while (!_emptyBuffers.IsEmpty) { ct.ThrowIfCancellationRequested(); - if (_emptyBuffers.TryDequeue(out var emptyBuf)) + if (_emptyBuffers.TryDequeue(out emptyBuf)) { dataQueue.Enqueue(buf); var dataNotify = (null == FilterData) ? _tcsFillBuf : _tcsFilterBufers; @@ -411,7 +427,7 @@ protected virtual async Task InternalUsbReadAsync(CancellationToken ct = default } catch (OperationCanceledException) { - _usbReadRequest.Cancel(); + _usbReadRequest?.Cancel(); } catch (Exception ex) { @@ -421,6 +437,8 @@ protected virtual async Task InternalUsbReadAsync(CancellationToken ct = default { if (null != buf) _emptyBuffers.Enqueue(buf); + if (null != emptyBuf) + _emptyBuffers.Enqueue(emptyBuf); } Debug.WriteLine($"[USBDRIVER]: read exit"); } @@ -440,6 +458,7 @@ public virtual async Task ReadAsync(byte[] dstBuf, int offset, int count, C var task = waitData.Task; Interlocked.Exchange(ref _tcsFillBuf, waitData); await task.WaitAsync(ct); + Interlocked.Exchange(ref _tcsFillBuf, null); } } // get buffered data, not more than the requested size @@ -481,13 +500,13 @@ public virtual async Task ReadAsync(byte[] dstBuf, int offset, int count, C public virtual async Task WriteAsync(byte[] wbuf, int offset, int count, CancellationToken ct = default) { ArgumentNullException.ThrowIfNull(_usbWriteRequest); - ArgumentNullException.ThrowIfNull(UsbDeviceConnection); using var buf = ByteBuffer.Wrap(wbuf, offset, count); using var crReg = ct.Register(() => _usbWriteRequest?.Cancel()); - _tcsWrite = new(); + Interlocked.Exchange(ref _tcsWrite, new()); //Debug.WriteLine($"[USBDRIVER]: write requested"); _usbWriteRequest.QueueReq(buf); UsbRequest response = await _tcsWrite.Task.WaitAsync(ct); + Interlocked.Exchange(ref _tcsWrite, null); ct.ThrowIfCancellationRequested(); int nwrite = buf.Position(); return nwrite; From 654a24925c7c11b017a3b4054dfcbd031720b872 Mon Sep 17 00:00:00 2001 From: "SAVPC\\Sav" Date: Fri, 14 Nov 2025 15:42:41 +0700 Subject: [PATCH 21/39] REFR - add simple logger, + small fixes --- .../Drivers/Base/UsbDriverBase.cs | 41 +++++++++++-------- .../Drivers/FtdiSerialDriver.cs | 2 +- UsbSerialForAndroid.Net/Logging/ILogger.cs | 14 +++++++ .../Logging/LoggerAndroid.cs | 12 ++++++ UsbSerialForAndroid.Net/Logging/LoggerNull.cs | 12 ++++++ 5 files changed, 64 insertions(+), 17 deletions(-) create mode 100644 UsbSerialForAndroid.Net/Logging/ILogger.cs create mode 100644 UsbSerialForAndroid.Net/Logging/LoggerAndroid.cs create mode 100644 UsbSerialForAndroid.Net/Logging/LoggerNull.cs diff --git a/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs b/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs index a16b4d0..6d6ed6d 100644 --- a/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs +++ b/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs @@ -6,13 +6,13 @@ using System.Buffers; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using UsbSerialForAndroid.Net.Enums; using UsbSerialForAndroid.Net.Exceptions; using UsbSerialForAndroid.Net.Extensions; using UsbSerialForAndroid.Net.Helper; +using UsbSerialForAndroid.Net.Logging; namespace UsbSerialForAndroid.Net.Drivers { @@ -130,13 +130,14 @@ private static UsbManager GetUsbManager() /// public virtual async void Close() { - Debug.WriteLine($"[USBDRIVER]: Close started"); + Logger.Trace($"[USBDRIVER]: Close"); await DeinitBuffersAsync(); UsbEndpointRead?.Dispose(); UsbEndpointRead = null; UsbEndpointWrite?.Dispose(); UsbEndpointWrite = null; UsbDeviceConnection?.ReleaseInterface(UsbInterface); UsbInterface?.Dispose(); UsbInterface = null; UsbDeviceConnection?.Close(); UsbDeviceConnection = null; + Logger.Trace($"[USBDRIVER]: Close - Ok"); } /// /// sync write @@ -231,6 +232,8 @@ public bool TestConnection() } } + public ILogger Logger = new NullLogger(); + public FilterDataFn? FilterData; public delegate int FilterDataFn(Span src, Span dst); @@ -252,7 +255,7 @@ public bool TestConnection() protected Task? _filterTask; protected void InitAsyncBuffers() { - Debug.WriteLine($"[USBDRIVER]: Init"); + Logger.Trace($"[USBDRIVER]: Init"); ArgumentNullException.ThrowIfNull(UsbDeviceConnection); ArgumentNullException.ThrowIfNull(UsbEndpointWrite); ArgumentNullException.ThrowIfNull(UsbEndpointRead); @@ -273,9 +276,11 @@ protected void InitAsyncBuffers() _filterTask = Task.Run(() => ProcessFilterAsync(_readerExit.Token)); _readTask = Task.Run(() => InternalUsbReadAsync(_readerExit.Token)); _dispatchTask = Task.Run(() => UsbDispatchAsync(_readerExit.Token)); + Logger.Trace($"[USBDRIVER]: Init - Ok"); } protected async Task DeinitBuffersAsync() { + Logger.Trace($"[USBDRIVER]: Deinit"); try { // cancel all tasks @@ -302,10 +307,11 @@ protected async Task DeinitBuffersAsync() // clear requests Interlocked.Exchange(ref _usbReadRequest, null)?.Dispose(); Interlocked.Exchange(ref _usbWriteRequest, null)?.Dispose(); + Logger.Trace($"[USBDRIVER]: Deinit - Ok"); } catch (Exception ex) { - Debug.WriteLine(ex); + Logger.Error($"[USBDRIVER]: crash {ex}"); } } protected virtual async Task ProcessFilterAsync(CancellationToken ct = default) @@ -340,17 +346,17 @@ protected virtual async Task ProcessFilterAsync(CancellationToken ct = default) } } } + Logger.Trace($"[USBDRIVER]: exit ProcessFilter"); } catch (Exception ex) { - Debug.WriteLine(ex); + Logger.Error($"[USBDRIVER]: crash {ex}"); } finally { if (null != buf) _emptyBuffers.Enqueue(buf); } - Debug.WriteLine($"[USBDRIVER]: exit ProcessFilter"); } protected virtual async Task UsbDispatchAsync(CancellationToken ct = default) { @@ -363,28 +369,30 @@ protected virtual async Task UsbDispatchAsync(CancellationToken ct = default) // ArgumentNullException.ThrowIfNull(response); if (null == response) { - Debug.WriteLine($"[USBDRIVER]: WARN response is null"); + Logger.Trace($"[USBDRIVER]: WARN response is null"); + if (!TestConnection()) + await Task.Run(Close, ct); continue; } if (ReferenceEquals(response, _usbReadRequest)) { - //Debug.WriteLine($"[USBDRIVER]: _tcsRead"); + //Logger.Trace($"[USBDRIVER]: _tcsRead"); _tcsRead?.TrySetResult(response); continue; } if (ReferenceEquals(response, _usbWriteRequest)) { - //Debug.WriteLine($"[USBDRIVER]: _tcsWrite"); + //Logger.Trace($"[USBDRIVER]: _tcsWrite"); _tcsWrite?.TrySetResult(response); continue; } } + Logger.Trace($"[USBDRIVER]: exit UsbDispatchAsync"); } catch (Exception ex) { - Debug.WriteLine(ex); + Logger.Error($"[USBDRIVER]: crash {ex}"); } - Debug.WriteLine($"[USBDRIVER]: exit UsbDispatchAsync"); } protected virtual async Task InternalUsbReadAsync(CancellationToken ct = default) { @@ -401,12 +409,12 @@ protected virtual async Task InternalUsbReadAsync(CancellationToken ct = default { buf.Rewind(); using var crReg = ct.Register(() => _usbReadRequest?.Cancel()); - //Debug.WriteLine($"[USBDRIVER]: read requested"); + //Logger.Trace($"[USBDRIVER]: read requested"); Interlocked.Exchange(ref _tcsRead, new()); _usbReadRequest.QueueReq((ByteBuffer)buf); UsbRequest? response = await _tcsRead.Task.WaitAsync(ct); Interlocked.Exchange(ref _tcsRead, null); - //Debug.WriteLine($"[USBDRIVER]: read received"); + //Logger.Trace($"[USBDRIVER]: read received"); ct.ThrowIfCancellationRequested(); if (0 < buf.Position) { @@ -424,14 +432,16 @@ protected virtual async Task InternalUsbReadAsync(CancellationToken ct = default } } } + Logger.Trace($"[USBDRIVER]: exit InternalUsbReadAsync"); } catch (OperationCanceledException) { _usbReadRequest?.Cancel(); + Logger.Trace($"[USBDRIVER]: exit InternalUsbReadAsync"); } catch (Exception ex) { - Debug.WriteLine($"[USBDRIVER]: read Error {ex}"); + Logger.Error($"[USBDRIVER]: crash {ex}"); } finally { @@ -440,7 +450,6 @@ protected virtual async Task InternalUsbReadAsync(CancellationToken ct = default if (null != emptyBuf) _emptyBuffers.Enqueue(emptyBuf); } - Debug.WriteLine($"[USBDRIVER]: read exit"); } public virtual async Task ReadAsync(byte[] dstBuf, int offset, int count, CancellationToken ct = default) { @@ -503,7 +512,7 @@ public virtual async Task WriteAsync(byte[] wbuf, int offset, int count, Ca using var buf = ByteBuffer.Wrap(wbuf, offset, count); using var crReg = ct.Register(() => _usbWriteRequest?.Cancel()); Interlocked.Exchange(ref _tcsWrite, new()); - //Debug.WriteLine($"[USBDRIVER]: write requested"); + //Logger.Trace($"[USBDRIVER]: write requested"); _usbWriteRequest.QueueReq(buf); UsbRequest response = await _tcsWrite.Task.WaitAsync(ct); Interlocked.Exchange(ref _tcsWrite, null); diff --git a/UsbSerialForAndroid.Net/Drivers/FtdiSerialDriver.cs b/UsbSerialForAndroid.Net/Drivers/FtdiSerialDriver.cs index 0ca2945..efdc1ea 100644 --- a/UsbSerialForAndroid.Net/Drivers/FtdiSerialDriver.cs +++ b/UsbSerialForAndroid.Net/Drivers/FtdiSerialDriver.cs @@ -73,8 +73,8 @@ public override void Open(int baudRate = DefaultBaudRate, byte dataBits = Defaul SetParameter(baudRate, dataBits, stopBits, parity); SetLatency(1); - InitAsyncBuffers(); FilterData = FilterBuf; + InitAsyncBuffers(); } /// /// Reset the USB device diff --git a/UsbSerialForAndroid.Net/Logging/ILogger.cs b/UsbSerialForAndroid.Net/Logging/ILogger.cs new file mode 100644 index 0000000..10f11bd --- /dev/null +++ b/UsbSerialForAndroid.Net/Logging/ILogger.cs @@ -0,0 +1,14 @@ +using System; + +namespace UsbSerialForAndroid.Net.Logging; + +public interface ILogger +{ +#if DEBUG + public void Debug(string msg); +#endif + void Trace(string msg); + void Warning(string msg); + void Error(string msg); + void Error(Exception ex); +} \ No newline at end of file diff --git a/UsbSerialForAndroid.Net/Logging/LoggerAndroid.cs b/UsbSerialForAndroid.Net/Logging/LoggerAndroid.cs new file mode 100644 index 0000000..df3b6cd --- /dev/null +++ b/UsbSerialForAndroid.Net/Logging/LoggerAndroid.cs @@ -0,0 +1,12 @@ +using System; + +namespace UsbSerialForAndroid.Net.Logging; + +public class LoggerAndroid : ILogger +{ + public void Debug(string msg) => Android.Util.Log.Debug("Debug", msg); + public void Error(string msg) => Android.Util.Log.Error("Error", msg); + public void Error(Exception ex) => Android.Util.Log.Error("Error", ex.ToString()); + public void Trace(string msg) => Android.Util.Log.Verbose("Trace", msg); + public void Warning(string msg) => Android.Util.Log.Warn("Warning", msg); +} \ No newline at end of file diff --git a/UsbSerialForAndroid.Net/Logging/LoggerNull.cs b/UsbSerialForAndroid.Net/Logging/LoggerNull.cs new file mode 100644 index 0000000..0cae52c --- /dev/null +++ b/UsbSerialForAndroid.Net/Logging/LoggerNull.cs @@ -0,0 +1,12 @@ +using System; + +namespace UsbSerialForAndroid.Net.Logging; + +public class NullLogger : ILogger +{ + public void Debug(string msg) { } + public void Error(string msg) { } + public void Error(Exception ex) { } + public void Trace(string msg) { } + public void Warning(string msg) { } +} From 91f6c40fdcc7969e178df64be3076a2ad0a9c983 Mon Sep 17 00:00:00 2001 From: "SAVPC\\Sav" Date: Mon, 17 Nov 2025 18:22:32 +0700 Subject: [PATCH 22/39] FEAT - replace ConcurrentQueue buffer with Cannel --- .../Drivers/Base/UsbDriverBase.cs | 290 ++++++++++-------- .../Drivers/FtdiSerialDriver.cs | 8 +- .../Helper/NetDirectByteBuffer.cs | 27 +- 3 files changed, 182 insertions(+), 143 deletions(-) diff --git a/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs b/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs index 6d6ed6d..32b8bd0 100644 --- a/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs +++ b/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs @@ -4,9 +4,9 @@ using Java.Nio; using System; using System.Buffers; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading; +using System.Threading.Channels; using System.Threading.Tasks; using UsbSerialForAndroid.Net.Enums; using UsbSerialForAndroid.Net.Exceptions; @@ -234,25 +234,28 @@ public bool TestConnection() public ILogger Logger = new NullLogger(); + public int ReadHeaderLength = 0; public FilterDataFn? FilterData; public delegate int FilterDataFn(Span src, Span dst); protected UsbRequest? _usbWriteRequest; protected UsbRequest? _usbReadRequest; - protected TaskCompletionSource? _tcsRead; - protected TaskCompletionSource? _tcsWrite; - protected TaskCompletionSource? _tcsFilterBufers; - protected TaskCompletionSource? _tcsFillBuf; + NetDirectByteBuffer? _current = null; protected CancellationTokenSource? _readerExit; protected List _allBuffers = []; - protected ConcurrentQueue _emptyBuffers = new(); - protected ConcurrentQueue _fillBuffers = new(); - protected ConcurrentQueue _filterBufers = new(); protected Task? _dispatchTask; protected Task? _readTask; protected Task? _filterTask; + + Channel? _writecChannel; + Channel? _readChannel; + + Channel? _emptyChannel; + Channel? _filterChannel; + Channel? _dataChannel; + protected void InitAsyncBuffers() { Logger.Trace($"[USBDRIVER]: Init"); @@ -263,13 +266,46 @@ protected void InitAsyncBuffers() _usbReadRequest.Initialize(UsbDeviceConnection, UsbEndpointRead); _usbWriteRequest = new(); _usbWriteRequest.Initialize(UsbDeviceConnection, UsbEndpointWrite); + _writecChannel = Channel.CreateBounded(new BoundedChannelOptions(1) + { + SingleReader = true, + SingleWriter = true, + AllowSynchronousContinuations = false, + FullMode = BoundedChannelFullMode.Wait + }); + _readChannel = Channel.CreateBounded(new BoundedChannelOptions(1) + { + SingleReader = true, + SingleWriter = true, + AllowSynchronousContinuations = false, + FullMode = BoundedChannelFullMode.Wait + }); + _emptyChannel = Channel.CreateUnbounded(new UnboundedChannelOptions() + { + SingleReader = true, + SingleWriter = false, + AllowSynchronousContinuations = false, + }); + _filterChannel = Channel.CreateUnbounded(new UnboundedChannelOptions() + { + SingleReader = true, + SingleWriter = true, + AllowSynchronousContinuations = false, + }); + _dataChannel = Channel.CreateUnbounded(new UnboundedChannelOptions() + { + SingleReader = true, + SingleWriter = true, + AllowSynchronousContinuations = false, + }); int readBufLen = 512; int readBufCount = DefaultBufferLength / readBufLen; for (int i = 0; i < readBufCount; i++) { var newBuf = new NetDirectByteBuffer(readBufLen); _allBuffers.Add(newBuf); - _emptyBuffers.Enqueue(newBuf); + while (!_emptyChannel.Writer.TryWrite(newBuf)) + Task.Yield(); } _readerExit = new(); if (null != FilterData) @@ -285,22 +321,34 @@ protected async Task DeinitBuffersAsync() { // cancel all tasks _readerExit?.Cancel(); + _writecChannel?.Writer.Complete(); + _readChannel?.Writer.Complete(); + _emptyChannel?.Writer.Complete(); + _filterChannel?.Writer.Complete(); + _dataChannel?.Writer.Complete(); Interlocked.Exchange(ref _readerExit, null)?.Dispose(); - // await exit all tasks + // await exit all tasks // clear all tasks if (null != _dispatchTask) + { await _dispatchTask; + _dispatchTask = null; + } if (null != _readTask) + { await _readTask; + _readTask = null; + } if (null != _filterTask) + { await _filterTask; - // clear all tasks - _filterTask = null; - _dispatchTask = null; - _readTask = null; + _filterTask = null; + } // clear all buffers - _emptyBuffers.Clear(); - _fillBuffers.Clear(); - _filterBufers.Clear(); + _writecChannel = null; + _readChannel = null; + _emptyChannel = null; + _filterChannel = null; + _dataChannel = null; foreach (var item in _allBuffers) item.Dispose(); _allBuffers.Clear(); @@ -316,56 +364,50 @@ protected async Task DeinitBuffersAsync() } protected virtual async Task ProcessFilterAsync(CancellationToken ct = default) { - NetDirectByteBuffer? buf = null; + NetDirectByteBuffer? buf; + ChannelWriter? emptyQueue; try { ArgumentNullException.ThrowIfNull(FilterData); + ArgumentNullException.ThrowIfNull(_emptyChannel); + ArgumentNullException.ThrowIfNull(_filterChannel); + ArgumentNullException.ThrowIfNull(_dataChannel); + var inData = _filterChannel.Reader; + var outData = _dataChannel.Writer; + emptyQueue = _emptyChannel.Writer; while (!ct.IsCancellationRequested) { - if (_filterBufers.TryDequeue(out buf)) - { - var data = buf.NetBuffer.AsSpan(0, buf.Position); - buf.Position = FilterData(data, data); - if (0 < buf.Position) - { - _fillBuffers.Enqueue(buf); - _tcsFillBuf?.TrySetResult(); - } - else - _emptyBuffers.Enqueue(buf); - } + if (!inData.TryRead(out buf)) + buf = await inData.ReadAsync(ct); + var data = buf.MemBuffer.Span.Slice(0, buf.Position); + buf.Position = FilterData(data, data); + if (0 < buf.Position) + await outData.WriteAsync(buf, ct); else - { - TaskCompletionSource waitData = new(); - using (ct.Register(() => waitData.TrySetCanceled(ct))) - { - var task = waitData.Task; - Interlocked.Exchange(ref _tcsFilterBufers, waitData); - await task.WaitAsync(ct); - Interlocked.Exchange(ref _tcsFilterBufers, null); - } - } + await emptyQueue.WriteAsync(buf, ct); } - Logger.Trace($"[USBDRIVER]: exit ProcessFilter"); } + catch (OperationCanceledException) { } catch (Exception ex) { Logger.Error($"[USBDRIVER]: crash {ex}"); + return; } - finally - { - if (null != buf) - _emptyBuffers.Enqueue(buf); - } + Logger.Trace($"[USBDRIVER]: exit ProcessFilter"); } protected virtual async Task UsbDispatchAsync(CancellationToken ct = default) { try { ArgumentNullException.ThrowIfNull(UsbDeviceConnection); + ArgumentNullException.ThrowIfNull(_writecChannel); + ArgumentNullException.ThrowIfNull(_readChannel); + var wr = _writecChannel.Writer; + var read = _readChannel.Writer; + UsbRequest? response; while (!ct.IsCancellationRequested) { - UsbRequest? response = await UsbDeviceConnection.RequestWaitAsync(); + response = await UsbDeviceConnection.RequestWaitAsync(); // ArgumentNullException.ThrowIfNull(response); if (null == response) { @@ -377,149 +419,129 @@ protected virtual async Task UsbDispatchAsync(CancellationToken ct = default) if (ReferenceEquals(response, _usbReadRequest)) { //Logger.Trace($"[USBDRIVER]: _tcsRead"); - _tcsRead?.TrySetResult(response); + await read.WriteAsync(response, ct); continue; } if (ReferenceEquals(response, _usbWriteRequest)) { //Logger.Trace($"[USBDRIVER]: _tcsWrite"); - _tcsWrite?.TrySetResult(response); + await wr.WriteAsync(response, ct); continue; } } - Logger.Trace($"[USBDRIVER]: exit UsbDispatchAsync"); } + catch (OperationCanceledException) { } catch (Exception ex) { Logger.Error($"[USBDRIVER]: crash {ex}"); + return; } + Logger.Trace($"[USBDRIVER]: exit UsbDispatchAsync"); } protected virtual async Task InternalUsbReadAsync(CancellationToken ct = default) { - ConcurrentQueue dataQueue; + ChannelWriter outQueue; + ChannelReader emptyQueue; NetDirectByteBuffer? buf = null; - NetDirectByteBuffer? emptyBuf = null; try { ArgumentNullException.ThrowIfNull(_usbReadRequest); - dataQueue = (null == FilterData) ? _fillBuffers : _filterBufers; - while (!_emptyBuffers.TryDequeue(out buf)) - ct.ThrowIfCancellationRequested(); + ArgumentNullException.ThrowIfNull(_readChannel); + ArgumentNullException.ThrowIfNull(_emptyChannel); + ArgumentNullException.ThrowIfNull(_filterChannel); + ArgumentNullException.ThrowIfNull(_dataChannel); + var reader = _readChannel.Reader; + emptyQueue = _emptyChannel.Reader; + outQueue = (null == FilterData) ? _dataChannel.Writer : _filterChannel.Writer; while (!ct.IsCancellationRequested) { + if (null == buf && !emptyQueue.TryRead(out buf)) + buf = await emptyQueue.ReadAsync(ct); buf.Rewind(); - using var crReg = ct.Register(() => _usbReadRequest?.Cancel()); //Logger.Trace($"[USBDRIVER]: read requested"); - Interlocked.Exchange(ref _tcsRead, new()); _usbReadRequest.QueueReq((ByteBuffer)buf); - UsbRequest? response = await _tcsRead.Task.WaitAsync(ct); - Interlocked.Exchange(ref _tcsRead, null); - //Logger.Trace($"[USBDRIVER]: read received"); + _ = await reader.ReadAsync(ct); ct.ThrowIfCancellationRequested(); - if (0 < buf.Position) + if (ReadHeaderLength < buf.Position) { - while (!_emptyBuffers.IsEmpty) - { - ct.ThrowIfCancellationRequested(); - if (_emptyBuffers.TryDequeue(out emptyBuf)) - { - dataQueue.Enqueue(buf); - var dataNotify = (null == FilterData) ? _tcsFillBuf : _tcsFilterBufers; - dataNotify?.TrySetResult(); - buf = emptyBuf; - break; - } - } + //Logger.Trace($"[USBDRIVER]: received {buf.Position}"); + await outQueue.WriteAsync(buf, ct); + buf = null; } } - Logger.Trace($"[USBDRIVER]: exit InternalUsbReadAsync"); } catch (OperationCanceledException) { _usbReadRequest?.Cancel(); - Logger.Trace($"[USBDRIVER]: exit InternalUsbReadAsync"); } catch (Exception ex) { Logger.Error($"[USBDRIVER]: crash {ex}"); + return; } - finally - { - if (null != buf) - _emptyBuffers.Enqueue(buf); - if (null != emptyBuf) - _emptyBuffers.Enqueue(emptyBuf); - } + Logger.Trace($"[USBDRIVER]: exit InternalUsbReadAsync"); } public virtual async Task ReadAsync(byte[] dstBuf, int offset, int count, CancellationToken ct = default) { + ArgumentNullException.ThrowIfNull(_emptyChannel); + ArgumentNullException.ThrowIfNull(_dataChannel); + ChannelWriter emptyQueue = _emptyChannel.Writer; + ChannelReader dataQueue = _dataChannel.Reader; int readed = 0; - NetDirectByteBuffer? buf = default; - - //try peek already buffered data, - //if there is none, do async wait and try peek again - while (!_fillBuffers.TryPeek(out buf)) - { - ct.ThrowIfCancellationRequested(); - TaskCompletionSource waitData = new(); - using (ct.Register(() => waitData.TrySetCanceled(ct))) - { - var task = waitData.Task; - Interlocked.Exchange(ref _tcsFillBuf, waitData); - await task.WaitAsync(ct); - Interlocked.Exchange(ref _tcsFillBuf, null); - } - } + NetDirectByteBuffer? buf = null; + Interlocked.Exchange(ref buf, _current); + if (null == buf && !dataQueue.TryRead(out buf)) + buf = await dataQueue.ReadAsync(ct); // get buffered data, not more than the requested size - while (null != buf) + while (!ct.IsCancellationRequested && null != buf) { + //try peek already buffered data, + //if there is none, do async wait and try peek again + if (count < buf.Position) { - var qty = buf.Position; - var data = (byte[])buf; - data.AsSpan(0, count).CopyTo(dstBuf.AsSpan(offset)); - data.AsSpan(count, qty).CopyTo(data.AsSpan()); - buf.Position = qty - count; - break; + var data = buf.MemBuffer.Span; + data.Slice(0, count).CopyTo(dstBuf.AsSpan(offset)); + readed += count; + // move data + var qty = buf.Position - count; + data.Slice(count, qty).CopyTo(data.Slice(0, qty)); + buf.Position = qty; + Interlocked.Exchange(ref _current, buf); + return readed; } + //_current = null; var r = buf.Position; - ((byte[])buf).AsSpan(0, r).CopyTo(dstBuf.AsSpan(offset)); + buf.MemBuffer.Span.Slice(0, r).CopyTo(dstBuf.AsSpan(offset)); offset += r; count -= r; readed += r; - try - { - while (!_fillBuffers.TryDequeue(out buf)) - ct.ThrowIfCancellationRequested(); - } - finally - { - if (null != buf) - { - _emptyBuffers.Enqueue(buf); - buf = null; - } - } - while (false == _fillBuffers.IsEmpty && false == _fillBuffers.TryPeek(out buf)) - ct.ThrowIfCancellationRequested(); + await emptyQueue.WriteAsync(buf, ct); + dataQueue.TryRead(out buf); } return readed; - } public virtual async Task WriteAsync(byte[] wbuf, int offset, int count, CancellationToken ct = default) { - ArgumentNullException.ThrowIfNull(_usbWriteRequest); - using var buf = ByteBuffer.Wrap(wbuf, offset, count); - using var crReg = ct.Register(() => _usbWriteRequest?.Cancel()); - Interlocked.Exchange(ref _tcsWrite, new()); - //Logger.Trace($"[USBDRIVER]: write requested"); - _usbWriteRequest.QueueReq(buf); - UsbRequest response = await _tcsWrite.Task.WaitAsync(ct); - Interlocked.Exchange(ref _tcsWrite, null); - ct.ThrowIfCancellationRequested(); - int nwrite = buf.Position(); - return nwrite; + try + { + // TODO: split wbuf into 512 bytes and send in pieces to be able to cancel + ArgumentNullException.ThrowIfNull(_writecChannel); + ArgumentNullException.ThrowIfNull(_usbWriteRequest); + var reader = _writecChannel.Reader; + // it`s not copy its just wrapper + using var buf = new NetDirectByteBuffer(wbuf, offset, count); + //Logger.Trace($"[USBDRIVER]: write requested"); + _usbWriteRequest.QueueReq((ByteBuffer)buf); + _ = await reader.ReadAsync(ct); + ct.ThrowIfCancellationRequested(); + return buf.Position; + } + catch (Exception) + { + _usbWriteRequest?.Cancel(); + throw; + } } - } } \ No newline at end of file diff --git a/UsbSerialForAndroid.Net/Drivers/FtdiSerialDriver.cs b/UsbSerialForAndroid.Net/Drivers/FtdiSerialDriver.cs index efdc1ea..8daa90a 100644 --- a/UsbSerialForAndroid.Net/Drivers/FtdiSerialDriver.cs +++ b/UsbSerialForAndroid.Net/Drivers/FtdiSerialDriver.cs @@ -13,8 +13,6 @@ public class FtdiSerialDriver : UsbDriverBase { private bool baudRateWithPort = false; public const int RequestTypeHostToDevice = UsbConstants.UsbTypeVendor | (int)UsbAddressing.Out; - public const int ReadHeaderLength = 2; // contains MODEM_STATUS - public const int ModemControlDtrEnable = 0x0101; public const int ModemControlDtrDisable = 0x0100; @@ -32,7 +30,11 @@ public class FtdiSerialDriver : UsbDriverBase public const int SetLatencyTimerRequest = 9; // SET_LATENCY_TIMER_REQUEST public const int GetLatencyTimerRequest = 10; // GET_LATENCY_TIMER_REQUEST - public FtdiSerialDriver(UsbDevice usbDevice) : base(usbDevice) { } + public FtdiSerialDriver(UsbDevice usbDevice) + : base(usbDevice) + { + ReadHeaderLength = 2; + } /// /// Open the USB device /// diff --git a/UsbSerialForAndroid.Net/Helper/NetDirectByteBuffer.cs b/UsbSerialForAndroid.Net/Helper/NetDirectByteBuffer.cs index 6012311..3459954 100644 --- a/UsbSerialForAndroid.Net/Helper/NetDirectByteBuffer.cs +++ b/UsbSerialForAndroid.Net/Helper/NetDirectByteBuffer.cs @@ -8,20 +8,35 @@ namespace UsbSerialForAndroid.Net.Helper; public class NetDirectByteBuffer : IDisposable { - public NetDirectByteBuffer(int capacity = 512) + public NetDirectByteBuffer(byte[] array, int offset, int length) { - NetBuffer = new byte[capacity]; - _handle = GCHandle.Alloc(NetBuffer, GCHandleType.Pinned); - IntPtr ndb = JNIEnv.NewDirectByteBuffer(_handle.AddrOfPinnedObject(), capacity); + ArgumentOutOfRangeException.ThrowIfGreaterThan(length, array.Length - offset); + _netBuffer = array; + _handle = GCHandle.Alloc(array, GCHandleType.Pinned); + MemBuffer = MemoryMarshal.CreateFromPinnedArray(array, offset, length); + IntPtr ndb = JNIEnv.NewDirectByteBuffer(_handle.AddrOfPinnedObject() + offset, length); ByteBuffer? jdb = Java.Lang.Object.GetObject(ndb, JniHandleOwnership.TransferLocalRef); ArgumentNullException.ThrowIfNull(jdb); JavaBuffer = jdb; } - public readonly byte[] NetBuffer; + public NetDirectByteBuffer(int capacity = 512) + : this(new byte[capacity], 0, capacity) + { + //_netBuffer = new byte[capacity]; + //_handle = GCHandle.Alloc(_netBuffer, GCHandleType.Pinned); + //MemBuffer = MemoryMarshal.CreateFromPinnedArray(_netBuffer, 0, capacity); + //IntPtr ndb = JNIEnv.NewDirectByteBuffer(_handle.AddrOfPinnedObject(), capacity); + //ByteBuffer? jdb = Java.Lang.Object.GetObject(ndb, JniHandleOwnership.TransferLocalRef); + //ArgumentNullException.ThrowIfNull(jdb); + //JavaBuffer = jdb; + } + + public readonly Memory MemBuffer; public readonly ByteBuffer JavaBuffer; + readonly byte[] _netBuffer; GCHandle _handle; public static explicit operator ByteBuffer(NetDirectByteBuffer nb) => nb.JavaBuffer; - public static explicit operator byte[](NetDirectByteBuffer nb) => nb.NetBuffer; + //public static explicit operator byte[](NetDirectByteBuffer nb) => nb._netBuffer; public Java.Nio.Buffer? Rewind() => JavaBuffer.Rewind(); public int Position From e8740a37b0739a06473c14b9ea70f32f44a9539a Mon Sep 17 00:00:00 2001 From: "SAVPC\\Sav" Date: Fri, 21 Nov 2025 17:22:39 +0700 Subject: [PATCH 23/39] FEAT - make FIFO Channel , fix exception, fix UsbRequest.Cancel --- .../Drivers/Base/UsbDriverBase.cs | 230 +++++++++++------- .../Extensions/BufferExtensions.cs | 63 ----- .../Helper/NetDirectByteBuffer.cs | 13 + 3 files changed, 160 insertions(+), 146 deletions(-) delete mode 100644 UsbSerialForAndroid.Net/Extensions/BufferExtensions.cs diff --git a/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs b/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs index 32b8bd0..90d7b01 100644 --- a/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs +++ b/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs @@ -26,6 +26,8 @@ public abstract class UsbDriverBase public const byte XOFF = 19; public const int DefaultTimeout = 1000; public const int DefaultBufferLength = 1024 * 4; + public const int UsbBufLength = 512; // USB 2.0 High-Speed Endpoints Bulk: 512 bytes maximum + public const int UsbBufCount = 8; public const int DefaultBaudRate = 9600; public const byte DefaultDataBits = 8; public const StopBits DefaultStopBits = StopBits.One; @@ -249,13 +251,21 @@ public bool TestConnection() protected Task? _readTask; protected Task? _filterTask; - Channel? _writecChannel; + Channel? _writeChannel; Channel? _readChannel; Channel? _emptyChannel; Channel? _filterChannel; Channel? _dataChannel; + + ChannelReader? _emptyReader; + ChannelWriter? _emptyWriter; + ChannelReader? _filterReader; + ChannelWriter? _filterWriter; + ChannelReader? _dataReader; + ChannelWriter? _dataWriter; + protected void InitAsyncBuffers() { Logger.Trace($"[USBDRIVER]: Init"); @@ -266,13 +276,16 @@ protected void InitAsyncBuffers() _usbReadRequest.Initialize(UsbDeviceConnection, UsbEndpointRead); _usbWriteRequest = new(); _usbWriteRequest.Initialize(UsbDeviceConnection, UsbEndpointWrite); - _writecChannel = Channel.CreateBounded(new BoundedChannelOptions(1) + _writeChannel = Channel.CreateBounded(new BoundedChannelOptions(1) { SingleReader = true, SingleWriter = true, AllowSynchronousContinuations = false, FullMode = BoundedChannelFullMode.Wait }); + // write request queue - free items + while (!_writeChannel.Writer.TryWrite(_usbWriteRequest)) ; + _readChannel = Channel.CreateBounded(new BoundedChannelOptions(1) { SingleReader = true, @@ -282,29 +295,35 @@ protected void InitAsyncBuffers() }); _emptyChannel = Channel.CreateUnbounded(new UnboundedChannelOptions() { - SingleReader = true, + SingleReader = false, SingleWriter = false, - AllowSynchronousContinuations = false, + AllowSynchronousContinuations = true, }); _filterChannel = Channel.CreateUnbounded(new UnboundedChannelOptions() { - SingleReader = true, + SingleReader = false, SingleWriter = true, AllowSynchronousContinuations = false, }); _dataChannel = Channel.CreateUnbounded(new UnboundedChannelOptions() { - SingleReader = true, + SingleReader = false, SingleWriter = true, AllowSynchronousContinuations = false, }); - int readBufLen = 512; - int readBufCount = DefaultBufferLength / readBufLen; - for (int i = 0; i < readBufCount; i++) + + _emptyReader = _emptyChannel.Reader; + _emptyWriter = _emptyChannel.Writer; + _filterReader = _filterChannel.Reader; + _filterWriter = _filterChannel.Writer; + _dataReader = _dataChannel.Reader; + _dataWriter = _dataChannel.Writer; + + for (int i = 0; i < UsbBufCount; i++) { - var newBuf = new NetDirectByteBuffer(readBufLen); + var newBuf = new NetDirectByteBuffer(UsbBufLength); _allBuffers.Add(newBuf); - while (!_emptyChannel.Writer.TryWrite(newBuf)) + while (!_emptyWriter.TryWrite(newBuf)) Task.Yield(); } _readerExit = new(); @@ -321,7 +340,7 @@ protected async Task DeinitBuffersAsync() { // cancel all tasks _readerExit?.Cancel(); - _writecChannel?.Writer.Complete(); + _writeChannel?.Writer.Complete(); _readChannel?.Writer.Complete(); _emptyChannel?.Writer.Complete(); _filterChannel?.Writer.Complete(); @@ -344,7 +363,14 @@ protected async Task DeinitBuffersAsync() _filterTask = null; } // clear all buffers - _writecChannel = null; + _emptyReader = null; + _emptyWriter = null; + _filterReader = null; + _filterWriter = null; + _dataReader = null; + _dataWriter = null; + + _writeChannel = null; _readChannel = null; _emptyChannel = null; _filterChannel = null; @@ -365,26 +391,22 @@ protected async Task DeinitBuffersAsync() protected virtual async Task ProcessFilterAsync(CancellationToken ct = default) { NetDirectByteBuffer? buf; - ChannelWriter? emptyQueue; try { ArgumentNullException.ThrowIfNull(FilterData); - ArgumentNullException.ThrowIfNull(_emptyChannel); - ArgumentNullException.ThrowIfNull(_filterChannel); - ArgumentNullException.ThrowIfNull(_dataChannel); - var inData = _filterChannel.Reader; - var outData = _dataChannel.Writer; - emptyQueue = _emptyChannel.Writer; + ArgumentNullException.ThrowIfNull(_filterReader); + ArgumentNullException.ThrowIfNull(_dataWriter); + ArgumentNullException.ThrowIfNull(_emptyWriter); while (!ct.IsCancellationRequested) { - if (!inData.TryRead(out buf)) - buf = await inData.ReadAsync(ct); + if (!_filterReader.TryRead(out buf)) + buf = await _filterReader.ReadAsync(ct); var data = buf.MemBuffer.Span.Slice(0, buf.Position); buf.Position = FilterData(data, data); if (0 < buf.Position) - await outData.WriteAsync(buf, ct); + await _dataWriter.WriteAsync(buf, ct); else - await emptyQueue.WriteAsync(buf, ct); + await _emptyWriter.WriteAsync(buf, ct); } } catch (OperationCanceledException) { } @@ -400,18 +422,30 @@ protected virtual async Task UsbDispatchAsync(CancellationToken ct = default) try { ArgumentNullException.ThrowIfNull(UsbDeviceConnection); - ArgumentNullException.ThrowIfNull(_writecChannel); + ArgumentNullException.ThrowIfNull(_writeChannel); ArgumentNullException.ThrowIfNull(_readChannel); - var wr = _writecChannel.Writer; + var wr = _writeChannel.Writer; var read = _readChannel.Writer; - UsbRequest? response; + UsbRequest? response = null; while (!ct.IsCancellationRequested) { - response = await UsbDeviceConnection.RequestWaitAsync(); - // ArgumentNullException.ThrowIfNull(response); + try + { + response = await UsbDeviceConnection.RequestWaitAsync(); + } + catch (Java.Lang.IllegalArgumentException iEx) + { + Logger.Warning($"[USBDRIVER]: IllegalArgumentException {iEx}"); + continue; + } + catch (BufferOverflowException boEx) + { + Logger.Warning($"[USBDRIVER]: BufferOverflowException {boEx}"); + continue; + } if (null == response) { - Logger.Trace($"[USBDRIVER]: WARN response is null"); + Logger.Warning($"[USBDRIVER]: response is null"); if (!TestConnection()) await Task.Run(Close, ct); continue; @@ -440,23 +474,43 @@ protected virtual async Task UsbDispatchAsync(CancellationToken ct = default) } protected virtual async Task InternalUsbReadAsync(CancellationToken ct = default) { - ChannelWriter outQueue; - ChannelReader emptyQueue; NetDirectByteBuffer? buf = null; try { ArgumentNullException.ThrowIfNull(_usbReadRequest); ArgumentNullException.ThrowIfNull(_readChannel); - ArgumentNullException.ThrowIfNull(_emptyChannel); - ArgumentNullException.ThrowIfNull(_filterChannel); - ArgumentNullException.ThrowIfNull(_dataChannel); + ArgumentNullException.ThrowIfNull(_emptyReader); + ArgumentNullException.ThrowIfNull(_filterReader); + ArgumentNullException.ThrowIfNull(_dataReader); + var outQueue = (null == FilterData) ? _dataWriter : _filterWriter; + ArgumentNullException.ThrowIfNull(outQueue); var reader = _readChannel.Reader; - emptyQueue = _emptyChannel.Reader; - outQueue = (null == FilterData) ? _dataChannel.Writer : _filterChannel.Writer; while (!ct.IsCancellationRequested) { - if (null == buf && !emptyQueue.TryRead(out buf)) - buf = await emptyQueue.ReadAsync(ct); + while (null == buf) + { + ct.ThrowIfCancellationRequested(); + if (_emptyReader.TryRead(out buf)) + continue; + else + Logger.Trace($"[USBDRIVER]: FIND _emptyReader=0"); + + buf = Interlocked.Exchange(ref _current, null); + if (null != buf) + continue; + else + Logger.Trace($"[USBDRIVER]: FIND _current=0"); + + if (_dataReader.TryRead(out buf)) + continue; + else + Logger.Trace($"[USBDRIVER]: FIND _dataReader=0"); + + if (_filterReader.TryRead(out buf)) + continue; + else + Logger.Trace($"[USBDRIVER]: FIND _filterReader=0"); + } buf.Rewind(); //Logger.Trace($"[USBDRIVER]: read requested"); _usbReadRequest.QueueReq((ByteBuffer)buf); @@ -464,7 +518,7 @@ protected virtual async Task InternalUsbReadAsync(CancellationToken ct = default ct.ThrowIfCancellationRequested(); if (ReadHeaderLength < buf.Position) { - //Logger.Trace($"[USBDRIVER]: received {buf.Position}"); + Logger.Trace($"[USBDRIVER]: received {buf.Position}"); await outQueue.WriteAsync(buf, ct); buf = null; } @@ -483,63 +537,73 @@ protected virtual async Task InternalUsbReadAsync(CancellationToken ct = default } public virtual async Task ReadAsync(byte[] dstBuf, int offset, int count, CancellationToken ct = default) { - ArgumentNullException.ThrowIfNull(_emptyChannel); - ArgumentNullException.ThrowIfNull(_dataChannel); - ChannelWriter emptyQueue = _emptyChannel.Writer; - ChannelReader dataQueue = _dataChannel.Reader; + ArgumentNullException.ThrowIfNull(_emptyWriter); + ArgumentNullException.ThrowIfNull(_dataReader); int readed = 0; - NetDirectByteBuffer? buf = null; - Interlocked.Exchange(ref buf, _current); - if (null == buf && !dataQueue.TryRead(out buf)) - buf = await dataQueue.ReadAsync(ct); - // get buffered data, not more than the requested size + NetDirectByteBuffer? buf = Interlocked.Exchange(ref _current, null); // try get previos peeked data, + if (null == buf && !_dataReader.TryRead(out buf)) // try peek already buffered data, + buf = await _dataReader.ReadAsync(ct); // if there is none, do async wait + // get all buffered data, not more than the requested size while (!ct.IsCancellationRequested && null != buf) { - //try peek already buffered data, - //if there is none, do async wait and try peek again - - if (count < buf.Position) + var currLen = int.Min(count, buf.Position); + var data = buf.MemBuffer.Span; + buf.MemBuffer.Span.Slice(0, currLen).CopyTo(dstBuf.AsSpan(offset)); + readed += currLen; + offset += currLen; + count -= currLen; + var rest = buf.Position - currLen; + if (0 < rest) { - var data = buf.MemBuffer.Span; - data.Slice(0, count).CopyTo(dstBuf.AsSpan(offset)); - readed += count; - // move data - var qty = buf.Position - count; - data.Slice(count, qty).CopyTo(data.Slice(0, qty)); - buf.Position = qty; - Interlocked.Exchange(ref _current, buf); - return readed; + data.Slice(currLen, rest).CopyTo(data.Slice(0)); + buf.Position = rest; + buf = Interlocked.Exchange(ref _current, buf); + //return readed; + } + else + { + await _emptyWriter.WriteAsync(buf, ct); + _dataReader.TryRead(out buf); } - //_current = null; - var r = buf.Position; - buf.MemBuffer.Span.Slice(0, r).CopyTo(dstBuf.AsSpan(offset)); - offset += r; - count -= r; - readed += r; - await emptyQueue.WriteAsync(buf, ct); - dataQueue.TryRead(out buf); } return readed; } public virtual async Task WriteAsync(byte[] wbuf, int offset, int count, CancellationToken ct = default) { + ArgumentNullException.ThrowIfNull(_writeChannel); + UsbRequest? wr = null; try { - // TODO: split wbuf into 512 bytes and send in pieces to be able to cancel - ArgumentNullException.ThrowIfNull(_writecChannel); - ArgumentNullException.ThrowIfNull(_usbWriteRequest); - var reader = _writecChannel.Reader; - // it`s not copy its just wrapper - using var buf = new NetDirectByteBuffer(wbuf, offset, count); - //Logger.Trace($"[USBDRIVER]: write requested"); - _usbWriteRequest.QueueReq((ByteBuffer)buf); - _ = await reader.ReadAsync(ct); - ct.ThrowIfCancellationRequested(); - return buf.Position; + var writeRqQueueReader = _writeChannel.Reader; + int rest = count; + while (0 < rest) + { + if (null == wr) + wr = await writeRqQueueReader.ReadAsync(ct);// get a free write-request + using var buf = new NetDirectByteBuffer(wbuf, offset, int.Min(rest, UsbBufLength)); + wr.QueueReq((ByteBuffer)buf);//send request + wr = null; // here we no longer own the request + wr = await writeRqQueueReader.ReadAsync(ct);//wait response + offset += buf.Position; + rest -= buf.Position; + } + if (null != wr) + while (!_writeChannel.Writer.TryWrite(wr)) ; + return count - rest; + } + catch (OperationCanceledException) + { + // we need to wait for a request to remove from the queue, even if we cancel the request + // the UsbDispatchAsync thread will do wait and return the free request to _writeChannel queue + var isCanceled = wr?.Cancel(); + // isCanceled == true - operation canceled + // isCanceled == false - the operation does not require cancellation, because has already been completed + Logger.Trace($"[USBDRIVER]: cancel {isCanceled}"); + throw; } - catch (Exception) + catch (Exception ex) { - _usbWriteRequest?.Cancel(); + Logger.Error($"[USBDRIVER]: write Exception {ex}"); throw; } } diff --git a/UsbSerialForAndroid.Net/Extensions/BufferExtensions.cs b/UsbSerialForAndroid.Net/Extensions/BufferExtensions.cs deleted file mode 100644 index 5aa856a..0000000 --- a/UsbSerialForAndroid.Net/Extensions/BufferExtensions.cs +++ /dev/null @@ -1,63 +0,0 @@ -/* Copyright 2017 Tyler Technologies Inc. - * - * Project home page: https://github.com/anotherlab/xamarin-usb-serial-for-android - * Portions of this library are based on usb-serial-for-android (https://github.com/mik3y/usb-serial-for-android). - * Portions of this library are based on Xamarin USB Serial for Android (https://bitbucket.org/lusovu/xamarinusbserial). - */ - -using Android.Runtime; -using Java.Nio; -using System; -using System.Runtime.InteropServices; - -namespace UsbSerialForAndroid.Net.Extensions -{ - /// - /// Work around for faulty JNI wrapping in Xamarin library. Fixes a bug - /// where binding for Java.Nio.ByteBuffer.Get(byte[], int, int) allocates a new temporary - /// Java byte array on every call - /// See https://bugzilla.xamarin.com/show_bug.cgi?id=31260 - /// and http://stackoverflow.com/questions/30268400/xamarin-implementation-of-bytebuffer-get-wrong - /// - public static class BufferExtensions - { - static nint _byteBufferClassRef; - static nint _byteBufferGetBii; - static nint _byteBufferGetArrayMethodRef; - - // init on first call - static BufferExtensions() - { - _byteBufferClassRef = JNIEnv.FindClass("java/nio/ByteBuffer"); - _byteBufferGetArrayMethodRef = JNIEnv.GetMethodID(_byteBufferClassRef, "array", "()[B"); - } - - public static ByteBuffer? Get(this ByteBuffer buffer, JavaArray dst, int dstOffset, int byteCount) - { - if (_byteBufferClassRef == nint.Zero) - _byteBufferClassRef = JNIEnv.FindClass("java/nio/ByteBuffer"); - if (_byteBufferGetBii == nint.Zero) - _byteBufferGetBii = JNIEnv.GetMethodID(_byteBufferClassRef, "get", "([BII)Ljava/nio/ByteBuffer;"); - - return Java.Lang.Object.GetObject( - JNIEnv.CallObjectMethod(buffer.Handle, _byteBufferGetBii, [new(dst), new(dstOffset), new(byteCount)]), - JniHandleOwnership.TransferLocalRef); - } - - public static byte[]? ToByteArray(this ByteBuffer buffer) - { - nint resultHandle = JNIEnv.CallObjectMethod(buffer.Handle, _byteBufferGetArrayMethodRef); - byte[]? result = JNIEnv.GetArray(resultHandle); - JNIEnv.DeleteLocalRef(resultHandle); - return result; - } - public static void CopyTo(this ByteBuffer buffer, int srcOffset, byte[] dstArr, int offset, int count) - { - ArgumentOutOfRangeException.ThrowIfGreaterThan(count, buffer.Capacity() - srcOffset); - nint srcPtr = buffer.GetDirectBufferAddress(); - ArgumentOutOfRangeException.ThrowIfZero(srcPtr); - srcPtr += srcOffset; - Marshal.Copy(srcPtr, dstArr, offset, count); - } - } -} \ No newline at end of file diff --git a/UsbSerialForAndroid.Net/Helper/NetDirectByteBuffer.cs b/UsbSerialForAndroid.Net/Helper/NetDirectByteBuffer.cs index 3459954..50e466a 100644 --- a/UsbSerialForAndroid.Net/Helper/NetDirectByteBuffer.cs +++ b/UsbSerialForAndroid.Net/Helper/NetDirectByteBuffer.cs @@ -6,8 +6,17 @@ namespace UsbSerialForAndroid.Net.Helper; +// Direct buffer does not require additional copying: line 320 +// https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/hardware/usb/UsbRequest.java + public class NetDirectByteBuffer : IDisposable { + /// + /// it`s not copy its just wrapper for array + /// + /// + /// + /// public NetDirectByteBuffer(byte[] array, int offset, int length) { ArgumentOutOfRangeException.ThrowIfGreaterThan(length, array.Length - offset); @@ -19,6 +28,10 @@ public NetDirectByteBuffer(byte[] array, int offset, int length) ArgumentNullException.ThrowIfNull(jdb); JavaBuffer = jdb; } + /// + /// creates a Net array, pinned it and makes a DirectByteBuffer from it + /// + /// public NetDirectByteBuffer(int capacity = 512) : this(new byte[capacity], 0, capacity) { From 81681c7a496fc5429ff3d3b82b10f2a693ec121c Mon Sep 17 00:00:00 2001 From: "SAVPC\\Sav" Date: Mon, 24 Nov 2025 14:45:41 +0700 Subject: [PATCH 24/39] FEAT - introduce OpenAsync DisposeAsync --- .../Drivers/Base/UsbDriverBase.cs | 66 ++++++++++--------- .../Drivers/CdcAcmSerialDriver.cs | 9 ++- .../Drivers/FtdiSerialDriver.cs | 9 +-- .../Drivers/ProlificSerialDriver.cs | 15 +++-- .../Drivers/QinHengSerialDriver.cs | 8 +-- .../Drivers/SiliconLabsSerialDriver.cs | 10 +-- .../Helper/BaseDisposable.cs | 48 ++++++++++++++ .../Helper/IAnyDisposable.cs | 8 +++ .../Helper/SynchronousWaitExt.cs | 20 ++++++ UsbSerialForAndroid.Net/Logging/ILogger.cs | 5 ++ 10 files changed, 142 insertions(+), 56 deletions(-) create mode 100644 UsbSerialForAndroid.Net/Helper/BaseDisposable.cs create mode 100644 UsbSerialForAndroid.Net/Helper/IAnyDisposable.cs create mode 100644 UsbSerialForAndroid.Net/Helper/SynchronousWaitExt.cs diff --git a/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs b/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs index 90d7b01..ed3cc82 100644 --- a/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs +++ b/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs @@ -12,14 +12,13 @@ using UsbSerialForAndroid.Net.Exceptions; using UsbSerialForAndroid.Net.Extensions; using UsbSerialForAndroid.Net.Helper; -using UsbSerialForAndroid.Net.Logging; namespace UsbSerialForAndroid.Net.Drivers { /// /// USB driver base class /// - public abstract class UsbDriverBase + public abstract class UsbDriverBase : BaseDisposable { private static readonly UsbManager usbManager = GetUsbManager(); public const byte XON = 17; @@ -27,7 +26,7 @@ public abstract class UsbDriverBase public const int DefaultTimeout = 1000; public const int DefaultBufferLength = 1024 * 4; public const int UsbBufLength = 512; // USB 2.0 High-Speed Endpoints Bulk: 512 bytes maximum - public const int UsbBufCount = 8; + public const int UsbBufCount = 16; public const int DefaultBaudRate = 9600; public const byte DefaultDataBits = 8; public const StopBits DefaultStopBits = StopBits.One; @@ -116,7 +115,8 @@ private static UsbManager GetUsbManager() /// dataBits /// stopBits /// parity - public abstract void Open(int baudRate, byte dataBits, StopBits stopBits, Parity parity); + public void Open(int baudRate, byte dataBits, StopBits stopBits, Parity parity) => + OpenAsync(baudRate, dataBits, stopBits, parity).AsTask().SynchronousWait(); /// /// Set DTR enabled /// @@ -130,16 +130,19 @@ private static UsbManager GetUsbManager() /// /// close the usb device /// - public virtual async void Close() + public void Close() => DisposeAsync().AsTask().SynchronousWait(); + + public abstract ValueTask OpenAsync(int baudRate, byte dataBits, StopBits stopBits, Parity parity); + protected override async ValueTask DisposeAsyncCore() { - Logger.Trace($"[USBDRIVER]: Close"); + Logger.Trace($"[USBDRIVER]: DisposeAsync"); await DeinitBuffersAsync(); UsbEndpointRead?.Dispose(); UsbEndpointRead = null; UsbEndpointWrite?.Dispose(); UsbEndpointWrite = null; UsbDeviceConnection?.ReleaseInterface(UsbInterface); UsbInterface?.Dispose(); UsbInterface = null; UsbDeviceConnection?.Close(); UsbDeviceConnection = null; - Logger.Trace($"[USBDRIVER]: Close - Ok"); + Logger.Trace($"[USBDRIVER]: DisposeAsync - Ok"); } /// /// sync write @@ -234,8 +237,6 @@ public bool TestConnection() } } - public ILogger Logger = new NullLogger(); - public int ReadHeaderLength = 0; public FilterDataFn? FilterData; public delegate int FilterDataFn(Span src, Span dst); @@ -258,7 +259,6 @@ public bool TestConnection() Channel? _filterChannel; Channel? _dataChannel; - ChannelReader? _emptyReader; ChannelWriter? _emptyWriter; ChannelReader? _filterReader; @@ -266,9 +266,9 @@ public bool TestConnection() ChannelReader? _dataReader; ChannelWriter? _dataWriter; - protected void InitAsyncBuffers() + protected async Task InitBuffersAsync() { - Logger.Trace($"[USBDRIVER]: Init"); + Logger.Trace($"[USBDRIVER]: InitAsync"); ArgumentNullException.ThrowIfNull(UsbDeviceConnection); ArgumentNullException.ThrowIfNull(UsbEndpointWrite); ArgumentNullException.ThrowIfNull(UsbEndpointRead); @@ -284,7 +284,7 @@ protected void InitAsyncBuffers() FullMode = BoundedChannelFullMode.Wait }); // write request queue - free items - while (!_writeChannel.Writer.TryWrite(_usbWriteRequest)) ; + await _writeChannel.Writer.WriteAsync(_usbWriteRequest); _readChannel = Channel.CreateBounded(new BoundedChannelOptions(1) { @@ -323,19 +323,18 @@ protected void InitAsyncBuffers() { var newBuf = new NetDirectByteBuffer(UsbBufLength); _allBuffers.Add(newBuf); - while (!_emptyWriter.TryWrite(newBuf)) - Task.Yield(); + await _emptyWriter.WriteAsync(newBuf); } _readerExit = new(); if (null != FilterData) _filterTask = Task.Run(() => ProcessFilterAsync(_readerExit.Token)); - _readTask = Task.Run(() => InternalUsbReadAsync(_readerExit.Token)); + _readTask = Task.Run(() => UsbReadAsync(_readerExit.Token)); _dispatchTask = Task.Run(() => UsbDispatchAsync(_readerExit.Token)); - Logger.Trace($"[USBDRIVER]: Init - Ok"); + Logger.Trace($"[USBDRIVER]: InitAsync - Ok"); } protected async Task DeinitBuffersAsync() { - Logger.Trace($"[USBDRIVER]: Deinit"); + Logger.Trace($"[USBDRIVER]: DeinitAsync"); try { // cancel all tasks @@ -381,7 +380,7 @@ protected async Task DeinitBuffersAsync() // clear requests Interlocked.Exchange(ref _usbReadRequest, null)?.Dispose(); Interlocked.Exchange(ref _usbWriteRequest, null)?.Dispose(); - Logger.Trace($"[USBDRIVER]: Deinit - Ok"); + Logger.Trace($"[USBDRIVER]: DeinitAsync - Ok"); } catch (Exception ex) { @@ -472,7 +471,7 @@ protected virtual async Task UsbDispatchAsync(CancellationToken ct = default) } Logger.Trace($"[USBDRIVER]: exit UsbDispatchAsync"); } - protected virtual async Task InternalUsbReadAsync(CancellationToken ct = default) + protected virtual async Task UsbReadAsync(CancellationToken ct = default) { NetDirectByteBuffer? buf = null; try @@ -493,23 +492,20 @@ protected virtual async Task InternalUsbReadAsync(CancellationToken ct = default if (_emptyReader.TryRead(out buf)) continue; else - Logger.Trace($"[USBDRIVER]: FIND _emptyReader=0"); - + Logger.Debug($"[USBDRIVER]: FIND _emptyReader=0"); buf = Interlocked.Exchange(ref _current, null); if (null != buf) continue; else - Logger.Trace($"[USBDRIVER]: FIND _current=0"); - + Logger.Debug($"[USBDRIVER]: FIND _current=0"); if (_dataReader.TryRead(out buf)) continue; else - Logger.Trace($"[USBDRIVER]: FIND _dataReader=0"); - + Logger.Debug($"[USBDRIVER]: FIND _dataReader=0"); if (_filterReader.TryRead(out buf)) continue; else - Logger.Trace($"[USBDRIVER]: FIND _filterReader=0"); + Logger.Debug($"[USBDRIVER]: FIND _filterReader=0"); } buf.Rewind(); //Logger.Trace($"[USBDRIVER]: read requested"); @@ -518,7 +514,7 @@ protected virtual async Task InternalUsbReadAsync(CancellationToken ct = default ct.ThrowIfCancellationRequested(); if (ReadHeaderLength < buf.Position) { - Logger.Trace($"[USBDRIVER]: received {buf.Position}"); + Logger.Debug($"[USBDRIVER]: received {buf.Position}"); await outQueue.WriteAsync(buf, ct); buf = null; } @@ -533,7 +529,7 @@ protected virtual async Task InternalUsbReadAsync(CancellationToken ct = default Logger.Error($"[USBDRIVER]: crash {ex}"); return; } - Logger.Trace($"[USBDRIVER]: exit InternalUsbReadAsync"); + Logger.Trace($"[USBDRIVER]: exit UsbReadAsync"); } public virtual async Task ReadAsync(byte[] dstBuf, int offset, int count, CancellationToken ct = default) { @@ -578,6 +574,7 @@ public virtual async Task WriteAsync(byte[] wbuf, int offset, int count, Ca int rest = count; while (0 < rest) { + ct.ThrowIfCancellationRequested(); if (null == wr) wr = await writeRqQueueReader.ReadAsync(ct);// get a free write-request using var buf = new NetDirectByteBuffer(wbuf, offset, int.Min(rest, UsbBufLength)); @@ -587,8 +584,6 @@ public virtual async Task WriteAsync(byte[] wbuf, int offset, int count, Ca offset += buf.Position; rest -= buf.Position; } - if (null != wr) - while (!_writeChannel.Writer.TryWrite(wr)) ; return count - rest; } catch (OperationCanceledException) @@ -606,6 +601,15 @@ public virtual async Task WriteAsync(byte[] wbuf, int offset, int count, Ca Logger.Error($"[USBDRIVER]: write Exception {ex}"); throw; } + finally + { + if (null != wr) + { + // we will get here from "wr.QueueReq" + // or upon completion of sending + await _writeChannel.Writer.WriteAsync(wr); + } + } } } } \ No newline at end of file diff --git a/UsbSerialForAndroid.Net/Drivers/CdcAcmSerialDriver.cs b/UsbSerialForAndroid.Net/Drivers/CdcAcmSerialDriver.cs index b527797..13059fa 100644 --- a/UsbSerialForAndroid.Net/Drivers/CdcAcmSerialDriver.cs +++ b/UsbSerialForAndroid.Net/Drivers/CdcAcmSerialDriver.cs @@ -1,6 +1,5 @@ using Android.Hardware.Usb; using System; -using System.Buffers; using System.Threading.Tasks; using UsbSerialForAndroid.Net.Enums; using UsbSerialForAndroid.Net.Exceptions; @@ -19,7 +18,7 @@ public class CdcAcmSerialDriver : UsbDriverBase private UsbInterface? controlInterface; private UsbEndpoint? controlEndpoint; public CdcAcmSerialDriver(UsbDevice usbDevice) : base(usbDevice) { } - public override void Open(int baudRate, byte dataBits, StopBits stopBits, Parity parity) + public override async ValueTask OpenAsync(int baudRate, byte dataBits, StopBits stopBits, Parity parity) { UsbDeviceConnection = UsbManager.OpenDevice(UsbDevice); @@ -33,7 +32,7 @@ public override void Open(int baudRate, byte dataBits, StopBits stopBits, Parity OpenInterface(); } SetParameters(baudRate, dataBits, stopBits, parity); - InitAsyncBuffers(); + await InitBuffersAsync(); } private void OpenSingleInterface() { @@ -238,12 +237,12 @@ private void SetParameters(int baudRate, byte dataBits, StopBits stopBits, Parit if (result < 0) throw new ControlTransferException("Set parameters failed", result, UsbRtAcm, SetLineCoding, 0, controlIndex, buffer, buffer.Length, ControlTimeout); } - public override void Close() + protected override ValueTask DisposeAsyncCore() { controlEndpoint?.Dispose(); controlEndpoint = null; UsbDeviceConnection?.ReleaseInterface(controlInterface); controlInterface?.Dispose(); controlInterface = null; - base.Close(); + return base.DisposeAsyncCore(); } public override void SetDtrEnabled(bool value) { diff --git a/UsbSerialForAndroid.Net/Drivers/FtdiSerialDriver.cs b/UsbSerialForAndroid.Net/Drivers/FtdiSerialDriver.cs index 8daa90a..e0f2fc5 100644 --- a/UsbSerialForAndroid.Net/Drivers/FtdiSerialDriver.cs +++ b/UsbSerialForAndroid.Net/Drivers/FtdiSerialDriver.cs @@ -1,6 +1,7 @@ using Android.Hardware.Usb; using System; using System.Buffers; +using System.Threading.Tasks; using UsbSerialForAndroid.Net.Enums; using UsbSerialForAndroid.Net.Exceptions; @@ -30,8 +31,8 @@ public class FtdiSerialDriver : UsbDriverBase public const int SetLatencyTimerRequest = 9; // SET_LATENCY_TIMER_REQUEST public const int GetLatencyTimerRequest = 10; // GET_LATENCY_TIMER_REQUEST - public FtdiSerialDriver(UsbDevice usbDevice) - : base(usbDevice) + public FtdiSerialDriver(UsbDevice usbDevice) + : base(usbDevice) { ReadHeaderLength = 2; } @@ -43,7 +44,7 @@ public FtdiSerialDriver(UsbDevice usbDevice) /// /// /// - public override void Open(int baudRate = DefaultBaudRate, byte dataBits = DefaultDataBits, StopBits stopBits = DefaultStopBits, Parity parity = DefaultParity) + public override async ValueTask OpenAsync(int baudRate = DefaultBaudRate, byte dataBits = DefaultDataBits, StopBits stopBits = DefaultStopBits, Parity parity = DefaultParity) { UsbDeviceConnection = UsbManager.OpenDevice(UsbDevice); ArgumentNullException.ThrowIfNull(UsbDeviceConnection); @@ -76,7 +77,7 @@ public override void Open(int baudRate = DefaultBaudRate, byte dataBits = Defaul SetParameter(baudRate, dataBits, stopBits, parity); SetLatency(1); FilterData = FilterBuf; - InitAsyncBuffers(); + await InitBuffersAsync(); } /// /// Reset the USB device diff --git a/UsbSerialForAndroid.Net/Drivers/ProlificSerialDriver.cs b/UsbSerialForAndroid.Net/Drivers/ProlificSerialDriver.cs index 40c225c..8fb76c9 100644 --- a/UsbSerialForAndroid.Net/Drivers/ProlificSerialDriver.cs +++ b/UsbSerialForAndroid.Net/Drivers/ProlificSerialDriver.cs @@ -1,5 +1,6 @@ using Android.Hardware.Usb; using System; +using System.Threading.Tasks; using UsbSerialForAndroid.Net.Enums; using UsbSerialForAndroid.Net.Exceptions; @@ -55,7 +56,7 @@ public ProlificSerialDriver(UsbDevice usbDevice) : base(usbDevice) { } /// /// /// - public override void Open(int baudRate = DefaultBaudRate, byte dataBits = DefaultDataBits, StopBits stopBits = DefaultStopBits, Parity parity = DefaultParity) + public override async ValueTask OpenAsync(int baudRate = DefaultBaudRate, byte dataBits = DefaultDataBits, StopBits stopBits = DefaultStopBits, Parity parity = DefaultParity) { UsbDeviceConnection = UsbManager.OpenDevice(UsbDevice); ArgumentNullException.ThrowIfNull(UsbDeviceConnection); @@ -127,12 +128,12 @@ public override void Open(int baudRate = DefaultBaudRate, byte dataBits = Defaul SetFlowControl(FlowControl); SetParameter(baudRate, dataBits, stopBits, parity); - InitAsyncBuffers(); + await InitBuffersAsync(); } - public override void Close() + protected override ValueTask DisposeAsyncCore() { UsbEndpointInterupt?.Dispose(); UsbEndpointInterupt = null; - base.Close(); + return base.DisposeAsyncCore(); } /// /// Set parameter @@ -145,9 +146,9 @@ private void SetParameter(int baudRate, byte dataBits, StopBits stopBits, Parity { var para = new byte[7]; para[0] = (byte)(baudRate & 0xFF); - para[1] = (byte)(baudRate >> 8 & 0xFF); - para[2] = (byte)(baudRate >> 16 & 0xFF); - para[3] = (byte)(baudRate >> 24 & 0xFF); + para[1] = (byte)((baudRate >> 8) & 0xFF); + para[2] = (byte)((baudRate >> 16) & 0xFF); + para[3] = (byte)((baudRate >> 24) & 0xFF); switch (stopBits) { case StopBits.None: diff --git a/UsbSerialForAndroid.Net/Drivers/QinHengSerialDriver.cs b/UsbSerialForAndroid.Net/Drivers/QinHengSerialDriver.cs index 237fe9c..5d34924 100644 --- a/UsbSerialForAndroid.Net/Drivers/QinHengSerialDriver.cs +++ b/UsbSerialForAndroid.Net/Drivers/QinHengSerialDriver.cs @@ -1,5 +1,6 @@ using Android.Hardware.Usb; using System; +using System.Threading.Tasks; using UsbSerialForAndroid.Net.Enums; using UsbSerialForAndroid.Net.Exceptions; @@ -48,7 +49,7 @@ public QinHengSerialDriver(UsbDevice usbDevice) : base(usbDevice) { } /// /// /// - public override void Open(int baudRate = DefaultBaudRate, byte dataBits = DefaultDataBits, StopBits stopBits = DefaultStopBits, Parity parity = DefaultParity) + public override async ValueTask OpenAsync(int baudRate = DefaultBaudRate, byte dataBits = DefaultDataBits, StopBits stopBits = DefaultStopBits, Parity parity = DefaultParity) { UsbDeviceConnection = UsbManager.OpenDevice(UsbDevice); ArgumentNullException.ThrowIfNull(UsbDeviceConnection); @@ -80,10 +81,9 @@ public override void Open(int baudRate = DefaultBaudRate, byte dataBits = Defaul } } } - Initialize(); SetParameter(baudRate, dataBits, stopBits, parity); - InitAsyncBuffers(); + await InitBuffersAsync(); } /// /// Initialize the device @@ -182,7 +182,7 @@ private void SetBaudRate(int baudRate) if (ChipVersion > 0x27) index1 |= 0x80; // BIT(7) ControlOut("Error setting baud rate. #1", CH341_REQ_WRITE_REG, value1, index1); - const int value2 = 0x0f2c; + const int value2 = 0x0f2c; int index2 = baud[(i * 3) + 2]; ControlOut("Error setting baud rate. #2", CH341_REQ_WRITE_REG, value2, index2); return; diff --git a/UsbSerialForAndroid.Net/Drivers/SiliconLabsSerialDriver.cs b/UsbSerialForAndroid.Net/Drivers/SiliconLabsSerialDriver.cs index 5f06a5e..e958b2a 100644 --- a/UsbSerialForAndroid.Net/Drivers/SiliconLabsSerialDriver.cs +++ b/UsbSerialForAndroid.Net/Drivers/SiliconLabsSerialDriver.cs @@ -1,5 +1,6 @@ using Android.Hardware.Usb; using System; +using System.Threading.Tasks; using UsbSerialForAndroid.Net.Enums; using UsbSerialForAndroid.Net.Exceptions; @@ -36,7 +37,7 @@ public SiliconLabsSerialDriver(UsbDevice usbDevice) : base(usbDevice) { } /// /// /// - public override void Open(int baudRate = DefaultBaudRate, byte dataBits = DefaultDataBits, StopBits stopBits = DefaultStopBits, Parity parity = DefaultParity) + public override async ValueTask OpenAsync(int baudRate = DefaultBaudRate, byte dataBits = DefaultDataBits, StopBits stopBits = DefaultStopBits, Parity parity = DefaultParity) { UsbDeviceConnection = UsbManager.OpenDevice(UsbDevice); ArgumentNullException.ThrowIfNull(UsbDeviceConnection); @@ -63,21 +64,20 @@ public override void Open(int baudRate = DefaultBaudRate, byte dataBits = Defaul } } } - SetUartEnabled(); SetConfigSingle(SilabserSetMhsRequestCode, McrAll | ControlDtrDisable | ControlRtsDisable); //SetConfigSingle(SilabserSetBauddivRequestCode, BaudRateGenFreq / DefaultBaudRate); SetParameter(baudRate, dataBits, stopBits, parity); - InitAsyncBuffers(); + await InitBuffersAsync(); } /// /// close port /// - public override void Close() + protected override ValueTask DisposeAsyncCore() { PurgeHwBuffers(true, true); SetConfigSingle(SilabserIcfEnableRquestCode, UartDisable); - base.Close(); + return base.DisposeAsyncCore(); } /// /// Set the UART enabled diff --git a/UsbSerialForAndroid.Net/Helper/BaseDisposable.cs b/UsbSerialForAndroid.Net/Helper/BaseDisposable.cs new file mode 100644 index 0000000..3b8990b --- /dev/null +++ b/UsbSerialForAndroid.Net/Helper/BaseDisposable.cs @@ -0,0 +1,48 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using UsbSerialForAndroid.Net.Logging; + +namespace UsbSerialForAndroid.Net.Helper; + +public class BaseDisposable : IAnyDisposable, IHaveLogger +{ + private int _isDisposed = 0; + public bool IsDisposed => 0 != _isDisposed; + public ILogger Logger { get; set; } = new LoggerAndroid(); + + ~BaseDisposable() + { + if (IsDisposed) + return; + DisposeInternal(false).SynchronousWait(); + } + public async ValueTask DisposeAsync() + { + await DisposeInternal(true).ConfigureAwait(false); + GC.SuppressFinalize(this); + } + public async void Dispose() + { + await DisposeInternal(true).ConfigureAwait(false); + GC.SuppressFinalize(this); + } + private async Task DisposeInternal(bool disposing) + { + if (0 != Interlocked.Exchange(ref _isDisposed, 1)) + return; + try + { + if (!disposing) + Logger.Warning($"MEMORY LEAK: {GetType().FullName}"); + await DisposeAsyncCore().ConfigureAwait(false); + Dispose(disposing); + } + catch (Exception ex) + { + Logger.Error($"FAILED Dispose {ex}"); + } + } + protected virtual void Dispose(bool disposing) { } + protected virtual ValueTask DisposeAsyncCore() => ValueTask.CompletedTask; +} diff --git a/UsbSerialForAndroid.Net/Helper/IAnyDisposable.cs b/UsbSerialForAndroid.Net/Helper/IAnyDisposable.cs new file mode 100644 index 0000000..6c90cf7 --- /dev/null +++ b/UsbSerialForAndroid.Net/Helper/IAnyDisposable.cs @@ -0,0 +1,8 @@ +using System; +using UsbSerialForAndroid.Net.Logging; + +namespace UsbSerialForAndroid.Net.Helper; + +public interface IAnyDisposable : IDisposable, IAsyncDisposable +{ +} \ No newline at end of file diff --git a/UsbSerialForAndroid.Net/Helper/SynchronousWaitExt.cs b/UsbSerialForAndroid.Net/Helper/SynchronousWaitExt.cs new file mode 100644 index 0000000..688217d --- /dev/null +++ b/UsbSerialForAndroid.Net/Helper/SynchronousWaitExt.cs @@ -0,0 +1,20 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace UsbSerialForAndroid.Net.Helper; +public static class SynchronousWaitExt +{ + public static void SynchronousWait(this Task t) + { + if (SynchronizationContext.Current == null && TaskScheduler.Current == TaskScheduler.Default) + t.GetAwaiter().GetResult(); + else + Task.Run(() => t).GetAwaiter().GetResult(); + } + public static T SynchronousWait(this Task t) + { + if (SynchronizationContext.Current == null && TaskScheduler.Current == TaskScheduler.Default) + return t.GetAwaiter().GetResult(); + return Task.Run(() => t).GetAwaiter().GetResult(); + } +} diff --git a/UsbSerialForAndroid.Net/Logging/ILogger.cs b/UsbSerialForAndroid.Net/Logging/ILogger.cs index 10f11bd..1ce1aee 100644 --- a/UsbSerialForAndroid.Net/Logging/ILogger.cs +++ b/UsbSerialForAndroid.Net/Logging/ILogger.cs @@ -11,4 +11,9 @@ public interface ILogger void Warning(string msg); void Error(string msg); void Error(Exception ex); +} + +public interface IHaveLogger +{ + ILogger Logger { get; } } \ No newline at end of file From fdc0ffcd1dab802a36143b1a6251e4e682680450 Mon Sep 17 00:00:00 2001 From: "SAVPC\\Sav" Date: Fri, 28 Nov 2025 10:47:27 +0700 Subject: [PATCH 25/39] add test: IO speed --- Demo/MauiDemo/MainPage.xaml | 33 ++++- Demo/MauiDemo/MainPage.xaml.cs | 16 +- Demo/MauiDemo/Models/IOTestModel.cs | 139 ++++++++++++++++++ Demo/MauiDemo/ViewModels/MainViewModel.cs | 38 +++++ .../Drivers/Base/UsbDriverBase.cs | 75 +++++++++- UsbSerialForAndroid.Net/Logging/ILogger.cs | 4 +- .../Logging/LoggerAndroid.cs | 7 +- 7 files changed, 298 insertions(+), 14 deletions(-) create mode 100644 Demo/MauiDemo/Models/IOTestModel.cs diff --git a/Demo/MauiDemo/MainPage.xaml b/Demo/MauiDemo/MainPage.xaml index 7be44fe..91a6521 100644 --- a/Demo/MauiDemo/MainPage.xaml +++ b/Demo/MauiDemo/MainPage.xaml @@ -53,6 +53,8 @@ 57600 115200 194000 + 460800 + 921600 @@ -106,7 +108,7 @@ - + @@ -147,6 +149,35 @@ + + +