mirror of
https://github.com/hakril/PythonForWindows
synced 2026-06-08 14:31:45 +00:00
[Device] Implement API to retrieve resources associated with devices
This commit is contained in:
@@ -7,11 +7,14 @@ def main():
|
||||
print("-Class %s %s [%s]" % (device_class.name, " "*(60 - min(60,len(device_class.name))), device_class.guid))
|
||||
|
||||
for device in device_class.devices:
|
||||
if device.name != "N/A":
|
||||
if device.name != None:
|
||||
print(" -Device : %s" % (device.name))
|
||||
else:
|
||||
print(" -Device : N/A")
|
||||
|
||||
# for resource in device.resources:
|
||||
# print(' -%s : [0x%08x - 0x%08x] (0x%04x)' % (resource.type, resource.start, resource.end, resource.flags))
|
||||
for resource in device.resources:
|
||||
print(' -%s' % (resource))
|
||||
# print(' -%s : [0x%08x - 0x%08x] (0x%04x)' % (resource.type, resource.start, resource.end, resource.flags))
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -3158,7 +3158,20 @@ FindCloseParams = ((1, 'hFindFile'),)
|
||||
CM_Enumerate_ClassesPrototype = WINFUNCTYPE(CR_STATUS, DWORD, POINTER(GUID), DWORD)
|
||||
CM_Enumerate_ClassesParams = ((1, 'ClassIndex'), (1, 'pGUID'), (1,'Params'),)
|
||||
|
||||
CM_Get_First_Log_ConfPrototype = WINFUNCTYPE(CR_STATUS, PHANDLE, HANDLE, ULONG)
|
||||
CM_Get_First_Log_ConfParams = ((1, 'plcLogConf'), (1, 'dnDevInst'), (1,'ulFlags'),)
|
||||
|
||||
CM_Get_Next_Res_DesPrototype = WINFUNCTYPE(CR_STATUS, PHANDLE, HANDLE, ULONG, PULONG, ULONG)
|
||||
CM_Get_Next_Res_DesParams = ((1, 'prdResDes'),(1, 'rdResDes'),(1, 'ForResource'),(1, 'pResourceID'),(1, 'ulFlags'),)
|
||||
|
||||
CM_Free_Res_Des_HandlePrototype = WINFUNCTYPE(CR_STATUS, HANDLE)
|
||||
CM_Free_Res_Des_HandleParams = ((1, 'rdResDes'),)
|
||||
|
||||
CM_Get_Res_Des_Data_SizePrototype = WINFUNCTYPE(CR_STATUS, PULONG, HANDLE, ULONG)
|
||||
CM_Get_Res_Des_Data_SizeParams = ((1, 'pulSize'),(1, 'rdResDes'),(1, 'ulFlags'),)
|
||||
|
||||
CM_Get_Res_Des_DataPrototype = WINFUNCTYPE(CR_STATUS, HANDLE, PVOID, ULONG, ULONG)
|
||||
CM_Get_Res_Des_DataParams = ((1, 'rdResDes'), (1, 'Buffer'), (1, 'BufferLen'), (1, 'ulFlags'),)
|
||||
|
||||
|
||||
SetupDiClassNameFromGuidAPrototype = WINFUNCTYPE(BOOL, POINTER(GUID), LPCSTR, DWORD , POINTER(DWORD))
|
||||
|
||||
+131
-7
@@ -7,23 +7,99 @@ import windows.generated_def as gdef
|
||||
SPDRP_DEVICEDESC = 0x0000000
|
||||
SPDRP_FRIENDLYNAME = 0x000000C
|
||||
|
||||
# Resource types
|
||||
# source :
|
||||
ResourceType_All = 0x00000000 # query every resource available
|
||||
ResourceType_Mem = 0x00000001 # Physical address resource
|
||||
ResourceType_IO = 0x00000002 # Physical I/O address resource
|
||||
ResourceType_DMA = 0x00000003 # DMA channels resource
|
||||
ResourceType_IRQ = 0x00000004 # IRQ resource
|
||||
ResourceType_DoNotUse = 0x00000005 # Do not use it, idiot
|
||||
ResourceType_BusNumber = 0x00000006 # (PCI) bus number
|
||||
ResourceType_MAX = 0x00000007
|
||||
|
||||
# Log conf
|
||||
# source : https://docs.microsoft.com/en-us/windows/win32/api/cfgmgr32/nf-cfgmgr32-cm_get_first_log_conf
|
||||
BASIC_LOG_CONF = 0x00000000 # basic configuration information.
|
||||
FILTERED_LOG_CONF = 0x00000001 # filtered configuration information.
|
||||
ALLOC_LOG_CONF = 0x00000002 # allocated configuration information.
|
||||
BOOT_LOG_CONF = 0x00000003 # boot configuration information.
|
||||
FORCED_LOG_CONF = 0x00000004 # forced configuration information.
|
||||
OVERRIDE_LOG_CONF = 0x00000005 # override configuration information.
|
||||
|
||||
class AbstractDeviceResource(object):
|
||||
""" An abstract Python object representing a setup device resource. """
|
||||
pass
|
||||
|
||||
class MmioDeviceResource(AbstractDeviceResource):
|
||||
""" A Python object representing a setup device MMIO resource. """
|
||||
|
||||
def __init__(self, data):
|
||||
pass
|
||||
|
||||
class IoDeviceResource(AbstractDeviceResource):
|
||||
""" A Python object representing a setup device IO port resource. """
|
||||
|
||||
def __init__(self, data):
|
||||
pass
|
||||
|
||||
class DmaDeviceResource(AbstractDeviceResource):
|
||||
""" A Python object representing a setup device DMA resource. """
|
||||
|
||||
def __init__(self, data):
|
||||
pass
|
||||
|
||||
class IrqDeviceResource(AbstractDeviceResource):
|
||||
""" A Python object representing a setup device Irq resource. """
|
||||
|
||||
def __init__(self, data):
|
||||
pass
|
||||
|
||||
class DeviceResource(object):
|
||||
""" A Python object representing a setup device resource. """
|
||||
|
||||
@classmethod
|
||||
def parse(cls, resource_type, data):
|
||||
|
||||
subclasses = {
|
||||
ResourceType_Mem : MmioDeviceResource,
|
||||
ResourceType_IO : IoDeviceResource,
|
||||
ResourceType_DMA : DmaDeviceResource,
|
||||
ResourceType_IRQ : IrqDeviceResource,
|
||||
}
|
||||
|
||||
return subclasses[resource_type](data)
|
||||
|
||||
|
||||
class DeviceObject(object):
|
||||
|
||||
""" A Python object representing a setup device instance. """
|
||||
|
||||
def __init__(self, dev_data, device_name):
|
||||
|
||||
# gdef.winstructs.SP_DEVINFO_DATA: associated device info
|
||||
self.dev_data = dev_data
|
||||
|
||||
# str: Device instance name (optional)
|
||||
self._name = device_name
|
||||
|
||||
# list(AbstractDeviceResource): list of resources registered by the device
|
||||
self._resources = None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def resources(self):
|
||||
if not self._resources:
|
||||
self._resources = list(res for res in self.get_resources())
|
||||
|
||||
return self._resources
|
||||
|
||||
|
||||
@classmethod
|
||||
def from_device_info(cls, h_devs, device_data):
|
||||
|
||||
|
||||
# DO NOT STORE h_devs since it may be invalidated in the future
|
||||
try:
|
||||
device_name = winproxy.SetupDiGetDeviceRegistryPropertyW(h_devs, device_data, SPDRP_FRIENDLYNAME, None)
|
||||
@@ -34,27 +110,75 @@ class DeviceObject(object):
|
||||
if device_name:
|
||||
device_name = device_name.decode('utf-16-le').rstrip('\x00')
|
||||
else:
|
||||
device_name = "N/A"
|
||||
device_name = None
|
||||
|
||||
except WindowsError as e:
|
||||
if e.winerror == gdef.ERROR_INVALID_DATA:
|
||||
return cls(device_data, "N/A")
|
||||
return cls(device_data, None)
|
||||
raise
|
||||
|
||||
|
||||
return cls(device_data, device_name)
|
||||
|
||||
def get_resources(self, resource_type = None):
|
||||
|
||||
conf = self._get_first_log_conf()
|
||||
if conf is None:
|
||||
return
|
||||
|
||||
if resource_type:
|
||||
yield from self._get_resources_by_type(conf, resource_type)
|
||||
else:
|
||||
yield from self._get_resources_by_type(conf, ResourceType_Mem)
|
||||
yield from self._get_resources_by_type(conf, ResourceType_IO)
|
||||
yield from self._get_resources_by_type(conf, ResourceType_DMA)
|
||||
yield from self._get_resources_by_type(conf, ResourceType_IRQ)
|
||||
|
||||
# TODO : free "conf" automagically
|
||||
|
||||
def _get_first_log_conf(self):
|
||||
""" Try to retrieve the first log conf by using several flags """
|
||||
|
||||
conf = winproxy.CM_Get_First_Log_Conf(self.dev_data.DevInst, ALLOC_LOG_CONF )
|
||||
if conf != None:
|
||||
return conf
|
||||
|
||||
conf = winproxy.CM_Get_First_Log_Conf(self.dev_data.DevInst, BOOT_LOG_CONF )
|
||||
if conf != None:
|
||||
return conf
|
||||
|
||||
|
||||
|
||||
|
||||
def _get_resources_by_type(self, conf, resource_type):
|
||||
|
||||
h_res = winproxy.CM_Get_Next_Res_Des(conf, resource_type)
|
||||
|
||||
while h_res != None:
|
||||
|
||||
# open resource
|
||||
resource_size = winproxy.CM_Get_Res_Des_Data_Size(h_res)
|
||||
if resource_size:
|
||||
resource_data = winproxy.CM_Get_Res_Des_Data(h_res, resource_size)
|
||||
yield DeviceResource.parse(resource_type, resource_data)
|
||||
|
||||
# goto next resource
|
||||
h_res = winproxy.CM_Get_Next_Res_Des(h_res, resource_type)
|
||||
|
||||
# TODO : free "h_res" automagically
|
||||
|
||||
class DeviceClass(object):
|
||||
""" A Python object representing a setup device class. """
|
||||
|
||||
def __init__(self, guid):
|
||||
|
||||
# class GUID
|
||||
# gdef.winstructs.GUID: class GUID
|
||||
self._guid = guid
|
||||
|
||||
# associated class name
|
||||
# str: associated class name
|
||||
self._name = None
|
||||
|
||||
# List of devices registered under the class
|
||||
# list(DeviceObject): list of devices registered under the class
|
||||
self._devices = None
|
||||
|
||||
@property
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import ctypes
|
||||
from ctypes import wintypes
|
||||
import windows.generated_def as gdef
|
||||
|
||||
from ..apiproxy import ApiProxy, NeededParameter, is_implemented
|
||||
@@ -6,13 +7,15 @@ from ..error import succeed_on_zero, no_error_check
|
||||
|
||||
class CfgMgr32Proxy(ApiProxy):
|
||||
APIDLL = "CfgMgr32"
|
||||
default_error_check = staticmethod(succeed_on_zero)
|
||||
|
||||
# We suppress error checks since CM_** APIs return either :
|
||||
# - CR_SUCCESS on success
|
||||
# - or a custom status, e.g. CR_NO_SUCH_VALUE on an invalid class index
|
||||
# if necessary, we can convert them to Win32 error usign CM_MapCrToWin32Err
|
||||
default_error_check = staticmethod(no_error_check)
|
||||
|
||||
|
||||
# We suppress error checks since CM_Enumerate_Classes return either :
|
||||
# - CR_SUCCESS on success
|
||||
# - usually CR_NO_SUCH_VALUE on an invalid class index
|
||||
@CfgMgr32Proxy(error_check=no_error_check)
|
||||
@CfgMgr32Proxy()
|
||||
def CM_Enumerate_Classes(ClassIndex, Params):
|
||||
"""
|
||||
Given a class index, either return the class GUID or None.
|
||||
@@ -27,22 +30,87 @@ def CM_Enumerate_Classes(ClassIndex, Params):
|
||||
return guid
|
||||
|
||||
|
||||
# @CfgMgr32Proxy()
|
||||
# def CM_Get_First_Log_Conf(hDevInst):
|
||||
# return CM_Get_First_Log_Conf.ctypes_function(hDevInst)
|
||||
@CfgMgr32Proxy()
|
||||
def CM_Get_First_Log_Conf(hDevInst, Flags):
|
||||
|
||||
# @CfgMgr32Proxy()
|
||||
# def CM_Get_Next_Res_Des(hRes, ResourceType):
|
||||
# return CM_Get_Next_Res_Des.ctypes_function(hRes, ResourceType)
|
||||
# TODO : test if process is running as wow64 on a windows >8 and raise an exception if true
|
||||
|
||||
# @CfgMgr32Proxy()
|
||||
# def CM_Get_Res_Des_Data_Size(hRes):
|
||||
# return CM_Get_Res_Des_Data_Size.ctypes_function(hRes)
|
||||
|
||||
# @CfgMgr32Proxy()
|
||||
# def CM_Get_Res_Des_Data(hRes, ResourceSize):
|
||||
# return CM_Get_Res_Des_Data.ctypes_function(hRes, ResourceSize)
|
||||
conf = wintypes.HANDLE(0)
|
||||
|
||||
# @CfgMgr32Proxy()
|
||||
# def CM_Free_Res_Des_Handle(hRes):
|
||||
# return CM_Free_Res_Des_Handle.ctypes_function(hRes)
|
||||
cr_status = CM_Get_First_Log_Conf.ctypes_function(
|
||||
ctypes.byref(conf),
|
||||
hDevInst,
|
||||
Flags
|
||||
)
|
||||
|
||||
if cr_status != gdef.CR_SUCCESS:
|
||||
return None
|
||||
|
||||
return conf
|
||||
|
||||
@CfgMgr32Proxy()
|
||||
def CM_Free_Res_Des_Handle(hRes):
|
||||
return CM_Free_Res_Des_Handle.ctypes_function(hRes)
|
||||
|
||||
@CfgMgr32Proxy()
|
||||
def CM_Get_Next_Res_Des(hRes, ResourceType):
|
||||
|
||||
# TODO : test if process is running as wow64 on a windows >8 and raise an exception if true
|
||||
|
||||
updated_hRes = wintypes.HANDLE(0)
|
||||
|
||||
# TODO : support ResType_All query (design change since we need to return the resource type queried)
|
||||
if not ResourceType:
|
||||
raise ValueError("ResType_All not supported")
|
||||
|
||||
if ResourceType > 7:
|
||||
raise ValueError("ResourceType %x > ResType_MAX" % ResourceType)
|
||||
|
||||
status = CM_Get_Next_Res_Des.ctypes_function(
|
||||
ctypes.byref(updated_hRes),
|
||||
hRes,
|
||||
ResourceType,
|
||||
None,
|
||||
0 # flags parameter is not used, and must always be 0
|
||||
)
|
||||
|
||||
# clean up previous hRes
|
||||
CM_Free_Res_Des_Handle(hRes)
|
||||
|
||||
if status != gdef.CR_SUCCESS:
|
||||
return None
|
||||
|
||||
return updated_hRes
|
||||
|
||||
@CfgMgr32Proxy()
|
||||
def CM_Get_Res_Des_Data_Size(hRes):
|
||||
|
||||
resource_size = wintypes.ULONG(0)
|
||||
status = CM_Get_Res_Des_Data_Size.ctypes_function(
|
||||
ctypes.byref(resource_size),
|
||||
hRes,
|
||||
0 # flags parameter is not used, and must always be 0
|
||||
)
|
||||
|
||||
if status != gdef.CR_SUCCESS:
|
||||
return None
|
||||
|
||||
return resource_size.value
|
||||
|
||||
@CfgMgr32Proxy()
|
||||
def CM_Get_Res_Des_Data(hRes, ResourceSize):
|
||||
|
||||
resource_buffer = ctypes.create_string_buffer(ResourceSize)
|
||||
result = CM_Get_Res_Des_Data.ctypes_function(
|
||||
hRes,
|
||||
ctypes.byref(resource_buffer),
|
||||
ResourceSize,
|
||||
0 # flags parameter is not used, and must always be 0
|
||||
)
|
||||
|
||||
if result != gdef.CR_SUCCESS:
|
||||
return None
|
||||
|
||||
# truncate and return data
|
||||
return bytes(resource_buffer)[0:ResourceSize]
|
||||
|
||||
@@ -112,9 +112,6 @@ def SetupDiGetDeviceRegistryPropertyA(hDevInfo, DevData, Property, PropertyType,
|
||||
ctypes.byref(bytes_written),
|
||||
)
|
||||
|
||||
if not success:
|
||||
return None
|
||||
|
||||
# Truncate read data
|
||||
registry_data = bytes(property_buffer)
|
||||
registry_data = registry_data[0:bytes_written.value]
|
||||
@@ -138,9 +135,6 @@ def SetupDiGetDeviceRegistryPropertyW(hDevInfo, DevData, Property, PropertyType,
|
||||
ctypes.byref(bytes_written),
|
||||
)
|
||||
|
||||
if not success:
|
||||
return None
|
||||
|
||||
# Truncate read data
|
||||
registry_data = bytes(property_buffer)
|
||||
registry_data = registry_data[0:bytes_written.value]
|
||||
|
||||
Reference in New Issue
Block a user