// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/system/core_scheduling.h"
#include <errno.h>
#include <sys/prctl.h>
#include <set>
#include <string>
#include "base/feature_list.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/metrics/field_trial_params.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/system/sys_info.h"
#include "base/threading/thread_restrictions.h"
// Downstream core scheduling interface for 4.19, 5.4 kernels.
// TODO(b/152605392): Remove once those kernel versions are obsolete.
#ifndef PR_SET_CORE_SCHED
#define PR_SET_CORE_SCHED 0x200
#endif
// Upstream interface for core scheduling.
#ifndef PR_SCHED_CORE
#define PR_SCHED_CORE 62
#define PR_SCHED_CORE_GET 0
#define PR_SCHED_CORE_CREATE 1
#define PR_SCHED_CORE_SHARE_TO 2
#define PR_SCHED_CORE_SHARE_FROM 3
#define PR_SCHED_CORE_MAX 4
#endif
#ifndef PID_T_MAX
#define PID_T_MAX (1 << 30)
#endif
enum pid_type { PIDTYPE_PID = 0, PIDTYPE_TGID, PIDTYPE_PGID };
namespace chromeos {
namespace system {
namespace {
BASE_FEATURE(kCoreScheduling,
"CoreSchedulingEnabled",
base::FEATURE_ENABLED_BY_DEFAULT);
}
void EnableCoreSchedulingIfAvailable() {
if (!base::FeatureList::IsEnabled(kCoreScheduling)) {
return;
}
// prctl(2) will return EINVAL for unknown functions. We're tolerant to this
// and will log an error message for non EINVAL errnos.
if (prctl(PR_SET_CORE_SCHED, 1) == -1 &&
prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE, 0, PIDTYPE_PID, 0) == -1) {
PLOG_IF(WARNING, errno != EINVAL) << "Unable to set core scheduling";
}
}
bool IsCoreSchedulingAvailable() {
static const bool kernel_support = []() {
// Test for kernel 4.19, 5.4 downstream support.
// Pass bad param `prctl(0x200, 2)`. If it is supported, we will get ERANGE
// rather than EINVAL.
if (prctl(PR_SET_CORE_SCHED, 2) == -1 && errno == ERANGE) {
VLOG(1) << "Core scheduling legacy interface supported";
return true;
}
// Test for kernel 5.10 upstream support.
// Pass bad param pid=PID_T_MAX. If it is supported, we will get ENODEV
// (supported by not enabled) or ESRCH (bad param).
int res =
prctl(PR_SCHED_CORE, PR_SCHED_CORE_CREATE, PID_T_MAX, PIDTYPE_PID, 0);
int saved_errno = errno;
if (res == -1 && saved_errno == ENODEV) {
VLOG(1) << "Core scheduling supported, but not enabled "
"(likely because SMT is not enabled)";
return false;
}
if (res == -1 && saved_errno == ESRCH) {
VLOG(1) << "Core scheduling supported and enabled";
return true;
}
VLOG(1) << "Core scheduling not supported in kernel";
return false;
}();
if (!kernel_support) {
return false;
}
static const bool has_vulns = []() {
// Reading from sysfs doesn't block.
base::ScopedAllowBlocking scoped_allow_blocking;
base::FilePath sysfs_vulns("/sys/devices/system/cpu/vulnerabilities");
std::string buf;
for (const std::string& s : {"l1tf", "mds"}) {
base::FilePath vuln = sysfs_vulns.Append(s);
if (!base::ReadFileToString(vuln, &buf)) {
LOG(ERROR) << "Could not read " << vuln;
continue;
}
base::TrimWhitespaceASCII(buf, base::TRIM_ALL, &buf);
if (buf != "Not affected") {
VLOG(1) << "Core scheduling available: " << vuln << "=" << buf;
return true;
}
}
VLOG(1) << "Core scheduling not required";
return false;
}();
return has_vulns;
}
int NumberOfPhysicalCores() {
// cat /sys/devices/system/cpu/cpu[0-9]*/topology/thread_siblings_list |\
// sort -u | wc -l
static const int num_physical_cores = []() {
// Reading from sysfs doesn't block.
base::ScopedAllowBlocking scoped_allow_blocking;
std::set<std::string> lists;
std::string buf;
for (int i = 0; i < base::SysInfo::NumberOfProcessors(); ++i) {
base::FilePath list(base::StringPrintf(
"/sys/devices/system/cpu/cpu%d/topology/thread_siblings_list", i));
if (!base::ReadFileToString(list, &buf)) {
LOG(ERROR) << "Could not read " << list;
} else {
lists.insert(buf);
}
}
return lists.size() > 0 ? lists.size() : 1;
}();
return num_physical_cores;
}
} // namespace system
} // namespace chromeos