mirror of
https://github.com/anrieff/libcpuid
synced 2025-07-02 14:04:15 +00:00
Add Python bindings
The bindings are implemented via python-cffi and cover all current functionality of the library. They do not provide a 1-to-1 mapping of the functionality, but a more "Pythonic" API instead. When new functionality is added to the libcpuid library in the future, the changes must be manually added to the bindings in order to appear in the Python interface. However, if only a C enum is extended, the changes will be automatically reflected in the Python interface.
This commit is contained in:
parent
6c87edab5a
commit
40e2d5fcb6
14 changed files with 1133 additions and 0 deletions
12
python/pyproject.toml
Normal file
12
python/pyproject.toml
Normal file
|
@ -0,0 +1,12 @@
|
|||
[build-system]
|
||||
requires = ["setuptools", "cffi"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "libcpuid"
|
||||
version = "0.1.0"
|
||||
readme = "README.md"
|
||||
dependencies = ["cffi"]
|
||||
license = {text = "BSD-3-Clause"}
|
||||
authors = [{name = "Pavol Žáčik", email = "zacikpa@gmail.com"}]
|
||||
description = "Python bindings for the libcpuid C library"
|
5
python/setup.py
Normal file
5
python/setup.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
from setuptools import setup
|
||||
|
||||
setup(
|
||||
cffi_modules=["src/libcpuid/_ffi_build.py:ffibuilder"],
|
||||
)
|
68
python/src/libcpuid/__init__.py
Normal file
68
python/src/libcpuid/__init__.py
Normal file
|
@ -0,0 +1,68 @@
|
|||
"""
|
||||
The libcpuid package defines Python bindings to the
|
||||
libcpuid C library, which provides CPU identification.
|
||||
"""
|
||||
|
||||
from libcpuid._libcpuid_cffi import ( # pylint: disable=no-name-in-module, import-error
|
||||
ffi,
|
||||
lib,
|
||||
)
|
||||
from libcpuid import enums
|
||||
from libcpuid.errors import CLibraryError
|
||||
from libcpuid._utils import c_string_to_str
|
||||
|
||||
|
||||
def version() -> str:
|
||||
"""Returns the version of the libcpuid library."""
|
||||
return c_string_to_str(lib.cpuid_lib_version())
|
||||
|
||||
|
||||
def cpuid_present() -> bool:
|
||||
"""Checks if the `cpuid` instruction is supported."""
|
||||
return bool(lib.cpuid_present())
|
||||
|
||||
|
||||
def get_total_cpus() -> int:
|
||||
"""Returns the total number of logical CPU threads."""
|
||||
return lib.cpuid_get_total_cpus()
|
||||
|
||||
|
||||
def get_vendor() -> enums.CPUVendor:
|
||||
"""Returns the CPU vendor of the current CPU."""
|
||||
try:
|
||||
return enums.CPUVendor(lib.cpuid_get_vendor())
|
||||
except ValueError:
|
||||
return enums.CPUVendor.UNKNOWN
|
||||
|
||||
|
||||
def get_cpu_list(vendor: enums.CPUVendor) -> list[str]:
|
||||
"""Gets a list of CPU :meth:`codenames <info.CPUInfo.cpu_codename>` 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 CLibraryError
|
||||
cpu_list = [
|
||||
c_string_to_str(name) for name in c_cpu_list.names[0 : c_cpu_list.num_entries]
|
||||
]
|
||||
lib.cpuid_free_cpu_list(c_cpu_list)
|
||||
return cpu_list
|
||||
|
||||
|
||||
def exec_cpuid(
|
||||
eax: bytes,
|
||||
ebx: bytes = 4 * b"\x00",
|
||||
ecx: bytes = 4 * b"\x00",
|
||||
edx: bytes = 4 * b"\x00",
|
||||
) -> tuple[bytes, bytes, bytes, bytes]:
|
||||
"""Executes the `cpuid` instruction with the given content of input registers."""
|
||||
regs = ffi.new(
|
||||
"uint32_t[4]",
|
||||
[
|
||||
int.from_bytes(eax),
|
||||
int.from_bytes(ebx),
|
||||
int.from_bytes(ecx),
|
||||
int.from_bytes(edx),
|
||||
],
|
||||
)
|
||||
lib.cpu_exec_cpuid_ext(regs)
|
||||
return tuple(reg.to_bytes(4) for reg in regs)
|
25
python/src/libcpuid/_ffi_build.py
Normal file
25
python/src/libcpuid/_ffi_build.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
"""
|
||||
Module for compiling the C FFI.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from cffi import FFI
|
||||
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
from _ffi_build_utils import ( # pylint: disable=import-error, wrong-import-position
|
||||
get_include_flags,
|
||||
find_header_file,
|
||||
preprocess_header,
|
||||
eval_sizeofs,
|
||||
)
|
||||
|
||||
include_flags = get_include_flags()
|
||||
preprocessed_header = preprocess_header(find_header_file(include_flags))
|
||||
no_sizeof_header = eval_sizeofs(preprocessed_header, include_flags)
|
||||
ffibuilder = FFI()
|
||||
ffibuilder.cdef(no_sizeof_header)
|
||||
ffibuilder.set_source_pkgconfig(
|
||||
"libcpuid._libcpuid_cffi", ["libcpuid"], "#include <libcpuid.h>"
|
||||
)
|
105
python/src/libcpuid/_ffi_build_utils.py
Normal file
105
python/src/libcpuid/_ffi_build_utils.py
Normal file
|
@ -0,0 +1,105 @@
|
|||
"""
|
||||
Utility functions for building the FFI.
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import os
|
||||
import re
|
||||
import tempfile
|
||||
|
||||
|
||||
class FFIBuildException(Exception):
|
||||
"""Generic exception for errors occuring during the CFFI build."""
|
||||
|
||||
|
||||
def get_include_flags():
|
||||
"""
|
||||
Obtains libcpuid include flags via pkg-config.
|
||||
"""
|
||||
try:
|
||||
cflags = (
|
||||
subprocess.check_output(["pkg-config", "libcpuid", "--cflags-only-I"])
|
||||
.decode()
|
||||
.strip()
|
||||
.split()
|
||||
)
|
||||
return cflags
|
||||
except subprocess.CalledProcessError as e:
|
||||
if e.returncode == 127:
|
||||
raise FFIBuildException(
|
||||
"The pkg-config command is necessary to build python-libcpuid."
|
||||
) from e
|
||||
if e.returncode == 1:
|
||||
raise FFIBuildException(
|
||||
"The libcpuid C library (devel) was not found."
|
||||
) from e
|
||||
raise FFIBuildException("Error looking for the libcpuid library") from e
|
||||
|
||||
|
||||
def find_header_file(include_flags):
|
||||
"""
|
||||
Obtains main libcpuid header file location from include flags.
|
||||
"""
|
||||
header_path = None # pylint: disable=invalid-name
|
||||
for cflag in include_flags:
|
||||
header_candidate = os.path.join(cflag[2:], "libcpuid.h")
|
||||
if os.path.isfile(header_candidate):
|
||||
header_path = header_candidate
|
||||
break
|
||||
if header_path is None:
|
||||
raise FFIBuildException("Could not find header file of the libcpuid library.")
|
||||
return header_path
|
||||
|
||||
|
||||
def preprocess_header(header_path):
|
||||
"""
|
||||
Preprocesses the header file (python-cffi only accepts preprocessed C definitions)
|
||||
at the given path and returns it as a string.
|
||||
"""
|
||||
try:
|
||||
return subprocess.check_output(
|
||||
["gcc", "-U __GNUC__", "-E", header_path]
|
||||
).decode()
|
||||
except subprocess.CalledProcessError as e:
|
||||
if e.returncode == 127:
|
||||
raise FFIBuildException(
|
||||
"The gcc compiler is necessary to build python-libcpuid."
|
||||
) from e
|
||||
raise FFIBuildException(
|
||||
f"Error preprocessing the libcpuid header file: {e.stderr}"
|
||||
) from e
|
||||
|
||||
|
||||
def _get_sizeof_eval_source(sizeof):
|
||||
return f"""
|
||||
#include <libcpuid.h>
|
||||
#include <stdio.h>
|
||||
|
||||
int main() {{
|
||||
printf("%ld", {sizeof});
|
||||
return 0;
|
||||
}}
|
||||
"""
|
||||
|
||||
|
||||
def eval_sizeofs(header, cflags):
|
||||
"""
|
||||
Evaluates each sizeof found in the given C header and replaces all
|
||||
occurences of the sizeof with its computed value.
|
||||
"""
|
||||
sizeofs = set(re.findall(r"sizeof\([^\)]*\)", header))
|
||||
tmp_dir = tempfile.mkdtemp()
|
||||
c_program_path = os.path.join(tmp_dir, "sizeof.c")
|
||||
executable_path = os.path.join(tmp_dir, "sizeof")
|
||||
|
||||
for sizeof in sizeofs:
|
||||
with open(c_program_path, "w", encoding="UTF-8") as c_program_file:
|
||||
c_program_file.write(_get_sizeof_eval_source(sizeof))
|
||||
subprocess.check_call(["gcc", c_program_path, *cflags, "-o", executable_path])
|
||||
size = subprocess.check_output([executable_path]).decode()
|
||||
header = header.replace(sizeof, size)
|
||||
|
||||
os.remove(c_program_path)
|
||||
os.remove(executable_path)
|
||||
os.rmdir(tmp_dir)
|
||||
return header
|
31
python/src/libcpuid/_utils.py
Normal file
31
python/src/libcpuid/_utils.py
Normal file
|
@ -0,0 +1,31 @@
|
|||
"""
|
||||
Internal module containing utility functions.
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
from libcpuid._libcpuid_cffi import ( # pylint: disable=no-name-in-module, import-error
|
||||
ffi,
|
||||
lib,
|
||||
)
|
||||
|
||||
|
||||
def c_string_to_str(c_string) -> str:
|
||||
"""Converts an FFI C string to a Python string."""
|
||||
return ffi.string(c_string).decode()
|
||||
|
||||
|
||||
def optional_int(value: int) -> Optional[int]:
|
||||
"""Returns the given integer if it is not -1, otherwise None."""
|
||||
return value if value != -1 else None
|
||||
|
||||
|
||||
def get_enum_options( # pylint: disable=dangerous-default-value
|
||||
prefix, ignore=[]
|
||||
) -> dict[str, int]:
|
||||
"""Returns a dictionary of C enum option names and their values based on name prefix."""
|
||||
enum_options = [x for x in dir(lib) if x.startswith(prefix)]
|
||||
return {
|
||||
x[len(prefix) :]: getattr(lib, x)
|
||||
for x in enum_options
|
||||
if x[len(prefix) :] not in ignore
|
||||
}
|
123
python/src/libcpuid/clock.py
Normal file
123
python/src/libcpuid/clock.py
Normal file
|
@ -0,0 +1,123 @@
|
|||
"""
|
||||
Module for CPU clock/frequency calculation.
|
||||
"""
|
||||
|
||||
from enum import Enum, auto
|
||||
from libcpuid.errors import LibcpuidError
|
||||
from libcpuid._libcpuid_cffi import ( # pylint: disable=no-name-in-module, import-error
|
||||
lib,
|
||||
ffi,
|
||||
)
|
||||
|
||||
|
||||
def exec_rdtsc() -> int:
|
||||
"""
|
||||
Exectutes the `rdtsc` (read timestamp counter) instruction
|
||||
and returns the value of the counter.
|
||||
"""
|
||||
result = ffi.new("uint64_t *")
|
||||
lib.cpu_rdtsc(result)
|
||||
return result[0]
|
||||
|
||||
|
||||
class TSC:
|
||||
"""
|
||||
Class for calculating clock frequency using differences
|
||||
in time and in the timestamp counter.
|
||||
"""
|
||||
|
||||
class State(Enum):
|
||||
"""State of the measurement."""
|
||||
|
||||
NOT_STARTED = auto()
|
||||
STARTED = auto()
|
||||
STOPPED = auto()
|
||||
|
||||
def __init__(self):
|
||||
self._c_cpu_mark = ffi.new("struct cpu_mark_t *")
|
||||
self._state = self.State.NOT_STARTED
|
||||
|
||||
@property
|
||||
def state(self) -> State:
|
||||
"""Returns the current state of the measurement."""
|
||||
return self._state
|
||||
|
||||
def start(self):
|
||||
"""Starts the measurement."""
|
||||
lib.cpu_tsc_mark(self._c_cpu_mark)
|
||||
self._state = self.State.STARTED
|
||||
|
||||
def stop(self):
|
||||
"""Stops the measurement."""
|
||||
if self._state == self.State.NOT_STARTED:
|
||||
raise LibcpuidError("The measurement has not been started.")
|
||||
if self._state == self.State.STOPPED:
|
||||
raise LibcpuidError("The measurement has already been stopped.")
|
||||
lib.cpu_tsc_unmark(self._c_cpu_mark)
|
||||
self._state = self.State.STOPPED
|
||||
|
||||
def frequency(self) -> int:
|
||||
"""
|
||||
Calculates the CPU clock frequency from the time and TSC
|
||||
difference between the calls to :meth:`start` and :meth:`stop`.
|
||||
"""
|
||||
if self._state == self.State.NOT_STARTED:
|
||||
raise LibcpuidError("The measurementhas not been started.")
|
||||
if self._state == self.State.STARTED:
|
||||
raise LibcpuidError("The measurement has not been stopped.")
|
||||
result = lib.cpu_clock_by_mark(self._c_cpu_mark)
|
||||
if result < 0:
|
||||
raise LibcpuidError("Could not compute clock frequency.")
|
||||
return result
|
||||
|
||||
|
||||
def frequency_measure(millis: int = 100, quad_check: bool = False) -> int:
|
||||
"""
|
||||
Measures the CPU clock frequency by busy-waiting for a specified
|
||||
number of milliseconds and calculating the difference between TSC
|
||||
values. With the :const:`quad_check` option on, the function will
|
||||
run 4 consecutive measurements and return the average of the two
|
||||
most consistent results.
|
||||
"""
|
||||
result = lib.cpu_clock_measure(millis, int(quad_check))
|
||||
if result < 0:
|
||||
raise LibcpuidError("Could not measure clock frequency.")
|
||||
return result
|
||||
|
||||
|
||||
def frequency_by_os() -> int:
|
||||
"""Returns the CPU clock frequency as reported by the OS."""
|
||||
result = lib.cpu_clock_by_os()
|
||||
if result < 0:
|
||||
raise LibcpuidError("Clock frequency unavailable.")
|
||||
return result
|
||||
|
||||
|
||||
def frequency_by_ic(millis: int = 50, runs: int = 4) -> int:
|
||||
"""
|
||||
Measures the CPU clock frequency using instruction counting for
|
||||
a specified number of milliseconds and runs.
|
||||
"""
|
||||
result = lib.cpu_clock_by_ic(millis, runs)
|
||||
if result < 0:
|
||||
raise LibcpuidError("Could not measure clock frequency.")
|
||||
return result
|
||||
|
||||
|
||||
def frequency_by_tsc() -> int:
|
||||
"""Computes the CPU clock **base** frequency based on information from `cpuid`."""
|
||||
result = lib.cpu_clock_by_tsc(ffi.NULL)
|
||||
if result < 0:
|
||||
raise LibcpuidError("Could not compute clock frequency.")
|
||||
return result
|
||||
|
||||
|
||||
def frequency() -> int:
|
||||
"""
|
||||
All in one method for getting CPU clock frequency. It tries to use the OS
|
||||
and falls back to other methods if the OS does not have the information.
|
||||
"""
|
||||
result = lib.cpu_clock()
|
||||
if result < 0:
|
||||
raise LibcpuidError("Could not get clock frequency.")
|
||||
return result
|
54
python/src/libcpuid/enums.py
Normal file
54
python/src/libcpuid/enums.py
Normal file
|
@ -0,0 +1,54 @@
|
|||
"""
|
||||
Module providing access to enums from the libcpuid library,
|
||||
e.g., CPU architectures, vendors, or features.
|
||||
|
||||
For compatibility with the underlying C library, the values
|
||||
of all enums are integers. However, calling the :func:`str`
|
||||
or :func:`print` function on an enum member returns or prints
|
||||
its string representation.
|
||||
"""
|
||||
|
||||
from enum import IntEnum
|
||||
from libcpuid._utils import c_string_to_str, get_enum_options
|
||||
from libcpuid._libcpuid_cffi import ( # pylint: disable=no-name-in-module, import-error
|
||||
lib,
|
||||
)
|
||||
|
||||
|
||||
CPUArchitecture = IntEnum("CPUArchitecture", get_enum_options("ARCHITECTURE_"))
|
||||
CPUArchitecture.__str__ = lambda self: c_string_to_str(lib.cpu_architecture_str(self))
|
||||
CPUArchitecture.__doc__ = "CPU architectures."
|
||||
|
||||
CPUVendor = IntEnum("CPUVendor", get_enum_options("VENDOR_"))
|
||||
CPUVendor.__str__ = lambda self: self.name
|
||||
CPUVendor.__doc__ = "CPU vendors."
|
||||
|
||||
CPUPurpose = IntEnum("CPUPurpose", get_enum_options("PURPOSE_"))
|
||||
CPUPurpose.__str__ = lambda self: c_string_to_str(lib.cpu_purpose_str(self))
|
||||
CPUPurpose.__doc__ = "CPU purposes."
|
||||
|
||||
HypervisorVendor = IntEnum(
|
||||
"HypervisorVendor", get_enum_options("HYPERVISOR_", ignore=["NONE"])
|
||||
)
|
||||
HypervisorVendor.__str__ = lambda self: self.name
|
||||
HypervisorVendor.__doc__ = (
|
||||
"Hypervisor vendors, as guessed from the CPU_FEATURE_HYPERVISOR flag"
|
||||
)
|
||||
|
||||
CPUFeature = IntEnum("CPUFeature", get_enum_options("CPU_FEATURE_"))
|
||||
CPUFeature.__str__ = lambda self: c_string_to_str(lib.cpu_feature_str(self))
|
||||
CPUFeature.__doc__ = "CPU feature identifiers (flags)"
|
||||
|
||||
CPUFeatureLevel = IntEnum("CPUFeatureLevel", get_enum_options("FEATURE_LEVEL_"))
|
||||
CPUFeatureLevel.__str__ = lambda self: c_string_to_str(lib.cpu_feature_level_str(self))
|
||||
CPUFeatureLevel.__doc__ = """
|
||||
CPU feature levels, also known as microarchitecture levels (x86) or architecture versions (ARM).
|
||||
"""
|
||||
|
||||
CPUHint = IntEnum("CPUHint", get_enum_options("CPU_HINT_"))
|
||||
CPUHint.__str__ = lambda self: self.name
|
||||
CPUHint.__doc__ = "CPU detection hint identifiers."
|
||||
|
||||
SGXFeature = IntEnum("SGXFeature", get_enum_options("INTEL_"))
|
||||
SGXFeature.__str__ = lambda self: self.name
|
||||
SGXFeature.__doc__ = "SGX feature flags."
|
22
python/src/libcpuid/errors.py
Normal file
22
python/src/libcpuid/errors.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
"""
|
||||
Module containing custom exceptions of the library.
|
||||
"""
|
||||
|
||||
from libcpuid._utils import c_string_to_str
|
||||
from libcpuid._libcpuid_cffi import ( # pylint: disable=no-name-in-module, import-error
|
||||
lib,
|
||||
)
|
||||
|
||||
|
||||
class LibcpuidError(Exception):
|
||||
"""Main exception of the library."""
|
||||
|
||||
|
||||
class CLibraryError(LibcpuidError):
|
||||
"""
|
||||
Raised when an error code is returned by some function
|
||||
in the underlying libcpuid C library.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(c_string_to_str(lib.cpuid_error()))
|
419
python/src/libcpuid/info.py
Normal file
419
python/src/libcpuid/info.py
Normal file
|
@ -0,0 +1,419 @@
|
|||
"""
|
||||
The main module, providing a class that holds CPU information.
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
from libcpuid import enums
|
||||
from libcpuid.sgx import SGX
|
||||
from libcpuid.raw import CPURawData, CPURawDataArray
|
||||
from libcpuid.errors import CLibraryError
|
||||
from libcpuid._utils import c_string_to_str, optional_int
|
||||
from libcpuid._libcpuid_cffi import ( # pylint: disable=no-name-in-module, import-error
|
||||
lib,
|
||||
ffi,
|
||||
)
|
||||
|
||||
|
||||
class CPUInfo: # pylint: disable=too-many-public-methods
|
||||
"""
|
||||
The main class, holding information about a CPU
|
||||
in human-friendly format.
|
||||
"""
|
||||
|
||||
def __init__(self, c_cpu_id):
|
||||
self._c_cpu_id = c_cpu_id
|
||||
self._features = None
|
||||
self._detection_hints = None
|
||||
|
||||
@classmethod
|
||||
def from_c(cls, c_cpu_id):
|
||||
"""Creates a :class:`CPUInfo` instance from the corresponding C structure."""
|
||||
cpu_info = cls(c_cpu_id)
|
||||
if cpu_info.architecture == enums.CPUArchitecture.X86:
|
||||
return X86Info(c_cpu_id)
|
||||
if cpu_info.architecture == enums.CPUArchitecture.ARM:
|
||||
return ARMInfo(c_cpu_id)
|
||||
return cpu_info
|
||||
|
||||
@classmethod
|
||||
def from_current_cpu(cls):
|
||||
"""Creates a :class:`CPUInfo` instance by identifying the current CPU."""
|
||||
c_cpu_id = ffi.new("struct cpu_id_t *")
|
||||
if lib.cpu_identify(ffi.NULL, c_cpu_id) != 0:
|
||||
raise CLibraryError
|
||||
return cls.from_c(c_cpu_id)
|
||||
|
||||
@classmethod
|
||||
def from_purpose(cls, purpose: enums.CPUPurpose):
|
||||
"""Creates a :class:`CPUInfo` instance by identifying a CPU with the requested purpose."""
|
||||
c_cpu_id = ffi.new("struct cpu_id_t *")
|
||||
if lib.cpu_request_core_type(purpose, ffi.NULL, c_cpu_id) != 0:
|
||||
raise CLibraryError
|
||||
return CPUInfo.from_c(c_cpu_id)
|
||||
|
||||
@classmethod
|
||||
def from_raw(cls, raw_data: CPURawData):
|
||||
"""Creates a :class:`CPUInfo` instance from an instance of :class:`CPURawData`"""
|
||||
c_cpu_id = ffi.new("struct cpu_id_t *")
|
||||
if lib.cpu_identify(raw_data.c_cpu_raw_data, c_cpu_id) != 0:
|
||||
raise CLibraryError
|
||||
return CPUInfo.from_c(c_cpu_id)
|
||||
|
||||
@classmethod
|
||||
def from_raw_array_and_purpose(
|
||||
cls, raw_data_array: CPURawDataArray, purpose: enums.CPUPurpose
|
||||
):
|
||||
"""Identifies a CPU with a given purpose, returning a :class:`CPUInfo` instance."""
|
||||
c_cpu_id = ffi.new("struct cpu_id_t *")
|
||||
if (
|
||||
lib.cpu_request_core_type(
|
||||
purpose, raw_data_array.c_cpu_raw_data_array, c_cpu_id
|
||||
)
|
||||
!= 0
|
||||
):
|
||||
raise CLibraryError
|
||||
return CPUInfo.from_c(c_cpu_id)
|
||||
|
||||
@property
|
||||
def architecture(self) -> enums.CPUArchitecture:
|
||||
"""The CPU Architecture."""
|
||||
try:
|
||||
return enums.CPUArchitecture(self._c_cpu_id.architecture)
|
||||
except ValueError:
|
||||
return enums.CPUArchitecture.UNKNOWN
|
||||
|
||||
@property
|
||||
def feature_level(self) -> enums.CPUFeatureLevel:
|
||||
"""The CPU feature level."""
|
||||
try:
|
||||
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._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._c_cpu_id.brand_str)
|
||||
|
||||
@property
|
||||
def vendor(self) -> enums.CPUVendor:
|
||||
"""The CPU vendor."""
|
||||
try:
|
||||
return enums.CPUVendor(self._c_cpu_id.vendor)
|
||||
except ValueError:
|
||||
return enums.CPUVendor.UNKNOWN
|
||||
|
||||
@property
|
||||
def features(self) -> set[enums.CPUFeature]:
|
||||
"""The set of supported CPU features."""
|
||||
if self._features is None:
|
||||
self._features = {
|
||||
flag
|
||||
for flag in enums.CPUFeature
|
||||
if flag >= 0 and self._c_cpu_id.flags[flag]
|
||||
}
|
||||
return self._features
|
||||
|
||||
@property
|
||||
def num_cores(self) -> int:
|
||||
"""The number of CPU cores."""
|
||||
return self._c_cpu_id.num_cores
|
||||
|
||||
@property
|
||||
def num_logical_cpus(self) -> int:
|
||||
"""The number of logical processors."""
|
||||
return self._c_cpu_id.num_logical_cpus
|
||||
|
||||
@property
|
||||
def total_logical_cpus(self) -> int:
|
||||
"""The total number of logical processors."""
|
||||
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._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._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._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._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._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._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._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._c_cpu_id.l2_assoc)
|
||||
|
||||
@property
|
||||
def l3_assoc(self) -> Optional[int]:
|
||||
"""L3 cache associativity. :const:`None` if not determined."""
|
||||
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._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._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._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._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._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._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._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._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._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._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._c_cpu_id.l4_instances)
|
||||
|
||||
@property
|
||||
def cpu_codename(self) -> str:
|
||||
"""A human-friendly CPU codename."""
|
||||
return c_string_to_str(self._c_cpu_id.cpu_codename)
|
||||
|
||||
@property
|
||||
def detection_hints(self) -> set[enums.CPUHint]:
|
||||
"""A set of miscellaneous detection hints."""
|
||||
if self._detection_hints is None:
|
||||
self._detection_hints = {
|
||||
hint
|
||||
for hint in enums.CPUHint
|
||||
if hint >= 0 and self._c_cpu_id.detection_hints[hint]
|
||||
}
|
||||
return self._detection_hints
|
||||
|
||||
@property
|
||||
def affinity_mask(self) -> bytes:
|
||||
"""A bit mask of the affinity IDs that this processor type is occupying."""
|
||||
bit_mask = getattr(self._c_cpu_id.affinity_mask, "__bits")
|
||||
return b"".join(reversed([byte.to_bytes(1) for byte in bit_mask])).lstrip(
|
||||
b"\x00"
|
||||
)
|
||||
|
||||
@property
|
||||
def purpose(self) -> enums.CPUPurpose:
|
||||
"""The purpose of the CPU type, relevant for hybrid CPUs."""
|
||||
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._c_cpu_id)
|
||||
return enums.HypervisorVendor(hypervisor) if hypervisor != 0 else None
|
||||
|
||||
|
||||
class X86Info(CPUInfo):
|
||||
"""
|
||||
The :class:`CPUInfo` child class for x86 CPUs.
|
||||
"""
|
||||
|
||||
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._c_cpu_id.family
|
||||
|
||||
@property
|
||||
def model(self) -> int:
|
||||
"""The CPU model number."""
|
||||
return self._c_cpu_id.model
|
||||
|
||||
@property
|
||||
def stepping(self) -> int:
|
||||
"""The CPU stepping."""
|
||||
return self._c_cpu_id.stepping
|
||||
|
||||
@property
|
||||
def ext_family(self) -> int:
|
||||
"""The CPU display family number."""
|
||||
return self._c_cpu_id.ext_family
|
||||
|
||||
@property
|
||||
def ext_model(self) -> int:
|
||||
"""The CPU display model number."""
|
||||
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._c_cpu_id.sse_size)
|
||||
|
||||
@property
|
||||
def sgx(self) -> Optional[SGX]:
|
||||
"""SGX-related features if present, otherwise :const:`None`."""
|
||||
return SGX(self._c_cpu_id.sgx) if self._c_cpu_id.sgx.present == 1 else None
|
||||
|
||||
|
||||
class ARMInfo(CPUInfo):
|
||||
"""
|
||||
The :class:`CPUInfo` child class for ARM CPUs.
|
||||
"""
|
||||
|
||||
def __init__(self, c_cpu_id):
|
||||
super().__init__(c_cpu_id)
|
||||
self._c_cpu_id = c_cpu_id
|
||||
|
||||
@property
|
||||
def implementer(self) -> int:
|
||||
"""The CPU implementer code."""
|
||||
return self._c_cpu_id.implementer
|
||||
|
||||
@property
|
||||
def variant(self) -> int:
|
||||
"""The CPU variant number."""
|
||||
return self._c_cpu_id.variant
|
||||
|
||||
@property
|
||||
def part_num(self) -> int:
|
||||
"""The CPU primary part number."""
|
||||
return self._c_cpu_id.part_num
|
||||
|
||||
@property
|
||||
def revision(self) -> int:
|
||||
"""The CPU revision number."""
|
||||
return self._c_cpu_id.revision
|
||||
|
||||
|
||||
class SystemInfo:
|
||||
"""
|
||||
Class for holding structured :class:`CPUInfo` information about multiple CPUs.
|
||||
Instances of this class can be indexed like lists, each item (of type :class:`CPUInfo`)
|
||||
corresponds to a different :class:`CPUPurpose`.
|
||||
"""
|
||||
|
||||
def __init__(self, c_system_id, cpu_info_list: list[CPUInfo]):
|
||||
self._c_system_id = c_system_id
|
||||
self._cpu_info_list = cpu_info_list
|
||||
|
||||
def __del__(self):
|
||||
lib.cpuid_free_system_id(self._c_system_id)
|
||||
|
||||
def __getitem__(self, index: int) -> CPUInfo:
|
||||
return self._cpu_info_list[index]
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self._cpu_info_list)
|
||||
|
||||
@classmethod
|
||||
def from_c(cls, c_system_id):
|
||||
"""Create a :class:`SystemInfo` 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("struct cpu_id_t")
|
||||
)
|
||||
cpu_id_list.append(CPUInfo.from_c(new_c_cpu_id))
|
||||
return cls(c_system_id, cpu_id_list)
|
||||
|
||||
@classmethod
|
||||
def from_all_cpus(cls):
|
||||
"""Create a :class:`SystemInfo` instance by indentifying all CPUs."""
|
||||
c_system_id = ffi.new("struct system_id_t *")
|
||||
if lib.cpu_identify_all(ffi.NULL, c_system_id) != 0:
|
||||
raise CLibraryError
|
||||
return cls.from_c(c_system_id)
|
||||
|
||||
@classmethod
|
||||
def from_raw_array(cls, raw_data_array: CPURawDataArray):
|
||||
"""Create a :class:`SystemInfo` instance from an instance of :class:`CPURawDataArray`"""
|
||||
c_system_id = ffi.new("struct system_id_t *")
|
||||
if lib.cpu_identify_all(raw_data_array.c_cpu_raw_data_array, c_system_id) != 0:
|
||||
raise CLibraryError
|
||||
return SystemInfo.from_c(c_system_id)
|
||||
|
||||
@property
|
||||
def l1_data_total_instances(self) -> Optional[int]:
|
||||
"""The 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]:
|
||||
"""The 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]:
|
||||
"""The 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]:
|
||||
"""The 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]:
|
||||
"""The number of total L4 cache instances. :const:`None` if not undetermined"""
|
||||
return optional_int(self._c_system_id.l4_total_instances)
|
63
python/src/libcpuid/msr.py
Normal file
63
python/src/libcpuid/msr.py
Normal file
|
@ -0,0 +1,63 @@
|
|||
"""
|
||||
Module providing access to information from model-specific registers.
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
from libcpuid.errors import CLibraryError
|
||||
from libcpuid._utils import get_enum_options
|
||||
from libcpuid._libcpuid_cffi import ( # pylint: disable=no-name-in-module, import-error
|
||||
lib,
|
||||
ffi,
|
||||
)
|
||||
|
||||
|
||||
class MSR:
|
||||
"""
|
||||
Class for reading information from model-specific registers (MSR).
|
||||
|
||||
Since `rdmsr` is a privileged instruction, you need ring 0 access to
|
||||
instantiate this class.
|
||||
|
||||
To see the documentation of properties, look for :const:`cpu_msrinfo_request_t`
|
||||
in :const:`libcpuid.h`, the main header file of the underlying C library.
|
||||
"""
|
||||
|
||||
def __init__(self, core: int = 0):
|
||||
self._handle = lib.cpu_msr_driver_open_core(core)
|
||||
if self._handle == ffi.NULL:
|
||||
raise CLibraryError
|
||||
|
||||
def __del__(self):
|
||||
if lib.cpu_msr_driver_close(self._handle) != 0:
|
||||
raise CLibraryError
|
||||
|
||||
def serialize_raw_data(self, filename: str):
|
||||
"""Writes raw MSR data to a file."""
|
||||
if lib.msr_serialize_raw_data(self._handle, filename.encode()) != 0:
|
||||
raise CLibraryError
|
||||
|
||||
def read_register(self, msr_index: int) -> bytes:
|
||||
"""Returns the content of a model-specific register with the given index."""
|
||||
result = ffi.new("uint64_t *")
|
||||
if lib.cpu_rdmsr(self._handle, msr_index, result) != 0:
|
||||
raise CLibraryError
|
||||
return result[0].to_bytes(8)
|
||||
|
||||
|
||||
def _get_msrinfo_request(enum_value):
|
||||
def _msrinfo_request(self) -> Optional[int]:
|
||||
"""May return :const:`None` if not available."""
|
||||
result = lib.cpu_msrinfo(
|
||||
self._handle, enum_value # pylint: disable=protected-access
|
||||
)
|
||||
return None if result == 0x3FFFFFFF else result
|
||||
|
||||
return _msrinfo_request
|
||||
|
||||
|
||||
# This adds properties to the MSR class, copying the possible
|
||||
# inputs to the cpu_msrinfo function of the C library, such as
|
||||
# mperf, aperf, min_multiplier, temperature, or voltage.
|
||||
# See cpu_msr_info_request_t in libcpuid.h for details.
|
||||
for name, value in get_enum_options("INFO_", ignore=["BCLK"]).items():
|
||||
setattr(MSR, name.lower(), property(_get_msrinfo_request(value)))
|
105
python/src/libcpuid/raw.py
Normal file
105
python/src/libcpuid/raw.py
Normal file
|
@ -0,0 +1,105 @@
|
|||
"""
|
||||
Module providing access to raw CPU data.
|
||||
"""
|
||||
|
||||
from libcpuid.errors import CLibraryError
|
||||
from libcpuid._libcpuid_cffi import ( # pylint: disable=no-name-in-module, import-error
|
||||
lib,
|
||||
ffi,
|
||||
)
|
||||
|
||||
|
||||
class CPURawData:
|
||||
"""
|
||||
Class holding raw data about a single logical CPU.
|
||||
"""
|
||||
|
||||
def __init__(self, c_cpu_raw_data):
|
||||
self._c_cpu_raw_data = c_cpu_raw_data
|
||||
|
||||
@property
|
||||
def c_cpu_raw_data(self):
|
||||
"""Returns the underlying C structure."""
|
||||
return self._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())
|
||||
|
||||
@classmethod
|
||||
def from_current_cpu(cls):
|
||||
"""Creates a :class:`CPURawData` instance by running `cpuid` on the current CPU."""
|
||||
c_cpu_raw_data = ffi.new("struct cpu_raw_data_t *")
|
||||
if lib.cpuid_get_raw_data(c_cpu_raw_data) != 0:
|
||||
raise CLibraryError
|
||||
return cls(c_cpu_raw_data)
|
||||
|
||||
@classmethod
|
||||
def from_cpu_core(cls, logical_cpu: int):
|
||||
"""Creates a :class:`CPURawData` instance by running `cpuid` on the specified CPU."""
|
||||
c_cpu_raw_data = ffi.new("struct cpu_raw_data_t *")
|
||||
if lib.cpuid_get_raw_data_core(c_cpu_raw_data, logical_cpu) != 0:
|
||||
raise CLibraryError
|
||||
return cls(c_cpu_raw_data)
|
||||
|
||||
@classmethod
|
||||
def from_file(cls, filename: str):
|
||||
"""Creates a :class:`CPURawData` instance by parsing the data from a provided file."""
|
||||
c_cpu_raw_data = ffi.new("struct cpu_raw_data_t *")
|
||||
if lib.cpuid_deserialize_raw_data(c_cpu_raw_data, filename.encode()) != 0:
|
||||
raise CLibraryError
|
||||
return cls(c_cpu_raw_data)
|
||||
|
||||
|
||||
class CPURawDataArray:
|
||||
"""
|
||||
Class holding raw data about multiple CPUs.
|
||||
Instances of this class can be indexed like lists. each item
|
||||
(of type :class:`CPURawData`) holds data about one **logical** CPU.
|
||||
"""
|
||||
|
||||
def __init__(self, c_cpu_raw_data_array):
|
||||
self._c_cpu_raw_data_array = c_cpu_raw_data_array
|
||||
self._cpu_raw_data_list = [
|
||||
CPURawData(c_cpu_raw_data)
|
||||
for c_cpu_raw_data in self._c_cpu_raw_data_array.raw[
|
||||
0 : self._c_cpu_raw_data_array.num_raw
|
||||
]
|
||||
]
|
||||
|
||||
def __del__(self):
|
||||
lib.cpuid_free_raw_data_array(self._c_cpu_raw_data_array)
|
||||
|
||||
def __getitem__(self, index: int) -> CPURawData:
|
||||
return self._cpu_raw_data_list[index]
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self._cpu_raw_data_list)
|
||||
|
||||
@property
|
||||
def c_cpu_raw_data_array(self):
|
||||
"""Returns the underlying C structure."""
|
||||
return self._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())
|
||||
|
||||
@classmethod
|
||||
def from_all_cpus(cls):
|
||||
"""Creates a :class:`CPURawDataArray` instance by running `cpuid` on all CPUs."""
|
||||
c_cpu_raw_data_array = ffi.new("struct cpu_raw_data_array_t *")
|
||||
if lib.cpuid_get_all_raw_data(c_cpu_raw_data_array) != 0:
|
||||
raise CLibraryError
|
||||
return cls(c_cpu_raw_data_array)
|
||||
|
||||
@classmethod
|
||||
def from_file(cls, filename: str):
|
||||
"""Creates a :class:`CPURawDataArray` instance by parsing the data from a provided file."""
|
||||
c_cpu_raw_data_array = ffi.new("struct cpu_raw_data_array_t *")
|
||||
if (
|
||||
lib.cpuid_deserialize_all_raw_data(c_cpu_raw_data_array, filename.encode())
|
||||
!= 0
|
||||
):
|
||||
raise CLibraryError
|
||||
return cls(c_cpu_raw_data_array)
|
96
python/src/libcpuid/sgx.py
Normal file
96
python/src/libcpuid/sgx.py
Normal file
|
@ -0,0 +1,96 @@
|
|||
"""
|
||||
Module dealing with SGX-related CPU information.
|
||||
"""
|
||||
|
||||
from libcpuid import enums
|
||||
from libcpuid._libcpuid_cffi import ( # pylint: disable=no-name-in-module, import-error
|
||||
lib,
|
||||
ffi,
|
||||
)
|
||||
|
||||
|
||||
class EPC:
|
||||
"""
|
||||
Class holding information about a single EPC (Enclave Page Cache) area.
|
||||
"""
|
||||
|
||||
def __init__(self, c_epc):
|
||||
self._c_epc = c_epc
|
||||
|
||||
@property
|
||||
def start_addr(self) -> int:
|
||||
"""Start address."""
|
||||
return self._c_epc.start_addr
|
||||
|
||||
@property
|
||||
def length(self) -> int:
|
||||
"""Length of the area."""
|
||||
return self._c_epc.length
|
||||
|
||||
|
||||
class SGX:
|
||||
"""
|
||||
Class holding the SGX-related information about a CPU.
|
||||
"""
|
||||
|
||||
def __init__(self, c_sgx):
|
||||
self._c_sgx = c_sgx
|
||||
self._features = None
|
||||
|
||||
@property
|
||||
def max_enclave_32bit(self) -> int:
|
||||
"""The maximum enclave size in 32-bit mode."""
|
||||
return self._c_sgx.max_enclave_32bit
|
||||
|
||||
@property
|
||||
def max_enclave_64bit(self) -> int:
|
||||
"""The maximum enclave size in 64-bit mode."""
|
||||
return self._c_sgx.max_enclave_64bit
|
||||
|
||||
@property
|
||||
def features(self) -> set[enums.SGXFeature]:
|
||||
"""Set of CPU SGX features."""
|
||||
if self._features is None:
|
||||
self._features = {
|
||||
flag
|
||||
for flag in enums.SGXFeature
|
||||
if flag >= 0 and self._c_sgx.flags[flag]
|
||||
}
|
||||
return self._features
|
||||
|
||||
@property
|
||||
def num_epc_sections(self) -> int:
|
||||
"""The number of Enclave Page Cache (EPC) sections."""
|
||||
return self._c_sgx.num_epc_sections
|
||||
|
||||
@property
|
||||
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._c_sgx.misc_select.to_bytes(4)
|
||||
|
||||
@property
|
||||
def secs_attributes(self) -> bytes:
|
||||
"""
|
||||
Bit vector of the attributes that can be set to SECS.ATTRIBUTES
|
||||
via ECREATE.
|
||||
"""
|
||||
return self._c_sgx.secs_attributes.to_bytes(8)
|
||||
|
||||
@property
|
||||
def secs_xfrm(self) -> bytes:
|
||||
"""
|
||||
Bit vector of the attributes that can be set in the XSAVE feature
|
||||
request mask.
|
||||
"""
|
||||
return self._c_sgx.secs_xfrm.to_bytes(8)
|
||||
|
||||
def get_epc(self, index: int) -> EPC:
|
||||
"""Fetches information about a single EPC area given by `index`."""
|
||||
if not 0 <= index < self.num_epc_sections:
|
||||
raise IndexError(
|
||||
f"Invalid index {index}, the CPU has {self.num_epc_sections} EPC sections."
|
||||
)
|
||||
return EPC(lib.cpuid_get_epc(index, ffi.NULL))
|
Loading…
Add table
Add a link
Reference in a new issue