From bfd8c90381d45de51cf5667c78520fa593be9b20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavol=20=C5=BD=C3=A1=C4=8Dik?= Date: Tue, 2 Jul 2024 15:58:45 +0200 Subject: [PATCH] Add support for multiple CPUS and raw data manipulation --- python/docs/api/info.rst | 12 +- python/docs/api/raw.rst | 8 ++ python/docs/index.rst | 1 + python/src/libcpuid/__init__.py | 38 +++-- python/src/libcpuid/{cpuid.py => cpu_id.py} | 146 ++++++++++++-------- python/src/libcpuid/cpu_raw_data.py | 66 +++++++++ python/src/libcpuid/enums.py | 2 +- python/src/libcpuid/sgx.py | 36 ++--- python/src/libcpuid/system_id.py | 82 +++++++++++ python/src/libcpuid/{_utils.py => utils.py} | 0 10 files changed, 301 insertions(+), 90 deletions(-) create mode 100644 python/docs/api/raw.rst rename python/src/libcpuid/{cpuid.py => cpu_id.py} (61%) create mode 100644 python/src/libcpuid/cpu_raw_data.py create mode 100644 python/src/libcpuid/system_id.py rename python/src/libcpuid/{_utils.py => utils.py} (100%) diff --git a/python/docs/api/info.rst b/python/docs/api/info.rst index 2e508ff5..05181a1d 100644 --- a/python/docs/api/info.rst +++ b/python/docs/api/info.rst @@ -1,14 +1,20 @@ Classes storing CPU information =============================== -.. autoclass:: libcpuid.cpuid.CPUID +.. autoclass:: libcpuid.cpu_id.CPUID :members: -.. autoclass:: libcpuid.x86.X86ID +.. autoclass:: libcpuid.cpu_id.X86ID :members: -.. autoclass:: libcpuid.arm.ARMID +.. autoclass:: libcpuid.cpu_id.ARMID + :members: + +.. autoclass:: libcpuid.system_id.SystemID :members: .. autoclass:: libcpuid.sgx.SGX :members: + +.. autoclass:: libcpuid.sgx.EPC + :members: \ No newline at end of file diff --git a/python/docs/api/raw.rst b/python/docs/api/raw.rst new file mode 100644 index 00000000..be4393fb --- /dev/null +++ b/python/docs/api/raw.rst @@ -0,0 +1,8 @@ +Raw CPU data manipulation +=============================== + +.. autoclass:: libcpuid.cpu_raw_data.CPURawData + :members: + +.. autoclass:: libcpuid.cpu_raw_data.CPURawDataArray + :members: \ No newline at end of file diff --git a/python/docs/index.rst b/python/docs/index.rst index 6bb9e1d5..2de3a8ea 100644 --- a/python/docs/index.rst +++ b/python/docs/index.rst @@ -49,3 +49,4 @@ to the libcpuid C library. api/libcpuid api/info api/enums + api/raw diff --git a/python/src/libcpuid/__init__.py b/python/src/libcpuid/__init__.py index cbc040d1..7afb2f78 100644 --- a/python/src/libcpuid/__init__.py +++ b/python/src/libcpuid/__init__.py @@ -7,8 +7,8 @@ ffi, lib, ) -from libcpuid import _cpuid_builder, cpuid, enums -from libcpuid._utils import c_string_to_str, LibCPUIDError +from libcpuid import cpu_id, system_id, cpu_raw_data, enums +from libcpuid.utils import c_string_to_str, LibCPUIDError def version() -> str: @@ -34,20 +34,34 @@ def get_vendor() -> enums.CPUVendor: return enums.CPUVendor.UNKNOWN -def cpu_identify() -> cpuid.CPUID: - """Identifies the CPU and returns a :class:`cpuid.CPUID` instance.""" - return _cpuid_builder.CPUIDBuilder.from_current_cpu() +def cpu_identify() -> cpu_id.CPUID: + """Identifies the CPU and returns a :class:`cpu_id.CPUID` instance.""" + return cpu_id.CPUIDBuilder.build_from_current_cpu() + + +def cpu_identify_all() -> system_id.SystemID: + """Identify all system CPUs and returns a :class:`system_id.SystemID` instance.""" + return system_id.SystemIDBuilder.build_from_all_cpus() + + +def cpuid_get_raw_data() -> cpu_raw_data.CPURawData: + """Obtains the raw CPUID data from the current CPU.""" + return cpu_raw_data.CPURawDataBuilder.build_from_current_cpu() + + +def cpuid_get_all_raw_data() -> cpu_raw_data.CPURawDataArray: + """Obtains the raw CPUID data from all CPUs.""" + return cpu_raw_data.CPURawDataBuilder.build_array_from_all_cpus() def get_cpu_list(vendor: enums.CPUVendor) -> list[str]: - """Gets a list of CPU :meth:`codenames ` for a specific vendor.""" - raw_cpu_list = ffi.new("struct cpu_list_t *") - lib.cpuid_get_cpu_list(vendor, raw_cpu_list) - if raw_cpu_list.num_entries == 0: + """Gets a list of CPU :meth:`codenames ` for a specific vendor.""" + c_cpu_list = ffi.new("struct cpu_list_t *") + lib.cpuid_get_cpu_list(vendor, c_cpu_list) + if c_cpu_list.num_entries == 0: raise LibCPUIDError(c_string_to_str(lib.cpuid_error())) cpu_list = [ - c_string_to_str(name) - for name in raw_cpu_list.names[0 : raw_cpu_list.num_entries] + c_string_to_str(name) for name in c_cpu_list.names[0 : c_cpu_list.num_entries] ] - lib.cpuid_free_cpu_list(raw_cpu_list) + lib.cpuid_free_cpu_list(c_cpu_list) return cpu_list diff --git a/python/src/libcpuid/cpuid.py b/python/src/libcpuid/cpu_id.py similarity index 61% rename from python/src/libcpuid/cpuid.py rename to python/src/libcpuid/cpu_id.py index 673aa718..466addc5 100644 --- a/python/src/libcpuid/cpuid.py +++ b/python/src/libcpuid/cpu_id.py @@ -3,8 +3,8 @@ """ from typing import Optional -from libcpuid import enums, sgx -from libcpuid._utils import c_string_to_str, optional_int +from libcpuid import enums, sgx, cpu_raw_data +from libcpuid.utils import c_string_to_str, optional_int, check_error from libcpuid._libcpuid_cffi import ( # pylint: disable=no-name-in-module, import-error lib, ffi, @@ -17,12 +17,12 @@ class CPUID: # pylint: disable=too-many-public-methods Use :func:`libcpuid.cpu_identify` to create an instance of this class: - >>> cpuid = libcpuid.cpu_identify() - >>> print(cpuid.architecture.name) + >>> cpu_id = libcpuid.cpu_identify() + >>> print(cpu_id.architecture.name) """ - def __init__(self, raw): - self._raw = raw + def __init__(self, c_cpu_id): + self._c_cpu_id = c_cpu_id self._flags = None self._detection_hints = None @@ -30,7 +30,7 @@ def __init__(self, raw): def architecture(self) -> enums.CPUArchitecture: """The CPU Architecture.""" try: - return enums.CPUArchitecture(self._raw.architecture) + return enums.CPUArchitecture(self._c_cpu_id.architecture) except ValueError: return enums.CPUArchitecture.UNKNOWN @@ -38,25 +38,25 @@ def architecture(self) -> enums.CPUArchitecture: def feature_level(self) -> enums.CPUFeatureLevel: """The CPU feature level.""" try: - return enums.CPUFeatureLevel(self._raw.feature_level) + return enums.CPUFeatureLevel(self._c_cpu_id.feature_level) except ValueError: return enums.CPUFeatureLevel.UNKNOWN @property def vendor_str(self) -> str: """The CPU vendor string, e.g., :const:`'GenuineIntel'`.""" - return c_string_to_str(self._raw.vendor_str) + return c_string_to_str(self._c_cpu_id.vendor_str) @property def brand_str(self) -> str: """The CPU brand string, e.g., :const:`'Intel(R) Xeon(TM) CPU 2.40GHz'`.""" - return c_string_to_str(self._raw.brand_str) + return c_string_to_str(self._c_cpu_id.brand_str) @property def vendor(self) -> enums.CPUVendor: """The CPU vendor.""" try: - return enums.CPUVendor(self._raw.vendor) + return enums.CPUVendor(self._c_cpu_id.vendor) except ValueError: return enums.CPUVendor.UNKNOWN @@ -65,129 +65,131 @@ def flags(self) -> set[enums.CPUFeature]: """Set of CPU features.""" if self._flags is None: self._flags = { - flag for flag in enums.CPUFeature if flag >= 0 and self._raw.flags[flag] + flag + for flag in enums.CPUFeature + if flag >= 0 and self._c_cpu_id.flags[flag] } return self._flags @property def num_cores(self) -> int: """The number of CPU cores.""" - return self._raw.num_cores + return self._c_cpu_id.num_cores @property def num_logical_cpus(self) -> int: """The number of logical CPU cores.""" - return self._raw.num_logical_cpus + return self._c_cpu_id.num_logical_cpus @property def total_logical_cpus(self) -> int: """The total number of logical processors.""" - return self._raw.total_logical_cpus + return self._c_cpu_id.total_logical_cpus @property def l1_data_cache(self) -> Optional[int]: """L1 data cache size in KB. :const:`None` if not determined.""" - return optional_int(self._raw.l1_data_cache) + return optional_int(self._c_cpu_id.l1_data_cache) @property def l1_instruction_cache(self) -> Optional[int]: """L1 instruction cache size in KB. :const:`None` if not determined.""" - return optional_int(self._raw.l1_instruction_cache) + return optional_int(self._c_cpu_id.l1_instruction_cache) @property def l2_cache(self) -> Optional[int]: """L2 cache size in KB. :const:`None` if not determined.""" - return optional_int(self._raw.l2_cache) + return optional_int(self._c_cpu_id.l2_cache) @property def l3_cache(self) -> Optional[int]: """L3 cache size in KB. :const:`None` if not determined.""" - return optional_int(self._raw.l3_cache) + return optional_int(self._c_cpu_id.l3_cache) @property def l4_cache(self) -> Optional[int]: """L4 cache size in KB. :const:`None` if not determined.""" - return optional_int(self._raw.l4_cache) + return optional_int(self._c_cpu_id.l4_cache) @property def l1_data_assoc(self) -> Optional[int]: """L1 data cache associativity. :const:`None` if not determined.""" - return optional_int(self._raw.l1_data_assoc) + return optional_int(self._c_cpu_id.l1_data_assoc) @property def l1_instruction_assoc(self) -> Optional[int]: """L1 instruction cache associativity. :const:`None` if not determined.""" - return optional_int(self._raw.l1_instruction_assoc) + return optional_int(self._c_cpu_id.l1_instruction_assoc) @property def l2_assoc(self) -> Optional[int]: """L2 cache associativity. :const:`None` if not determined.""" - return optional_int(self._raw.l2_assoc) + return optional_int(self._c_cpu_id.l2_assoc) @property def l3_assoc(self) -> Optional[int]: """L3 cache associativity. :const:`None` if not determined.""" - return optional_int(self._raw.l3_assoc) + return optional_int(self._c_cpu_id.l3_assoc) @property def l4_assoc(self) -> Optional[int]: """L4 cache associativity. :const:`None` if not determined.""" - return optional_int(self._raw.l4_assoc) + return optional_int(self._c_cpu_id.l4_assoc) @property def l1_data_cacheline(self) -> Optional[int]: """L1 data cache line size. :const:`None` if not determined.""" - return optional_int(self._raw.l1_data_cacheline) + return optional_int(self._c_cpu_id.l1_data_cacheline) @property def l1_instruction_cacheline(self) -> Optional[int]: """L1 instruction cache line size. :const:`None` if not determined.""" - return optional_int(self._raw.l1_instruction_cacheline) + return optional_int(self._c_cpu_id.l1_instruction_cacheline) @property def l2_cacheline(self) -> Optional[int]: """L2 cache line size. :const:`None` if not determined.""" - return optional_int(self._raw.l2_cacheline) + return optional_int(self._c_cpu_id.l2_cacheline) @property def l3_cacheline(self) -> Optional[int]: """L3 cache line size. :const:`None` if not determined.""" - return optional_int(self._raw.l3_cacheline) + return optional_int(self._c_cpu_id.l3_cacheline) @property def l4_cacheline(self) -> Optional[int]: """L4 cache line size. :const:`None` if not determined.""" - return optional_int(self._raw.l4_cacheline) + return optional_int(self._c_cpu_id.l4_cacheline) @property def l1_data_instances(self) -> Optional[int]: """Number of L1 data cache instances. :const:`None` if not determined.""" - return optional_int(self._raw.l1_data_instances) + return optional_int(self._c_cpu_id.l1_data_instances) @property def l1_instruction_instances(self) -> Optional[int]: """Number of L1 instruction cache instances. :const:`None` if not determined.""" - return optional_int(self._raw.l1_instruction_instances) + return optional_int(self._c_cpu_id.l1_instruction_instances) @property def l2_instances(self) -> Optional[int]: """Number of L2 cache instances. :const:`None` if not determined.""" - return optional_int(self._raw.l2_instances) + return optional_int(self._c_cpu_id.l2_instances) @property def l3_instances(self) -> Optional[int]: """Number of L3 cache instances. :const:`None` if not determined.""" - return optional_int(self._raw.l3_instances) + return optional_int(self._c_cpu_id.l3_instances) @property def l4_instances(self) -> Optional[int]: """Number of L4 cache instances. :const:`None` if not determined.""" - return optional_int(self._raw.l4_instances) + return optional_int(self._c_cpu_id.l4_instances) @property def cpu_codename(self) -> str: """Human-friendly CPU codename.""" - return c_string_to_str(self._raw.cpu_codename) + return c_string_to_str(self._c_cpu_id.cpu_codename) @property def detection_hints(self) -> set[enums.CPUHint]: @@ -196,25 +198,25 @@ def detection_hints(self) -> set[enums.CPUHint]: self._detection_hints = { hint for hint in enums.CPUHint - if hint >= 0 and self._raw.detection_hints[hint] + if hint >= 0 and self._c_cpu_id.detection_hints[hint] } return self._detection_hints @property def affinity_mask(self) -> list[bool]: """Boolean mask of the affinity IDs that this processor type is occupying.""" - bit_mask = getattr(self._raw.affinity_mask, "__bits") + bit_mask = getattr(self._c_cpu_id.affinity_mask, "__bits") return [bool(bit) for bit in bit_mask] @property def purpose(self) -> enums.CPUPurpose: """The purpose of the CPU type, relevant for hybrid CPUs.""" - return enums.CPUPurpose(self._raw.purpose) + return enums.CPUPurpose(self._c_cpu_id.purpose) @property def hypervisor(self) -> Optional[enums.HypervisorVendor]: """The hypervisor vendor or :const:`None` if not detected.""" - hypervisor = lib.cpuid_get_hypervisor(ffi.NULL, self._raw) + hypervisor = lib.cpuid_get_hypervisor(ffi.NULL, self._c_cpu_id) return enums.HypervisorVendor(hypervisor) if hypervisor != 0 else None @@ -223,44 +225,44 @@ class X86ID(CPUID): CPUID child class for x86 CPUs. """ - def __init__(self, raw): - super().__init__(raw) - self._raw = raw + def __init__(self, c_cpu_id): + super().__init__(c_cpu_id) + self._c_cpu_id = c_cpu_id @property def family(self) -> int: """The CPU family number.""" - return self._raw.family + return self._c_cpu_id.family @property def model(self) -> int: """The CPU model number.""" - return self._raw.model + return self._c_cpu_id.model @property def stepping(self) -> int: """The CPU stepping.""" - return self._raw.stepping + return self._c_cpu_id.stepping @property def ext_family(self) -> int: """The CPU display family number.""" - return self._raw.ext_family + return self._c_cpu_id.ext_family @property def ext_model(self) -> int: """The CPU display model number.""" - return self._raw.ext_model + return self._c_cpu_id.ext_model @property def sse_size(self) -> Optional[int]: """SSE execution unit size (64 or 128), :const:`None` if not available.""" - return optional_int(self._raw.sse_size) + return optional_int(self._c_cpu_id.sse_size) @property def sgx(self) -> Optional[sgx.SGX]: """SGX-related features if present.""" - return sgx.SGX(self._raw.sgx) if self._raw.sgx.present == 1 else None + return sgx.SGX(self._c_cpu_id.sgx) if self._c_cpu_id.sgx.present == 1 else None class ARMID(CPUID): @@ -268,26 +270,56 @@ class ARMID(CPUID): CPUID child class for ARM CPUs. """ - def __init__(self, raw): - super().__init__(raw) - self._raw = raw + def __init__(self, c_cpu_id): + super().__init__(c_cpu_id) + self._c_cpu_id = c_cpu_id @property def implementer(self) -> int: """CPU implementer code.""" - return self._raw.implementer + return self._c_cpu_id.implementer @property def variant(self) -> int: """CPU variant number.""" - return self._raw.variant + return self._c_cpu_id.variant @property def part_num(self) -> int: """CPU primary part number.""" - return self._raw.part_num + return self._c_cpu_id.part_num @property def revision(self) -> int: """CPU revision number.""" - return self._raw.revision + return self._c_cpu_id.revision + + +class CPUIDBuilder: + """ + Factory class for building CPUID instances. + """ + + @staticmethod + def build_from_c(c_cpu_id) -> CPUID: + """Create a CPUID instance from the corresponding C structure.""" + cpu_info = CPUID(c_cpu_id) + if cpu_info.architecture == enums.CPUArchitecture.X86: + return X86ID(c_cpu_id) + if cpu_info.architecture == enums.CPUArchitecture.ARM: + return ARMID(c_cpu_id) + return cpu_info + + @staticmethod + def build_from_current_cpu() -> CPUID: + """Create a CPUID instance by indentifying the current CPU.""" + c_cpu_id = ffi.new("struct cpu_id_t *") + check_error(lib.cpu_identify(ffi.NULL, c_cpu_id)) + return CPUIDBuilder.build_from_c(c_cpu_id) + + @staticmethod + def build_from_raw_data(raw_data: cpu_raw_data.CPURawData) -> CPUID: + """Create a CPUID instance from raw CPUID data.""" + c_cpu_id = ffi.new("struct cpu_id_t *") + check_error(lib.cpu_identify(raw_data.c_cpu_raw_data, c_cpu_id)) + return CPUIDBuilder.build_from_c(c_cpu_id) diff --git a/python/src/libcpuid/cpu_raw_data.py b/python/src/libcpuid/cpu_raw_data.py new file mode 100644 index 00000000..2c99d974 --- /dev/null +++ b/python/src/libcpuid/cpu_raw_data.py @@ -0,0 +1,66 @@ +""" +Module providing access to raw CPU data. +""" + +from libcpuid.utils import check_error +from libcpuid._libcpuid_cffi import ( # pylint: disable=no-name-in-module, import-error + lib, + ffi, +) + + +class CPURawData: # pylint: disable=too-few-public-methods + """Constains raw CPUID data.""" + + def __init__(self, c_cpu_raw_data): + self.c_cpu_raw_data = c_cpu_raw_data + + def serialize(self, filename: str): + """Exports the raw data into a provided file.""" + lib.cpuid_serialize_raw_data(self.c_cpu_raw_data, filename.encode()) + + +class CPURawDataArray: + """Contains raw CPUID data about multiple CPUs""" + + def __init__(self, c_cpu_raw_data_array): + self.c_cpu_raw_data_array = c_cpu_raw_data_array + + def serialize(self, filename: str): + """Exports the raw data array into a provided file.""" + lib.cpuid_serialize_all_raw_data(self.c_cpu_raw_data_array, filename.encode()) + + def __del__(self): + lib.cpuid_free_raw_data_array(self.c_cpu_raw_data_array) + + +class CPURawDataBuilder: + """Factory class for building raw CPUID data.""" + + @staticmethod + def build_from_current_cpu() -> CPURawData: + """Creates a CPURawData instance by running CPUID on the current CPU.""" + c_cpu_raw_data = ffi.new("struct cpu_raw_data_t *") + check_error(lib.cpuid_get_raw_data(c_cpu_raw_data)) + return CPURawData(c_cpu_raw_data) + + @staticmethod + def build_array_from_all_cpus() -> CPURawDataArray: + """Creates a CPURawDataArray instance by running CPUID on all CPUs.""" + c_cpu_raw_data_array = ffi.new("struct cpu_raw_data_array_t *") + check_error(lib.cpuid_get_all_raw_data(c_cpu_raw_data_array)) + return CPURawDataArray(c_cpu_raw_data_array) + + @staticmethod + def build_from_file(filename: str) -> CPURawData: + """Creates a CPURawData instance by parsing the data from a provided file.""" + c_cpu_raw_data = ffi.new("struct cpu_raw_data_t *") + check_error(lib.cpuid_deserialize_raw_data(c_cpu_raw_data, filename)) + return CPURawData(c_cpu_raw_data) + + @staticmethod + def build_array_from_file(filename: str) -> CPURawDataArray: + """Creates a CPURawDataArray instance by parsing the data from a provided file.""" + c_cpu_raw_data_array = ffi.new("struct cpu_raw_data_array_t *") + check_error(lib.cpuid_deserialize_all_raw_data(c_cpu_raw_data_array, filename)) + return CPURawDataArray(c_cpu_raw_data_array) diff --git a/python/src/libcpuid/enums.py b/python/src/libcpuid/enums.py index b97fe8ac..e1bbbbbb 100644 --- a/python/src/libcpuid/enums.py +++ b/python/src/libcpuid/enums.py @@ -4,7 +4,7 @@ """ from enum import IntEnum -from libcpuid._utils import c_string_to_str +from libcpuid.utils import c_string_to_str from libcpuid._libcpuid_cffi import ( # pylint: disable=no-name-in-module, import-error lib, ) diff --git a/python/src/libcpuid/sgx.py b/python/src/libcpuid/sgx.py index 74d71a71..843a434c 100644 --- a/python/src/libcpuid/sgx.py +++ b/python/src/libcpuid/sgx.py @@ -3,7 +3,7 @@ """ from libcpuid import enums -from libcpuid._utils import LibCPUIDError +from libcpuid.utils import LibCPUIDError from libcpuid._libcpuid_cffi import ( # pylint: disable=no-name-in-module, import-error lib, ffi, @@ -15,59 +15,61 @@ class EPC: Holds information about a single EPC (Enclave Page Cache) area. """ - def __init__(self, raw): - self._raw = raw + def __init__(self, c_epc): + self._c_epc = c_epc @property def start_addr(self) -> int: """Start address.""" - return self._raw.start_addr + return self._c_epc.start_addr @property def length(self) -> int: """Length of the area.""" - return self._raw.length + return self._c_epc.length class SGX: """ Holds the SGX-related information of a CPU. - Obtainable as :attr:`libcpuid.cpuid.CPUID.sgx`: + Obtainable as :attr:`libcpuid.cpu_id.CPUID.sgx`: - >>> cpuid = libcpuid.cpu_identify() - >>> sgx = cpuid.sgx + >>> cpu_id = libcpuid.cpu_identify() + >>> sgx = cpu_id.sgx >>> if sgx is not None: >>> print(sgx.max_enclave_32bit) """ - def __init__(self, raw): - self._raw = raw + def __init__(self, c_sgx): + self._c_sgx = c_sgx self._flags = None @property def max_enclave_32bit(self) -> int: """The maximum enclave size in 32-bit mode.""" - return self._raw.max_enclave_32bit + return self._c_sgx.max_enclave_32bit @property def max_enclave_64bit(self) -> int: """The maximum enclave size in 64-bit mode.""" - return self._raw.max_enclave_64bit + return self._c_sgx.max_enclave_64bit @property def flags(self) -> set[enums.SGXFeature]: """Set of CPU SGX features.""" if self._flags is None: self._flags = { - flag for flag in enums.SGXFeature if flag >= 0 and self._raw.flags[flag] + flag + for flag in enums.SGXFeature + if flag >= 0 and self._c_sgx.flags[flag] } return self._flags @property def num_epc_sections(self) -> int: """The number of Enclave Page Cache (EPC) sections.""" - return self._raw.num_epc_sections + return self._c_sgx.num_epc_sections @property def misc_select(self) -> bytes: @@ -75,7 +77,7 @@ def misc_select(self) -> bytes: Bit vector of the supported extended features that can be written to the MISC region of the SSA (Save State Area) """ - return self._raw.misc_select.to_bytes(4, byteorder="big") + return self._c_sgx.misc_select.to_bytes(4, byteorder="big") @property def secs_attributes(self) -> bytes: @@ -83,7 +85,7 @@ def secs_attributes(self) -> bytes: Bit vector of the attributes that can be set to SECS.ATTRIBUTES via ECREATE. """ - return self._raw.secs_attributes.to_bytes(8, byteorder="big") + return self._c_sgx.secs_attributes.to_bytes(8, byteorder="big") @property def secs_xfrm(self) -> bytes: @@ -91,7 +93,7 @@ def secs_xfrm(self) -> bytes: Bit vector of the attributes that can be set in the XSAVE feature request mask. """ - return self._raw.secs_xfrm.to_bytes(8, byteorder="big") + return self._c_sgx.secs_xfrm.to_bytes(8, byteorder="big") def get_epc(self, index: int) -> EPC: """Fetches information about a single EPC area given by `index`.""" diff --git a/python/src/libcpuid/system_id.py b/python/src/libcpuid/system_id.py new file mode 100644 index 00000000..afe5a25f --- /dev/null +++ b/python/src/libcpuid/system_id.py @@ -0,0 +1,82 @@ +""" +Module providing a class for holding information about multiple CPUs. +""" + +from typing import Optional +from libcpuid import cpu_raw_data, cpu_id +from libcpuid.utils import optional_int, check_error +from libcpuid._libcpuid_cffi import ( # pylint: disable=no-name-in-module, import-error + lib, + ffi, +) + + +class SystemID: + """Class for holding information about multiple CPUs.""" + + def __init__(self, c_system_id, cpu_types: list[cpu_id.CPUID]): + self._c_system_id = c_system_id + self._cpu_types = cpu_types + + def __del__(self): + lib.cpuid_free_system_id(self._c_system_id) + + @property + def cpu_types(self) -> list[cpu_id.CPUID]: + """List of CPUID instances for each CPU in the system.""" + return self._cpu_types + + @property + def l1_data_total_instances(self) -> Optional[int]: + """Number of total L1 data cache instances. :const:`None` if not determined.""" + return optional_int(self._c_system_id.l1_data_total_instances) + + @property + def l1_instruction_total_instances(self) -> Optional[int]: + """Number of total L1 instruction cache instances. :const:`None` if not determined.""" + return optional_int(self._c_system_id.l1_instruction_total_instances) + + @property + def l2_total_instances(self) -> Optional[int]: + """Number of total L2 cache instances. :const:`None` if not undetermined""" + return optional_int(self._c_system_id.l2_total_instances) + + @property + def l3_total_instances(self) -> Optional[int]: + """Number of total L3 cache instances. :const:`None` if not undetermined""" + return optional_int(self._c_system_id.l3_total_instances) + + @property + def l4_total_instances(self) -> Optional[int]: + """Number of total L4 cache instances. :const:`None` if not undetermined""" + return optional_int(self._c_system_id.l4_total_instances) + + +class SystemIDBuilder: + """Factory class for building SystemID instances.""" + + @staticmethod + def build_from_c(c_system_id) -> SystemID: + """Create a SystemID instance from the corresponding C structure.""" + cpu_id_list = [] + for c_cpu_id in c_system_id.cpu_types[0 : c_system_id.num_cpu_types]: + new_c_cpu_id = ffi.new("struct cpu_id_t *") + ffi.memmove(new_c_cpu_id, ffi.addressof(c_cpu_id), ffi.sizeof(new_c_cpu_id)) + cpu_id_list.append(cpu_id.CPUIDBuilder.build_from_c(new_c_cpu_id)) + return SystemID(c_system_id, cpu_id_list) + + @staticmethod + def build_from_all_cpus() -> SystemID: + """Create a SystemID instance by indentifying the all CPUs.""" + c_system_id = ffi.new("struct system_id_t *") + check_error(lib.cpu_identify_all(ffi.NULL, c_system_id)) + return SystemIDBuilder.build_from_c(c_system_id) + + @staticmethod + def build_from_raw_data_array( + raw_data_array: cpu_raw_data.CPURawDataArray, + ) -> SystemID: + """Create a SystemID instance from raw CPUID array data.""" + c_system_id = ffi.new("struct system_id_t *") + check_error(lib.cpu_identify_all(raw_data_array.c_cpu_raw_data, c_system_id)) + return SystemIDBuilder.build_from_c(c_system_id) diff --git a/python/src/libcpuid/_utils.py b/python/src/libcpuid/utils.py similarity index 100% rename from python/src/libcpuid/_utils.py rename to python/src/libcpuid/utils.py