blob: 63f46ba98fa29c7601125eb5c27fc4de423eebf1 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "thread.h"
#include <fuchsia/scheduler/c/fidl.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fdio.h>
#include <stdio.h>
#include <zircon/assert.h>
#include <zircon/threads.h>
#include <fbl/auto_call.h>
#include <fbl/auto_lock.h>
#include "utils.h"
// Static storage for Thread::
zx::channel Thread::scheduler_service_;
std::array<zx::profile, Thread::PRIORITY_LEVELS> Thread::profiles_;
Thread::Thread(uint32_t prio) : prio_(prio) {
snprintf(name_, sizeof(name_), "mutex_pi_thread %02u", prio_);
}
// static
zx_status_t Thread::ConnectSchedulerService() {
if (scheduler_service_.is_valid()) {
return ZX_ERR_BAD_STATE;
}
zx::channel server_ep;
zx::channel::create(0, &scheduler_service_, &server_ep);
zx_status_t res;
res = fdio_service_connect("/svc/" fuchsia_scheduler_ProfileProvider_Name, server_ep.release());
if (res != ZX_OK) {
fprintf(stderr, "Failed to connect schedule.ProfileProvider! (res %d)\n", res);
}
return res;
}
// static
zx_status_t Thread::EnsureProfile(uint32_t prio_level) {
if (prio_level >= profiles_.size()) {
return ZX_ERR_INVALID_ARGS;
}
zx::profile& profile = profiles_[prio_level];
if (profile.is_valid()) {
return ZX_OK;
}
char name[32];
size_t name_len = snprintf(name, sizeof(name), "mutex_pi_exerciser %02u", prio_level);
zx_status_t get_profile_res;
zx_status_t res;
res = fuchsia_scheduler_ProfileProviderGetProfile(scheduler_service_.get(), prio_level, name,
name_len, &get_profile_res,
profile.reset_and_get_address());
if ((res != ZX_OK) || (get_profile_res != ZX_OK)) {
fprintf(stderr, "Failed to obtain profile for priority %u (res = %d, gp_res = %d)\n",
prio_level, res, get_profile_res);
if (res == ZX_OK) {
res = get_profile_res;
}
}
return res;
}
zx_status_t Thread::Start(Thunk thunk) {
zx_status_t res;
if (!static_cast<bool>(thunk)) {
return ZX_ERR_INVALID_ARGS;
}
auto state = state_.load();
if ((state != State::INIT) && (state != State::WAITING_TO_START)) {
return ZX_ERR_BAD_STATE;
}
// If we have not created our thread yet, do so now.
if (state == State::INIT) {
barrier_.Reset();
auto trampoline = [](void* ctx) -> int { return reinterpret_cast<Thread*>(ctx)->EntryPoint(); };
int thrd_create_res = thrd_create_with_name(&thread_, trampoline, this, name_);
if (thrd_create_res != ZX_OK) {
return ZX_ERR_NO_RESOURCES;
}
auto cleanup = fbl::MakeAutoCall([this]() { Exit(); });
zx_status_t res = EnsureProfile(prio_);
if (res != ZX_OK) {
return res;
}
zx::unowned_thread thread(thrd_get_zx_handle(thread_));
res = thread->set_profile(profiles_[prio_], 0);
if (res != ZX_OK) {
return res;
}
res = thread->duplicate(ZX_RIGHT_SAME_RIGHTS, &(handle_));
if (res != ZX_OK) {
return res;
}
res = WaitForState(State::WAITING_TO_START);
if (res != ZX_OK) {
return res;
}
cleanup.cancel();
}
// Install our new thunk, let the thread go, and wait until it is running.
ZX_DEBUG_ASSERT(state_.load() == State::WAITING_TO_START);
{
fbl::AutoLock lock(&thunk_lock_);
thunk_ = std::move(thunk);
}
barrier_.Signal();
res = WaitForState(State::RUNNING);
if (res != ZX_OK) {
return res;
}
barrier_.Reset();
return ZX_OK;
}
int Thread::EntryPoint() {
state_.store(State::WAITING_TO_START);
while (true) {
barrier_.Wait();
{
fbl::AutoLock lock(&thunk_lock_);
if (static_cast<bool>(thunk_)) {
state_.store(State::RUNNING);
thunk_();
thunk_ = nullptr;
state_.store(State::WAITING_TO_START);
} else {
break;
}
}
}
state_.store(State::EXITED);
return 0;
}
void Thread::Exit() {
if (handle_.is_valid()) {
ZX_ASSERT(WaitForState(State::WAITING_TO_START) == ZX_OK);
barrier_.Signal();
thrd_join(thread_, nullptr);
}
barrier_.Reset();
handle_.reset();
state_.store(State::INIT);
}
zx_status_t Thread::WaitForState(State target_state) {
zx_status_t res;
res = WaitFor([this, target_state]() { return state_.load() == target_state; }, zx::msec(500));
if (res != ZX_OK) {
fprintf(stderr, "timed out waiting for \"%s\" to achieve state (%u)\n", name_,
static_cast<uint32_t>(target_state));
return res;
}
return ZX_OK;
}