| #include "performance_service.h" |
| |
| #include <sstream> |
| |
| #include <sched.h> |
| #include <sys/prctl.h> |
| #include <unistd.h> |
| |
| #include <pdx/default_transport/service_endpoint.h> |
| #include <pdx/rpc/argument_encoder.h> |
| #include <pdx/rpc/message_buffer.h> |
| #include <pdx/rpc/remote_method.h> |
| #include <private/android_filesystem_config.h> |
| #include <private/dvr/performance_rpc.h> |
| #include <private/dvr/trusted_uids.h> |
| |
| #include "task.h" |
| |
| // This prctl is only available in Android kernels. |
| #define PR_SET_TIMERSLACK_PID 41 |
| |
| using android::dvr::IsTrustedUid; |
| using android::dvr::Task; |
| using android::pdx::ErrorStatus; |
| using android::pdx::Message; |
| using android::pdx::Status; |
| using android::pdx::default_transport::Endpoint; |
| using android::pdx::rpc::DispatchRemoteMethod; |
| |
| namespace { |
| |
| const char kCpuSetBasePath[] = "/dev/cpuset"; |
| |
| const char kRootCpuSet[] = "/"; |
| |
| const char kVrAppRenderPolicy[] = "vr:app:render"; |
| |
| const bool kAllowAppsToRequestVrAppRenderPolicy = false; |
| |
| constexpr unsigned long kTimerSlackForegroundNs = 50000; |
| constexpr unsigned long kTimerSlackBackgroundNs = 40000000; |
| |
| // Expands the given parameter pack expression using an initializer list to |
| // guarantee ordering and a comma expression to guarantee even void expressions |
| // are valid elements of the initializer list. |
| #define EXPAND_PACK(...) \ |
| std::initializer_list<int> { (__VA_ARGS__, 0)... } |
| |
| // Returns true if the sender's euid matches any of the uids in |UIDs|. |
| template <uid_t... UIDs> |
| struct UserId { |
| static bool Check(const Message& sender, const Task&) { |
| const uid_t uid = sender.GetEffectiveUserId(); |
| bool allow = false; |
| EXPAND_PACK(allow |= (uid == UIDs)); |
| return allow; |
| } |
| }; |
| |
| // Returns true if the sender's egid matches any of the gids in |GIDs|. |
| template <gid_t... GIDs> |
| struct GroupId { |
| static bool Check(const Message& sender, const Task&) { |
| const gid_t gid = sender.GetEffectiveGroupId(); |
| bool allow = false; |
| EXPAND_PACK(allow |= (gid == GIDs)); |
| return allow; |
| } |
| }; |
| |
| // Returns true if the sender's euid is trusted according to VR manager service. |
| struct Trusted { |
| static bool Check(const Message& sender, const Task&) { |
| return IsTrustedUid(sender.GetEffectiveUserId()); |
| } |
| }; |
| |
| // Returns returns true if the task belongs to the sending process. |
| struct SameProcess { |
| static bool Check(const Message& sender, const Task& task) { |
| return sender.GetProcessId() == task.thread_group_id(); |
| } |
| }; |
| |
| // Returns true if any of the checks in |Allows| pass, false otherwise. |
| template <typename... Allows> |
| struct CheckOr { |
| static bool Check(const Message& sender, const Task& task) { |
| bool allow = false; |
| EXPAND_PACK(allow |= Allows::Check(sender, task)); |
| return allow; |
| } |
| }; |
| |
| // Returns true if all of the checks in |Allows| pass, false otherwise. |
| template <typename... Allows> |
| struct CheckAnd { |
| static bool Check(const Message& sender, const Task& task) { |
| bool allow = true; |
| EXPAND_PACK(allow &= Allows::Check(sender, task)); |
| return allow; |
| } |
| }; |
| |
| } // anonymous namespace |
| |
| namespace android { |
| namespace dvr { |
| |
| PerformanceService::PerformanceService() |
| : BASE("PerformanceService", |
| Endpoint::Create(PerformanceRPC::kClientPath)) { |
| cpuset_.Load(kCpuSetBasePath); |
| |
| Task task(getpid()); |
| ALOGI("Running in cpuset=%s uid=%d gid=%d", task.GetCpuSetPath().c_str(), |
| task.user_id()[Task::kUidReal], task.group_id()[Task::kUidReal]); |
| |
| // Errors here are checked in IsInitialized(). |
| sched_fifo_min_priority_ = sched_get_priority_min(SCHED_FIFO); |
| sched_fifo_max_priority_ = sched_get_priority_max(SCHED_FIFO); |
| |
| const int fifo_range = sched_fifo_max_priority_ - sched_fifo_min_priority_; |
| const int fifo_low = sched_fifo_min_priority_; |
| const int fifo_medium = sched_fifo_min_priority_ + fifo_range / 5; |
| |
| // TODO(eieio): Make this configurable on the command line or config file. |
| cpuset_.MoveUnboundTasks("/kernel"); |
| |
| // TODO(eieio): Replace this witha device-specific config file. This is just a |
| // hack for now to put some form of permission logic in place while a longer |
| // term solution is developed. |
| using AllowRootSystemGraphics = |
| CheckAnd<SameProcess, CheckOr<UserId<AID_ROOT, AID_SYSTEM, AID_GRAPHICS>, |
| GroupId<AID_SYSTEM, AID_GRAPHICS>>>; |
| using AllowRootSystemAudio = |
| CheckAnd<SameProcess, CheckOr<UserId<AID_ROOT, AID_SYSTEM, AID_AUDIO>, |
| GroupId<AID_SYSTEM, AID_AUDIO>>>; |
| using AllowRootSystemTrusted = |
| CheckOr<Trusted, UserId<AID_ROOT, AID_SYSTEM>, GroupId<AID_SYSTEM>>; |
| |
| auto vr_app_render_permission_check = []( |
| const pdx::Message& sender, const Task& task) { |
| // For vr:app:render, in addition to system/root apps and VrCore, we |
| // also allow apps to request vr:app:render if |
| // kAllowAppsToRequestVrAppRenderPolicy == true, but not for other |
| // apps, only for themselves. |
| return (task && task.thread_group_id() == sender.GetProcessId() && |
| kAllowAppsToRequestVrAppRenderPolicy) |
| || AllowRootSystemTrusted::Check(sender, task); |
| }; |
| |
| partition_permission_check_ = AllowRootSystemTrusted::Check; |
| |
| // Setup the scheduler classes. |
| // TODO(eieio): Replace this with a device-specific config file. |
| scheduler_policies_ = { |
| {"audio:low", |
| {.timer_slack = kTimerSlackForegroundNs, |
| .scheduler_policy = SCHED_FIFO | SCHED_RESET_ON_FORK, |
| .priority = fifo_medium, |
| .permission_check = AllowRootSystemAudio::Check}}, |
| {"audio:high", |
| {.timer_slack = kTimerSlackForegroundNs, |
| .scheduler_policy = SCHED_FIFO | SCHED_RESET_ON_FORK, |
| .priority = fifo_medium + 3, |
| .permission_check = AllowRootSystemAudio::Check}}, |
| {"graphics", |
| {.timer_slack = kTimerSlackForegroundNs, |
| .scheduler_policy = SCHED_FIFO | SCHED_RESET_ON_FORK, |
| .priority = fifo_medium, |
| .permission_check = AllowRootSystemGraphics::Check}}, |
| {"graphics:low", |
| {.timer_slack = kTimerSlackForegroundNs, |
| .scheduler_policy = SCHED_FIFO | SCHED_RESET_ON_FORK, |
| .priority = fifo_medium, |
| .permission_check = AllowRootSystemGraphics::Check}}, |
| {"graphics:high", |
| {.timer_slack = kTimerSlackForegroundNs, |
| .scheduler_policy = SCHED_FIFO | SCHED_RESET_ON_FORK, |
| .priority = fifo_medium + 2, |
| .permission_check = AllowRootSystemGraphics::Check}}, |
| {"sensors", |
| {.timer_slack = kTimerSlackForegroundNs, |
| .scheduler_policy = SCHED_FIFO | SCHED_RESET_ON_FORK, |
| .priority = fifo_low, |
| .permission_check = AllowRootSystemTrusted::Check}}, |
| {"sensors:low", |
| {.timer_slack = kTimerSlackForegroundNs, |
| .scheduler_policy = SCHED_FIFO | SCHED_RESET_ON_FORK, |
| .priority = fifo_low, |
| .permission_check = AllowRootSystemTrusted::Check}}, |
| {"sensors:high", |
| {.timer_slack = kTimerSlackForegroundNs, |
| .scheduler_policy = SCHED_FIFO | SCHED_RESET_ON_FORK, |
| .priority = fifo_low + 1, |
| .permission_check = AllowRootSystemTrusted::Check}}, |
| {"vr:system:arp", |
| {.timer_slack = kTimerSlackForegroundNs, |
| .scheduler_policy = SCHED_FIFO | SCHED_RESET_ON_FORK, |
| .priority = fifo_medium + 2, |
| .permission_check = AllowRootSystemTrusted::Check, |
| "/system/performance"}}, |
| {kVrAppRenderPolicy, |
| {.timer_slack = kTimerSlackForegroundNs, |
| .scheduler_policy = SCHED_FIFO | SCHED_RESET_ON_FORK, |
| .priority = fifo_medium + 1, |
| .permission_check = vr_app_render_permission_check, |
| "/application/performance"}}, |
| {"normal", |
| {.timer_slack = kTimerSlackForegroundNs, |
| .scheduler_policy = SCHED_NORMAL, |
| .priority = 0}}, |
| {"foreground", |
| {.timer_slack = kTimerSlackForegroundNs, |
| .scheduler_policy = SCHED_NORMAL, |
| .priority = 0}}, |
| {"background", |
| {.timer_slack = kTimerSlackBackgroundNs, |
| .scheduler_policy = SCHED_BATCH, |
| .priority = 0}}, |
| {"batch", |
| {.timer_slack = kTimerSlackBackgroundNs, |
| .scheduler_policy = SCHED_BATCH, |
| .priority = 0}}, |
| }; |
| } |
| |
| bool PerformanceService::IsInitialized() const { |
| return BASE::IsInitialized() && cpuset_ && sched_fifo_min_priority_ >= 0 && |
| sched_fifo_max_priority_ >= 0; |
| } |
| |
| std::string PerformanceService::DumpState(size_t /*max_length*/) { |
| std::ostringstream stream; |
| stream << "vr_app_render_thread: " << vr_app_render_thread_ << std::endl; |
| cpuset_.DumpState(stream); |
| return stream.str(); |
| } |
| |
| Status<void> PerformanceService::OnSetSchedulerPolicy( |
| Message& message, pid_t task_id, const std::string& scheduler_policy) { |
| ALOGI( |
| "PerformanceService::OnSetSchedulerPolicy: task_id=%d " |
| "scheduler_policy=%s", |
| task_id, scheduler_policy.c_str()); |
| |
| Task task(task_id); |
| if (!task) { |
| ALOGE( |
| "PerformanceService::OnSetSchedulerPolicy: Unable to access /proc/%d " |
| "to gather task information.", |
| task_id); |
| return ErrorStatus(EINVAL); |
| } |
| |
| auto search = scheduler_policies_.find(scheduler_policy); |
| if (search != scheduler_policies_.end()) { |
| auto config = search->second; |
| |
| // Make sure the sending process is allowed to make the requested change to |
| // this task. |
| if (!config.IsAllowed(message, task)) |
| return ErrorStatus(EPERM); |
| |
| if (scheduler_policy == kVrAppRenderPolicy) { |
| // We only allow one vr:app:render thread at a time |
| SetVrAppRenderThread(task_id); |
| } |
| |
| // Get the thread group's cpu set. Policies that do not specify a cpuset |
| // should default to this cpuset. |
| std::string thread_group_cpuset; |
| Task thread_group{task.thread_group_id()}; |
| if (thread_group) { |
| thread_group_cpuset = thread_group.GetCpuSetPath(); |
| } else { |
| ALOGE( |
| "PerformanceService::OnSetSchedulerPolicy: Failed to get thread " |
| "group tgid=%d for task_id=%d", |
| task.thread_group_id(), task_id); |
| thread_group_cpuset = kRootCpuSet; |
| } |
| |
| std::string target_cpuset; |
| if (config.cpuset.empty()) { |
| target_cpuset = thread_group_cpuset; |
| } else { |
| target_cpuset = config.cpuset; |
| } |
| ALOGI("PerformanceService::OnSetSchedulerPolicy: Using cpuset=%s", |
| target_cpuset.c_str()); |
| |
| auto target_set = cpuset_.Lookup(target_cpuset); |
| if (target_set) { |
| auto attach_status = target_set->AttachTask(task_id); |
| ALOGW_IF(!attach_status, |
| "PerformanceService::OnSetSchedulerPolicy: Failed to attach " |
| "task=%d to cpuset=%s: %s", |
| task_id, target_cpuset.c_str(), |
| attach_status.GetErrorMessage().c_str()); |
| } else { |
| ALOGW( |
| "PerformanceService::OnSetSchedulerPolicy: Failed to lookup " |
| "cpuset=%s", |
| target_cpuset.c_str()); |
| } |
| |
| struct sched_param param; |
| param.sched_priority = config.priority; |
| |
| sched_setscheduler(task_id, config.scheduler_policy, ¶m); |
| prctl(PR_SET_TIMERSLACK_PID, config.timer_slack, task_id); |
| return {}; |
| } else { |
| ALOGE( |
| "PerformanceService::OnSetSchedulerPolicy: Invalid scheduler_policy=%s " |
| "requested by task=%d.", |
| scheduler_policy.c_str(), task_id); |
| return ErrorStatus(EINVAL); |
| } |
| } |
| |
| Status<void> PerformanceService::OnSetCpuPartition( |
| Message& message, pid_t task_id, const std::string& partition) { |
| Task task(task_id); |
| if (!task) |
| return ErrorStatus(EINVAL); |
| if (task.thread_group_id() != message.GetProcessId()) |
| return ErrorStatus(EPERM); |
| |
| // Temporary permission check. |
| // TODO(eieio): Replace this with a configuration file. |
| if (partition_permission_check_ && |
| !partition_permission_check_(message, task)) { |
| return ErrorStatus(EPERM); |
| } |
| |
| auto target_set = cpuset_.Lookup(partition); |
| if (!target_set) |
| return ErrorStatus(ENOENT); |
| |
| auto attach_status = target_set->AttachTask(task_id); |
| if (!attach_status) |
| return attach_status; |
| |
| return {}; |
| } |
| |
| Status<void> PerformanceService::OnSetSchedulerClass( |
| Message& message, pid_t task_id, const std::string& scheduler_class) { |
| Task task(task_id); |
| if (!task) |
| return ErrorStatus(EINVAL); |
| |
| auto search = scheduler_policies_.find(scheduler_class); |
| if (search != scheduler_policies_.end()) { |
| auto config = search->second; |
| |
| // Make sure the sending process is allowed to make the requested change to |
| // this task. |
| if (!config.IsAllowed(message, task)) |
| return ErrorStatus(EPERM); |
| |
| if (scheduler_class == kVrAppRenderPolicy) { |
| // We only allow one vr:app:render thread at a time |
| SetVrAppRenderThread(task_id); |
| } |
| |
| struct sched_param param; |
| param.sched_priority = config.priority; |
| |
| sched_setscheduler(task_id, config.scheduler_policy, ¶m); |
| prctl(PR_SET_TIMERSLACK_PID, config.timer_slack, task_id); |
| ALOGI("PerformanceService::OnSetSchedulerClass: Set task=%d to class=%s.", |
| task_id, scheduler_class.c_str()); |
| return {}; |
| } else { |
| ALOGE( |
| "PerformanceService::OnSetSchedulerClass: Invalid class=%s requested " |
| "by task=%d.", |
| scheduler_class.c_str(), task_id); |
| return ErrorStatus(EINVAL); |
| } |
| } |
| |
| Status<std::string> PerformanceService::OnGetCpuPartition(Message& message, |
| pid_t task_id) { |
| // Make sure the task id is valid and belongs to the sending process. |
| Task task(task_id); |
| if (!task) |
| return ErrorStatus(EINVAL); |
| if (task.thread_group_id() != message.GetProcessId()) |
| return ErrorStatus(EPERM); |
| |
| return task.GetCpuSetPath(); |
| } |
| |
| Status<void> PerformanceService::HandleMessage(Message& message) { |
| ALOGD_IF(TRACE, "PerformanceService::HandleMessage: op=%d", message.GetOp()); |
| switch (message.GetOp()) { |
| case PerformanceRPC::SetSchedulerPolicy::Opcode: |
| DispatchRemoteMethod<PerformanceRPC::SetSchedulerPolicy>( |
| *this, &PerformanceService::OnSetSchedulerPolicy, message); |
| return {}; |
| |
| case PerformanceRPC::SetCpuPartition::Opcode: |
| DispatchRemoteMethod<PerformanceRPC::SetCpuPartition>( |
| *this, &PerformanceService::OnSetCpuPartition, message); |
| return {}; |
| |
| case PerformanceRPC::SetSchedulerClass::Opcode: |
| DispatchRemoteMethod<PerformanceRPC::SetSchedulerClass>( |
| *this, &PerformanceService::OnSetSchedulerClass, message); |
| return {}; |
| |
| case PerformanceRPC::GetCpuPartition::Opcode: |
| DispatchRemoteMethod<PerformanceRPC::GetCpuPartition>( |
| *this, &PerformanceService::OnGetCpuPartition, message); |
| return {}; |
| |
| default: |
| return Service::HandleMessage(message); |
| } |
| } |
| |
| void PerformanceService::SetVrAppRenderThread(pid_t new_vr_app_render_thread) { |
| ALOGI("SetVrAppRenderThread old=%d new=%d", |
| vr_app_render_thread_, new_vr_app_render_thread); |
| |
| if (vr_app_render_thread_ >= 0 && |
| vr_app_render_thread_ != new_vr_app_render_thread) { |
| // Restore the default scheduler policy and priority on the previous |
| // vr:app:render thread. |
| struct sched_param param; |
| param.sched_priority = 0; |
| if (sched_setscheduler(vr_app_render_thread_, SCHED_NORMAL, ¶m) < 0) { |
| if (errno == ESRCH) { |
| ALOGI("Failed to revert %s scheduler policy. Couldn't find thread %d." |
| " Was the app killed?", kVrAppRenderPolicy, vr_app_render_thread_); |
| } else { |
| ALOGE("Failed to revert %s scheduler policy: %s", |
| kVrAppRenderPolicy, strerror(errno)); |
| } |
| } |
| |
| // Restore the default timer slack on the previous vr:app:render thread. |
| prctl(PR_SET_TIMERSLACK_PID, kTimerSlackForegroundNs, |
| vr_app_render_thread_); |
| } |
| |
| // We could also reset the thread's cpuset here, but the cpuset is already |
| // managed by Android. Better to let Android adjust the cpuset as the app |
| // moves to the background, rather than adjust it ourselves here, and risk |
| // stomping on the value set by Android. |
| |
| vr_app_render_thread_ = new_vr_app_render_thread; |
| } |
| |
| } // namespace dvr |
| } // namespace android |