blob: 2682dcce38f107f61f1e7dfa351f43eb27232813 [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 "driver_host.h"
#include "log.h"
zx_device::zx_device(DriverHostContext* ctx, std::string name, zx_driver_t* drv)
: driver(drv), driver_host_context_(ctx) {
size_t len = name.length();
// TODO(teisenbe): I think this is overly aggresive, and could be changed
// to |len > ZX_DEVICE_NAME_MAX| and |len = ZX_DEVICE_NAME_MAX|.
if (len >= ZX_DEVICE_NAME_MAX) {
LOGF(WARNING, "Name too large for device %p: %s", this, name.c_str());
len = ZX_DEVICE_NAME_MAX - 1;
magic = 0;
}
memcpy(name_, name.data(), len);
name_[len] = '\0';
inspect_.emplace(driver->inspect().devices(), name_);
}
zx_status_t zx_device::Create(DriverHostContext* ctx, std::string name, zx_driver_t* driver,
fbl::RefPtr<zx_device>* out_dev) {
*out_dev = fbl::AdoptRef(new zx_device(ctx, name, driver));
(*out_dev)->vnode = fbl::MakeRefCounted<DevfsVnode>(*out_dev);
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::set_unbind_children_conn(fit::callback<void(zx_status_t)> conn) {
fbl::AutoLock<fbl::Mutex> lock(&unbind_children_conn_lock_);
unbind_children_conn_ = std::move(conn);
}
fit::callback<void(zx_status_t)> zx_device::take_unbind_children_conn() {
fbl::AutoLock<fbl::Mutex> lock(&unbind_children_conn_lock_);
auto conn = std::move(unbind_children_conn_);
unbind_children_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 >= std::size(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;
}
inspect_->set_power_states(power_states, count);
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 >= std::size(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;
}
inspect_->set_performance_states(performance_states, count);
return ZX_OK;
}
void zx_device::CloseAllConnections() {
for (auto& child : children_) {
if (child.flags_ & DEV_FLAG_INSTANCE) {
child.CloseAllConnections();
}
}
// Posted to the main event loop to synchronize with any other calls that may manipulate
// the state of this Vnode (such as dev->vnode being reset by DevfsVnode::Close or
// DriverHostContext::DriverManagerRemove)
async::PostTask(internal::ContextForApi()->loop().dispatcher(),
[dev = fbl::RefPtr<zx_device>(this)] {
if (dev->vnode) {
dev->driver_host_context_->vfs()->CloseAllConnectionsForVnode(
*dev->vnode, /*callback=*/nullptr);
}
});
}
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];
}
inspect_->set_system_power_state_mapping(mapping);
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 = !driver_host_context_->api_lock().IsHeldByCurrentThread();
if (acq_lock) {
driver_host_context_->api_lock().Acquire();
}
auto unlock = fbl::MakeAutoCall([this, acq_lock]() TA_NO_THREAD_SAFETY_ANALYSIS {
if (acq_lock) {
driver_host_context_->api_lock().Release();
}
});
if (this->flags_ & DEV_FLAG_INSTANCE) {
// these don't get removed, so mark dead state here
this->set_flag(DEV_FLAG_DEAD);
}
if (this->flags() & DEV_FLAG_BUSY) {
// this can happen if creation fails
// the caller to device_add() will free it
LOGD(WARNING, *this, "Not releasing device %p, it is busy", this);
return;
}
VLOGD(1, *this, "Releasing device %p", this);
if (!(this->flags() & DEV_FLAG_DEAD)) {
LOGD(WARNING, *this, "Releasing device %p which is not yet dead", this);
}
if (!this->children().is_empty()) {
LOGD(WARNING, *this, "Releasing device %p which still has children", this);
}
composite_.reset();
this->event.reset();
this->local_event.reset();
driver_host_context_->QueueDeviceForFinalization(this);
}
static fbl::Mutex local_id_map_lock_;
static fbl::TaggedWAVLTree<uint64_t, fbl::RefPtr<zx_device>, zx_device::LocalIdMapTag,
zx_device::LocalIdKeyTraits>
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));
}
inspect_->set_local_id(id);
// Update parent local id all inspect data of children.
// This is needed because sometimes parent local id is set after the children are created.
for (auto& child : children_) {
child.inspect().set_parent(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::Unbound() {
if (flags_ & DEV_FLAG_INSTANCE) {
return parent_->Unbound();
}
return flags_ & DEV_FLAG_UNBOUND;
}
bool zx_device::has_composite() { return !!composite_; }
fbl::RefPtr<CompositeDevice> zx_device::take_composite() { return std::move(composite_); }
void zx_device::set_composite(fbl::RefPtr<CompositeDevice> composite) {
composite_ = std::move(composite);
inspect_->set_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;
}
void zx_device::add_child(zx_device* child) {
children_.push_back(child);
if (child->flags_ & DEV_FLAG_INSTANCE) {
inspect_->increment_instance_count();
} else {
inspect_->increment_child_count();
}
}
void zx_device::remove_child(zx_device& child) {
children_.erase(child);
if (child.flags_ & DEV_FLAG_INSTANCE) {
inspect_->decrement_instance_count();
} else {
inspect_->decrement_child_count();
}
}