768a28394c
Between v5 and v6 of the series, the socket loop of qemu-vmsr-helper was changed to allow sending multiple requests on the same socket. Unfortunately, the condition of the while loop is botched and the loop will never be entered. Clean it up, and also unify the handling of error reporting. Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
533 lines
14 KiB
C
533 lines
14 KiB
C
/*
|
|
* Privileged RAPL MSR helper commands for QEMU
|
|
*
|
|
* Copyright (C) 2024 Red Hat, Inc. <aharivel@redhat.com>
|
|
*
|
|
* Author: Anthony Harivel <aharivel@redhat.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; under version 2 of the License.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include <getopt.h>
|
|
#include <stdbool.h>
|
|
#include <sys/ioctl.h>
|
|
#ifdef CONFIG_LIBCAP_NG
|
|
#include <cap-ng.h>
|
|
#endif
|
|
#include <pwd.h>
|
|
#include <grp.h>
|
|
|
|
#include "qemu/help-texts.h"
|
|
#include "qapi/error.h"
|
|
#include "qemu/cutils.h"
|
|
#include "qemu/main-loop.h"
|
|
#include "qemu/module.h"
|
|
#include "qemu/error-report.h"
|
|
#include "qemu/config-file.h"
|
|
#include "qemu-version.h"
|
|
#include "qapi/error.h"
|
|
#include "qemu/error-report.h"
|
|
#include "qemu/log.h"
|
|
#include "qemu/systemd.h"
|
|
#include "io/channel.h"
|
|
#include "io/channel-socket.h"
|
|
#include "trace/control.h"
|
|
#include "qemu-version.h"
|
|
#include "rapl-msr-index.h"
|
|
|
|
#define MSR_PATH_TEMPLATE "/dev/cpu/%u/msr"
|
|
|
|
static char *socket_path;
|
|
static char *pidfile;
|
|
static enum { RUNNING, TERMINATE, TERMINATING } state;
|
|
static QIOChannelSocket *server_ioc;
|
|
static int server_watch;
|
|
static int num_active_sockets = 1;
|
|
|
|
#ifdef CONFIG_LIBCAP_NG
|
|
static int uid = -1;
|
|
static int gid = -1;
|
|
#endif
|
|
|
|
static void compute_default_paths(void)
|
|
{
|
|
g_autofree char *state = qemu_get_local_state_dir();
|
|
|
|
socket_path = g_build_filename(state, "run", "qemu-vmsr-helper.sock", NULL);
|
|
pidfile = g_build_filename(state, "run", "qemu-vmsr-helper.pid", NULL);
|
|
}
|
|
|
|
static int is_intel_processor(void)
|
|
{
|
|
int result;
|
|
int ebx, ecx, edx;
|
|
|
|
/* Execute CPUID instruction with eax=0 (basic identification) */
|
|
asm volatile (
|
|
"cpuid"
|
|
: "=b" (ebx), "=c" (ecx), "=d" (edx)
|
|
: "a" (0)
|
|
);
|
|
|
|
/*
|
|
* Check if processor is "GenuineIntel"
|
|
* 0x756e6547 = "Genu"
|
|
* 0x49656e69 = "ineI"
|
|
* 0x6c65746e = "ntel"
|
|
*/
|
|
result = (ebx == 0x756e6547) && (edx == 0x49656e69) && (ecx == 0x6c65746e);
|
|
|
|
return result;
|
|
}
|
|
|
|
static int is_rapl_enabled(void)
|
|
{
|
|
const char *path = "/sys/class/powercap/intel-rapl/enabled";
|
|
FILE *file = fopen(path, "r");
|
|
int value = 0;
|
|
|
|
if (file != NULL) {
|
|
if (fscanf(file, "%d", &value) != 1) {
|
|
error_report("INTEL RAPL not enabled");
|
|
}
|
|
fclose(file);
|
|
} else {
|
|
error_report("Error opening %s", path);
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
/*
|
|
* Check if the TID that request the MSR read
|
|
* belongs to the peer. It be should a TID of a vCPU.
|
|
*/
|
|
static bool is_tid_present(pid_t pid, pid_t tid)
|
|
{
|
|
g_autofree char *tidPath = g_strdup_printf("/proc/%d/task/%d", pid, tid);
|
|
|
|
/* Check if the TID directory exists within the PID directory */
|
|
if (access(tidPath, F_OK) == 0) {
|
|
return true;
|
|
}
|
|
|
|
error_report("Failed to open /proc at %s", tidPath);
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Only the RAPL MSR in target/i386/cpu.h are allowed
|
|
*/
|
|
static bool is_msr_allowed(uint32_t reg)
|
|
{
|
|
switch (reg) {
|
|
case MSR_RAPL_POWER_UNIT:
|
|
case MSR_PKG_POWER_LIMIT:
|
|
case MSR_PKG_ENERGY_STATUS:
|
|
case MSR_PKG_POWER_INFO:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static uint64_t vmsr_read_msr(uint32_t msr_register, unsigned int cpu_id)
|
|
{
|
|
int fd;
|
|
uint64_t result = 0;
|
|
|
|
g_autofree char *path = g_strdup_printf(MSR_PATH_TEMPLATE, cpu_id);
|
|
|
|
fd = open(path, O_RDONLY);
|
|
if (fd < 0) {
|
|
error_report("Failed to open MSR file at %s", path);
|
|
return result;
|
|
}
|
|
|
|
if (pread(fd, &result, sizeof(result), msr_register) != sizeof(result)) {
|
|
error_report("Failed to read MSR");
|
|
result = 0;
|
|
}
|
|
|
|
close(fd);
|
|
return result;
|
|
}
|
|
|
|
static void usage(const char *name)
|
|
{
|
|
(printf) (
|
|
"Usage: %s [OPTIONS] FILE\n"
|
|
"Virtual RAPL MSR helper program for QEMU\n"
|
|
"\n"
|
|
" -h, --help display this help and exit\n"
|
|
" -V, --version output version information and exit\n"
|
|
"\n"
|
|
" -d, --daemon run in the background\n"
|
|
" -f, --pidfile=PATH PID file when running as a daemon\n"
|
|
" (default '%s')\n"
|
|
" -k, --socket=PATH path to the unix socket\n"
|
|
" (default '%s')\n"
|
|
" -T, --trace [[enable=]<pattern>][,events=<file>][,file=<file>]\n"
|
|
" specify tracing options\n"
|
|
#ifdef CONFIG_LIBCAP_NG
|
|
" -u, --user=USER user to drop privileges to\n"
|
|
" -g, --group=GROUP group to drop privileges to\n"
|
|
#endif
|
|
"\n"
|
|
QEMU_HELP_BOTTOM "\n"
|
|
, name, pidfile, socket_path);
|
|
}
|
|
|
|
static void version(const char *name)
|
|
{
|
|
printf(
|
|
"%s " QEMU_FULL_VERSION "\n"
|
|
"Written by Anthony Harivel.\n"
|
|
"\n"
|
|
QEMU_COPYRIGHT "\n"
|
|
"This is free software; see the source for copying conditions. There is NO\n"
|
|
"warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
|
|
, name);
|
|
}
|
|
|
|
typedef struct VMSRHelperClient {
|
|
QIOChannelSocket *ioc;
|
|
Coroutine *co;
|
|
} VMSRHelperClient;
|
|
|
|
static void coroutine_fn vh_co_entry(void *opaque)
|
|
{
|
|
VMSRHelperClient *client = opaque;
|
|
Error *local_err = NULL;
|
|
unsigned int peer_pid;
|
|
uint32_t request[3];
|
|
uint64_t vmsr;
|
|
int r;
|
|
|
|
qio_channel_set_blocking(QIO_CHANNEL(client->ioc),
|
|
false, NULL);
|
|
|
|
qio_channel_set_follow_coroutine_ctx(QIO_CHANNEL(client->ioc), true);
|
|
|
|
/*
|
|
* Check peer credentials
|
|
*/
|
|
r = qio_channel_get_peerpid(QIO_CHANNEL(client->ioc),
|
|
&peer_pid,
|
|
&local_err);
|
|
if (r < 0) {
|
|
goto out;
|
|
}
|
|
|
|
for (;;) {
|
|
/*
|
|
* Read the requested MSR
|
|
* Only RAPL MSR in rapl-msr-index.h is allowed
|
|
*/
|
|
r = qio_channel_read_all_eof(QIO_CHANNEL(client->ioc),
|
|
(char *) &request, sizeof(request), &local_err);
|
|
if (r <= 0) {
|
|
break;
|
|
}
|
|
|
|
if (!is_msr_allowed(request[0])) {
|
|
error_report("Requested unallowed msr: %d", request[0]);
|
|
break;
|
|
}
|
|
|
|
vmsr = vmsr_read_msr(request[0], request[1]);
|
|
|
|
if (!is_tid_present(peer_pid, request[2])) {
|
|
error_report("Requested TID not in peer PID: %d %d",
|
|
peer_pid, request[2]);
|
|
vmsr = 0;
|
|
}
|
|
|
|
r = qio_channel_write_all(QIO_CHANNEL(client->ioc),
|
|
(char *) &vmsr,
|
|
sizeof(vmsr),
|
|
&local_err);
|
|
if (r < 0) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
out:
|
|
if (local_err) {
|
|
error_report_err(local_err);
|
|
}
|
|
|
|
object_unref(OBJECT(client->ioc));
|
|
g_free(client);
|
|
}
|
|
|
|
static gboolean accept_client(QIOChannel *ioc,
|
|
GIOCondition cond,
|
|
gpointer opaque)
|
|
{
|
|
QIOChannelSocket *cioc;
|
|
VMSRHelperClient *vmsrh;
|
|
|
|
cioc = qio_channel_socket_accept(QIO_CHANNEL_SOCKET(ioc),
|
|
NULL);
|
|
if (!cioc) {
|
|
return TRUE;
|
|
}
|
|
|
|
vmsrh = g_new(VMSRHelperClient, 1);
|
|
vmsrh->ioc = cioc;
|
|
vmsrh->co = qemu_coroutine_create(vh_co_entry, vmsrh);
|
|
qemu_coroutine_enter(vmsrh->co);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void termsig_handler(int signum)
|
|
{
|
|
qatomic_cmpxchg(&state, RUNNING, TERMINATE);
|
|
qemu_notify_event();
|
|
}
|
|
|
|
static void close_server_socket(void)
|
|
{
|
|
assert(server_ioc);
|
|
|
|
g_source_remove(server_watch);
|
|
server_watch = -1;
|
|
object_unref(OBJECT(server_ioc));
|
|
num_active_sockets--;
|
|
}
|
|
|
|
#ifdef CONFIG_LIBCAP_NG
|
|
static int drop_privileges(void)
|
|
{
|
|
/* clear all capabilities */
|
|
capng_clear(CAPNG_SELECT_BOTH);
|
|
|
|
if (capng_update(CAPNG_ADD, CAPNG_EFFECTIVE | CAPNG_PERMITTED,
|
|
CAP_SYS_RAWIO) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
const char *sopt = "hVk:f:dT:u:g:vq";
|
|
struct option lopt[] = {
|
|
{ "help", no_argument, NULL, 'h' },
|
|
{ "version", no_argument, NULL, 'V' },
|
|
{ "socket", required_argument, NULL, 'k' },
|
|
{ "pidfile", required_argument, NULL, 'f' },
|
|
{ "daemon", no_argument, NULL, 'd' },
|
|
{ "trace", required_argument, NULL, 'T' },
|
|
{ "verbose", no_argument, NULL, 'v' },
|
|
{ NULL, 0, NULL, 0 }
|
|
};
|
|
int opt_ind = 0;
|
|
int ch;
|
|
Error *local_err = NULL;
|
|
bool daemonize = false;
|
|
bool pidfile_specified = false;
|
|
bool socket_path_specified = false;
|
|
unsigned socket_activation;
|
|
|
|
struct sigaction sa_sigterm;
|
|
memset(&sa_sigterm, 0, sizeof(sa_sigterm));
|
|
sa_sigterm.sa_handler = termsig_handler;
|
|
sigaction(SIGTERM, &sa_sigterm, NULL);
|
|
sigaction(SIGINT, &sa_sigterm, NULL);
|
|
sigaction(SIGHUP, &sa_sigterm, NULL);
|
|
|
|
signal(SIGPIPE, SIG_IGN);
|
|
|
|
error_init(argv[0]);
|
|
module_call_init(MODULE_INIT_TRACE);
|
|
module_call_init(MODULE_INIT_QOM);
|
|
qemu_add_opts(&qemu_trace_opts);
|
|
qemu_init_exec_dir(argv[0]);
|
|
|
|
compute_default_paths();
|
|
|
|
/*
|
|
* Sanity check
|
|
* 1. cpu must be Intel cpu
|
|
* 2. RAPL must be enabled
|
|
*/
|
|
if (!is_intel_processor()) {
|
|
error_report("error: CPU is not INTEL cpu");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (!is_rapl_enabled()) {
|
|
error_report("error: RAPL driver not enable");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
while ((ch = getopt_long(argc, argv, sopt, lopt, &opt_ind)) != -1) {
|
|
switch (ch) {
|
|
case 'k':
|
|
g_free(socket_path);
|
|
socket_path = g_strdup(optarg);
|
|
socket_path_specified = true;
|
|
if (socket_path[0] != '/') {
|
|
error_report("socket path must be absolute");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
break;
|
|
case 'f':
|
|
g_free(pidfile);
|
|
pidfile = g_strdup(optarg);
|
|
pidfile_specified = true;
|
|
break;
|
|
#ifdef CONFIG_LIBCAP_NG
|
|
case 'u': {
|
|
unsigned long res;
|
|
struct passwd *userinfo = getpwnam(optarg);
|
|
if (userinfo) {
|
|
uid = userinfo->pw_uid;
|
|
} else if (qemu_strtoul(optarg, NULL, 10, &res) == 0 &&
|
|
(uid_t)res == res) {
|
|
uid = res;
|
|
} else {
|
|
error_report("invalid user '%s'", optarg);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
break;
|
|
}
|
|
case 'g': {
|
|
unsigned long res;
|
|
struct group *groupinfo = getgrnam(optarg);
|
|
if (groupinfo) {
|
|
gid = groupinfo->gr_gid;
|
|
} else if (qemu_strtoul(optarg, NULL, 10, &res) == 0 &&
|
|
(gid_t)res == res) {
|
|
gid = res;
|
|
} else {
|
|
error_report("invalid group '%s'", optarg);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
break;
|
|
}
|
|
#else
|
|
case 'u':
|
|
case 'g':
|
|
error_report("-%c not supported by this %s", ch, argv[0]);
|
|
exit(1);
|
|
#endif
|
|
case 'd':
|
|
daemonize = true;
|
|
break;
|
|
case 'T':
|
|
trace_opt_parse(optarg);
|
|
break;
|
|
case 'V':
|
|
version(argv[0]);
|
|
exit(EXIT_SUCCESS);
|
|
break;
|
|
case 'h':
|
|
usage(argv[0]);
|
|
exit(EXIT_SUCCESS);
|
|
break;
|
|
case '?':
|
|
error_report("Try `%s --help' for more information.", argv[0]);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
if (!trace_init_backends()) {
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
trace_init_file();
|
|
qemu_set_log(LOG_TRACE, &error_fatal);
|
|
|
|
socket_activation = check_socket_activation();
|
|
if (socket_activation == 0) {
|
|
SocketAddress saddr;
|
|
saddr = (SocketAddress){
|
|
.type = SOCKET_ADDRESS_TYPE_UNIX,
|
|
.u.q_unix.path = socket_path,
|
|
};
|
|
server_ioc = qio_channel_socket_new();
|
|
if (qio_channel_socket_listen_sync(server_ioc, &saddr,
|
|
1, &local_err) < 0) {
|
|
object_unref(OBJECT(server_ioc));
|
|
error_report_err(local_err);
|
|
return 1;
|
|
}
|
|
} else {
|
|
/* Using socket activation - check user didn't use -p etc. */
|
|
if (socket_path_specified) {
|
|
error_report("Unix socket can't be set when"
|
|
"using socket activation");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* Can only listen on a single socket. */
|
|
if (socket_activation > 1) {
|
|
error_report("%s does not support socket activation"
|
|
"with LISTEN_FDS > 1",
|
|
argv[0]);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
server_ioc = qio_channel_socket_new_fd(FIRST_SOCKET_ACTIVATION_FD,
|
|
&local_err);
|
|
if (server_ioc == NULL) {
|
|
error_reportf_err(local_err,
|
|
"Failed to use socket activation: ");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
qemu_init_main_loop(&error_fatal);
|
|
|
|
server_watch = qio_channel_add_watch(QIO_CHANNEL(server_ioc),
|
|
G_IO_IN,
|
|
accept_client,
|
|
NULL, NULL);
|
|
|
|
if (daemonize) {
|
|
if (daemon(0, 0) < 0) {
|
|
error_report("Failed to daemonize: %s", strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
if (daemonize || pidfile_specified) {
|
|
qemu_write_pidfile(pidfile, &error_fatal);
|
|
}
|
|
|
|
#ifdef CONFIG_LIBCAP_NG
|
|
if (drop_privileges() < 0) {
|
|
error_report("Failed to drop privileges: %s", strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
#endif
|
|
|
|
info_report("Listening on %s", socket_path);
|
|
|
|
state = RUNNING;
|
|
do {
|
|
main_loop_wait(false);
|
|
if (state == TERMINATE) {
|
|
state = TERMINATING;
|
|
close_server_socket();
|
|
}
|
|
} while (num_active_sockets > 0);
|
|
|
|
exit(EXIT_SUCCESS);
|
|
}
|