diff options
-rw-r--r-- | CMakeLists.txt | 25 | ||||
-rw-r--r-- | alc/helpers.cpp | 77 | ||||
-rw-r--r-- | config.h.in | 3 | ||||
-rw-r--r-- | core/dbus_wrap.cpp | 46 | ||||
-rw-r--r-- | core/dbus_wrap.h | 75 | ||||
-rw-r--r-- | core/rtkit.cpp | 240 | ||||
-rw-r--r-- | core/rtkit.h | 80 |
7 files changed, 528 insertions, 18 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index bd64c9d9..3f1894fa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -673,7 +673,30 @@ set(CORE_OBJS core/mastering.h core/resampler_limits.h core/uhjfilter.cpp - core/uhjfilter.h + core/uhjfilter.h) + +set(HAVE_RTKIT 0) +option(ALSOFT_REQUIRE_RTKIT "Require RTKit/D-Bus support" FALSE) +find_package(DBus1) +if(DBus1_FOUND) + option(ALSOFT_RTKIT "Enable RTKit support" ON) + if(ALSOFT_RTKIT) + set(HAVE_RTKIT 1) + set(CORE_OBJS ${CORE_OBJS} core/dbus_wrap.cpp core/dbus_wrap.h core/rtkit.cpp core/rtkit.h) + if(WIN32 OR HAVE_DLFCN_H) + set(INC_PATHS ${INC_PATHS} ${DBus1_INCLUDE_DIRS}) + set(CPP_DEFS ${CPP_DEFS} ${DBus1_DEFINITIONS}) + else() + set(EXTRA_LIBS ${EXTRA_LIBS} ${DBus1_LIBRARIES}) + endif() + endif() +endif() +if(ALSOFT_REQUIRE_RTKIT AND NOT HAVE_RTKIT) + message(FATAL_ERROR "Failed to enabled required RTKit support") +endif() + +# Default mixers, always available +set(CORE_OBJS ${CORE_OBJS} core/mixer/defs.h core/mixer/hrtfbase.h core/mixer/hrtfdefs.h diff --git a/alc/helpers.cpp b/alc/helpers.cpp index 6dbe4787..671a1ae5 100644 --- a/alc/helpers.cpp +++ b/alc/helpers.cpp @@ -203,6 +203,9 @@ void SetRTPriority(void) #include <pthread.h> #include <sched.h> #endif +#ifdef HAVE_RTKIT +#include "core/rtkit.h" +#endif const PathNamePair &GetProcBinary() { @@ -409,28 +412,68 @@ al::vector<std::string> SearchDataFiles(const char *ext, const char *subdir) void SetRTPriority() { + if(RTPrioLevel <= 0) + return; + + int err{-ENOTSUP}; #if defined(HAVE_PTHREAD_SETSCHEDPARAM) && !defined(__OpenBSD__) - if(RTPrioLevel > 0) - { - struct sched_param param{}; - /* Use the minimum real-time priority possible for now (on Linux this - * should be 1 for SCHED_RR). - */ - param.sched_priority = sched_get_priority_min(SCHED_RR); - int err; + struct sched_param param{}; + /* Use the minimum real-time priority possible for now (on Linux this + * should be 1 for SCHED_RR). + */ + param.sched_priority = sched_get_priority_min(SCHED_RR); #ifdef SCHED_RESET_ON_FORK - err = pthread_setschedparam(pthread_self(), SCHED_RR|SCHED_RESET_ON_FORK, ¶m); - if(err == EINVAL) + err = pthread_setschedparam(pthread_self(), SCHED_RR|SCHED_RESET_ON_FORK, ¶m); + if(err == EINVAL) +#endif + err = pthread_setschedparam(pthread_self(), SCHED_RR, ¶m); + if(err == 0) return; + + WARN("pthread_setschedparam failed: %s (%d)\n", std::strerror(err), err); #endif - err = pthread_setschedparam(pthread_self(), SCHED_RR, ¶m); - if(err != 0) - ERR("Failed to set real-time priority for thread: %s (%d)\n", std::strerror(err), err); +#ifdef HAVE_RTKIT + if(HasDBus()) + { + dbus::Error error; + if(dbus::ConnectionPtr conn{(*pdbus_bus_get)(DBUS_BUS_SYSTEM, &error.get())}) + { + /* Don't stupidly exit if the connection dies while doing this. */ + (*pdbus_connection_set_exit_on_disconnect)(conn.get(), false); + + int nicemin{}; + rtkit_get_min_nice_level(conn.get(), &nicemin); + int rtmax{rtkit_get_max_realtime_priority(conn.get())}; + TRACE("Maximum real-time priority: %d, minimum niceness: %d\n", rtmax, nicemin); + + if(rtmax > 0) + { + /* Use half the maximum real-time priority allowed. */ + TRACE("Making real-time with priority %d\n", (rtmax+1)/2); + err = rtkit_make_realtime(conn.get(), 0, (rtmax+1)/2); + if(err != 0) + { + err = std::abs(err); + WARN("Failed to set real-time priority: %s (%d)\n", std::strerror(err), err); + } + } + if(err != 0 && nicemin < 0) + { + TRACE("Making high priority with niceness %d\n", nicemin); + err = rtkit_make_high_priority(conn.get(), 0, nicemin); + if(err != 0) + { + err = std::abs(err); + WARN("Failed to set high priority: %s (%d)\n", std::strerror(err), err); + } + } + } + else + WARN("D-Bus connection failed with %s: %s\n", error->name, error->message); } -#else - /* Real-time priority not available */ - if(RTPrioLevel > 0) - ERR("Cannot set priority level for thread\n"); + else + WARN("D-Bus not available\n"); #endif + ERR("Could not set elevated priority: %s (%d)\n", std::strerror(err), err); } #endif diff --git a/config.h.in b/config.h.in index f43b3f73..5cc78ae5 100644 --- a/config.h.in +++ b/config.h.in @@ -13,6 +13,9 @@ /* Define if we have the getopt function */ #cmakedefine HAVE_GETOPT +/* Define if we have DBus/RTKit */ +#cmakedefine HAVE_RTKIT + /* Define if we have SSE CPU extensions */ #cmakedefine HAVE_SSE #cmakedefine HAVE_SSE2 diff --git a/core/dbus_wrap.cpp b/core/dbus_wrap.cpp new file mode 100644 index 00000000..506dd815 --- /dev/null +++ b/core/dbus_wrap.cpp @@ -0,0 +1,46 @@ + +#include "config.h" + +#include "dbus_wrap.h" + +#ifdef HAVE_DYNLOAD + +#include <mutex> +#include <type_traits> + +#include "logging.h" + + +void *dbus_handle{nullptr}; +#define DECL_FUNC(x) decltype(x) *p##x{}; +DBUS_FUNCTIONS(DECL_FUNC) +#undef DECL_FUNC + +void PrepareDBus() +{ + static constexpr char libname[] = "libdbus-1.so.3"; + + auto load_func = [](auto &f, const char *name) -> void + { f = reinterpret_cast<std::remove_reference_t<decltype(f)>>(GetSymbol(dbus_handle, name)); }; +#define LOAD_FUNC(x) do { \ + load_func(p##x, #x); \ + if(!p##x) \ + { \ + WARN("Failed to load function %s\n", #x); \ + CloseLib(dbus_handle); \ + dbus_handle = nullptr; \ + return; \ + } \ +} while(0); + + dbus_handle = LoadLib(libname); + if(!dbus_handle) + { + WARN("Failed to load %s\n", libname); + return; + } + +DBUS_FUNCTIONS(LOAD_FUNC) +#undef LOAD_FUNC +} +#endif diff --git a/core/dbus_wrap.h b/core/dbus_wrap.h new file mode 100644 index 00000000..61dbb971 --- /dev/null +++ b/core/dbus_wrap.h @@ -0,0 +1,75 @@ +#ifndef CORE_DBUS_WRAP_H +#define CORE_DBUS_WRAP_H + +#include <memory> + +#include <dbus/dbus.h> + +#include "dynload.h" + + +#define DBUS_FUNCTIONS(MAGIC) \ +MAGIC(dbus_error_init) \ +MAGIC(dbus_error_free) \ +MAGIC(dbus_bus_get) \ +MAGIC(dbus_connection_set_exit_on_disconnect) \ +MAGIC(dbus_connection_unref) \ +MAGIC(dbus_connection_send_with_reply_and_block) \ +MAGIC(dbus_message_unref) \ +MAGIC(dbus_message_new_method_call) \ +MAGIC(dbus_message_append_args) \ +MAGIC(dbus_message_iter_init) \ +MAGIC(dbus_message_iter_next) \ +MAGIC(dbus_message_iter_recurse) \ +MAGIC(dbus_message_iter_get_arg_type) \ +MAGIC(dbus_message_iter_get_basic) \ +MAGIC(dbus_set_error_from_message) + +#ifdef HAVE_DYNLOAD + +#include <mutex> + +extern void *dbus_handle; +#define DECL_FUNC(x) extern decltype(x) *p##x; +DBUS_FUNCTIONS(DECL_FUNC) +#undef DECL_FUNC + +void PrepareDBus(); + +inline auto HasDBus() +{ + static std::once_flag init_dbus{}; + std::call_once(init_dbus, PrepareDBus); + return dbus_handle; +} + +#else + +#define DECL_FUNC(x) constexpr auto p##x = &x; +DBUS_FUNCTIONS(DECL_FUNC) +#undef DECL_FUNC + +constexpr bool HasDBus() noexcept { return true; } +#endif /* HAVE_DYNLOAD */ + + +namespace dbus { + +struct Error { + Error() { (*pdbus_error_init)(&mError); } + ~Error() { (*pdbus_error_free)(&mError); } + DBusError* operator->() { return &mError; } + DBusError &get() { return mError; } +private: + DBusError mError{}; +}; + +struct ConnectionDeleter { + void operator()(DBusConnection *c) { (*pdbus_connection_unref)(c); } +}; +using ConnectionPtr = std::unique_ptr<DBusConnection,ConnectionDeleter>; + +} // namespace dbus + + +#endif /* CORE_DBUS_WRAP_H */ diff --git a/core/rtkit.cpp b/core/rtkit.cpp new file mode 100644 index 00000000..8b489e71 --- /dev/null +++ b/core/rtkit.cpp @@ -0,0 +1,240 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +/*** + Copyright 2009 Lennart Poettering + Copyright 2010 David Henningsson <[email protected]> + Copyright 2021 Chris Robinson + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +***/ + +#include "config.h" + +#include "rtkit.h" + +#include <errno.h> + +#ifdef __linux__ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include <memory> +#include <string.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/syscall.h> + + +namespace dbus { + constexpr int TypeString{'s'}; + constexpr int TypeVariant{'v'}; + constexpr int TypeInt32{'i'}; + constexpr int TypeUInt32{'u'}; + constexpr int TypeInt64{'x'}; + constexpr int TypeUInt64{'t'}; + constexpr int TypeInvalid{'\0'}; + + struct MessageDeleter { + void operator()(DBusMessage *m) { (*pdbus_message_unref)(m); } + }; + using MessagePtr = std::unique_ptr<DBusMessage,MessageDeleter>; +} // namespace dbus + +namespace { + +inline pid_t _gettid() +{ return static_cast<pid_t>(syscall(SYS_gettid)); } + +int translate_error(const char *name) +{ + if(strcmp(name, DBUS_ERROR_NO_MEMORY) == 0) + return -ENOMEM; + if(strcmp(name, DBUS_ERROR_SERVICE_UNKNOWN) == 0 + || strcmp(name, DBUS_ERROR_NAME_HAS_NO_OWNER) == 0) + return -ENOENT; + if(strcmp(name, DBUS_ERROR_ACCESS_DENIED) == 0 + || strcmp(name, DBUS_ERROR_AUTH_FAILED) == 0) + return -EACCES; + return -EIO; +} + +int rtkit_get_int_property(DBusConnection *connection, const char *propname, long long *propval) +{ + dbus::MessagePtr m{(*pdbus_message_new_method_call)(RTKIT_SERVICE_NAME, RTKIT_OBJECT_PATH, + "org.freedesktop.DBus.Properties", "Get")}; + if(!m) return -ENOMEM; + + const char *interfacestr = RTKIT_SERVICE_NAME; + auto ready = (*pdbus_message_append_args)(m.get(), + dbus::TypeString, &interfacestr, + dbus::TypeString, &propname, + dbus::TypeInvalid); + if(!ready) return -ENOMEM; + + dbus::Error error; + dbus::MessagePtr r{(*pdbus_connection_send_with_reply_and_block)(connection, m.get(), -1, + &error.get())}; + if(!r) return translate_error(error->name); + + if((*pdbus_set_error_from_message)(&error.get(), r.get())) + return translate_error(error->name); + + int ret{-EBADMSG}; + DBusMessageIter iter{}; + (*pdbus_message_iter_init)(r.get(), &iter); + while(int curtype{(*pdbus_message_iter_get_arg_type)(&iter)}) + { + if(curtype == dbus::TypeVariant) + { + DBusMessageIter subiter{}; + (*pdbus_message_iter_recurse)(&iter, &subiter); + + while((curtype=(*pdbus_message_iter_get_arg_type)(&subiter)) != dbus::TypeInvalid) + { + if(curtype == dbus::TypeInt32) + { + dbus_int32_t i32{}; + (*pdbus_message_iter_get_basic)(&subiter, &i32); + *propval = i32; + ret = 0; + } + + if(curtype == dbus::TypeInt64) + { + dbus_int64_t i64{}; + (*pdbus_message_iter_get_basic)(&subiter, &i64); + *propval = i64; + ret = 0; + } + + (*pdbus_message_iter_next)(&subiter); + } + } + (*pdbus_message_iter_next)(&iter); + } + + return ret; +} + +} // namespace + +extern "C" { +int rtkit_get_max_realtime_priority(DBusConnection *connection) +{ + long long retval{}; + int err{rtkit_get_int_property(connection, "MaxRealtimePriority", &retval)}; + return err < 0 ? err : static_cast<int>(retval); +} + +int rtkit_get_min_nice_level(DBusConnection *connection, int *min_nice_level) +{ + long long retval{}; + int err{rtkit_get_int_property(connection, "MinNiceLevel", &retval)}; + if(err >= 0) *min_nice_level = static_cast<int>(retval); + return err; +} + +long long rtkit_get_rttime_usec_max(DBusConnection *connection) +{ + long long retval{}; + int err{rtkit_get_int_property(connection, "RTTimeUSecMax", &retval)}; + return err < 0 ? err : retval; +} + +int rtkit_make_realtime(DBusConnection *connection, pid_t thread, int priority) +{ + if(thread == 0) + thread = _gettid(); + + dbus::MessagePtr m{(*pdbus_message_new_method_call)(RTKIT_SERVICE_NAME, RTKIT_OBJECT_PATH, + "org.freedesktop.RealtimeKit1", "MakeThreadRealtime")}; + if(!m) return -ENOMEM; + + auto u64 = static_cast<dbus_uint64_t>(thread); + auto u32 = static_cast<dbus_uint32_t>(priority); + auto ready = (*pdbus_message_append_args)(m.get(), + dbus::TypeUInt64, &u64, + dbus::TypeUInt32, &u32, + dbus::TypeInvalid); + if(!ready) return -ENOMEM; + + dbus::Error error; + dbus::MessagePtr r{(*pdbus_connection_send_with_reply_and_block)(connection, m.get(), -1, + &error.get())}; + if(!r) return translate_error(error->name); + + if((*pdbus_set_error_from_message)(&error.get(), r.get())) + return translate_error(error->name); + + return 0; +} + +int rtkit_make_high_priority(DBusConnection *connection, pid_t thread, int nice_level) +{ + if(thread == 0) + thread = _gettid(); + + dbus::MessagePtr m{(*pdbus_message_new_method_call)(RTKIT_SERVICE_NAME, RTKIT_OBJECT_PATH, + "org.freedesktop.RealtimeKit1", "MakeThreadHighPriority")}; + if(!m) return -ENOMEM; + + auto u64 = static_cast<dbus_uint64_t>(thread); + auto s32 = static_cast<dbus_int32_t>(nice_level); + auto ready = (*pdbus_message_append_args)(m.get(), + dbus::TypeUInt64, &u64, + dbus::TypeInt32, &s32, + dbus::TypeInvalid); + if(!ready) return -ENOMEM; + + dbus::Error error; + dbus::MessagePtr r{(*pdbus_connection_send_with_reply_and_block)(connection, m.get(), -1, + &error.get())}; + if(!r) return translate_error(error->name); + + if((*pdbus_set_error_from_message)(&error.get(), r.get())) + return translate_error(error->name); + + return 0; +} +} // extern "C" + +#else + +extern "C" { +int rtkit_make_realtime(DBusConnection *connection, pid_t thread, int priority) +{ return -ENOTSUP; } + +int rtkit_make_high_priority(DBusConnection *connection, pid_t thread, int nice_level) +{ return -ENOTSUP; } + +int rtkit_get_max_realtime_priority(DBusConnection *connection) +{ return -ENOTSUP; } + +int rtkit_get_min_nice_level(DBusConnection *connection, int *min_nice_level) +{ return -ENOTSUP; } + +long long rtkit_get_rttime_usec_max(DBusConnection *connection) +{ return -ENOTSUP; } +} // extern "C" + +#endif diff --git a/core/rtkit.h b/core/rtkit.h new file mode 100644 index 00000000..96e81d4a --- /dev/null +++ b/core/rtkit.h @@ -0,0 +1,80 @@ +/*-*- Mode: C; c-basic-offset: 8 -*-*/ + +#ifndef foortkithfoo +#define foortkithfoo + +/*** + Copyright 2009 Lennart Poettering + Copyright 2010 David Henningsson <[email protected]> + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +***/ + +#include <sys/types.h> + +#include "dbus_wrap.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* This is the reference implementation for a client for + * RealtimeKit. You don't have to use this, but if do, just copy these + * sources into your repository */ + +#define RTKIT_SERVICE_NAME "org.freedesktop.RealtimeKit1" +#define RTKIT_OBJECT_PATH "/org/freedesktop/RealtimeKit1" + +/* This is mostly equivalent to sched_setparam(thread, SCHED_RR, { + * .sched_priority = priority }). 'thread' needs to be a kernel thread + * id as returned by gettid(), not a pthread_t! If 'thread' is 0 the + * current thread is used. The returned value is a negative errno + * style error code, or 0 on success. */ +int rtkit_make_realtime(DBusConnection *system_bus, pid_t thread, int priority); + +/* This is mostly equivalent to setpriority(PRIO_PROCESS, thread, + * nice_level). 'thread' needs to be a kernel thread id as returned by + * gettid(), not a pthread_t! If 'thread' is 0 the current thread is + * used. The returned value is a negative errno style error code, or 0 + * on success.*/ +int rtkit_make_high_priority(DBusConnection *system_bus, pid_t thread, int nice_level); + +/* Return the maximum value of realtime priority available. Realtime requests + * above this value will fail. A negative value is an errno style error code. + */ +int rtkit_get_max_realtime_priority(DBusConnection *system_bus); + +/* Retreive the minimum value of nice level available. High prio requests + * below this value will fail. The returned value is a negative errno + * style error code, or 0 on success.*/ +int rtkit_get_min_nice_level(DBusConnection *system_bus, int *min_nice_level); + +/* Return the maximum value of RLIMIT_RTTIME to set before attempting a + * realtime request. A negative value is an errno style error code. + */ +long long rtkit_get_rttime_usec_max(DBusConnection *system_bus); + + +#ifdef __cplusplus +} +#endif + +#endif |