diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c43c500..17ac222 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,25 +18,15 @@ jobs: c_compiler: cl - os: ubuntu-latest - c_compiler: gcc + c_compiler: musl-gcc steps: - name: Checkout uses: actions/checkout@v4 - if: runner.os == 'Linux' - name: Install CUDA toolkit on Linux - uses: Jimver/cuda-toolkit@v0.2.16 - with: - method: network - sub-packages: '["nvcc", "nvml-dev"]' - - - if: runner.os == 'Windows' - name: Install CUDA toolkit on Windows - uses: Jimver/cuda-toolkit@v0.2.16 - with: - method: network - sub-packages: '["cudart", "nvcc", "nvml_dev"]' + name: Install musl + run: sudo apt-get install --yes musl musl-tools - name: Configure project run: > diff --git a/CMakeLists.txt b/CMakeLists.txt index f4068c3..e30058b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,46 +1,53 @@ # Specify the minimum required version of CMake cmake_minimum_required(VERSION 3.16) -# Include the FetchContent module -include(FetchContent) +# Include the ExternalProject module +include(ExternalProject) # Define the project name and programming language project(nvidia-pstated C) -# Find the CUDAToolkit package -find_package(CUDAToolkit REQUIRED COMPONENTS nvml) - -# Declare the nvapi package -FetchContent_Declare( - nvapi - +# Download and set up the NvAPI +ExternalProject_Add(nvapi URL https://download.nvidia.com/XFree86/nvapi-open-source-sdk/R555-OpenSource.tar URL_HASH SHA256=71339c274a6a633f19b6bd358c7f3045063c6bc106b7dc488aaa7360a6d2b9d7 + + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND ${CMAKE_COMMAND} -E copy_directory /R555-OpenSource /include ) -# Download and make the nvapi content available for use -FetchContent_MakeAvailable(nvapi) +# Download and set up the NVML +ExternalProject_Add(nvml + URL https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2404/x86_64/cuda-nvml-dev-12-6_12.6.68-1_amd64.deb + URL_HASH SHA256=fda6d4fdf26e20db4ca4950489033f4c6747c7473db3f9dc0529d56f2cc237de + + CONFIGURE_COMMAND "" + BUILD_COMMAND ${CMAKE_COMMAND} -E tar xf /data.tar.xz + INSTALL_COMMAND ${CMAKE_COMMAND} -E copy_directory /usr/local/cuda-12.6/targets/x86_64-linux/include /include +) # Define the executable target add_executable(nvidia-pstated src/main.c src/nvapi.c + src/nvml.c src/utils.c ) -# Include directories for the target -target_include_directories(nvidia-pstated SYSTEM PRIVATE - ${nvapi_SOURCE_DIR}/R555-OpenSource +# Set dependencies for the executable +add_dependencies(nvidia-pstated + nvapi + nvml ) -# Link libraries -target_link_libraries(nvidia-pstated PRIVATE - CUDA::nvml +# Include directories for the target +target_include_directories(nvidia-pstated SYSTEM PRIVATE + ${CMAKE_CURRENT_BINARY_DIR}/nvapi-prefix/include + ${CMAKE_CURRENT_BINARY_DIR}/nvml-prefix/include ) # Conditional linking for Linux platform if(UNIX AND NOT APPLE) - target_link_libraries(nvidia-pstated PRIVATE - dl - ) + target_link_libraries(nvidia-pstated PRIVATE -static dl) endif() diff --git a/src/nvapi.c b/src/nvapi.c index ede95ab..a9d0e4a 100644 --- a/src/nvapi.c +++ b/src/nvapi.c @@ -2,13 +2,8 @@ #include #include -#ifdef _WIN32 - #include -#elif __linux__ - #include -#endif - #include "nvapi.h" +#include "utils.h" /***** ***** ***** ***** ***** TYPES ***** ***** ***** ***** *****/ @@ -65,22 +60,22 @@ NvAPI_Status NvAPI_GetErrorMessage(NvAPI_Status nr, NvAPI_ShortString szDesc) { } NvAPI_Status NvAPI_Initialize() { - // Check the platform and load the appropriate NvAPI library + // Load the appropriate NvAPI library #ifdef _WIN32 if (!lib) { - lib = LoadLibrary("nvapi64.dll"); + lib = library_open("nvapi64.dll"); } if (!lib) { - lib = LoadLibrary("nvapi.dll"); + lib = library_open("nvapi.dll"); } #elif __linux__ if (!lib) { - lib = dlopen("libnvidia-api.so.1", RTLD_LAZY); + lib = library_open("libnvidia-api.so.1"); } if (!lib) { - lib = dlopen("libnvidia-api.so", RTLD_LAZY); + lib = library_open("libnvidia-api.so"); } #endif @@ -97,11 +92,7 @@ NvAPI_Status NvAPI_Initialize() { nvapi_QueryInterface_t nvapi_QueryInterface; // Get the address of the nvapi_QueryInterface function from the loaded library - #ifdef _WIN32 - nvapi_QueryInterface = (nvapi_QueryInterface_t) GetProcAddress((HMODULE) lib, "nvapi_QueryInterface"); - #elif __linux__ - nvapi_QueryInterface = (nvapi_QueryInterface_t) dlsym(lib, "nvapi_QueryInterface"); - #endif + nvapi_QueryInterface = (nvapi_QueryInterface_t) library_proc(lib, "nvapi_QueryInterface"); // If the function pointer is still null, gathering the address failed if (!nvapi_QueryInterface) { @@ -144,12 +135,8 @@ NvAPI_Status NvAPI_Unload() { _NvAPI_Initialize = NULL; _NvAPI_Unload = NULL; - // Free the loaded library based on the platform - #ifdef _WIN32 - FreeLibrary((HMODULE) lib); - #elif __linux__ - dlclose(lib); - #endif + // Free the loaded library + library_close(lib); } } diff --git a/src/nvml.c b/src/nvml.c new file mode 100644 index 0000000..6476e5d --- /dev/null +++ b/src/nvml.c @@ -0,0 +1,152 @@ +#include + +#include "nvml.h" +#include "utils.h" + +/***** ***** ***** ***** ***** TYPES ***** ***** ***** ***** *****/ + +typedef nvmlReturn_t (*nvmlDeviceGetHandleByIndex_v2_t)(unsigned int, nvmlDevice_t); +typedef nvmlReturn_t (*nvmlDeviceGetName_t)(nvmlDevice_t, char *, unsigned int); +typedef nvmlReturn_t (*nvmlDeviceGetTemperature_t)(nvmlDevice_t, nvmlTemperatureSensors_t, unsigned int); +typedef nvmlReturn_t (*nvmlDeviceGetUtilizationRates_t)(nvmlDevice_t, nvmlUtilization_t); +typedef char * (*nvmlErrorString_t)(nvmlReturn_t); +typedef nvmlReturn_t (*nvmlInit_v2_t)(void); +typedef nvmlReturn_t (*nvmlShutdown_t)(void); + +/***** ***** ***** ***** ***** VARIABLES ***** ***** ***** ***** *****/ + +static void * lib; + +static nvmlDeviceGetHandleByIndex_v2_t _nvmlDeviceGetHandleByIndex_v2; +static nvmlDeviceGetName_t _nvmlDeviceGetName; +static nvmlDeviceGetTemperature_t _nvmlDeviceGetTemperature; +static nvmlDeviceGetUtilizationRates_t _nvmlDeviceGetUtilizationRates; +static nvmlErrorString_t _nvmlErrorString; +static nvmlInit_v2_t _nvmlInit_v2; +static nvmlShutdown_t _nvmlShutdown; + +/***** ***** ***** ***** ***** MACROS ***** ***** ***** ***** *****/ + +#define NVML_POINTER(pointer) do { \ + if (pointer == NULL) { \ + return NVML_ERROR_UNINITIALIZED; \ + } \ +} while(0) + +/***** ***** ***** ***** ***** IMPLEMENTATION ***** ***** ***** ***** *****/ + +nvmlReturn_t nvmlDeviceGetHandleByIndex_v2(unsigned int index, nvmlDevice_t * device) { + // Ensure the function pointer is valid + NVML_POINTER(_nvmlDeviceGetHandleByIndex_v2); + + // Invoke the function using the provided parameters + return _nvmlDeviceGetHandleByIndex_v2(index, *device); +} + +nvmlReturn_t nvmlDeviceGetName(nvmlDevice_t device, char * name, unsigned int length) { + // Ensure the function pointer is valid + NVML_POINTER(_nvmlDeviceGetName); + + // Invoke the function using the provided parameters + return _nvmlDeviceGetName(device, name, length); +} + +nvmlReturn_t nvmlDeviceGetTemperature(nvmlDevice_t device, nvmlTemperatureSensors_t sensorType, unsigned int * temp) { + // Ensure the function pointer is valid + NVML_POINTER(_nvmlDeviceGetTemperature); + + // Invoke the function using the provided parameters + return _nvmlDeviceGetTemperature(device, sensorType, *temp); +} + +nvmlReturn_t nvmlDeviceGetUtilizationRates(nvmlDevice_t device, nvmlUtilization_t * utilization) { + // Ensure the function pointer is valid + NVML_POINTER(_nvmlDeviceGetUtilizationRates); + + // Invoke the function using the provided parameters + return _nvmlDeviceGetUtilizationRates(device, *utilization); +} + +const char * nvmlErrorString(nvmlReturn_t result) { + // Ensure the function pointer is valid + if (_nvmlErrorString == NULL) { + return ""; + } + + // Invoke the function using the provided parameters + return _nvmlErrorString(result); +} + +nvmlReturn_t nvmlInit_v2(void) { + // Check the platform and load the appropriate NVML library + #ifdef _WIN32 + if (!lib) { + lib = library_open("nvml64.dll"); + } + + if (!lib) { + lib = library_open("nvml.dll"); + } + #elif __linux__ + if (!lib) { + lib = library_open("libnvidia-ml.so.1"); + } + + if (!lib) { + lib = library_open("libnvidia-ml.so"); + } + #endif + + // If the library handle is still not initialized, loading the library failed + if (!lib) { + // Print an error message indicating failure to load the NVML library + fprintf(stderr, "Unable to load NVML library\n"); + + // Return an error status indicating that the library was not found + return NVML_ERROR_LIBRARY_NOT_FOUND; + } + + // Retrieve the addresses of specific NVML functions + _nvmlDeviceGetHandleByIndex_v2 = (nvmlDeviceGetHandleByIndex_v2_t) library_proc(lib, "nvmlDeviceGetHandleByIndex_v2"); + _nvmlDeviceGetName = (nvmlDeviceGetName_t) library_proc(lib, "nvmlDeviceGetName"); + _nvmlDeviceGetTemperature = (nvmlDeviceGetTemperature_t) library_proc(lib, "nvmlDeviceGetTemperature"); + _nvmlDeviceGetUtilizationRates = (nvmlDeviceGetUtilizationRates_t) library_proc(lib, "nvmlDeviceGetUtilizationRates"); + _nvmlErrorString = (nvmlErrorString_t) library_proc(lib, "nvmlErrorString"); + _nvmlInit_v2 = (nvmlInit_v2_t) library_proc(lib, "nvmlInit_v2"); + _nvmlShutdown = (nvmlShutdown_t) library_proc(lib, "nvmlShutdown"); + + // Ensure the function pointer is valid + NVML_POINTER(_nvmlInit_v2); + + // Invoke the function using the provided parameters + return _nvmlInit_v2(); +} + +nvmlReturn_t nvmlShutdown(void) { + // Ensure the function pointer is valid + NVML_POINTER(_nvmlShutdown); + + // Invoke the function using the provided parameters + nvmlReturn_t ret = _nvmlShutdown(); + + // If the function call was successful, proceed with cleanup + if (ret == NVML_SUCCESS) { + // If the library handle is initialized + if (lib) { + // Nullify all the function pointers to prevent further use + _nvmlDeviceGetHandleByIndex_v2 = NULL; + _nvmlDeviceGetName = NULL; + _nvmlDeviceGetTemperature = NULL; + _nvmlDeviceGetUtilizationRates = NULL; + _nvmlErrorString = NULL; + _nvmlInit_v2 = NULL; + _nvmlShutdown = NULL; + + // Free the loaded library + library_close(lib); + } + } + + // Return the status of the function call + return ret; +} diff --git a/src/nvml.h b/src/nvml.h index 8caef52..a882f03 100644 --- a/src/nvml.h +++ b/src/nvml.h @@ -2,6 +2,7 @@ #include +#define __declspec(_) /* to suppress warnings */ #include /***** ***** ***** ***** ***** MACROS ***** ***** ***** ***** *****/ diff --git a/src/utils.c b/src/utils.c index 0919a97..07ebeb7 100644 --- a/src/utils.c +++ b/src/utils.c @@ -5,6 +5,36 @@ #include #include +#ifdef _WIN32 + #include +#elif __linux__ + #include +#endif + +void * library_open(const char * filename) { + #ifdef _WIN32 + return LoadLibrary(filename); + #elif __linux__ + return dlopen(filename, RTLD_LAZY); + #endif +} + +void * library_proc(void * handle, const char * symbol) { + #ifdef _WIN32 + return GetProcAddress((HMODULE) handle, symbol); + #elif __linux__ + return dlsym(handle, symbol); + #endif +} + +int library_close(void * handle) { + #ifdef _WIN32 + FreeLibrary((HMODULE) handle); + #elif __linux__ + dlclose(handle); + #endif +} + bool parse_ulong(const char *arg, unsigned long *value) { // Check if the input or output argument is invalid if (arg == NULL || value == NULL) { diff --git a/src/utils.h b/src/utils.h index b0f9a56..9b0a49d 100644 --- a/src/utils.h +++ b/src/utils.h @@ -16,7 +16,7 @@ /* Jump to the specified label */ \ goto label; \ } \ -} while (0); +} while (0) // Macro to check if a condition is false and jump to a label if it is not #define ASSERT_FALSE(call, label) do { \ @@ -28,7 +28,7 @@ /* Jump to the specified label */ \ goto label; \ } \ -} while (0); +} while (0) // Macro to check if there is a next argument #define HAS_NEXT_ARG (i + 1 < argc) @@ -43,9 +43,13 @@ \ /* Set the pointer to NULL */ \ ptr = NULL; \ -} while (0); +} while (0) /***** ***** ***** ***** ***** FUNCTIONS ***** ***** ***** ***** *****/ +void * library_open(const char * filename); +void * library_proc(void * handle, const char * symbol); +int library_close(void * handle); + bool parse_ulong(const char *arg, unsigned long *value); bool parse_ulong_array(const char *arg, const char *delimiter, const size_t max_count, unsigned long *values, size_t *count);