Search code examples
c#winapiusbpower-managementwdm

Getting current USB power state


I've been trying to read the current power state of an USB port (D0/D1/D2/D3). I haven't been able to find much information on how to access the actual state. Here's a description of the USB Device Power States on Microsoft docs. It has a whole section on changing, but really didn't get how to read it. I have very little experience with working on the Windows and hardware level, so excuse me if it is obvious.

I've also found this Microsoft debug application written in C called USBView. If you install it and open the USB tree, the first information displayed for individual ports is its power state.

e.g.

Device Power State:               PowerDeviceD2

It has source available on GitHub, but the files are over 5000 lines long and I can't navigate C code good enough to tell how to actually read the power state.

I'm trying to implement this into a C# application, but help in any language will be appreciated!


Solution

  • After lots of and lots of digging in the USBView source code, I figured out you need to do the following:

    1. Get the handle to the device info set using

      IntPtr deviceInfoSet = SetupDiGetClassDevs(ref classGuid, null, IntPtr.Zero, 0x00000002 | 0x00000010)

      where the classGuid for USB devices is "A5DCBF10-6530-11D2-901F-00C04FB951ED"

    2. Get the individual device info using

      SetupDiEnumDeviceInfo(deviceInfoSet, index, ref deviceInfoData)

      where deviceInfoData is an instance of the SP_DEVINFO_DATA struct with the value of cbSize inicializace to 28. (The device info will be then stored in this struct.) You start with index = 0 and then increment until the method returns false and Marshal.GetLastWin32Error() returns 259 (ERROR_NO_MORE_ITEMS) to get all the devices.

    3. You then retrieve the power property using SetupDiGetDeviceRegistryProperty.

      You can either:

      A. pass in CM_POWER_DATA (then you should replace byte[] in the DllImport method signature with ref CM_POWER_DATA)

      B. pass in a byte array and then parse the byte array to CM_POWER_DATA (code from this answer proved very good at it).

    Here I'm showing option B - passing in byte array. The power data will be in the data variable. (Conversion skipped.)

      //sizeof evaluates to 56, if you want to hardcode it
      byte[] data = new byte[Marshal.SizeOf<CM_POWER_DATA>()];
    
      SetupDiGetDeviceRegistryProperty(
                        deviceInfoSet,
                        ref deviceInfoData,
                        0x0000001E, //the property SPDRP_DEVICE_POWER_DATA
                        out uint type,
                        data,
                        data.Length, 
                        out uint size)
                    );
    

    You can then query additional information with the SetupDiGetDeviceRegistryProperty function, like the HardwareID and description.


    Here is my code.

    Don't forget

    using System.Runtime.InteropServices;
    

    Constants of various GUIDs and properities

        public const uint SPDRP_DEVICEDESC = (0x00000000);  // DeviceDesc (R/W)
        public const uint SPDRP_HARDWAREID = (0x00000001);  // HardwareID (R/W)
        public const uint SPDRP_COMPATIBLEIDS = (0x00000002);  // CompatibleIDs (R/W)
        public const uint SPDRP_UNUSED0 = (0x00000003);  // unused
        public const uint SPDRP_SERVICE = (0x00000004);  // Service (R/W)
        public const uint SPDRP_UNUSED1 = (0x00000005);  // unused
        public const uint SPDRP_UNUSED2 = (0x00000006);  // unused
        public const uint SPDRP_CLASS = (0x00000007);  // Class (R--tied to ClassGUID)
        public const uint SPDRP_CLASSGUID = (0x00000008);  // ClassGUID (R/W)
        public const uint SPDRP_DRIVER = (0x00000009);  // Driver (R/W)
        public const uint SPDRP_CONFIGFLAGS = (0x0000000A);  // ConfigFlags (R/W)
        public const uint SPDRP_MFG = (0x0000000B);  // Mfg (R/W)
        public const uint SPDRP_FRIENDLYNAME = (0x0000000C);  // FriendlyName (R/W)
        public const uint SPDRP_LOCATION_INFORMATION = (0x0000000D);  // LocationInformation (R/W)
        public const uint SPDRP_PHYSICAL_DEVICE_OBJECT_NAME = (0x0000000E);  // PhysicalDeviceObjectName (R)
        public const uint SPDRP_CAPABILITIES = (0x0000000F);  // Capabilities (R)
        public const uint SPDRP_UI_NUMBER = (0x00000010);  // UiNumber (R)
        public const uint SPDRP_UPPERFILTERS = (0x00000011);  // UpperFilters (R/W)
        public const uint SPDRP_LOWERFILTERS = (0x00000012);  // LowerFilters (R/W)
        public const uint SPDRP_BUSTYPEGUID = (0x00000013);  // BusTypeGUID (R)
        public const uint SPDRP_LEGACYBUSTYPE = (0x00000014);  // LegacyBusType (R)
        public const uint SPDRP_BUSNUMBER = (0x00000015);  // BusNumber (R)
        public const uint SPDRP_ENUMERATOR_NAME = (0x00000016);  // Enumerator Name (R)
        public const uint SPDRP_SECURITY = (0x00000017);  // Security (R/W, binary form)
        public const uint SPDRP_SECURITY_SDS = (0x00000018);  // Security (W, SDS form)
        public const uint SPDRP_DEVTYPE = (0x00000019);  // Device Type (R/W)
        public const uint SPDRP_EXCLUSIVE = (0x0000001A);  // Device is exclusive-access (R/W)
        public const uint SPDRP_CHARACTERISTICS = (0x0000001B);  // Device Characteristics (R/W)
        public const uint SPDRP_ADDRESS = (0x0000001C);  // Device Address (R)
        public const uint SPDRP_UI_NUMBER_DESC_FORMAT = (0X0000001D);  // UiNumberDescFormat (R/W)
        public const uint SPDRP_DEVICE_POWER_DATA = (0x0000001E);  // Device Power Data (R)
        public const uint SPDRP_REMOVAL_POLICY = (0x0000001F);  // Removal Policy (R)
        public const uint SPDRP_REMOVAL_POLICY_HW_DEFAULT = (0x00000020);  // Hardware Removal Policy (R)
        public const uint SPDRP_REMOVAL_POLICY_OVERRIDE = (0x00000021);  // Removal Policy Override (RW)
        public const uint SPDRP_INSTALL_STATE = (0x00000022);  // Device Install State (R)
        public const uint SPDRP_LOCATION_PATHS = (0x00000023);  // Device Location Paths (R)
        public const uint SPDRP_BASE_CONTAINERID = (0x00000024);  // Base ContainerID (R)
    
        public const uint SPDRP_MAXIMUM_PROPERTY = (0x00000025);  // Upper bound on ordinals
    
    
        public const string GUID_DEVINTERFACE_USB_HUB = "f18a0e88-c30c-11d0-8815-00a0c906bed8";
        public const string GUID_DEVINTERFACE_USB_DEVICE = "A5DCBF10-6530-11D2-901F-00C04FB951ED";
        public const string GUID_DEVINTERFACE_USB_HOST_CONTROLLER = "3ABF6F2D-71C4-462a-8A92-1E6861E6AF27";
        public const string GUID_USB_WMI_STD_DATA = "4E623B20-CB14-11D1-B331-00A0C959BBD2";
        public const string GUID_USB_WMI_STD_NOTIFICATION = "4E623B20-CB14-11D1-B331-00A0C959BBD2";
    
        public const string GUID_USB_WMI_DEVICE_PERF_INFO = "66C1AA3C-499F-49a0-A9A5-61E2359F6407";
        public const string GUID_USB_WMI_NODE_INFO = "{9C179357-DC7A-4f41-B66B-323B9DDCB5B1}";
        public const string GUID_USB_WMI_TRACING = "3a61881b-b4e6-4bf9-ae0f-3cd8f394e52f";
        public const string GUID_USB_TRANSFER_TRACING = "{681EB8AA-403D-452c-9F8A-F0616FAC9540}";
        public const string GUID_USB_PERFORMANCE_TRACING = "{D5DE77A6-6AE9-425c-B1E2-F5615FD348A9}";
        public const string GUID_USB_WMI_SURPRISE_REMOVAL_NOTIFICATION = "{9BBBF831-A2F2-43B4-96D1-86944B5914B3}";
    

    Structs and enums:

    public enum DEVICE_POWER_STATE {
        PowerDeviceUnspecified,
        PowerDeviceD0,
        PowerDeviceD1,
        PowerDeviceD2,
        PowerDeviceD3,
        PowerDeviceMaximum
    }
    
    public enum SYSTEM_POWER_STATE {
        PowerSystemUnspecified,
        PowerSystemWorking,
        PowerSystemSleeping1,
        PowerSystemSleeping2,
        PowerSystemSleeping3,
        PowerSystemHibernate,
        PowerSystemShutdown,
        PowerSystemMaximum
    }
    
    [StructLayout(LayoutKind.Sequential, Pack = 8)]
    public struct SP_DEVINFO_DATA {
        public UInt32 cbSize;
        public Guid ClassGuid;
        public UInt32 DevInst;
        public IntPtr Reserved;
    }
    
    [StructLayout(LayoutKind.Sequential, Pack = 8)]
    public struct CM_POWER_DATA {
        public uint PD_Size;
        public DEVICE_POWER_STATE PD_MostRecentPowerState;
        public uint PD_Capabilities;
        public uint PD_D1Latency;
        public uint PD_D2Latency;
        public uint PD_D3Latency;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 7)]
        public DEVICE_POWER_STATE[] PD_PowerStateMapping;
        public SYSTEM_POWER_STATE PD_DeepestSystemWake;
    }
    

    DllImports

    [DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern bool SetupDiGetDeviceRegistryPropertyA(
            IntPtr DeviceInfoSet,
            ref SP_DEVINFO_DATA DeviceInfoData,
            uint Property,
            out RegistryDataType PropertyRegDataType,
            byte[] PropertyBuffer,
            //ref CM_POWER_DATA PropertyBuffer,
            uint PropertyBufferSize,
            out UInt32 RequiredSize
        );
    
    
        [DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr SetupDiGetClassDevsA(
                                              ref Guid ClassGuid,
                                              [MarshalAs(UnmanagedType.LPTStr)] string Enumerator,
                                              IntPtr hwndParent,
                                              uint Flags
                                             );
    
    
        [DllImport("setupapi.dll", SetLastError=true)]
        private static extern bool SetupDiEnumDeviceInfo(
                                                IntPtr DeviceInfoSet, 
                                                uint MemberIndex, 
                                                ref SP_DEVINFO_DATA DeviceInfoData
                                                );
    

    Various function for making the retrieve a bit fancier and easier to use. The main one and the one I've described above is the first one - GetInfoWithSetupDi. (You need to pass the GUID_DEVINTERFACE_USB_DEVICE to it.)

        /// <summary>
        /// Gets information about devices in the device class including power state.
        /// </summary>
        /// <param name="classGuid">The GUID of the class in which to get information about devices from.</param>
        /// <returns></returns>
        public static List<DeviceInfo> GetInfoWithSetupDi(Guid classGuid) {
            List<DeviceInfo> deviceInfos = new List<DeviceInfo>();
    
            IntPtr deviceInfoSet = SetupDiGetClassDevsA(ref classGuid, null, IntPtr.Zero, 0x00000002 | 0x00000010);           
    
            uint index = 0;
            int error = 0;
    
            while (error == 0) {
                DeviceInfo curDevice = new DeviceInfo {
                    //Initializing SP_DEVINFO_DATA to be passed to SetupDiEnumDeviceInfo.
                    deviceInfoData = new SP_DEVINFO_DATA {
                        cbSize = 28
                    }
                };
    
                //Retrieves the information about the specified device.
                bool success = SetupDiEnumDeviceInfo(deviceInfoSet, index, ref curDevice.deviceInfoData);
                index++;
                error = Marshal.GetLastWin32Error();
    
                if (error == 259)
                    { break; }
    
                if (!success)
                    { throw new Exception("Native method call error: " + error.ToString()); }
    
                //Only add device after it was at least successfully retrieved.
                deviceInfos.Add(curDevice);
    
                //Retrieving individual information.
                RegistryData localGetData(uint property) => GetData(deviceInfoSet, curDevice.deviceInfoData, property);
    
                curDevice.hardwareId = (string[]) localGetData(SPDRP_HARDWAREID).parsed;
                try {
                    curDevice.description = (string)localGetData(SPDRP_DEVICEDESC).parsed;
                } catch {
                    curDevice.description = "Description not set.";
                }               
                curDevice.cmPowerData = MarshallingUtils.FromBytes<CM_POWER_DATA>(
                    localGetData(SPDRP_DEVICE_POWER_DATA).data
                );
            }
    
            return deviceInfos;           
        }
    
    
        /// <summary>
        /// Gets the required size in bytes for the given property.
        /// </summary>
        /// <param name="deviceInfoSet">A handle to the set of devices.</param>
        /// <param name="property">The property for which the get the required size.</param>
        private static uint GetRequiredSize(IntPtr deviceInfoSet, uint Property) {
            SP_DEVINFO_DATA deviceInfoData = new SP_DEVINFO_DATA {
                cbSize = 28
            };
    
            ThrowErrorIfNotSuccessful(SetupDiEnumDeviceInfo(deviceInfoSet, 0, ref deviceInfoData));
    
            return GetRequiredSize(deviceInfoSet, Property, deviceInfoData);
        }
    
    
        /// <summary>
        /// Gets the required size in bytes for the given property.
        /// </summary>
        /// <param name="deviceInfoSet">A handle to the set of devices.</param>
        /// <param name="property">The property for which the get the required size.</param>
        /// <param name="deviceInfoData">Info of a device.</param>
        /// <returns></returns>
        private static uint GetRequiredSize(IntPtr deviceInfoSet, uint property, SP_DEVINFO_DATA deviceInfoData) {
            ThrowErrorIfNotSuccessful(
                SetupDiGetDeviceRegistryPropertyA(
                    deviceInfoSet,
                    ref deviceInfoData,
                    property,
                    out RegistryDataType type,
                    new byte[1000],
                    1000,
                    out uint size)
                );
    
            return size;
        }
    
    
        /// <summary>
        /// Gets the property using SetupDi of the device on the given index in the given device class.
        /// </summary>
        /// <param name="classGuid">The GUID of the device class.</param>
        /// <param name="index">The index of the device.</param>
        /// <param name="property">The property to retrieve.</param>
        /// <returns></returns>
        public static RegistryData GetData(Guid classGuid, uint index, uint property) {
            IntPtr deviceInfoSet = SetupDiGetClassDevsA(ref classGuid, null, IntPtr.Zero, 0x00000002 | 0x00000010);
    
            SP_DEVINFO_DATA deviceInfoData = new SP_DEVINFO_DATA {
                cbSize = 28
            };
    
            ThrowErrorIfNotSuccessful(SetupDiEnumDeviceInfo(deviceInfoSet, index, ref deviceInfoData));
    
            return GetData(deviceInfoSet, deviceInfoData, property);
        }
    
    
        /// <summary>
        /// Gets the specified property using SetupDi of the device described in deviceInfoData in the given device info set.
        /// The size is retrieved automatically.
        /// </summary>
        /// <param name="deviceInfoSet">A handle to the device info set.</param>
        /// <param name="deviceInfoData">Description of the device.</param>
        /// <param name="property">The property to retrieve.</param>
        /// <returns></returns>
        public static RegistryData GetData(IntPtr deviceInfoSet, SP_DEVINFO_DATA deviceInfoData, uint property) {
            uint size = GetRequiredSize(deviceInfoSet, property, deviceInfoData);
    
            return GetData(deviceInfoSet, deviceInfoData, property, size);
        }
    
    
        /// <summary>
        /// Gets the specified property using SetupDi of the device described in deviceInfoData in the given device info set.
        /// </summary>
        /// <param name="deviceInfoSet">A handle to the device info set.</param>
        /// <param name="deviceInfoData">Description of the device.</param>
        /// <param name="property">The property to retrieve.</param>
        /// <returns></returns>
        public static RegistryData GetData(IntPtr deviceInfoSet, SP_DEVINFO_DATA deviceInfoData, uint property, uint size) {
            byte[] data = new byte[size];
    
            ThrowErrorIfNotSuccessful(
                SetupDiGetDeviceRegistryPropertyA(
                    deviceInfoSet,
                    ref deviceInfoData,
                    property,
                    out RegistryDataType type,
                    data,
                    size,
                    out uint dummysize)
                );
    
            return new RegistryData(type, data);
        }
    
        /// <summary>
        /// Method used to wrap native DLL calls. Throws the last system error when the call is unsuccessful.
        /// </summary>
        /// <param name="success">Return value of the native method. ( Usually used as ThrowErrorIfNotSuccessful(MethodCall()); )</param>
        private static void ThrowErrorIfNotSuccessful(bool success) {
            if (!success) {
                throw new Exception("Native method call error: " + Marshal.GetLastWin32Error().ToString());
            }
        }
    

    Generic version of the byte array <-> struct parser mentioned above.

    public static class MarshallingUtils {
        public static byte[] GetBytes<T>(T str) where T : struct {
            int size = Marshal.SizeOf(str);
            byte[] arr = new byte[size];
    
            IntPtr ptr = Marshal.AllocHGlobal(size);
            Marshal.StructureToPtr(str, ptr, true);
            Marshal.Copy(ptr, arr, 0, size);
            Marshal.FreeHGlobal(ptr);
            return arr;
        }
    
        public static T FromBytes<T>(byte[] arr) where T : struct {
            T str = new T();
    
            int size = Marshal.SizeOf(str);
            IntPtr ptr = Marshal.AllocHGlobal(size);
    
            Marshal.Copy(arr, 0, ptr, size);
    
            str = (T)Marshal.PtrToStructure(ptr, str.GetType());
            Marshal.FreeHGlobal(ptr);
    
            return str;
        }
    }
    

    Helper class for parsing the retrieved registry data.

    public struct RegistryData {
        public readonly RegistryDataType type;
        public readonly byte[] data;
        public readonly object parsed;
    
        public RegistryData(RegistryDataType type, byte[] data) {
            this.type = type;
            this.data = data;
            this.parsed = ParseData(type, data);
        }
    
    
        public static string ParseString(byte[] data) {
            string s = "";
            foreach (byte b in data) {
                if (b == 0) {
                    break;
                }
                s += (char)b;
            }
            return s;
        }
    
    
        public static string[] ParseMultiString(byte[] data) {
            List<string> list = new List<string>();
            string current = "";
            bool terminator = false;
    
            foreach (byte b in data) {
                if (b == 0) {
                    if (terminator) {
                        break;
                    }
                    else {
                        terminator = true;
                        //Start a new string.
                        list.Add(current);
                        current = "";
                    }
                }
                else {
                    terminator = false;
                    current += (char)b;
                }
            }
            return list.ToArray();
        }
    
    
        private static object ParseData(RegistryDataType type, byte[] data) {
            switch (type) {
                case RegistryDataType.REG_SZ:
                    return (object)ParseString(data);
    
                case RegistryDataType.REG_MULTI_SZ:
                    return (object)ParseMultiString(data);
    
                default: return null;
            }
        }
    
    
        public override string ToString() {
            switch (type) {
                case RegistryDataType.REG_SZ: return (string)parsed;
                case RegistryDataType.REG_MULTI_SZ: return String.Join(";", (List<string>)parsed);
                default: return null;
            }
        }
    }
    

    And one last enum for the retrieved registry data type.

    public enum RegistryDataType {
        REG_NONE = (0),   // No value type
        REG_SZ = (1),   // Unicode nul terminated string
        REG_EXPAND_SZ = (2),   // Unicode nul terminated string
                               // = (with environment variable references)
        REG_BINARY = (3),   // Free form binary
                            //REG_DWORD = (4),   // 32-bit number
        REG_DWORD_LITTLE_ENDIAN = (4),   // 32-bit number = (same as REG_DWORD)
        REG_DWORD_BIG_ENDIAN = (5),   // 32-bit number
        REG_LINK = (6),   // Symbolic Link = (unicode)
        REG_MULTI_SZ = (7),   // Multiple Unicode strings
        REG_RESOURCE_LIST = (8),   // Resource list in the resource map
        REG_FULL_RESOURCE_DESCRIPTOR = (9),  // Resource list in the hardware description
        REG_RESOURCE_REQUIREMENTS_LIST = (10),
        //REG_QWORD = (11),  // 64-bit number
        REG_QWORD_LITTLE_ENDIAN = (11),  // 64-bit number = (same as REG_QWORD)
    }
    

    And also a class to hold the info.

    public class DeviceInfo {
        public SP_DEVINFO_DATA deviceInfoData;
        public string[] hardwareId;
        public string description;
        public CM_POWER_DATA cmPowerData;
    }