Skip to content

Commit

Permalink
Implement Python bindings
Browse files Browse the repository at this point in the history
  • Loading branch information
zacikpa committed May 2, 2024
1 parent 954d9d4 commit db69540
Show file tree
Hide file tree
Showing 16 changed files with 866 additions and 1 deletion.
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,9 @@ cpuid_tool/x32
cpuid_tool/x64
*.vcxproj.user
build
docs
/docs
.vscode
python/src/libcpuid/__pycache__
python/docs/_build
python/dist
*.egg-info
15 changes: 15 additions & 0 deletions .readthedocs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Read the Docs configuration file

version: 2

build:
os: ubuntu-22.04
tools:
python: latest

sphinx:
configuration: python/docs/conf.py

python:
install:
- requirements: python/docs/requirements.txt
7 changes: 7 additions & 0 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ For non-developers, who still want to contribute tests for the project,
use [this page](http://libcpuid.sourceforge.net/bugreport.php) to report
misdetections or new CPUs that libcpuid doesn't handle well yet.

Python bindings
---------------

libcpuid features Python bindings, which can be installed as a library
using `python -m pip install libcpuid`.
See [python/README.md](python/README.md) for details.

Users
-----

Expand Down
8 changes: 8 additions & 0 deletions python/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# libcpuid

libcpuid is a package that provides Python bindings to
the C library of the same name. Its main feature is
CPU identification for the x86/x86_64 architecture.

Visit the [documentation at Read the Docs](https://libcpuid.readthedocs.io/en/latest/index.html#)
to see how the library is used.
5 changes: 5 additions & 0 deletions python/docs/api/cpuinfo.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Classes storing CPU information
===============================

.. automodule:: libcpuid.cpuinfo
:members:
6 changes: 6 additions & 0 deletions python/docs/api/enums.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Enumeration classes
===================

.. automodule:: libcpuid.enums
:members:
:undoc-members:
5 changes: 5 additions & 0 deletions python/docs/api/libcpuid.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Top-level library functionality
===============================

.. automodule:: libcpuid
:members:
35 changes: 35 additions & 0 deletions python/docs/conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Configuration file for the Sphinx documentation builder.
#
# For the full list of built-in configuration values, see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html

# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information

import os
import sys

sys.path.insert(0, os.path.abspath("../src"))

project = "libcpuid"
copyright = "2024, Pavol Žáčik"
author = "Pavol Žáčik"

# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration

extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.doctest",
"sphinx_rtd_theme",
]

templates_path = ["_templates"]
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
language = "en"

# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output

html_theme = "sphinx_rtd_theme"
autodoc_member_order = "bysource"
50 changes: 50 additions & 0 deletions python/docs/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
Welcome to libcpuid's documentation!
====================================

**libcpuid** provides a Python interface
to the libcpuid C library.

.. code-block:: python
import sys
import libcpuid
# Initialize the libcpuid library
try:
lib = libcpuid.LibCPUID()
except libcpuid.LibCPUIDError as err:
print(err)
sys.exit(1)
# print the version of the libcpuid library
print(lib.version())
# print the number of CPU cores
print(lib.get_total_cpus())
# check if the cpuid instruction is available
if not lib.cpuid_present():
print("CPUID instruction is not available")
sys.exit(1)
try:
# run the main CPU identification function
# and print information about the CPU
cpuid = lib.cpu_identify()
print(cpuid.vendor.name)
print(cpuid.architecture.name)
print(cpuid.has_feature(libcpuid.enums.CPUFeature.FPU))
# print the list of all Intel CPU code names
print(lib.get_cpu_list(libcpuid.enums.CPUVendor.INTEL))
except libcpuid.LibCPUIDError as err:
print(err)
sys.exit(1)
.. toctree::
:maxdepth: 2

Home <self>
api/libcpuid
api/cpuinfo
api/enums
1 change: 1 addition & 0 deletions python/docs/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
sphinx-rtd-theme
12 changes: 12 additions & 0 deletions python/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"

[project]
name = "libcpuid"
version = "0.1.0"
readme = "README.md"
license = {text = "BSD-3-Clause"}
authors = [
{name = "Pavol Žáčik", email = "[email protected]"}
]
100 changes: 100 additions & 0 deletions python/src/libcpuid/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
"""
The libcpuid package defines Python bindings to the
libcpuid C library, which provides CPU identification.
"""

from sys import platform
from ctypes import CDLL, c_bool, c_char_p, POINTER, byref
from . import enums, types, cpuinfo


class LibCPUIDError(Exception):
"""Raised when an error occurs in the libcpuid library."""


class LibCPUID:
"""The main class wrapping the libcpuid C library."""

def __init__(self):
if platform.startswith("win32") or platform.startswith("cygwin"):
dll_name = "libcpuid.dll"
elif platform.startswith("linux"):
dll_name = "libcpuid.so"
else:
raise LibCPUIDError(f"Unsupported platform: {platform}")
try:
self._dll = CDLL(dll_name)
self._set_function_prototypes()
except OSError as ex:
raise LibCPUIDError("Unable to load the libcpuid library") from ex

def _check_error(self, return_code: int) -> None:
"""Translates return codes to exceptions."""
if return_code != 0:
raise LibCPUIDError(self._dll.cpuid_error().decode())

def _set_function_prototypes(self):
"""Sets the C function prototypes according to the libcpuid API."""
# char *cpuid_lib_version(void);
self._dll.cpuid_lib_version.argtypes = []
self._dll.cpuid_lib_version.restype = c_char_p

# bool cpuid_present(void)
self._dll.cpuid_present.argtypes = []
self._dll.cpuid_present.restype = c_bool

# int cpu_identify(struct cpu_raw_data_t* raw, struct cpu_id_t* data)
self._dll.cpu_identify.argtypes = [
POINTER(types.CPURawData),
POINTER(types.CPUID),
]
self._dll.cpu_identify.restype = self._check_error

# void cpuid_get_cpu_list(cpu_vendor_t vendor, struct cpu_list_t* list)
self._dll.cpuid_get_cpu_list.argtypes = [
types.CPUVendor,
POINTER(types.CPUList),
]
self._dll.cpuid_get_cpu_list.restype = None

# void cpuid_free_cpu_list(struct cpu_list_t* list);
self._dll.cpuid_free_cpu_list.argtypes = [POINTER(types.CPUList)]
self._dll.cpuid_free_cpu_list.restype = None

# const char* cpuid_error(void);
self._dll.cpuid_error.argtypes = []
self._dll.cpuid_error.restype = c_char_p

# hypervisor_vendor_t
# cpuid_get_hypervisor(struct cpu_raw_data_t* raw, struct cpu_id_t* data)
self._dll.cpuid_get_hypervisor.argtypes = [
POINTER(types.CPURawData),
POINTER(types.CPUID),
]
self._dll.cpuid_get_hypervisor.restype = types.HypervisorVendor

def version(self) -> str:
"""Returns the version of the libcpuid library."""
return self._dll.cpuid_lib_version().decode()

def cpuid_present(self) -> bool:
"""Checks if the cpuid instruction is supported."""
return self._dll.cpuid_present()

def get_total_cpus(self) -> int:
"""Returns the total number of logical CPU threads."""
return self._dll.cpuid_get_total_cpus()

def cpu_identify(self) -> cpuinfo.CPUID:
"""Identifies the CPU and returns a :class:`cpuinfo.CPUID` instance."""
cpu_id = types.CPUID()
self._dll.cpu_identify(None, byref(cpu_id))
return cpuinfo.CPUID(cpu_id, self._dll)

def get_cpu_list(self, vendor: enums.CPUVendor) -> list[str]:
"""Gets a list of CPU :meth:`codenames <cpuinfo.CPUID.cpu_codename>` for a specific vendor."""
cpu_list = types.CPUList()
self._dll.cpuid_get_cpu_list(vendor, byref(cpu_list))
if cpu_list.num_entries == 0:
raise LibCPUIDError(self._dll.cpuid_error().decode())
return [name.decode() for name in cpu_list.names[: cpu_list.num_entries]]
20 changes: 20 additions & 0 deletions python/src/libcpuid/consts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"""
Constants, as defined by the libcpuid library.
"""

NUM_REGS = 4
VENDOR_STR_MAX = 16
BRAND_STR_MAX = 64
CODENAME_STR_MAX = 64
CPU_FLAGS_MAX = 128
MAX_CPUID_LEVEL = 32
MAX_EXT_CPUID_LEVEL = 32
MAX_INTELFN4_LEVEL = 8
MAX_INTELFN11_LEVEL = 4
MAX_INTELFN12H_LEVEL = 4
MAX_INTELFN14H_LEVEL = 4
MAX_AMDFN8000001DH_LEVEL = 4
CPU_HINTS_MAX = 16
SGX_FLAGS_MAX = 14
MASK_NCPUBITS = 8
MASK_SETSIZE = (1 << (2 * MASK_NCPUBITS)) // MASK_NCPUBITS
Loading

0 comments on commit db69540

Please sign in to comment.