blob: 3aef7fe53700f401e9b46194d1a338f99d3abdca [file] [log] [blame]
// Copyright 2021 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 "src/devices/mcu/drivers/chromiumos-ec-core/chromiumos_ec_core.h"
#include <fidl/fuchsia.hardware.acpi/cpp/wire_types.h>
#include <fidl/fuchsia.hardware.google.ec/cpp/wire_types.h>
#include <fidl/fuchsia.io/cpp/markers.h>
#include <lib/ddk/debug.h>
#include <lib/fidl-async/cpp/bind.h>
#include <lib/fidl/llcpp/server.h>
#include <zircon/errors.h>
#include <chromiumos-platform-ec/ec_commands.h>
#include "src/devices/mcu/drivers/chromiumos-ec-core/chromiumos_ec_core_bind.h"
#include "src/devices/mcu/drivers/chromiumos-ec-core/subdriver.h"
namespace chromiumos_ec_core {
namespace {
// The mapping of features to numbers comes from <chromiumos-platform-ec/ec_commands.h>.
constexpr const char* kEcFeatureNames[] = {
/* [0] = */ "LIMITED",
/* [1] = */ "FLASH",
/* [2] = */ "PWM_FAN",
/* [3] = */ "PWM_KEYB",
/* [4] = */ "LIGHTBAR",
/* [5] = */ "LED",
/* [6] = */ "MOTION_SENSE",
/* [7] = */ "KEYB",
/* [8] = */ "PSTORE",
/* [9] = */ "PORT80",
/* [10] = */ "THERMAL",
/* [11] = */ "BKLIGHT_SWITCH",
/* [12] = */ "WIFI_SWITCH",
/* [13] = */ "HOST_EVENTS",
/* [14] = */ "GPIO",
/* [15] = */ "I2C",
/* [16] = */ "CHARGER",
/* [17] = */ "BATTERY",
/* [18] = */ "SMART_BATTERY",
/* [19] = */ "HANG_DETECT",
/* [20] = */ "PMU",
/* [21] = */ "SUB_MCU",
/* [22] = */ "USB_PD",
/* [23] = */ "USB_MUX",
/* [24] = */ "MOTION_SENSE_FIFO",
/* [25] = */ "VSTORE",
/* [26] = */ "USBC_SS_MUX_VIRTUAL",
/* [27] = */ "RTC",
/* [28] = */ "FINGERPRINT",
/* [29] = */ "TOUCHPAD",
/* [30] = */ "RWSIG",
/* [31] = */ "DEVICE_EVENT",
/* [32] = */ "UNIFIED_WAKE_MASKS",
/* [33] = */ "HOST_EVENT64",
/* [34] = */ "EXEC_IN_RAM",
/* [35] = */ "CEC",
/* [36] = */ "MOTION_SENSE_TIGHT_TIMESTAMPS",
/* [37] = */ "REFINED_TABLET_MODE_HYSTERESIS",
/* [38] = */ "EFS2",
/* [39] = */ "SCP",
/* [40] = */ "ISH",
/* [41] = */ "TYPEC_CMD",
/* [42] = */ "TYPEC_REQUIRE_AP_MODE_ENTRY",
/* [43] = */ "TYPEC_MUX_REQUIRE_AP_ACK",
};
} // namespace
zx_status_t ChromiumosEcCore::Bind(void* ctx, zx_device_t* dev) {
auto device = std::make_unique<ChromiumosEcCore>(dev);
zx_status_t status = device->Bind();
if (status == ZX_OK) {
// Release ownership of the device to the DDK.
__UNUSED auto unused = device.release();
}
return status;
}
zx_status_t ChromiumosEcCore::Bind() {
zx_status_t st = loop_.StartThread("cros-ec-core-fidl");
if (st != ZX_OK) {
return st;
}
// NON_BINDABLE because we will manually add children based on available features.
return DdkAdd(ddk::DeviceAddArgs("chromiumos_ec_core")
.set_inspect_vmo(inspect_.DuplicateVmo())
.set_flags(DEVICE_ADD_NON_BINDABLE));
}
fpromise::promise<void, zx_status_t> ChromiumosEcCore::GetFeatures() {
return IssueCommand(EC_CMD_GET_FEATURES, 0)
.and_then([this](CommandResult& result) mutable -> fpromise::result<void, zx_status_t> {
auto features = result.GetData<ec_response_get_features>();
if (features == nullptr) {
zxlogf(ERROR, "Did not get enough bytes for GET_FEATURE");
init_txn_->Reply(ZX_ERR_BUFFER_TOO_SMALL);
return fpromise::error(ZX_ERR_BUFFER_TOO_SMALL);
}
features_ = *features;
std::string feature_str;
for (size_t i = 0; i < std::size(kEcFeatureNames); i++) {
if (HasFeature(i)) {
if (!feature_str.empty()) {
feature_str += ", ";
}
feature_str += kEcFeatureNames[i];
}
}
core_.CreateString(kPropFeatures, feature_str, &inspect_);
return fpromise::ok();
});
}
fpromise::promise<void, zx_status_t> ChromiumosEcCore::GetVersion() {
return IssueCommand(EC_CMD_GET_VERSION, 0)
.and_then([this](CommandResult& result) mutable -> fpromise::result<void, zx_status_t> {
auto version = result.GetData<ec_response_get_version>();
if (version == nullptr) {
zxlogf(ERROR, "GET_VERSION response was too short (0x%lx, want 0x%lx)",
result.data.size(), sizeof(*version));
init_txn_->Reply(ZX_ERR_BUFFER_TOO_SMALL);
return fpromise::error(ZX_ERR_BUFFER_TOO_SMALL);
}
version_string_rw_ = std::string(version->version_string_rw);
core_.CreateString(kPropVersionRo, std::string(version->version_string_ro), &inspect_);
core_.CreateString(kPropVersionRw, std::string(version->version_string_rw), &inspect_);
core_.CreateUint(kPropCurrentImage, version->current_image, &inspect_);
return fpromise::ok();
});
}
void ChromiumosEcCore::DdkInit(ddk::InitTxn txn) {
auto ec_endpoints = fidl::CreateEndpoints<fuchsia_hardware_google_ec::Device>();
if (ec_endpoints.is_error()) {
txn.Reply(ec_endpoints.status_value());
return;
}
zx_status_t result = DdkConnectFidlProtocol(std::move(ec_endpoints->server));
if (result != ZX_OK) {
txn.Reply(result);
return;
}
auto acpi_endpoints = fidl::CreateEndpoints<fuchsia_hardware_acpi::Device>();
if (acpi_endpoints.is_error()) {
txn.Reply(acpi_endpoints.status_value());
return;
}
result = DdkConnectFidlProtocol(std::move(acpi_endpoints->server));
if (result != ZX_OK) {
txn.Reply(result);
return;
}
init_txn_ = std::move(txn);
auto promise =
BindFidlClients(std::move(ec_endpoints->client), std::move(acpi_endpoints->client))
.and_then([this]() -> fpromise::promise<void, zx_status_t> {
return fpromise::join_promises(GetFeatures(), GetVersion())
.then(
[this](fpromise::result<std::tuple<fpromise::result<void, zx_status_t>,
fpromise::result<void, zx_status_t>>,
void>& result) -> fpromise::result<void, zx_status_t> {
auto results = result.take_value();
auto get_features_result = std::get<0>(results);
if (get_features_result.is_error()) {
return get_features_result.take_error_result();
}
auto get_version_result = std::get<1>(results);
if (get_version_result.is_error()) {
return get_version_result.take_error_result();
}
// Bind child drivers.
BindSubdrivers(this);
init_txn_->Reply(ZX_OK);
return fpromise::ok();
});
})
.and_then([this]() { ScheduleInspectCommands(); })
.or_else([this](zx_status_t& status) {
zxlogf(ERROR, "IssueCommand failed: %s", zx_status_get_string(status));
init_txn_->Reply(status);
});
executor_.schedule_task(std::move(promise));
}
void ChromiumosEcCore::DdkUnbind(ddk::UnbindTxn txn) {
if (acpi_client_.is_valid()) {
acpi_client_.AsyncTeardown();
} else {
acpi_teardown_.completer.complete_ok();
}
if (ec_client_.is_valid()) {
ec_client_.AsyncTeardown();
} else {
ec_teardown_.completer.complete_ok();
}
if (notify_ref_.has_value()) {
notify_ref_->Close(ZX_ERR_CANCELED);
} else {
server_teardown_.completer.complete_ok();
}
// Once both clients have been torn down, reply.
executor_.schedule_task(
fpromise::join_promises(ec_teardown_.consumer.promise(), acpi_teardown_.consumer.promise(),
server_teardown_.consumer.promise())
.discard_result()
.then([unbind = std::move(txn)](fpromise::result<>& type) mutable { unbind.Reply(); }));
}
fpromise::promise<void, zx_status_t> ChromiumosEcCore::BindFidlClients(
fidl::ClientEnd<fuchsia_hardware_google_ec::Device> ec_client,
fidl::ClientEnd<fuchsia_hardware_acpi::Device> acpi_client) {
fpromise::bridge<void, zx_status_t> bridge;
async::PostTask(loop_.dispatcher(), [this, ec_client = std::move(ec_client),
acpi_client = std::move(acpi_client),
completer = std::move(bridge.completer)]() mutable {
auto endpoints = fidl::CreateEndpoints<fuchsia_hardware_acpi::NotifyHandler>();
if (!endpoints.is_ok()) {
completer.complete_error(endpoints.status_value());
}
notify_ref_ = fidl::BindServer<fidl::WireServer<fuchsia_hardware_acpi::NotifyHandler>>(
loop_.dispatcher(), std::move(endpoints->server), this,
[this](fidl::WireServer<fuchsia_hardware_acpi::NotifyHandler>*, fidl::UnbindInfo,
fidl::ServerEnd<fuchsia_hardware_acpi::NotifyHandler>) {
server_teardown_.completer.complete_ok();
});
ec_client_.Bind(std::move(ec_client), loop_.dispatcher(),
fidl::ObserveTeardown([this]() { ec_teardown_.completer.complete_ok(); }));
acpi_client_.Bind(std::move(acpi_client), loop_.dispatcher(),
fidl::ObserveTeardown([this]() { acpi_teardown_.completer.complete_ok(); }));
acpi_client_
->InstallNotifyHandler(fuchsia_hardware_acpi::wire::NotificationMode::kDevice,
std::move(endpoints->client))
.ThenExactlyOnce(
[completer = std::move(completer)](
fidl::WireUnownedResult<fuchsia_hardware_acpi::Device::InstallNotifyHandler>&
result) mutable {
if (!result.ok()) {
zxlogf(ERROR, "Failed to install notify handler: %s",
result.FormatDescription().data());
completer.complete_error(result.status());
} else if (result->is_error()) {
zxlogf(ERROR, "Failed to install notify handler: %u", int(result->error_value()));
completer.complete_error(ZX_ERR_INTERNAL);
} else {
completer.complete_ok();
}
});
});
return bridge.consumer.promise();
}
void ChromiumosEcCore::DdkRelease() {
loop_.Shutdown();
delete this;
}
void ChromiumosEcCore::Handle(HandleRequestView request, HandleCompleter::Sync& completer) {
std::scoped_lock lock(callback_lock_);
for (auto& callback : callbacks_) {
callback(request->value);
}
completer.Reply();
}
bool ChromiumosEcCore::HasFeature(size_t feature) {
if (feature < 32) {
return features_.flags[0] & EC_FEATURE_MASK_0(feature);
}
if (feature < 64) {
return features_.flags[1] & EC_FEATURE_MASK_1(feature);
}
zxlogf(ERROR, "Unknown feature %zu", feature);
return false;
}
void ChromiumosEcCore::ScheduleInspectCommands() {
executor_.schedule_task(
IssueCommand(EC_CMD_GET_BUILD_INFO, 0).and_then([this](CommandResult& result) {
core_.CreateString(
kPropBuildInfo,
std::string(reinterpret_cast<char*>(result.data.data()), result.data.size()),
&inspect_);
}));
executor_.schedule_task(
IssueCommand(EC_CMD_GET_CHIP_INFO, 0).and_then([this](CommandResult& result) {
auto chip_info = result.GetData<ec_response_get_chip_info>();
if (chip_info == nullptr) {
zxlogf(ERROR, "GET_CHIP_INFO response was too short");
return;
}
core_.CreateString(kPropChipVendor, chip_info->vendor, &inspect_);
core_.CreateString(kPropChipName, chip_info->name, &inspect_);
core_.CreateString(kPropChipRevision, chip_info->revision, &inspect_);
}));
executor_.schedule_task(
IssueCommand(EC_CMD_GET_BOARD_VERSION, 0).and_then([this](CommandResult& result) {
auto board_version = result.GetData<ec_response_board_version>();
if (board_version == nullptr) {
zxlogf(ERROR, "GET_BOARD_VERSION response was too short");
return;
}
core_.CreateUint(kPropBoardVersion, board_version->board_version, &inspect_);
}));
}
fpromise::promise<CommandResult, zx_status_t> ChromiumosEcCore::IssueRawCommand(
uint16_t command, uint8_t version, cpp20::span<uint8_t> input) {
fpromise::bridge<CommandResult, zx_status_t> result;
ec_client_
->RunCommand(command, version,
fidl::VectorView<uint8_t>::FromExternal(input.data(), input.size()))
.ThenExactlyOnce([command, version, completer = std::move(result.completer)](
fidl::WireUnownedResult<fuchsia_hardware_google_ec::Device::RunCommand>&
response) mutable {
if (!response.ok()) {
zxlogf(ERROR, "Failed to send FIDL for EC command %u version %u: %s", command, version,
response.FormatDescription().data());
completer.complete_error(response.status());
return;
}
if (response->is_error()) {
zxlogf(ERROR, "Failed to execute EC command %u version %u: %s", command, version,
zx_status_get_string(response->error_value()));
completer.complete_error(response->error_value());
return;
}
CommandResult ret;
auto& view = response->value()->data;
ret.data.assign(view.begin(), view.end());
ret.status = response->value()->result;
completer.complete_ok(std::move(ret));
});
return result.consumer.promise();
}
static zx_driver_ops_t chromiumos_ec_core_driver_ops = {
.version = DRIVER_OPS_VERSION,
.bind = ChromiumosEcCore::Bind,
};
} // namespace chromiumos_ec_core
// clang-format off
ZIRCON_DRIVER(chromiumos-ec-core, chromiumos_ec_core::chromiumos_ec_core_driver_ops, "zircon", "0.1");