diff --git a/meson.build b/meson.build index 30a8e1ce..a8f843e7 100644 --- a/meson.build +++ b/meson.build @@ -84,10 +84,14 @@ if is_unixy dep_wayland_client = dependency('wayland-client', required: get_option('with_wayland'), version : '>=1.11') dbus_dep = dependency('dbus-1', required: get_option('with_dbus')).partial_dependency(compile_args : true, includes : true) + dep_libdrm = dependency('libdrm', required: get_option('with_libdrm_amdgpu')).partial_dependency(compile_args : true, includes : true) +# dep_libdrm_amdgpu = dependency('libdrm_amdgpu', version : '>=2.4.79', required: get_option('with_libdrm_amdgpu')).partial_dependency(compile_args : true, includes : true) else dep_x11 = null_dep dep_wayland_client = null_dep dbus_dep = null_dep + dep_libdrm = null_dep +# dep_libdrm_amdgpu = null_dep endif if dep_x11.found() diff --git a/meson_options.txt b/meson_options.txt index 7fba6171..c3526553 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -12,3 +12,4 @@ option('with_x11', type : 'feature', value : 'enabled') option('with_wayland', type : 'feature', value : 'disabled') option('with_dbus', type : 'feature', value : 'enabled') option('with_dlsym', type : 'feature', value : 'disabled') +option('with_libdrm_amdgpu', type : 'feature', value : 'enabled', description: 'Get amdgpu sensor info through libdrm_amdgpu') diff --git a/src/auth.cpp b/src/auth.cpp new file mode 100644 index 00000000..5d0d5517 --- /dev/null +++ b/src/auth.cpp @@ -0,0 +1,56 @@ +/* + Inspired by radeontop +*/ +#include "auth.h" +#include +#include +#include +#include +#include +#include +#include + +/* Try to authenticate the DRM client with help from the X server. */ +bool authenticate_drm_xcb(drm_magic_t magic) { + xcb_connection_t *conn = xcb_connect(NULL, NULL); + if (!conn) { + return false; + } + if (xcb_connection_has_error(conn)) { + xcb_disconnect(conn); + return false; + } + + xcb_screen_t *screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data; + xcb_window_t window = screen->root; + + /* Authenticate our client via the X server using the magic. */ + xcb_dri2_authenticate_cookie_t auth_cookie = + xcb_dri2_authenticate(conn, window, magic); + xcb_dri2_authenticate_reply_t *auth_reply = + xcb_dri2_authenticate_reply(conn, auth_cookie, NULL); + free(auth_reply); + + xcb_disconnect(conn); + return true; +} + +bool authenticate_drm(int fd) { + drm_magic_t magic; + + /* Obtain magic for our DRM client. */ + if (drmGetMagic(fd, &magic) < 0) { + return false; + } + + /* Try self-authenticate (if we are somehow the master). */ + if (drmAuthMagic(fd, magic) == 0) { + if (drmDropMaster(fd)) { + perror("MANGOHUD: Failed to drop DRM master"); + fprintf(stderr, "\n\tWARNING: other DRM clients will crash on VT switch\n"); + } + return true; + } + + return authenticate_drm_xcb(magic); +} diff --git a/src/auth.h b/src/auth.h new file mode 100644 index 00000000..79645e9b --- /dev/null +++ b/src/auth.h @@ -0,0 +1,5 @@ +#pragma once +#include + +bool authenticate_drm_xcb(drm_magic_t magic); +bool authenticate_drm(int fd); diff --git a/src/gpu.cpp b/src/gpu.cpp index eb639943..5daf6a9e 100644 --- a/src/gpu.cpp +++ b/src/gpu.cpp @@ -1,12 +1,30 @@ #include "gpu.h" #include +#include +#include +#include +#include #include "nvctrl.h" +#include "timing.hpp" #ifdef HAVE_NVML #include "nvidia_info.h" #endif -struct gpuInfo gpu_info; +#ifdef HAVE_LIBDRM_AMDGPU +//#include "auth.h" +#include +#include +#include +#include +#include +#include "loaders/loader_libdrm.h" +#endif + +using namespace std::chrono_literals; + +struct gpuInfo gpu_info {}; amdgpu_files amdgpu {}; +decltype(&getAmdGpuInfo) getAmdGpuInfo_actual = nullptr; bool checkNvidia(const char *pci_dev){ bool nvSuccess = false; @@ -120,3 +138,172 @@ void getAmdGpuInfo(){ gpu_info.powerUsage = value / 1000000; } } + +#ifdef HAVE_LIBDRM_AMDGPU +#define DRM_ATLEAST_VERSION(ver, maj, min) \ + (ver->version_major > maj || (ver->version_major == maj && ver->version_minor >= min)) + +enum { + GRBM_STATUS = 0x8010, +}; + +static std::unique_ptr libdrm_ptr; + +static int getgrbm_amdgpu(amdgpu_device_handle dev, uint32_t *out) { + return libdrm_ptr->amdgpu_read_mm_registers(dev, GRBM_STATUS / 4, 1, + 0xffffffff, 0, out); +} + +struct amdgpu_handles +{ + amdgpu_device_handle dev; + int fd; + uint32_t version_major, version_minor, gui_percent {0}; + const uint32_t ticks = 120; + std::chrono::nanoseconds sleep_interval {}; + + bool quit = false; + std::thread collector; + + amdgpu_handles(amdgpu_device_handle dev_, int fd_, uint32_t major, uint32_t minor) + : dev(dev_) + , fd(fd_) + , version_major(major) + , version_minor(minor) + { + set_sampling_period(500000000 /* 500ms */); + collector = std::thread(&amdgpu_handles::amdgpu_poll, this); + } + + ~amdgpu_handles() + { + quit = true; + if (collector.joinable()) + collector.join(); + libdrm_ptr->amdgpu_device_deinitialize(dev); + close(fd); + } + + void set_sampling_period(uint32_t period) + { + sleep_interval = std::chrono::nanoseconds(period) / ticks; + } + + void amdgpu_poll() + { + uint32_t stat = 0, gui = 0, curr = 0; + while (!quit) + { + getgrbm_amdgpu(dev, &stat); + if (stat & (1U << 31)) // gui + gui++; + + std::this_thread::sleep_for(sleep_interval); + curr++; + curr %= ticks; + if (!curr) + { + gui_percent = gui * 100 / ticks; + gui = 0; + } + } + } +}; + +typedef std::unique_ptr amdgpu_ptr; +static amdgpu_ptr amdgpu_dev; + +void amdgpu_set_sampling_period(uint32_t period) +{ + if (amdgpu_dev) + amdgpu_dev->set_sampling_period(period); +} + +bool amdgpu_open(const char *path) { + if (!libdrm_ptr) + libdrm_ptr = std::make_unique(); + + if (!libdrm_ptr->IsLoaded()) + return false; + + int fd = open(path, O_RDWR | O_CLOEXEC); + + if (fd < 0) { + perror("MANGOHUD: Failed to open DRM device: "); // Gives sensible perror message? + return false; + } + + drmVersionPtr ver = libdrm_ptr->drmGetVersion(fd); + + if (!ver) { + perror("MANGOHUD: Failed to query driver version: "); + close(fd); + return false; + } + + if (strcmp(ver->name, "amdgpu") || !DRM_ATLEAST_VERSION(ver, 3, 11)) { + fprintf(stderr, "MANGOHUD: Unsupported driver/version: %s %d.%d.%d\n", ver->name, ver->version_major, ver->version_minor, ver->version_patchlevel); + close(fd); + libdrm_ptr->drmFreeVersion(ver); + return false; + } + libdrm_ptr->drmFreeVersion(ver); + +/* + if (!authenticate_drm(fd)) { + close(fd); + return false; + } +*/ + + uint32_t drm_major, drm_minor; + amdgpu_device_handle dev; + if (libdrm_ptr->amdgpu_device_initialize(fd, &drm_major, &drm_minor, &dev)){ + perror("MANGOHUD: Failed to initialize amdgpu device"); + close(fd); + return false; + } + + amdgpu_dev = std::make_unique(dev, fd, drm_major, drm_minor); + return true; +} + +void getAmdGpuInfo_libdrm() +{ + uint64_t value = 0; + uint32_t value32 = 0; + + if (!DRM_ATLEAST_VERSION(amdgpu_dev, 3, 11)) + { + getAmdGpuInfo(); + getAmdGpuInfo_actual = getAmdGpuInfo; + return; + } + + if (!libdrm_ptr || !libdrm_ptr->IsLoaded()) + return; + + if (!libdrm_ptr->amdgpu_query_info(amdgpu_dev->dev, AMDGPU_INFO_VRAM_USAGE, sizeof(uint64_t), &value)) + gpu_info.memoryUsed = float(value) / (1024 * 1024 * 1024); + + // FIXME probably not correct sensor + if (!libdrm_ptr->amdgpu_query_info(amdgpu_dev->dev, AMDGPU_INFO_MEMORY, sizeof(uint64_t), &value)) + gpu_info.memoryTotal = float(value) / (1024 * 1024 * 1024); + + if (!libdrm_ptr->amdgpu_query_sensor_info(amdgpu_dev->dev, AMDGPU_INFO_SENSOR_GFX_SCLK, sizeof(uint32_t), &value32)) + gpu_info.CoreClock = value32; + + if (!libdrm_ptr->amdgpu_query_sensor_info(amdgpu_dev->dev, AMDGPU_INFO_SENSOR_GFX_MCLK, sizeof(uint32_t), &value32)) // XXX Doesn't work on APUs + gpu_info.MemClock = value32; + + //if (!libdrm_ptr->amdgpu_query_sensor_info(amdgpu_dev->dev, AMDGPU_INFO_SENSOR_GPU_LOAD, sizeof(uint32_t), &value32)) + // gpu_info.load = value32; + gpu_info.load = amdgpu_dev->gui_percent; + + if (!libdrm_ptr->amdgpu_query_sensor_info(amdgpu_dev->dev, AMDGPU_INFO_SENSOR_GPU_TEMP, sizeof(uint32_t), &value32)) + gpu_info.temp = value32 / 1000; + + if (!libdrm_ptr->amdgpu_query_sensor_info(amdgpu_dev->dev, AMDGPU_INFO_SENSOR_GPU_AVG_POWER, sizeof(uint32_t), &value32)) + gpu_info.powerUsage = value32; +} +#endif diff --git a/src/gpu.h b/src/gpu.h index 8c2b0638..f60d4953 100644 --- a/src/gpu.h +++ b/src/gpu.h @@ -2,7 +2,8 @@ #ifndef MANGOHUD_GPU_H #define MANGOHUD_GPU_H -#include +#include +#include struct amdgpu_files { @@ -31,6 +32,12 @@ extern struct gpuInfo gpu_info; void getNvidiaGpuInfo(void); void getAmdGpuInfo(void); +#ifdef HAVE_LIBDRM_AMDGPU +void getAmdGpuInfo_libdrm(); +bool amdgpu_open(const char *path); +void amdgpu_set_sampling_period(uint32_t period); +#endif +extern decltype(&getAmdGpuInfo) getAmdGpuInfo_actual; bool checkNvidia(const char *pci_dev); extern void nvapi_util(); extern bool checkNVAPI(); diff --git a/src/loaders/loader_libdrm.cpp b/src/loaders/loader_libdrm.cpp new file mode 100644 index 00000000..1e95d5a2 --- /dev/null +++ b/src/loaders/loader_libdrm.cpp @@ -0,0 +1,135 @@ + +#include "loaders/loader_libdrm.h" +#include + +// Put these sanity checks here so that they fire at most once +// (to avoid cluttering the build output). +#if !defined(LIBRARY_LOADER_LIBDRM_H_DLOPEN) && !defined(LIBRARY_LOADER_LIBDRM_H_DT_NEEDED) +#error neither LIBRARY_LOADER_LIBDRM_H_DLOPEN nor LIBRARY_LOADER_LIBDRM_H_DT_NEEDED defined +#endif +#if defined(LIBRARY_LOADER_LIBDRM_H_DLOPEN) && defined(LIBRARY_LOADER_LIBDRM_H_DT_NEEDED) +#error both LIBRARY_LOADER_LIBDRM_H_DLOPEN and LIBRARY_LOADER_LIBDRM_H_DT_NEEDED defined +#endif + +libdrm_loader::libdrm_loader() : loaded_(false) { + Load(); +} + +libdrm_loader::~libdrm_loader() { + CleanUp(loaded_); +} + +bool libdrm_loader::Load() { + if (loaded_) { + return true; + } + +#if defined(LIBRARY_LOADER_LIBDRM_H_DLOPEN) + library_drm = dlopen("libdrm.so.2", RTLD_LAZY); + if (!library_drm) { + std::cerr << "MANGOHUD: Failed to open " << "" MANGOHUD_ARCH << " libdrm.so.2: " << dlerror() << std::endl; + return false; + } + + library_amdgpu = dlopen("libdrm_amdgpu.so.1", RTLD_LAZY); + if (!library_amdgpu) { + std::cerr << "MANGOHUD: Failed to open " << "" MANGOHUD_ARCH << " libdrm_amdgpu.so.1: " << dlerror() << std::endl; + CleanUp(true); + return false; + } + + drmGetVersion = + reinterpret_castdrmGetVersion)>( + dlsym(library_drm, "drmGetVersion")); + if (!drmGetVersion) { + CleanUp(true); + return false; + } + + drmFreeVersion = + reinterpret_castdrmFreeVersion)>( + dlsym(library_drm, "drmFreeVersion")); + if (!drmFreeVersion) { + CleanUp(true); + return false; + } + + amdgpu_device_initialize = + reinterpret_castamdgpu_device_initialize)>( + dlsym(library_amdgpu, "amdgpu_device_initialize")); + if (!amdgpu_device_initialize) { + CleanUp(true); + return false; + } + + amdgpu_device_deinitialize = + reinterpret_castamdgpu_device_deinitialize)>( + dlsym(library_amdgpu, "amdgpu_device_deinitialize")); + if (!amdgpu_device_deinitialize) { + CleanUp(true); + return false; + } + + amdgpu_query_info = + reinterpret_castamdgpu_query_info)>( + dlsym(library_amdgpu, "amdgpu_query_info")); + if (!amdgpu_query_info) { + CleanUp(true); + return false; + } + + amdgpu_query_sensor_info = + reinterpret_castamdgpu_query_sensor_info)>( + dlsym(library_amdgpu, "amdgpu_query_sensor_info")); + if (!amdgpu_query_sensor_info) { + CleanUp(true); + return false; + } + + amdgpu_read_mm_registers = + reinterpret_castamdgpu_read_mm_registers)>( + dlsym(library_amdgpu, "amdgpu_read_mm_registers")); + if (!amdgpu_read_mm_registers) { + CleanUp(true); + return false; + } + +#endif + +#if defined(LIBRARY_LOADER_LIBDRM_H_DT_NEEDED) + drmGetVersion = &::drmGetVersion; + drmFreeVersion = &::drmFreeVersion; + + amdgpu_device_initialize = &::amdgpu_device_initialize; + amdgpu_device_deinitialize = &::amdgpu_device_deinitialize; + amdgpu_query_info = &::amdgpu_query_info; + amdgpu_query_sensor_info = &::amdgpu_query_sensor_info; + amdgpu_read_mm_registers = &::amdgpu_read_mm_registers; + +#endif + + loaded_ = true; + return true; +} + +void libdrm_loader::CleanUp(bool unload) { +#if defined(LIBRARY_LOADER_LIBDRM_H_DLOPEN) + if (unload) { + dlclose(library_drm); + library_drm = nullptr; + if (library_amdgpu) + dlclose(library_amdgpu); + library_amdgpu = nullptr; + } +#endif + loaded_ = false; + drmGetVersion = nullptr; + drmFreeVersion = nullptr; + + amdgpu_device_initialize = nullptr; + amdgpu_device_deinitialize = nullptr; + amdgpu_query_info = nullptr; + amdgpu_query_sensor_info = nullptr; + amdgpu_read_mm_registers = nullptr; + +} diff --git a/src/loaders/loader_libdrm.h b/src/loaders/loader_libdrm.h new file mode 100644 index 00000000..6da4464d --- /dev/null +++ b/src/loaders/loader_libdrm.h @@ -0,0 +1,58 @@ + +#ifndef LIBRARY_LOADER_LIBDRM_H +#define LIBRARY_LOADER_LIBDRM_H + +#define LIBRARY_LOADER_LIBDRM_H_DLOPEN + +#include +#include +//#include +//#include + +typedef struct amdgpu_device *amdgpu_device_handle; +int amdgpu_device_initialize(int fd, + uint32_t *major_version, + uint32_t *minor_version, + amdgpu_device_handle *device_handle); +int amdgpu_device_deinitialize(amdgpu_device_handle device_handle); +int amdgpu_query_info(amdgpu_device_handle dev, unsigned info_id, + unsigned size, void *value); +int amdgpu_query_sensor_info(amdgpu_device_handle dev, unsigned sensor_type, + unsigned size, void *value); +int amdgpu_read_mm_registers(amdgpu_device_handle dev, unsigned dword_offset, + unsigned count, uint32_t instance, uint32_t flags, + uint32_t *values); + +class libdrm_loader { + public: + libdrm_loader(); + ~libdrm_loader(); + + bool Load(); + bool IsLoaded() { return loaded_; } + + decltype(&::drmGetVersion) drmGetVersion; + decltype(&::drmFreeVersion) drmFreeVersion; + + decltype(&::amdgpu_device_initialize) amdgpu_device_initialize; + decltype(&::amdgpu_device_deinitialize) amdgpu_device_deinitialize; + decltype(&::amdgpu_query_info) amdgpu_query_info; + decltype(&::amdgpu_query_sensor_info) amdgpu_query_sensor_info; + decltype(&::amdgpu_read_mm_registers) amdgpu_read_mm_registers; + + private: + void CleanUp(bool unload); + +#if defined(LIBRARY_LOADER_LIBDRM_H_DLOPEN) + void* library_drm; + void* library_amdgpu; +#endif + + bool loaded_; + + // Disallow copy constructor and assignment operator. + libdrm_loader(const libdrm_loader&); + void operator=(const libdrm_loader&); +}; + +#endif // LIBRARY_LOADER_LIBDRM_H diff --git a/src/meson.build b/src/meson.build index 6fd11df1..35eb7c48 100644 --- a/src/meson.build +++ b/src/meson.build @@ -149,6 +149,16 @@ if is_unixy 'loaders/loader_dbus.cpp', ) endif + +# if get_option('with_libdrm_amdgpu').enabled() and dep_libdrm.found() and dep_libdrm_amdgpu.found() + if get_option('with_libdrm_amdgpu').enabled() and dep_libdrm.found() + pre_args += '-DHAVE_LIBDRM_AMDGPU' + #if dep_xcb.found() and dep_xcb_dri2.found() + vklayer_files += files( + 'loaders/loader_libdrm.cpp', + ) + #endif + endif endif link_args = cc.get_supported_link_arguments(['-Wl,-Bsymbolic-functions', '-Wl,-z,relro', '-Wl,--exclude-libs,ALL']) @@ -176,6 +186,8 @@ vklayer_mesa_overlay = shared_library( dependencies : [ vulkan_wsi_deps, dearimgui_dep, + dep_libdrm, + #dep_libdrm_amdgpu, dbus_dep, dep_dl, dep_rt, diff --git a/src/overlay.cpp b/src/overlay.cpp index 60abdd91..8284d3f0 100644 --- a/src/overlay.cpp +++ b/src/overlay.cpp @@ -57,8 +57,8 @@ void update_hw_info(struct swapchain_stats& sw_stats, struct overlay_params& par #endif } if (params.enabled[OVERLAY_PARAM_ENABLED_gpu_stats] || logger->is_active()) { - if (vendorID == 0x1002) - getAmdGpuInfo(); + if (vendorID == 0x1002 && getAmdGpuInfo_actual) + getAmdGpuInfo_actual(); if (vendorID == 0x10de) getNvidiaGpuInfo(); @@ -542,6 +542,8 @@ void init_gpu_stats(uint32_t& vendorID, overlay_params& params) || gpu.find("AMD") != std::string::npos) { string path; string drm = "/sys/class/drm/"; + getAmdGpuInfo_actual = getAmdGpuInfo; + bool using_libdrm = false; auto dirs = ls(drm.c_str(), "card"); for (auto& dir : dirs) { @@ -557,9 +559,8 @@ void init_gpu_stats(uint32_t& vendorID, overlay_params& params) if (line != "0x1002" || !file_exists(path + "/device/gpu_busy_percent")) continue; - path += "/device"; if (pci_bus_parsed && pci_dev) { - string pci_device = read_symlink(path.c_str()); + string pci_device = read_symlink((path + "/device").c_str()); #ifndef NDEBUG std::cerr << "PCI device symlink: " << pci_device << "\n"; #endif @@ -570,9 +571,30 @@ void init_gpu_stats(uint32_t& vendorID, overlay_params& params) } #ifndef NDEBUG - std::cerr << "using amdgpu path: " << path << std::endl; + std::cerr << "using amdgpu path: " << path << std::endl; #endif +#ifdef HAVE_LIBDRM_AMDGPU + int idx = -1; + //TODO make neater + int res = sscanf(path.c_str(), "/sys/class/drm/card%d", &idx); + std::string dri_path = "/dev/dri/card" + std::to_string(idx); + + if (!params.enabled[OVERLAY_PARAM_ENABLED_force_amdgpu_hwmon] && res == 1 && amdgpu_open(dri_path.c_str())) { + vendorID = 0x1002; + using_libdrm = true; + getAmdGpuInfo_actual = getAmdGpuInfo_libdrm; + amdgpu_set_sampling_period(params.fps_sampling_period); +#ifndef NDEBUG + std::cerr << "MANGOHUD: using libdrm\n"; +#endif + // fall through and open sysfs handles for fallback or check DRM version beforehand + } else if (!params.enabled[OVERLAY_PARAM_ENABLED_force_amdgpu_hwmon]) { + std::cerr << "MANGOHUD: Failed to open device '/dev/dri/card" << idx << "' with libdrm, falling back to using hwmon sysfs.\n"; + } +#endif + + path += "/device"; if (!amdgpu.busy) amdgpu.busy = fopen((path + "/gpu_busy_percent").c_str(), "r"); if (!amdgpu.vram_total) @@ -598,7 +620,7 @@ void init_gpu_stats(uint32_t& vendorID, overlay_params& params) } // don't bother then - if (!amdgpu.busy && !amdgpu.temp && !amdgpu.vram_total && !amdgpu.vram_used) { + if (!using_libdrm && !amdgpu.busy && !amdgpu.temp && !amdgpu.vram_total && !amdgpu.vram_used) { params.enabled[OVERLAY_PARAM_ENABLED_gpu_stats] = false; } } diff --git a/src/overlay_params.h b/src/overlay_params.h index 4c7663f7..e7530d2f 100644 --- a/src/overlay_params.h +++ b/src/overlay_params.h @@ -70,6 +70,7 @@ typedef unsigned long KeySym; OVERLAY_PARAM_BOOL(gamemode) \ OVERLAY_PARAM_BOOL(battery) \ OVERLAY_PARAM_BOOL(battery_icon) \ + OVERLAY_PARAM_BOOL(force_amdgpu_hwmon) \ OVERLAY_PARAM_CUSTOM(fps_sampling_period) \ OVERLAY_PARAM_CUSTOM(output_folder) \ OVERLAY_PARAM_CUSTOM(output_file) \