blob: e605a3ba014969645c88dd80ee88f386b8adbc2b [file] [log] [blame]
// Copyright 2018 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 "zx-device.h"
#include <stdio.h>
#include <fbl/auto_call.h>
#include <fbl/auto_lock.h>
#include <fbl/mutex.h>
#include "composite-device.h"
#include "devfs-connection.h"
#include "devhost.h"
zx_status_t zx_device::Create(fbl::RefPtr<zx_device>* out_dev) {
*out_dev = fbl::AdoptRef(new zx_device());
return ZX_OK;
}
void zx_device::set_bind_conn(fit::callback<void(zx_status_t)> conn) {
fbl::AutoLock<fbl::Mutex> lock(&bind_conn_lock_);
bind_conn_ = std::move(conn);
}
fit::callback<void(zx_status_t)> zx_device::take_bind_conn() {
fbl::AutoLock<fbl::Mutex> lock(&bind_conn_lock_);
auto conn = std::move(bind_conn_);
bind_conn_ = nullptr;
return conn;
}
void zx_device::set_rebind_conn(fit::callback<void(zx_status_t)> conn) {
fbl::AutoLock<fbl::Mutex> lock(&rebind_conn_lock_);
rebind_conn_ = std::move(conn);
}
fit::callback<void(zx_status_t)> zx_device::take_rebind_conn() {
fbl::AutoLock<fbl::Mutex> lock(&rebind_conn_lock_);
auto conn = std::move(rebind_conn_);
rebind_conn_ = nullptr;
return conn;
}
void zx_device::PushTestCompatibilityConn(fit::callback<void(zx_status_t)> conn) {
fbl::AutoLock<fbl::Mutex> lock(&test_compatibility_conn_lock_);
test_compatibility_conn_.push_back(std::move(conn));
}
fit::callback<void(zx_status_t)> zx_device::PopTestCompatibilityConn() {
fbl::AutoLock<fbl::Mutex> lock(&test_compatibility_conn_lock_);
auto conn = std::move(test_compatibility_conn_[0]);
test_compatibility_conn_.erase(0);
return conn;
}
void zx_device::set_rebind_drv_name(const char* drv_name) {
rebind_drv_name_ = std::string(drv_name);
}
const zx_device::DevicePowerStates& zx_device::GetPowerStates() const { return power_states_; }
const zx_device::PerformanceStates& zx_device::GetPerformanceStates() const {
return performance_states_;
}
const zx_device::SystemPowerStateMapping& zx_device::GetSystemPowerStateMapping() const {
return system_power_states_mapping_;
}
zx_status_t zx_device::SetPowerStates(const device_power_state_info_t* power_states,
uint8_t count) {
if (count < ::llcpp::fuchsia::device::MIN_DEVICE_POWER_STATES ||
count > ::llcpp::fuchsia::device::MAX_DEVICE_POWER_STATES) {
return ZX_ERR_INVALID_ARGS;
}
bool visited[::llcpp::fuchsia::device::MAX_DEVICE_POWER_STATES] = {false};
for (uint8_t i = 0; i < count; i++) {
const auto& info = power_states[i];
if (info.state_id >= fbl::count_of(visited)) {
return ZX_ERR_INVALID_ARGS;
}
if (visited[info.state_id]) {
return ZX_ERR_INVALID_ARGS;
}
auto state = &power_states_[info.state_id];
state->state_id = static_cast<::llcpp::fuchsia::device::DevicePowerState>(info.state_id);
state->is_supported = true;
state->restore_latency = info.restore_latency;
state->wakeup_capable = info.wakeup_capable;
state->system_wake_state = info.system_wake_state;
visited[info.state_id] = true;
}
if (!(power_states_[static_cast<uint8_t>(
::llcpp::fuchsia::device::DevicePowerState::DEVICE_POWER_STATE_D0)]
.is_supported) ||
!(power_states_[static_cast<uint8_t>(
::llcpp::fuchsia::device::DevicePowerState::DEVICE_POWER_STATE_D3COLD)]
.is_supported)) {
return ZX_ERR_INVALID_ARGS;
}
return ZX_OK;
}
zx_status_t zx_device::SetPerformanceStates(
const device_performance_state_info_t* performance_states, uint8_t count) {
if (count < fuchsia_device_MIN_DEVICE_PERFORMANCE_STATES ||
count > fuchsia_device_MAX_DEVICE_PERFORMANCE_STATES) {
return ZX_ERR_INVALID_ARGS;
}
bool visited[fuchsia_device_MAX_DEVICE_PERFORMANCE_STATES] = {false};
for (uint8_t i = 0; i < count; i++) {
const auto& info = performance_states[i];
if (info.state_id >= fbl::count_of(visited)) {
return ZX_ERR_INVALID_ARGS;
}
if (visited[info.state_id]) {
return ZX_ERR_INVALID_ARGS;
}
::llcpp::fuchsia::device::DevicePerformanceStateInfo* state =
&(performance_states_[info.state_id]);
state->state_id = info.state_id;
state->is_supported = true;
state->restore_latency = info.restore_latency;
visited[info.state_id] = true;
}
if (!(performance_states_[fuchsia_device_DEVICE_PERFORMANCE_STATE_P0].is_supported)) {
return ZX_ERR_INVALID_ARGS;
}
return ZX_OK;
}
zx_status_t zx_device::SetSystemPowerStateMapping(const SystemPowerStateMapping& mapping) {
for (size_t i = 0; i < mapping.size(); i++) {
auto info = &mapping[i];
if (!power_states_[static_cast<uint8_t>(info->dev_state)].is_supported) {
return ZX_ERR_INVALID_ARGS;
}
if (info->wakeup_enable &&
!power_states_[static_cast<uint8_t>(info->dev_state)].wakeup_capable) {
return ZX_ERR_INVALID_ARGS;
}
// TODO(ravoorir): Validate whether the system can wake up from that state,
// when power states make more sense. Currently we cannot compare the
// system sleep power states.
system_power_states_mapping_[i] = mapping[i];
}
return ZX_OK;
}
// We must disable thread-safety analysis due to not being able to statically
// guarantee the lock holding invariant. Instead, we acquire the lock if
// it's not already being held by the current thread.
void zx_device::fbl_recycle() TA_NO_THREAD_SAFETY_ANALYSIS {
bool acq_lock = !devmgr::DM_LOCK_HELD();
if (acq_lock) {
devmgr::DM_LOCK();
}
auto unlock = fbl::MakeAutoCall([acq_lock]() TA_NO_THREAD_SAFETY_ANALYSIS {
if (acq_lock) {
devmgr::DM_UNLOCK();
}
});
if (this->flags & DEV_FLAG_INSTANCE) {
// these don't get removed, so mark dead state here
this->flags |= DEV_FLAG_DEAD;
}
if (this->flags & DEV_FLAG_BUSY) {
// this can happen if creation fails
// the caller to device_add() will free it
printf("device: %p(%s): ref=0, busy, not releasing\n", this, this->name);
return;
}
#if TRACE_ADD_REMOVE
printf("device: %p(%s): ref=0. releasing.\n", this, this->name);
#endif
if (!(this->flags & DEV_FLAG_DEAD)) {
printf("device: %p(%s): not yet dead (this is bad)\n", this, this->name);
}
if (!this->children.is_empty()) {
printf("device: %p(%s): still has children! not good.\n", this, this->name);
}
composite_.reset();
this->event.reset();
this->local_event.reset();
// Put on the defered work list for finalization
devmgr::defer_device_list.push_back(this);
// Immediately finalize if there's not an active enumerator
if (devmgr::devhost_enumerators == 0) {
devmgr::devhost_finalize();
}
}
static fbl::Mutex local_id_map_lock_;
static fbl::WAVLTree<uint64_t, fbl::RefPtr<zx_device>, zx_device::LocalIdKeyTraits,
zx_device::LocalIdNode>
local_id_map_ TA_GUARDED(local_id_map_lock_);
void zx_device::set_local_id(uint64_t id) {
// If this is the last reference, we want it to go away outside of the lock
fbl::RefPtr<zx_device> old_entry;
fbl::AutoLock guard(&local_id_map_lock_);
if (local_id_ != 0) {
old_entry = local_id_map_.erase(*this);
ZX_ASSERT(old_entry.get() == this);
}
local_id_ = id;
if (id != 0) {
local_id_map_.insert(fbl::RefPtr(this));
}
}
fbl::RefPtr<zx_device> zx_device::GetDeviceFromLocalId(uint64_t local_id) {
fbl::AutoLock guard(&local_id_map_lock_);
auto itr = local_id_map_.find(local_id);
if (itr == local_id_map_.end()) {
return nullptr;
}
return fbl::RefPtr(&*itr);
}
bool zx_device::has_composite() { return !!composite_; }
fbl::RefPtr<devmgr::CompositeDevice> zx_device::take_composite() { return std::move(composite_); }
void zx_device::set_composite(fbl::RefPtr<devmgr::CompositeDevice> composite) {
composite_ = std::move(composite);
}
bool zx_device::IsPerformanceStateSupported(uint32_t requested_state) {
if (requested_state >= fuchsia_device_MAX_DEVICE_PERFORMANCE_STATES) {
return false;
}
return performance_states_[requested_state].is_supported;
}