blob: 58a38fa51d41eff490643a2bff535fdba6e8f60f [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include "object/profile_dispatcher.h"
#include <bits.h>
#include <lib/counters.h>
#include <zircon/errors.h>
#include <zircon/rights.h>
#include <zircon/types.h>
#include <fbl/alloc_checker.h>
#include <fbl/ref_ptr.h>
#include <ktl/bit.h>
#include <object/thread_dispatcher.h>
#include <object/vm_address_region_dispatcher.h>
#include <ktl/enforce.h>
KCOUNTER(dispatcher_profile_create_count, "dispatcher.profile.create")
KCOUNTER(dispatcher_profile_destroy_count, "dispatcher.profile.destroy")
static zx::result<cpu_mask_t> parse_cpu_mask(const zx_cpu_set_t& set) {
// The code below only supports reading up to 1 word in the mask.
static_assert(SMP_MAX_CPUS <= sizeof(set.mask[0]) * CHAR_BIT);
static_assert(SMP_MAX_CPUS <= sizeof(cpu_mask_t) * CHAR_BIT);
static_assert(SMP_MAX_CPUS <= ZX_CPU_SET_MAX_CPUS);
// We throw away any bits beyond SMP_MAX_CPUs.
return zx::ok(static_cast<cpu_mask_t>(set.mask[0] & BIT_MASK(SMP_MAX_CPUS)));
}
static zx::result<SchedulerState::BaseProfile> validate_and_create_profile(
const zx_profile_info_t& info) {
// Ensure that none of the flags outside of the set of valid flags has been set.
constexpr uint32_t kAllFlags = (ZX_PROFILE_INFO_FLAG_PRIORITY | ZX_PROFILE_INFO_FLAG_CPU_MASK |
ZX_PROFILE_INFO_FLAG_DEADLINE | ZX_PROFILE_INFO_FLAG_NO_INHERIT);
if ((info.flags & ~kAllFlags) != 0) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
// Ensure that exactly one of the "discipline selection" flags has been set.
constexpr uint32_t kDisciplineSelectionFlags =
ZX_PROFILE_INFO_FLAG_PRIORITY | ZX_PROFILE_INFO_FLAG_DEADLINE;
if (ktl::popcount(info.flags & kDisciplineSelectionFlags) != 1) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
// Deadline profiles may not be flagged as NO_INHERIT
constexpr uint32_t kDeadlineNoInherit =
ZX_PROFILE_INFO_FLAG_DEADLINE | ZX_PROFILE_INFO_FLAG_NO_INHERIT;
if ((info.flags & kDeadlineNoInherit) == kDeadlineNoInherit) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
// If selected, ensure priority is valid.
if ((info.flags & ZX_PROFILE_INFO_FLAG_PRIORITY) != 0) {
if ((info.priority < LOWEST_PRIORITY) || (info.priority > HIGHEST_PRIORITY)) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
}
// If selected, ensure the deadline parameters are valid. Note that deadline
// profiles must currently be inheritable.
const bool inheritable = (info.flags & ZX_PROFILE_INFO_FLAG_NO_INHERIT) == 0;
if ((info.flags & ZX_PROFILE_INFO_FLAG_DEADLINE) != 0) {
// TODO(eieio): Add additional admission criteria to prevent values that are
// too large or too small. These values are mediated by a privileged service
// so the risk of abuse is low, but it still might be good to implement some
// sort of failsafe check to prevent mistakes.
const bool admissible =
info.deadline_params.capacity > 0 &&
info.deadline_params.capacity <= info.deadline_params.relative_deadline &&
info.deadline_params.relative_deadline <= info.deadline_params.period && inheritable;
if (!admissible) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
}
if (info.flags & ZX_PROFILE_INFO_FLAG_PRIORITY) {
return zx::ok(SchedulerState::BaseProfile(info.priority, inheritable));
} else {
DEBUG_ASSERT(inheritable == true);
return zx::ok(SchedulerState::BaseProfile(info.deadline_params));
}
}
static zx::result<VmAddressRegion::MemoryPriority> parse_memory_priority(
const zx_profile_info_t& info) {
if (info.priority == ZX_PRIORITY_HIGH) {
return zx::ok(VmAddressRegion::MemoryPriority::HIGH);
}
if (info.priority == ZX_PRIORITY_DEFAULT) {
return zx::ok(VmAddressRegion::MemoryPriority::DEFAULT);
}
return zx::error(ZX_ERR_INVALID_ARGS);
}
zx_status_t ProfileDispatcher::Create(const zx_profile_info_t& info,
KernelHandle<ProfileDispatcher>* handle,
zx_rights_t* rights) {
// A profile must specify at least a set of scheduling parameters, or a cpu
// affinity mask, or a memory priority.
constexpr uint32_t kSchedFlags = ZX_PROFILE_INFO_FLAG_PRIORITY | ZX_PROFILE_INFO_FLAG_DEADLINE;
constexpr uint32_t kAffinityFlags = ZX_PROFILE_INFO_FLAG_CPU_MASK;
constexpr uint32_t kThreadFlags = kSchedFlags | kAffinityFlags;
constexpr uint32_t kMemoryFlags = ZX_PROFILE_INFO_FLAG_MEMORY_PRIORITY;
constexpr uint32_t kRequiredFlags = kThreadFlags | kMemoryFlags;
if ((info.flags & kRequiredFlags) == 0) {
return ZX_ERR_INVALID_ARGS;
}
// Memory flags are incompatible with thread related flags.
if ((info.flags & kMemoryFlags) != 0 && (info.flags & kThreadFlags) != 0) {
return ZX_ERR_INVALID_ARGS;
}
ktl::optional<SchedulerState::BaseProfile> profile;
if ((info.flags & kSchedFlags) != 0) {
zx::result<SchedulerState::BaseProfile> maybe_profile = validate_and_create_profile(info);
if (maybe_profile.is_error()) {
return maybe_profile.error_value();
}
profile = maybe_profile.value();
}
ktl::optional<cpu_mask_t> cpu_mask;
if ((info.flags & kAffinityFlags) != 0) {
zx::result<cpu_mask_t> maybe_mask = parse_cpu_mask(info.cpu_affinity_mask);
if (maybe_mask.is_error()) {
return maybe_mask.error_value();
}
cpu_mask = maybe_mask.value();
}
ktl::optional<VmAddressRegion::MemoryPriority> memory_priority;
if ((info.flags & kMemoryFlags) != 0) {
zx::result<VmAddressRegion::MemoryPriority> maybe_memory_priority = parse_memory_priority(info);
if (maybe_memory_priority.is_error()) {
return maybe_memory_priority.error_value();
}
memory_priority = maybe_memory_priority.value();
}
fbl::AllocChecker ac;
KernelHandle new_handle(
fbl::AdoptRef(new (&ac) ProfileDispatcher(profile, cpu_mask, memory_priority)));
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
*rights = default_rights();
*handle = ktl::move(new_handle);
return ZX_OK;
}
ProfileDispatcher::ProfileDispatcher(
const ktl::optional<SchedulerState::BaseProfile>& profile,
const ktl::optional<cpu_mask_t>& cpu_mask,
const ktl::optional<VmAddressRegion::MemoryPriority>& memory_priority)
: profile_(profile), cpu_mask_(cpu_mask), memory_priority_(memory_priority) {
kcounter_add(dispatcher_profile_create_count, 1);
}
ProfileDispatcher::~ProfileDispatcher() { kcounter_add(dispatcher_profile_destroy_count, 1); }
zx_status_t ProfileDispatcher::ApplyProfile(fbl::RefPtr<ThreadDispatcher> thread) {
// Set the profile, if the user explicitly specified one when creating this profile.
if (profile_.has_value()) {
if (zx_status_t result = thread->SetBaseProfile(profile_.value()); result != ZX_OK) {
return result;
}
}
// Set cpu affinity, if the user explicitly specified one when creating this profile.
if (cpu_mask_.has_value()) {
return thread->SetSoftAffinity(cpu_mask_.value());
}
return ZX_OK;
}
zx_status_t ProfileDispatcher::ApplyProfile(fbl::RefPtr<VmAddressRegionDispatcher> vmar) {
if (memory_priority_.has_value()) {
return vmar->SetMemoryPriority(memory_priority_.value());
}
return ZX_OK;
}