ScpControl/ScpDevice.cs (559 lines of code) (raw):

using System; using System.Globalization; using System.Reflection; using System.Runtime.InteropServices; using System.Text.RegularExpressions; using log4net; using ScpControl.Driver; using ScpControl.Usb; namespace ScpControl { /// <summary> /// Low-level representation of an Scp-compatible Usb device. /// </summary> public partial class ScpDevice { protected static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); #region Ctors protected ScpDevice() { } protected ScpDevice(Guid Class) { this._class = Class; } #endregion protected bool IsActive { get; set; } public string Path { get; protected set; } public short VendorId { get; protected set; } public short ProductId { get; protected set; } protected void GetHardwareId(string devicePath) { short vid, pid; GetHardwareId(devicePath, out vid, out pid); // get values VendorId = vid; ProductId = pid; } public static void GetHardwareId(string devicePath, out short vendorId, out short productId) { // regex to extract vendor ID and product ID from hardware ID string var regex = new Regex("VID_([0-9A-Z]{4})&PID_([0-9A-Z]{4})", RegexOptions.IgnoreCase); // matched groups var matches = regex.Match(devicePath).Groups; // very basic check if (matches.Count < 3) { vendorId = productId = 0; return; } // get values vendorId = short.Parse(matches[1].Value, NumberStyles.HexNumber); productId = short.Parse(matches[2].Value, NumberStyles.HexNumber); } public virtual bool Open(int instance = 0) { var devicePath = string.Empty; if (FindDevice(_class, ref devicePath, instance)) { Open(devicePath); } return IsActive; } public virtual bool Open(string devicePath) { GetHardwareId(devicePath); Path = devicePath.ToUpper(); if (GetDeviceHandle(Path)) { if (WinUsbWrapper.Initialize(FileHandle, ref _winUsbHandle)) { if (InitializeDevice()) { IsActive = true; } else { WinUsbWrapper.Free(_winUsbHandle); _winUsbHandle = (IntPtr)INVALID_HANDLE_VALUE; } } else { CloseHandle(FileHandle); } } return IsActive; } public virtual bool Start() { return IsActive; } public virtual bool Stop() { IsActive = false; if (!(_winUsbHandle == (IntPtr)INVALID_HANDLE_VALUE)) { WinUsbWrapper.AbortPipe(_winUsbHandle, IntIn); WinUsbWrapper.AbortPipe(_winUsbHandle, BulkIn); WinUsbWrapper.AbortPipe(_winUsbHandle, BulkOut); WinUsbWrapper.Free(_winUsbHandle); _winUsbHandle = (IntPtr)INVALID_HANDLE_VALUE; } if (FileHandle != IntPtr.Zero) { CloseHandle(FileHandle); FileHandle = IntPtr.Zero; } return true; } public virtual bool Close() { return Stop(); } protected static ushort ToValue(UsbHidClassDescriptorType type, byte index = 0x00) { return BitConverter.ToUInt16(new[] { (byte)index, (byte)type }, 0); } protected static ushort ToValue(UsbHidReportRequestType type) { return (ushort) ((byte) type << 8 | (byte) 0x00); } protected static ushort ToValue(UsbHidReportRequestType type, UsbHidReportRequestId id) { return BitConverter.ToUInt16(new[] { (byte)id, (byte)type }, 0); } protected bool IsBitSet(byte value, int offset) { return ((value >> offset) & 1) == 0x01; } #region WinUSB wrapper methods protected bool ReadIntPipe(byte[] buffer, int length, ref int transfered) { return IsActive && WinUsbWrapper.ReadPipe(_winUsbHandle, IntIn, buffer, length, ref transfered, IntPtr.Zero); } protected bool ReadBulkPipe(byte[] buffer, int length, ref int transfered) { return IsActive && WinUsbWrapper.ReadPipe(_winUsbHandle, BulkIn, buffer, length, ref transfered, IntPtr.Zero); } protected bool WriteIntPipe(byte[] buffer, int length, ref int transfered) { return IsActive && WinUsbWrapper.WritePipe(_winUsbHandle, IntOut, buffer, length, ref transfered, IntPtr.Zero); } protected bool WriteBulkPipe(byte[] buffer, int length, ref int transfered) { return IsActive && WinUsbWrapper.WritePipe(_winUsbHandle, BulkOut, buffer, length, ref transfered, IntPtr.Zero); } protected bool SendTransfer(UsbHidRequestType requestType, UsbHidRequest request, ushort value, byte[] buffer, ref int transfered) { return SendTransfer((byte)requestType, (byte)request, value, buffer, ref transfered); } protected bool SendTransfer(byte requestType, byte request, ushort value, byte[] buffer, ref int transfered) { if (!IsActive) return false; var setup = new WINUSB_SETUP_PACKET { RequestType = requestType, Request = request, Value = value, Index = 0, Length = (ushort)buffer.Length }; return WinUsbWrapper.ControlTransfer(_winUsbHandle, setup, buffer, buffer.Length, ref transfered, IntPtr.Zero); } #endregion #region Constant and Structure Definitions public const int SERVICE_CONTROL_STOP = 0x00000001; public const int SERVICE_CONTROL_SHUTDOWN = 0x00000005; public const int SERVICE_CONTROL_DEVICEEVENT = 0x0000000B; public const int SERVICE_CONTROL_POWEREVENT = 0x0000000D; public const int DBT_DEVICEARRIVAL = 0x8000; public const int DBT_DEVICEQUERYREMOVE = 0x8001; public const int DBT_DEVICEREMOVECOMPLETE = 0x8004; public const int DBT_DEVTYP_DEVICEINTERFACE = 0x0005; public const int DBT_DEVTYP_HANDLE = 0x0006; public const int PBT_APMRESUMEAUTOMATIC = 0x0012; public const int PBT_APMSUSPEND = 0x0004; public const int DEVICE_NOTIFY_WINDOW_HANDLE = 0x0000; public const int DEVICE_NOTIFY_SERVICE_HANDLE = 0x0001; public const int DEVICE_NOTIFY_ALL_INTERFACE_CLASSES = 0x0004; public const int WM_DEVICECHANGE = 0x0219; public const int DIGCF_PRESENT = 0x0002; public const int DIGCF_DEVICEINTERFACE = 0x0010; public delegate int ServiceControlHandlerEx(int Control, int Type, IntPtr Data, IntPtr Context); [StructLayout(LayoutKind.Sequential)] public class DEV_BROADCAST_DEVICEINTERFACE { internal int dbcc_size; internal int dbcc_devicetype; internal int dbcc_reserved; internal Guid dbcc_classguid; internal short dbcc_name; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] public class DEV_BROADCAST_DEVICEINTERFACE_M { public int dbcc_size; public int dbcc_devicetype; public int dbcc_reserved; [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 16)] public byte[] dbcc_classguid; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 255)] public char[] dbcc_name; } [StructLayout(LayoutKind.Sequential)] public class DEV_BROADCAST_HDR { public int dbch_size; public int dbch_devicetype; public int dbch_reserved; } [StructLayout(LayoutKind.Sequential)] protected struct SP_DEVICE_INTERFACE_DATA { internal int cbSize; internal Guid InterfaceClassGuid; internal int Flags; internal IntPtr Reserved; } private const uint FILE_ATTRIBUTE_NORMAL = 0x80; private const uint FILE_FLAG_OVERLAPPED = 0x40000000; private const uint FILE_SHARE_READ = 1; private const uint FILE_SHARE_WRITE = 2; private const uint GENERIC_READ = 0x80000000; private const uint GENERIC_WRITE = 0x40000000; private const int INVALID_HANDLE_VALUE = -1; private const uint OPEN_EXISTING = 3; protected const uint DEVICE_SPEED = 1; protected const byte USB_ENDPOINT_DIRECTION_MASK = 0x80; protected enum POLICY_TYPE { SHORT_PACKET_TERMINATE = 1, AUTO_CLEAR_STALL = 2, PIPE_TRANSFER_TIMEOUT = 3, IGNORE_SHORT_PACKETS = 4, ALLOW_PARTIAL_READS = 5, AUTO_FLUSH = 6, RAW_IO = 7 } protected enum USB_DEVICE_SPEED { UsbLowSpeed = 1, UsbFullSpeed = 2, UsbHighSpeed = 3 } [StructLayout(LayoutKind.Sequential)] protected struct USB_CONFIGURATION_DESCRIPTOR { internal byte bLength; internal byte bDescriptorType; internal ushort wTotalLength; internal byte bNumInterfaces; internal byte bConfigurationValue; internal byte iConfiguration; internal byte bmAttributes; internal byte MaxPower; } protected const int DIF_PROPERTYCHANGE = 0x12; protected const int DICS_ENABLE = 1; protected const int DICS_DISABLE = 2; protected const int DICS_PROPCHANGE = 3; protected const int DICS_FLAG_GLOBAL = 1; [StructLayout(LayoutKind.Sequential)] protected struct SP_CLASSINSTALL_HEADER { internal int cbSize; internal int InstallFunction; } [StructLayout(LayoutKind.Sequential)] protected struct SP_PROPCHANGE_PARAMS { internal SP_CLASSINSTALL_HEADER ClassInstallHeader; internal int StateChange; internal int Scope; internal int HwProfile; } public enum WmDeviceChangeEvent : int { /// <summary> /// A request to change the current configuration (dock or undock) has been canceled. /// </summary> DBT_CONFIGCHANGECANCELED = 0x0019, /// <summary> /// The current configuration has changed, due to a dock or undock. /// </summary> DBT_CONFIGCHANGED = 0x0018, /// <summary> /// A custom event has occurred. /// </summary> DBT_CUSTOMEVENT = 0x8006, /// <summary> /// A device or piece of media has been inserted and is now available. /// </summary> DBT_DEVICEARRIVAL = 0x8000, /// <summary> /// Permission is requested to remove a device or piece of media. Any application can deny this request and cancel the /// removal. /// </summary> DBT_DEVICEQUERYREMOVE = 0x8001, /// <summary> /// A request to remove a device or piece of media has been canceled. /// </summary> DBT_DEVICEQUERYREMOVEFAILED = 0x8002, /// <summary> /// A device or piece of media has been removed. /// </summary> DBT_DEVICEREMOVECOMPLETE = 0x8004, /// <summary> /// A device or piece of media is about to be removed. Cannot be denied. /// </summary> DBT_DEVICEREMOVEPENDING = 0x8003, /// <summary> /// A device-specific event has occurred. /// </summary> DBT_DEVICETYPESPECIFIC = 0x8005, /// <summary> /// A device has been added to or removed from the system. /// </summary> DBT_DEVNODES_CHANGED = 0x0007, /// <summary> /// Permission is requested to change the current configuration (dock or undock). /// </summary> DBT_QUERYCHANGECONFIG = 0x0017, /// <summary> /// The meaning of this message is user-defined. /// </summary> DBT_USERDEFINED = 0xFFFF } #endregion #region Protected Data Members private Guid _class = Guid.Empty; protected IntPtr FileHandle = IntPtr.Zero; private IntPtr _winUsbHandle = (IntPtr)INVALID_HANDLE_VALUE; protected byte IntIn = 0xFF; protected byte IntOut = 0xFF; protected byte BulkIn = 0xFF; protected byte BulkOut = 0xFF; #endregion #region Static Helper Methods public enum Notified { Ignore = 0x0000, Arrival = 0x8000, QueryRemove = 0x8001, Removal = 0x8004 }; public static bool RegisterNotify(IntPtr form, Guid Class, ref IntPtr handle, bool window = true) { var devBroadcastDeviceInterfaceBuffer = IntPtr.Zero; try { var devBroadcastDeviceInterface = new DEV_BROADCAST_DEVICEINTERFACE(); var size = Marshal.SizeOf(devBroadcastDeviceInterface); devBroadcastDeviceInterface.dbcc_size = size; devBroadcastDeviceInterface.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE; devBroadcastDeviceInterface.dbcc_reserved = 0; devBroadcastDeviceInterface.dbcc_classguid = Class; devBroadcastDeviceInterfaceBuffer = Marshal.AllocHGlobal(size); Marshal.StructureToPtr(devBroadcastDeviceInterface, devBroadcastDeviceInterfaceBuffer, true); handle = RegisterDeviceNotification(form, devBroadcastDeviceInterfaceBuffer, window ? DEVICE_NOTIFY_WINDOW_HANDLE : DEVICE_NOTIFY_SERVICE_HANDLE); Marshal.PtrToStructure(devBroadcastDeviceInterfaceBuffer, devBroadcastDeviceInterface); return handle != IntPtr.Zero; } catch (Exception ex) { Log.ErrorFormat("{0} {1}", ex.HelpLink, ex.Message); throw; } finally { if (devBroadcastDeviceInterfaceBuffer != IntPtr.Zero) { Marshal.FreeHGlobal(devBroadcastDeviceInterfaceBuffer); } } } public static bool UnregisterNotify(IntPtr handle) { try { return UnregisterDeviceNotification(handle); } catch (Exception ex) { Log.ErrorFormat("{0} {1}", ex.HelpLink, ex.Message); throw; } } #endregion #region Protected Methods protected static bool FindDevice(Guid target, ref string path, int instance = 0) { var detailDataBuffer = IntPtr.Zero; var deviceInfoSet = IntPtr.Zero; try { SP_DEVICE_INTERFACE_DATA deviceInterfaceData = new SP_DEVICE_INTERFACE_DATA(), da = new SP_DEVICE_INTERFACE_DATA(); int bufferSize = 0, memberIndex = 0; deviceInfoSet = SetupDiGetClassDevs(ref target, IntPtr.Zero, IntPtr.Zero, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); deviceInterfaceData.cbSize = da.cbSize = Marshal.SizeOf(deviceInterfaceData); while (SetupDiEnumDeviceInterfaces(deviceInfoSet, IntPtr.Zero, ref target, memberIndex, ref deviceInterfaceData)) { SetupDiGetDeviceInterfaceDetail(deviceInfoSet, ref deviceInterfaceData, IntPtr.Zero, 0, ref bufferSize, ref da); { detailDataBuffer = Marshal.AllocHGlobal(bufferSize); Marshal.WriteInt32(detailDataBuffer, (IntPtr.Size == 4) ? (4 + Marshal.SystemDefaultCharSize) : 8); if (SetupDiGetDeviceInterfaceDetail(deviceInfoSet, ref deviceInterfaceData, detailDataBuffer, bufferSize, ref bufferSize, ref da)) { var pDevicePathName = detailDataBuffer + 4; path = (Marshal.PtrToStringAuto(pDevicePathName) ?? "ERROR").ToUpper(); Marshal.FreeHGlobal(detailDataBuffer); if (memberIndex == instance) return true; } else Marshal.FreeHGlobal(detailDataBuffer); } memberIndex++; } } catch (Exception ex) { Log.ErrorFormat("{0} {1}", ex.HelpLink, ex.Message); throw; } finally { if (deviceInfoSet != IntPtr.Zero) { SetupDiDestroyDeviceInfoList(deviceInfoSet); } } return false; } protected virtual bool GetDeviceInstance(ref string instance) { var detailDataBuffer = IntPtr.Zero; var deviceInfoSet = IntPtr.Zero; try { SP_DEVICE_INTERFACE_DATA deviceInterfaceData = new SP_DEVICE_INTERFACE_DATA(), da = new SP_DEVICE_INTERFACE_DATA(); int bufferSize = 0, memberIndex = 0; deviceInfoSet = SetupDiGetClassDevs(ref _class, IntPtr.Zero, IntPtr.Zero, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); deviceInterfaceData.cbSize = da.cbSize = Marshal.SizeOf(deviceInterfaceData); while (SetupDiEnumDeviceInterfaces(deviceInfoSet, IntPtr.Zero, ref _class, memberIndex, ref deviceInterfaceData)) { SetupDiGetDeviceInterfaceDetail(deviceInfoSet, ref deviceInterfaceData, IntPtr.Zero, 0, ref bufferSize, ref da); { detailDataBuffer = Marshal.AllocHGlobal(bufferSize); Marshal.WriteInt32(detailDataBuffer, (IntPtr.Size == 4) ? (4 + Marshal.SystemDefaultCharSize) : 8); if (SetupDiGetDeviceInterfaceDetail(deviceInfoSet, ref deviceInterfaceData, detailDataBuffer, bufferSize, ref bufferSize, ref da)) { var pDevicePathName = detailDataBuffer + 4; var current = (Marshal.PtrToStringAuto(pDevicePathName) ?? "ERROR").ToUpper(); Marshal.FreeHGlobal(detailDataBuffer); if (current == Path) { const int nBytes = 256; var ptrInstanceBuf = Marshal.AllocHGlobal(nBytes); CM_Get_Device_ID(da.Flags, ptrInstanceBuf, nBytes, 0); instance = (Marshal.PtrToStringAuto(ptrInstanceBuf) ?? "ERROR").ToUpper(); Marshal.FreeHGlobal(ptrInstanceBuf); return true; } } else Marshal.FreeHGlobal(detailDataBuffer); } memberIndex++; } } catch (Exception ex) { Log.ErrorFormat("{0} {1}", ex.HelpLink, ex.Message); throw; } finally { if (deviceInfoSet != IntPtr.Zero) { SetupDiDestroyDeviceInfoList(deviceInfoSet); } } return false; } protected virtual bool GetDeviceHandle(string path) { FileHandle = CreateFile(path, (GENERIC_WRITE | GENERIC_READ), FILE_SHARE_READ | FILE_SHARE_WRITE, IntPtr.Zero, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, 0); if (FileHandle == IntPtr.Zero || FileHandle == (IntPtr)INVALID_HANDLE_VALUE) { FileHandle = IntPtr.Zero; var lastError = GetLastError(); Log.DebugFormat("LastError = {0}", lastError); } return !(FileHandle == IntPtr.Zero); } protected virtual bool UsbEndpointDirectionIn(int addr) { return (addr & 0x80) == 0x80; } protected virtual bool UsbEndpointDirectionOut(int addr) { return (addr & 0x80) == 0x00; } protected virtual bool InitializeDevice() { try { var ifaceDescriptor = new USB_INTERFACE_DESCRIPTOR(); var pipeInfo = new WINUSB_PIPE_INFORMATION(); if (WinUsbWrapper.QueryInterfaceSettings(_winUsbHandle, 0, ref ifaceDescriptor)) { for (var i = 0; i < ifaceDescriptor.bNumEndpoints; i++) { WinUsbWrapper.QueryPipe(_winUsbHandle, 0, Convert.ToByte(i), ref pipeInfo); if (((pipeInfo.PipeType == USBD_PIPE_TYPE.UsbdPipeTypeBulk) & UsbEndpointDirectionIn(pipeInfo.PipeId))) { BulkIn = pipeInfo.PipeId; WinUsbWrapper.FlushPipe(_winUsbHandle, BulkIn); } else if (((pipeInfo.PipeType == USBD_PIPE_TYPE.UsbdPipeTypeBulk) & UsbEndpointDirectionOut(pipeInfo.PipeId))) { BulkOut = pipeInfo.PipeId; WinUsbWrapper.FlushPipe(_winUsbHandle, BulkOut); } else if ((pipeInfo.PipeType == USBD_PIPE_TYPE.UsbdPipeTypeInterrupt) & UsbEndpointDirectionIn(pipeInfo.PipeId)) { IntIn = pipeInfo.PipeId; WinUsbWrapper.FlushPipe(_winUsbHandle, IntIn); } else if ((pipeInfo.PipeType == USBD_PIPE_TYPE.UsbdPipeTypeInterrupt) & UsbEndpointDirectionOut(pipeInfo.PipeId)) { IntOut = pipeInfo.PipeId; WinUsbWrapper.FlushPipe(_winUsbHandle, IntOut); } } return true; } return false; } catch (Exception ex) { Log.ErrorFormat("{0} {1}", ex.HelpLink, ex.Message); throw; } } protected virtual bool RestartDevice(string instanceId) { var deviceInfoSet = IntPtr.Zero; try { var deviceInterfaceData = new SP_DEVICE_INTERFACE_DATA(); deviceInterfaceData.cbSize = Marshal.SizeOf(deviceInterfaceData); deviceInfoSet = SetupDiGetClassDevs(ref _class, IntPtr.Zero, IntPtr.Zero, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); if (SetupDiOpenDeviceInfo(deviceInfoSet, instanceId, IntPtr.Zero, 0, ref deviceInterfaceData)) { var props = new SP_PROPCHANGE_PARAMS(); props.ClassInstallHeader = new SP_CLASSINSTALL_HEADER(); props.ClassInstallHeader.cbSize = Marshal.SizeOf(props.ClassInstallHeader); props.ClassInstallHeader.InstallFunction = DIF_PROPERTYCHANGE; props.Scope = DICS_FLAG_GLOBAL; props.StateChange = DICS_PROPCHANGE; props.HwProfile = 0x00; if (SetupDiSetClassInstallParams(deviceInfoSet, ref deviceInterfaceData, ref props, Marshal.SizeOf(props))) { return SetupDiChangeState(deviceInfoSet, ref deviceInterfaceData); } } } catch (Exception ex) { Log.ErrorFormat("{0} {1}", ex.HelpLink, ex.Message); throw; } finally { if (deviceInfoSet != IntPtr.Zero) { SetupDiDestroyDeviceInfoList(deviceInfoSet); } } return false; } #endregion } }