blob: 1ebafc5574d51420248b5e3ebe15f33ba40d6b21 [file] [log] [blame]
// Copyright 2023 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/block/drivers/ufs/device_manager.h"
#include <lib/driver/logging/cpp/logger.h>
#include <lib/fit/defer.h>
#include <lib/trace/event.h>
#include <lib/zx/clock.h>
#include "src/devices/block/drivers/ufs/registers.h"
#include "src/devices/block/drivers/ufs/transfer_request_processor.h"
#include "src/devices/block/drivers/ufs/ufs.h"
#include "src/devices/block/drivers/ufs/upiu/upiu_transactions.h"
#include "transfer_request_processor.h"
namespace ufs {
zx::result<std::unique_ptr<DeviceManager>> DeviceManager::Create(
Ufs &controller, TransferRequestProcessor &transfer_request_processor,
InspectProperties &properties) {
fbl::AllocChecker ac;
auto device_manager = fbl::make_unique_checked<DeviceManager>(
&ac, controller, transfer_request_processor, properties);
if (!ac.check()) {
fdf::error("Failed to allocate device manager.");
return zx::error(ZX_ERR_NO_MEMORY);
}
return zx::ok(std::move(device_manager));
}
zx::result<> DeviceManager::SendLinkStartUp() {
DmeLinkStartUpUicCommand link_startup_command(controller_);
if (zx::result<std::optional<uint32_t>> result = link_startup_command.SendCommand();
result.is_error()) {
fdf::error("Failed to startup UFS link: {}", result);
return result.take_error();
}
std::lock_guard<std::mutex> lock(power_lock_);
current_link_state_ = LinkState::kActive;
return zx::ok();
}
zx::result<> DeviceManager::DeviceInit() {
zx::time device_init_start_time = zx::clock::get_monotonic();
if (zx::result<> result = SetFlag(Flags::fDeviceInit); result.is_error()) {
return result.take_error();
}
zx::time device_init_time_out = device_init_start_time + zx::usec(kDeviceInitTimeoutUs);
while (true) {
zx::result<uint8_t> flag = ReadFlag(Flags::fDeviceInit);
if (flag.is_error()) {
return flag.take_error();
}
if (!flag.value())
break;
if (zx::clock::get_monotonic() > device_init_time_out) {
fdf::error("Wait for fDeviceInit timed out");
return zx::error(ZX_ERR_TIMED_OUT);
}
usleep(10000);
}
return zx::ok();
}
zx::result<> DeviceManager::GetControllerDescriptor() {
auto device_descriptor = ReadDescriptor<DeviceDescriptor>(DescriptorType::kDevice);
if (device_descriptor.is_error()) {
return device_descriptor.take_error();
}
device_descriptor_ = device_descriptor.value();
// The field definitions for VersionReg and wSpecVersion are the same.
// wSpecVersion use big-endian byte ordering.
auto version = VersionReg::Get().FromValue(betoh16(device_descriptor_.wSpecVersion));
fdf::info("UFS device version {}.{}{}", version.major_version_number(),
version.minor_version_number(), version.version_suffix());
fdf::info("{} enabled LUNs found", device_descriptor_.bNumberLU);
auto geometry_descriptor = ReadDescriptor<GeometryDescriptor>(DescriptorType::kGeometry);
if (geometry_descriptor.is_error()) {
return geometry_descriptor.take_error();
}
geometry_descriptor_ = geometry_descriptor.value();
// TODO(https://fxbug.dev/42075643): We need to functionalize the code to get max_lun_count_.
if (geometry_descriptor_.bMaxNumberLU == 0) {
max_lun_count_ = 8;
} else if (geometry_descriptor_.bMaxNumberLU == 1) {
max_lun_count_ = 32;
} else {
fdf::error("Invalid Geometry Descriptor bMaxNumberLU value={}",
geometry_descriptor_.bMaxNumberLU);
return zx::error(ZX_ERR_INVALID_ARGS);
}
// The kDeviceDensityUnit is defined in the spec as 512.
// qTotalRawDeviceCapacity use big-endian byte ordering.
constexpr uint32_t kDeviceDensityUnit = 512;
fdf::info("UFS device total size is {} bytes",
betoh64(geometry_descriptor_.qTotalRawDeviceCapacity) * kDeviceDensityUnit);
return zx::ok();
}
zx::result<uint32_t> DeviceManager::ReadAttribute(Attributes attribute, uint8_t index) {
ReadAttributeUpiu read_attribute_upiu(attribute, index);
auto query_response = req_processor_.SendQueryRequestUpiu(read_attribute_upiu);
if (query_response.is_error()) {
return query_response.take_error();
}
return zx::ok(query_response->GetResponse<AttributeResponseUpiu>().GetAttribute());
}
zx::result<> DeviceManager::WriteAttribute(Attributes attribute, uint32_t value, uint8_t index) {
WriteAttributeUpiu write_attribute_upiu(attribute, value, index);
auto query_response = req_processor_.SendQueryRequestUpiu(write_attribute_upiu);
if (query_response.is_error()) {
return query_response.take_error();
}
return zx::ok();
}
template <typename DescriptorReturnType>
zx::result<DescriptorReturnType> DeviceManager::ReadDescriptor(DescriptorType descriptor,
uint8_t index) {
ReadDescriptorUpiu read_descriptor_upiu(descriptor, index);
auto query_response = req_processor_.SendQueryRequestUpiu(read_descriptor_upiu);
if (query_response.is_error()) {
return query_response.take_error();
}
return zx::ok(
query_response->GetResponse<DescriptorResponseUpiu>().GetDescriptor<DescriptorReturnType>());
}
zx::result<uint8_t> DeviceManager::ReadFlag(Flags type) {
ReadFlagUpiu read_flag_upiu(type);
auto query_response = req_processor_.SendQueryRequestUpiu(read_flag_upiu);
if (query_response.is_error()) {
return query_response.take_error();
}
return zx::ok(query_response->GetResponse<FlagResponseUpiu>().GetFlag());
}
zx::result<> DeviceManager::SetFlag(Flags type) {
SetFlagUpiu set_flag_upiu(type);
auto query_response = req_processor_.SendQueryRequestUpiu(set_flag_upiu);
if (query_response.is_error()) {
return query_response.take_error();
}
return zx::ok();
}
zx::result<> DeviceManager::ClearFlag(Flags type) {
ClearFlagUpiu claer_flag_upiu(type);
auto query_response = req_processor_.SendQueryRequestUpiu(claer_flag_upiu);
if (query_response.is_error()) {
return query_response.take_error();
}
return zx::ok();
}
zx::result<uint32_t> DeviceManager::DmeGet(uint16_t mib_attribute) {
DmeGetUicCommand dme_get_command(controller_, mib_attribute, 0);
auto value = dme_get_command.SendCommand();
if (value.is_error()) {
return value.take_error();
}
if (!value.value().has_value()) {
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
return zx::ok(value.value().value());
}
zx::result<uint32_t> DeviceManager::DmePeerGet(uint16_t mib_attribute) {
DmePeerGetUicCommand dme_peer_get_command(controller_, mib_attribute, 0);
auto value = dme_peer_get_command.SendCommand();
if (value.is_error()) {
return value.take_error();
}
if (!value.value().has_value()) {
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
return zx::ok(value.value().value());
}
zx::result<> DeviceManager::DmeSet(uint16_t mib_attribute, uint32_t value) {
DmeSetUicCommand dme_set_command(controller_, mib_attribute, 0, 0, value);
if (auto result = dme_set_command.SendCommand(); result.is_error()) {
return result.take_error();
}
return zx::ok();
}
zx::result<uint32_t> DeviceManager::GetBootLunEnabled() {
// Read bBootLunEn to confirm device interface is ok.
zx::result<uint32_t> boot_lun_enabled = ReadAttribute(Attributes::bBootLunEn);
if (boot_lun_enabled.is_error()) {
return boot_lun_enabled.take_error();
}
fdf::debug("bBootLunEn 0x{:x}", boot_lun_enabled.value());
return zx::ok(boot_lun_enabled.value());
}
zx::result<UnitDescriptor> DeviceManager::ReadUnitDescriptor(uint8_t lun) {
return ReadDescriptor<UnitDescriptor>(DescriptorType::kUnit, lun);
}
zx::result<> DeviceManager::SetExceptionEventControl(ExceptionEventControl control) {
if (exception_event_control_.value == control.value) {
return zx::ok();
}
if (auto result = WriteAttribute(Attributes::wExceptionEventControl, control.value);
result.is_error()) {
return result.take_error();
}
exception_event_control_.value = control.value;
return zx::ok();
}
zx::result<ExceptionEventStatus> DeviceManager::GetExceptionEventStatus() {
auto ee_status_attribute = ReadAttribute(Attributes::wExceptionEventStatus);
if (ee_status_attribute.is_error()) {
return ee_status_attribute.take_error();
}
ExceptionEventStatus ee_status = {safemath::checked_cast<uint16_t>(ee_status_attribute.value())};
return zx::ok(ee_status);
}
zx::result<> DeviceManager::PostExceptionEventsTask() {
zx_status_t status = async::PostTask(controller_.exception_event_dispatcher().async_dispatcher(),
[this] { HandleExceptionEvents(); });
if (status != ZX_OK) {
fdf::error("Failed to post Exception Event task: {}", zx_status_get_string(status));
return zx::error(status);
}
return zx::ok();
}
void DeviceManager::HandleExceptionEvents() {
zx::result<ExceptionEventStatus> ee_status = GetExceptionEventStatus();
if (ee_status.is_error()) {
fdf::error("Failed to get Exception Event Status");
}
if (ee_status->urgent_bkops()) {
if (auto result = HandleBackgroundOpEvent(); result.is_error()) {
fdf::error("Failed to handle Background Operations Event");
}
}
if (ee_status->too_high_temp() || ee_status->too_low_temp()) {
// TODO(b/42075643): Implement temp exception handler.
fdf::info("A temperature exception has occurred");
}
}
zx::result<> DeviceManager::HandleBackgroundOpEvent() {
zx::result<BackgroundOpStatus> bkop_status = GetBackgroundOpStatus();
if (bkop_status.is_error()) {
return bkop_status.take_error();
}
if (bkop_status.value() >= urgent_bkop_threshold_) {
if (zx::result result = EnableBackgroundOp(); result.is_error()) {
return result.take_error();
}
}
return zx::ok();
}
zx::result<> DeviceManager::ConfigureWriteProtect(inspect::Node &wp_node) {
zx::result<uint8_t> flag = ReadFlag(Flags::fPowerOnWPEn);
if (flag.is_error()) {
return flag.take_error();
}
is_power_on_write_protect_enabled_ = flag.value();
properties_.is_power_on_write_protect_enabled =
wp_node.CreateBool("is_power_on_write_protect_enabled", is_power_on_write_protect_enabled_);
properties_.logical_lun_power_on_write_protect =
wp_node.CreateBool("logical_lun_power_on_write_protect", logical_lun_power_on_write_protect_);
return zx::ok();
}
void DeviceManager::SetLogicalLunPowerOnWriteProtect(bool value) {
logical_lun_power_on_write_protect_ = value;
properties_.logical_lun_power_on_write_protect.Set(value);
}
zx::result<> DeviceManager::ConfigureBackgroundOp(inspect::Node &bkop_node) {
zx::result<uint8_t> flag = ReadFlag(Flags::fBackgroundOpsEn);
if (flag.is_error()) {
return flag.take_error();
}
is_background_op_enabled_ = flag.value();
properties_.is_background_op_enabled =
bkop_node.CreateBool("is_background_op_enabled", is_background_op_enabled_);
// Currently we allow background operations in the active state. This may have a performance
// penalty.
// TODO(b/42075643): We should only perform background operations in the power suspended state.
if (zx::result<> result = EnableBackgroundOp(); result.is_error()) {
return result.take_error();
}
// For stable performance, set threshold of the Background Operation to `Required, not critical`.
urgent_bkop_threshold_ = BackgroundOpStatus::kRequiredNotCritical;
return zx::ok();
}
zx::result<> DeviceManager::EnableBackgroundOp() {
if (is_background_op_enabled_) {
return zx::ok();
}
if (zx::result<> result = SetFlag(Flags::fBackgroundOpsEn); result.is_error()) {
return result.take_error();
}
is_background_op_enabled_ = true;
properties_.is_background_op_enabled.Set(is_background_op_enabled_);
// No need for urgent background operation exceptions.
ExceptionEventControl control = {exception_event_control_.value};
control.set_urgent_bkops_en(false);
if (auto result = SetExceptionEventControl(control); result.is_error()) {
result.take_error();
}
return zx::ok();
}
zx::result<> DeviceManager::DisableBackgroundOp() {
if (!is_background_op_enabled_) {
return zx::ok();
}
// Need urgent background operation exceptions.
ExceptionEventControl control = {exception_event_control_.value};
control.set_urgent_bkops_en(true);
if (auto result = SetExceptionEventControl(control); result.is_error()) {
result.take_error();
}
if (zx::result<> result = ClearFlag(Flags::fBackgroundOpsEn); result.is_error()) {
return result.take_error();
}
is_background_op_enabled_ = false;
properties_.is_background_op_enabled.Set(is_background_op_enabled_);
return zx::ok();
}
zx::result<BackgroundOpStatus> DeviceManager::GetBackgroundOpStatus() {
zx::result<uint32_t> bkop_status_attribute = ReadAttribute(Attributes::bBackgroundOpStatus);
if (bkop_status_attribute.is_error()) {
return bkop_status_attribute.take_error();
}
if (bkop_status_attribute.value() > BackgroundOpStatus::kCritical) {
fdf::error("Invalid BackgroundOpStatus: {}", bkop_status_attribute.value());
return zx::error(ZX_ERR_BAD_STATE);
}
BackgroundOpStatus background_op_status =
static_cast<BackgroundOpStatus>(bkop_status_attribute.value());
return zx::ok(background_op_status);
}
zx::result<> DeviceManager::ConfigureWriteBooster(inspect::Node &wb_node) {
// Copy to access the unaligned value.
const ExtendedUfsFeaturesSupport kExtendedUfsFeaturesSupport{
betoh32(device_descriptor_.dExtendedUfsFeaturesSupport)};
if (!kExtendedUfsFeaturesSupport.writebooster_support()) {
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
if (zx::result<> result = EnableWriteBooster(wb_node); result.is_error()) {
fdf::error("Failed to enable WriteBooster: {}", result);
return result.take_error();
}
auto disable_write_booster = fit::defer([&] {
if (is_write_booster_enabled_) {
if (zx::result<> result = DisableWriteBooster(); result.is_error()) {
fdf::error("Failed to disable WriteBooster: {}", result);
} else {
fdf::warn("WriteBooster is disabled");
}
}
});
// Get WriteBooster buffer parameters.
write_booster_buffer_type_ =
static_cast<WriteBoosterBufferType>(device_descriptor_.bWriteBoosterBufferType);
properties_.write_booster_buffer_type = wb_node.CreateUint(
"write_booster_buffer_type", static_cast<uint8_t>(write_booster_buffer_type_));
user_space_configuration_option_ = static_cast<UserSpaceConfigurationOption>(
device_descriptor_.bWriteBoosterBufferPreserveUserSpaceEn);
properties_.user_space_configuration_option = wb_node.CreateUint(
"user_space_configuration_option", static_cast<uint8_t>(user_space_configuration_option_));
// Find the size of the write buffer.
uint32_t alloc_units = 0;
if (write_booster_buffer_type_ == WriteBoosterBufferType::kSharedBuffer) {
alloc_units = betoh32(device_descriptor_.dNumSharedWriteBoosterBufferAllocUnits);
} else if (write_booster_buffer_type_ == WriteBoosterBufferType::kLuDedicatedBuffer) {
for (uint8_t lun = 0; lun < max_lun_count_; ++lun) {
auto unit_descriptor = ReadDescriptor<UnitDescriptor>(DescriptorType::kUnit, lun);
if (unit_descriptor.is_error()) {
continue;
}
alloc_units = betoh32(unit_descriptor->dLUNumWriteBoosterBufferAllocUnits);
if (alloc_units > 0) {
// Found a dedicated buffer from LU.
write_booster_dedicated_lu_ = lun;
properties_.write_booster_dedicated_lu =
wb_node.CreateUint("write_booster_dedicated_lu", write_booster_dedicated_lu_);
break;
}
}
} else {
fdf::warn("Not supported WriteBooster buffer type: 0x{:x}",
static_cast<uint8_t>(write_booster_buffer_type_));
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
if (alloc_units == 0) {
// Unable to enable WriteBooster due to lack of resources.
fdf::warn("The WriteBooster buffer size is zero.");
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
uint32_t write_booster_buffer_size_in_bytes =
alloc_units * static_cast<uint32_t>(geometry_descriptor_.bAllocationUnitSize) *
(betoh32(geometry_descriptor_.dSegmentSize)) * kSectorSize;
properties_.write_booster_buffer_size_in_bytes =
wb_node.CreateUint("write_booster_buffer_size_in_bytes", write_booster_buffer_size_in_bytes);
zx::result<bool> result = IsWriteBoosterBufferLifeTimeLeft();
if (result.is_error()) {
fdf::error("Failed to IsWriteBoosterBufferLifeTimeLeft(): {}", result);
return result.take_error();
}
if (!result.value()) {
// Unable to enable WriteBooster due to lack of resources.
fdf::warn("Exceeded its maximum estimated WriteBooster Buffer life time");
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
// TODO(https://fxbug.dev/42075643): Need to handle WRITEBOOSTER_FLUSH_NEEDED exception case.
disable_write_booster.cancel();
fdf::info("WriteBooster is enabled");
return zx::ok();
}
zx::result<bool> DeviceManager::IsWriteBoosterBufferLifeTimeLeft() {
uint8_t buffer_lun = write_booster_buffer_type_ == WriteBoosterBufferType::kLuDedicatedBuffer
? write_booster_dedicated_lu_
: 0;
zx::result<uint32_t> life_time = ReadAttribute(Attributes::bWBBufferLifeTimeEst, buffer_lun);
if (life_time.is_error()) {
return life_time.take_error();
}
if (life_time.value()) {
if (life_time == kExceededWriteBoosterBufferLifeTime) {
return zx::ok(false);
}
}
return zx::ok(true);
}
zx::result<> DeviceManager::EnableWriteBooster(inspect::Node &wb_node) {
// Enable WriteBooster.
if (zx::result<> result = SetFlag(Flags::fWriteBoosterEn); result.is_error()) {
return result.take_error();
}
is_write_booster_enabled_ = true;
properties_.is_write_booster_enabled =
wb_node.CreateBool("is_write_booster_enabled", is_write_booster_enabled_);
// Enable WriteBooster buffer flush during hibernate.
if (zx::result<> result = SetFlag(Flags::fWBBufferFlushDuringHibernate); result.is_error()) {
return result.take_error();
}
properties_.writebooster_buffer_flush_during_hibernate =
wb_node.CreateBool("writebooster_buffer_flush_during_hibernate", true);
// Enable WriteBooster buffer flush.
// TODO(https://fxbug.dev/42075643): For Samsung Exynos, ignore this flush behaviour due to the
// quirk of not supporting manual flush.
if (zx::result<> result = SetFlag(Flags::fWBBufferFlushEn); result.is_error()) {
return result.take_error();
}
is_write_booster_flush_enabled_ = true;
properties_.writebooster_buffer_flush_enabled =
wb_node.CreateBool("writebooster_buffer_flush_enabled", is_write_booster_flush_enabled_);
return zx::ok();
}
zx::result<> DeviceManager::DisableWriteBooster() {
if (is_write_booster_flush_enabled_) {
// Disable WriteBooster buffer flush.
if (zx::result<> result = ClearFlag(Flags::fWBBufferFlushEn); result.is_error()) {
return result.take_error();
}
is_write_booster_flush_enabled_ = false;
properties_.writebooster_buffer_flush_enabled.Set(is_write_booster_flush_enabled_);
}
// Disable WriteBooster buffer flush during hibernate.
if (zx::result<> result = ClearFlag(Flags::fWBBufferFlushDuringHibernate); result.is_error()) {
return result.take_error();
}
properties_.writebooster_buffer_flush_during_hibernate.Set(false);
// Disable WriteBooster.
if (zx::result<> result = ClearFlag(Flags::fWriteBoosterEn); result.is_error()) {
return result.take_error();
}
is_write_booster_enabled_ = false;
properties_.is_write_booster_enabled.Set(is_write_booster_enabled_);
return zx::ok();
}
zx::result<bool> DeviceManager::NeedWriteBoosterBufferFlush() {
if (!is_write_booster_enabled_) {
return zx::ok(false);
}
auto result = IsWriteBoosterBufferLifeTimeLeft();
if (result.is_error()) {
return result.take_error();
}
if (!result.value()) {
if (auto result = DisableWriteBooster(); result.is_error()) {
fdf::error("Failed to disable WriteBooster: {}", result);
return result.take_error();
}
return zx::ok(false);
}
uint8_t buffer_lun = write_booster_buffer_type_ == WriteBoosterBufferType::kLuDedicatedBuffer
? write_booster_dedicated_lu_
: 0;
auto available_buffer_size = ReadAttribute(Attributes::bAvailableWBBufferSize, buffer_lun);
if (available_buffer_size.is_error()) {
return available_buffer_size.take_error();
}
switch (user_space_configuration_option_) {
case UserSpaceConfigurationOption::kUserSpaceReduction: {
// In kUserSpaceReduction mode, flush should be performed when 10% or less of the buffer is
// left.
constexpr uint32_t k10PercentBufferRemains = 0x01;
return zx::ok(available_buffer_size.value() <= k10PercentBufferRemains);
}
case UserSpaceConfigurationOption::kPreserveUserSpace: {
// In kPreserveUserSpace mode, flush should be performed when the current buffer is greater
// than 0 and the available buffer below write_booster_flush_threshold_ is left.
auto current_buffer_size = ReadAttribute(Attributes::dCurrentWBBufferSize, buffer_lun);
if (current_buffer_size.is_error()) {
return current_buffer_size.take_error();
}
if (!current_buffer_size.value()) {
return zx::ok(false);
}
return zx::ok(available_buffer_size.value() < write_booster_flush_threshold_);
}
default:
return zx::error(ZX_ERR_INVALID_ARGS);
}
}
zx::result<> DeviceManager::InitReferenceClock(inspect::Node &controller_node) {
// Intel UFSHCI reference clock = 19.2MHz
constexpr AttributeReferenceClock reference_clock = AttributeReferenceClock::k19_2MHz;
if (auto result = WriteAttribute(Attributes::bRefClkFreq, reference_clock); result.is_error()) {
return result.take_error();
}
std::string reference_clock_string;
switch (reference_clock) {
case AttributeReferenceClock::k19_2MHz:
reference_clock_string = "19.2 MHz";
break;
case AttributeReferenceClock::k26MHz:
reference_clock_string = "26 MHz";
break;
case AttributeReferenceClock::k38_4MHz:
reference_clock_string = "38.4 MHz";
break;
case AttributeReferenceClock::kObsolete:
reference_clock_string = "52 MHz (Obsolete))";
break;
default:
return zx::error(ZX_ERR_INVALID_ARGS);
}
properties_.reference_clock =
controller_node.CreateString("reference_clock", reference_clock_string);
return zx::ok();
}
zx::result<> DeviceManager::InitUniproAttributes(inspect::Node &unipro_node) {
// UniPro Version
// 7~15 = Above 2.0, 6 = 2.0, 5 = 1.8, 4 = 1.61, 3 = 1.6, 2 = 1.41, 1 = 1.40, 0 = Reserved
zx::result<uint32_t> remote_version = DmeGet(PA_RemoteVerInfo);
if (remote_version.is_error()) {
return remote_version.take_error();
}
zx::result<uint32_t> local_version = DmeGet(PA_LocalVerInfo);
if (local_version.is_error()) {
return local_version.take_error();
}
// UniPro automatically sets timing information such as PA_TActivate through the
// PACP_CAP_EXT1_ind command during Link Startup operation.
zx::result<uint32_t> host_t_activate = DmeGet(PA_TActivate);
if (host_t_activate.is_error()) {
return host_t_activate.take_error();
}
// Intel Lake-field UFSHCI has a quirk. We need to add 200us to the PEER's PA_TActivate.
DmePeerSetUicCommand dme_peer_set_t_activate(controller_, PA_TActivate, 0, 0,
host_t_activate.value() + 2);
if (auto result = dme_peer_set_t_activate.SendCommand(); result.is_error()) {
return result.take_error();
}
zx::result<uint32_t> device_t_activate = DmePeerGet(PA_TActivate);
if (device_t_activate.is_error()) {
return device_t_activate.take_error();
}
// PA_Granularity = 100us (1=1us, 2=4us, 3=8us, 4=16us, 5=32us, 6=100us)
zx::result<uint32_t> host_granularity = DmeGet(PA_Granularity);
if (host_granularity.is_error()) {
return host_granularity.take_error();
}
zx::result<uint32_t> device_granularity = DmePeerGet(PA_Granularity);
if (device_granularity.is_error()) {
return device_granularity.take_error();
}
properties_.remote_version = unipro_node.CreateUint("remote_version", remote_version.value());
properties_.local_version = unipro_node.CreateUint("local_version", local_version.value());
properties_.host_t_activate = unipro_node.CreateUint("host_t_activate", host_t_activate.value());
properties_.device_t_activate =
unipro_node.CreateUint("device_t_activate", device_t_activate.value());
properties_.host_granularity =
unipro_node.CreateUint("host_granularity", host_granularity.value());
properties_.device_granularity =
unipro_node.CreateUint("device_granularity", device_granularity.value());
return zx::ok();
}
zx::result<> DeviceManager::InitUicPowerMode(inspect::Node &unipro_node) {
if (controller_.skip_high_speed_gear_quirk()) {
return zx::ok();
}
if (zx::result<> result = controller_.Notify(NotifyEvent::kPrePowerModeChange, 0);
result.is_error()) {
return result.take_error();
}
// Update lanes with available TX/RX lanes.
zx::result<uint32_t> tx_lanes = DmeGet(PA_AvailTxDataLanes);
if (tx_lanes.is_error()) {
return tx_lanes.take_error();
}
zx::result<uint32_t> rx_lanes = DmeGet(PA_AvailRxDataLanes);
if (rx_lanes.is_error()) {
return rx_lanes.take_error();
}
// Get max HS-GEAR.
zx::result<uint32_t> max_rx_hs_gear = DmeGet(PA_MaxRxHSGear);
if (max_rx_hs_gear.is_error()) {
return max_rx_hs_gear.take_error();
}
// Set data lanes.
if (zx::result<> result = DmeSet(PA_ActiveTxDataLanes, tx_lanes.value()); result.is_error()) {
return result.take_error();
}
if (zx::result<> result = DmeSet(PA_ActiveRxDataLanes, rx_lanes.value()); result.is_error()) {
return result.take_error();
}
// Set HS-GEAR to max gear.
if (zx::result<> result = DmeSet(PA_TxGear, max_rx_hs_gear.value()); result.is_error()) {
return result.take_error();
}
if (zx::result<> result = DmeSet(PA_RxGear, max_rx_hs_gear.value()); result.is_error()) {
return result.take_error();
}
// Set termination.
// HS-MODE = ON / LS-MODE = OFF
if (zx::result<> result = DmeSet(PA_TxTermination, true); result.is_error()) {
return result.take_error();
}
// HS-MODE = ON / LS-MODE = OFF
if (zx::result<> result = DmeSet(PA_RxTermination, true); result.is_error()) {
return result.take_error();
}
// Set HSSerise (A = 1, B = 2)
constexpr uint32_t kHsSeries = 2;
if (zx::result<> result = DmeSet(PA_HSSeries, kHsSeries); result.is_error()) {
return result.take_error();
}
// Set Timeout values.
if (zx::result<> result = DmeSet(PA_PWRModeUserData0, DL_FC0ProtectionTimeOutVal_Default);
result.is_error()) {
return result.take_error();
}
if (zx::result<> result = DmeSet(PA_PWRModeUserData1, DL_TC0ReplayTimeOutVal_Default);
result.is_error()) {
return result.take_error();
}
if (zx::result<> result = DmeSet(PA_PWRModeUserData2, DL_AFC0ReqTimeOutVal_Default);
result.is_error()) {
return result.take_error();
}
if (zx::result<> result = DmeSet(PA_PWRModeUserData3, DL_FC0ProtectionTimeOutVal_Default);
result.is_error()) {
return result.take_error();
}
if (zx::result<> result = DmeSet(PA_PWRModeUserData4, DL_TC0ReplayTimeOutVal_Default);
result.is_error()) {
return result.take_error();
}
if (zx::result<> result = DmeSet(PA_PWRModeUserData5, DL_AFC0ReqTimeOutVal_Default);
result.is_error()) {
return result.take_error();
}
if (zx::result<> result =
DmeSet(DME_LocalFC0ProtectionTimeOutVal, DL_FC0ProtectionTimeOutVal_Default);
result.is_error()) {
return result.take_error();
}
if (zx::result<> result = DmeSet(DME_LocalTC0ReplayTimeOutVal, DL_TC0ReplayTimeOutVal_Default);
result.is_error()) {
return result.take_error();
}
if (zx::result<> result = DmeSet(DME_LocalAFC0ReqTimeOutVal, DL_AFC0ReqTimeOutVal_Default);
result.is_error()) {
return result.take_error();
}
// Set TX/RX PWRMode.
// TX[3:0], RX[7:4]
// Fast_Mode=1, Slow_Mode=2, FastAuto_Mode=4, SlowAuto_Mode=5
constexpr uint32_t kFastMode = 1;
constexpr uint32_t kRxBitShift = 4;
constexpr uint32_t kPwrMode = kFastMode << kRxBitShift | kFastMode;
if (zx::result<> result = DmeSet(PA_PWRMode, kPwrMode); result.is_error()) {
return result.take_error();
}
// Wait for power mode changed.
auto wait_for_completion = [&]() -> bool {
return InterruptStatusReg::Get().ReadFrom(&controller_.GetMmio()).uic_power_mode_status();
};
fbl::String timeout_message = "Timeout waiting for Power Mode Change";
if (zx_status_t status = controller_.WaitWithTimeout(
wait_for_completion, zx::usec(kDeviceInitTimeoutUs), timeout_message);
status != ZX_OK) {
return zx::error(status);
}
// Clear 'Power Mode completion status'
InterruptStatusReg::Get().FromValue(0).set_uic_power_mode_status(true).WriteTo(
&controller_.GetMmio());
HostControllerStatusReg::PowerModeStatus power_mode_status =
HostControllerStatusReg::Get()
.ReadFrom(&controller_.GetMmio())
.uic_power_mode_change_request_status();
if (power_mode_status != HostControllerStatusReg::PowerModeStatus::kPowerLocal) {
fdf::error("Failed to change power mode: power_mode_status = 0x{:x}",
static_cast<uint32_t>(power_mode_status));
return zx::error(ZX_ERR_BAD_STATE);
}
if (zx::result<> result = controller_.Notify(NotifyEvent::kPostPowerModeChange, 0);
result.is_error()) {
return result.take_error();
}
// Intel Lake-field UFSHCI has a quirk. We need to wait 1250us and clear dme error.
usleep(1250);
// Test with dme_peer_get to make sure there are no errors.
zx::result<uint32_t> device_granularity = DmePeerGet(PA_Granularity);
if (device_granularity.is_error()) {
return device_granularity.take_error();
}
properties_.pa_active_tx_data_lanes =
unipro_node.CreateUint("PA_ActiveTxDataLanes", tx_lanes.value());
properties_.pa_active_rx_data_lanes =
unipro_node.CreateUint("PA_ActiveRxDataLanes", rx_lanes.value());
properties_.pa_max_rx_hs_gear = unipro_node.CreateUint("PA_MaxRxHSGear", max_rx_hs_gear.value());
properties_.pa_tx_gear = unipro_node.CreateUint("PA_TxGear", max_rx_hs_gear.value());
properties_.pa_rx_gear = unipro_node.CreateUint("PA_RxGear", max_rx_hs_gear.value());
properties_.tx_termination = unipro_node.CreateBool("tx_termination", true);
properties_.rx_termination = unipro_node.CreateBool("rx_termination", true);
properties_.pa_hs_series = unipro_node.CreateUint("PA_HSSeries", kHsSeries);
properties_.power_mode = unipro_node.CreateUint("power_mode", kPwrMode);
return zx::ok();
}
zx::result<> DeviceManager::SetPowerCondition(scsi::PowerCondition target_power_condition) {
if (current_power_condition_ == target_power_condition) {
return zx::ok();
}
auto scsi_lun = Ufs::TranslateUfsLunToScsiLun(static_cast<uint8_t>(WellKnownLuns::kUfsDevice));
if (scsi_lun.is_error()) {
return scsi_lun.take_error();
}
// Send START STOP UNIT to change power mode
zx_status_t status = controller_.StartStopUnit(kPlaceholderTarget, scsi_lun.value(),
/*immed*/ false, target_power_condition);
if (status != ZX_OK) {
fdf::error("Failed to send START STOP UNIT SCSI command: {}", zx_status_get_string(status));
return zx::error(status);
}
current_power_condition_ = target_power_condition;
return zx::ok();
}
zx::result<> DeviceManager::SuspendPower() {
const UfsPowerMode target_power_mode = UfsPowerMode::kSleep;
const scsi::PowerCondition target_power_condition = power_mode_map_[target_power_mode].first;
const LinkState target_link_state = power_mode_map_[target_power_mode].second;
std::lock_guard<std::mutex> lock(power_lock_);
if (current_power_mode_ == target_power_mode &&
current_power_condition_ == target_power_condition &&
current_link_state_ == target_link_state) {
return zx::ok();
}
if (current_power_mode_ != UfsPowerMode::kActive ||
current_power_condition_ != scsi::PowerCondition::kActive ||
current_link_state_ != LinkState::kActive) {
return zx::error(ZX_ERR_BAD_STATE);
}
// TODO(b/42075643): We need to wait for the in flight I/O.
// TODO(b/42075643): If we turn off the power(vcc off) while LogicalLunPowerOnWriteProtect is
// enabled, we will lose write protection. To avoid this, power should be maintained when write
// protect is enabled. This requires more fine-grained power control(VCC, VCCQ, VCCQ2).
// TODO(b/42075643): In the case of power suspended state, we can apply a policy to perform
// background operations in the suspended state. Currently, background operations are not
// performed when suspend.
if (zx::result<> result = DisableBackgroundOp(); result.is_error()) {
return result.take_error();
}
// We should check if WriteBooster Flush is needed. If so, we should postpone changing the power
// mode.
zx::result<bool> result = NeedWriteBoosterBufferFlush();
if (result.is_error()) {
return result.take_error();
}
if (result.value()) {
// TODO(b/42075643): We need to keep the power mode active until the
// Writebooster flush is complete.
fdf::warn("WriteBooster buffer flush is needed");
return zx::ok();
}
if (zx::result<> result = controller_.Notify(NotifyEvent::kPrePowerModeChange, 0);
result.is_error()) {
return result.take_error();
}
if (zx::result<> result = SetPowerCondition(target_power_condition); result.is_error()) {
return result.take_error();
}
DmeHibernateEnterCommand dme_hibernate_enter_command(controller_);
if (auto result = dme_hibernate_enter_command.SendCommand(); result.is_error()) {
// TODO(b/42075643): Link has a problem and needs to perform error recovery.
return result.take_error();
}
current_link_state_ = target_link_state;
if (zx::result<> result = controller_.Notify(NotifyEvent::kPostPowerModeChange, 0);
result.is_error()) {
return result.take_error();
}
current_power_mode_ = target_power_mode;
properties_.power_suspended.Set(true);
fdf::info("Power suspended.");
return zx::ok();
}
zx::result<> DeviceManager::ResumePower() {
const UfsPowerMode target_power_mode = UfsPowerMode::kActive;
const scsi::PowerCondition target_power_condition = power_mode_map_[target_power_mode].first;
const LinkState target_link_state = power_mode_map_[target_power_mode].second;
std::lock_guard<std::mutex> lock(power_lock_);
if (current_power_mode_ == target_power_mode &&
current_power_condition_ == target_power_condition &&
current_link_state_ == target_link_state) {
return zx::ok();
}
if (current_power_mode_ != UfsPowerMode::kSleep ||
current_power_condition_ != scsi::PowerCondition::kIdle ||
current_link_state_ != LinkState::kHibernate) {
return zx::error(ZX_ERR_BAD_STATE);
}
if (zx::result<> result = controller_.Notify(NotifyEvent::kPrePowerModeChange, 0);
result.is_error()) {
return result.take_error();
}
DmeHibernateExitCommand dme_hibernate_exit_command(controller_);
if (auto result = dme_hibernate_exit_command.SendCommand(); result.is_error()) {
// TODO(https://fxbug.dev/42075643): Link has a problem and needs to perform error recovery.
return result.take_error();
}
current_link_state_ = target_link_state;
if (zx::result<> result = SetPowerCondition(target_power_condition); result.is_error()) {
return result.take_error();
}
if (zx::result<> result = controller_.Notify(NotifyEvent::kPostPowerModeChange, 0);
result.is_error()) {
return result.take_error();
}
// TODO(b/42075643): We should only perform background operations in the power suspended state.
if (zx::result<> result = EnableBackgroundOp(); result.is_error()) {
return result.take_error();
}
current_power_mode_ = target_power_mode;
properties_.power_suspended.Set(false);
fdf::info("Power resumed.");
return zx::ok();
}
zx::result<> DeviceManager::InitUfsPowerMode(inspect::Node &controller_node,
inspect::Node &attributes_node) {
std::lock_guard<std::mutex> lock(power_lock_);
// Read current power mode (bCurrentPowerMode, bActiveIccLevel)
zx::result<uint32_t> power_mode = ReadAttribute(Attributes::bCurrentPowerMode);
if (power_mode.is_error()) {
return power_mode.take_error();
}
current_power_mode_ = static_cast<UfsPowerMode>(power_mode.value());
if (current_power_mode_ != UfsPowerMode::kActive) {
fdf::error("Initial power mode is not active: 0x{:x}",
static_cast<uint8_t>(current_power_mode_));
return zx::error(ZX_ERR_BAD_STATE);
}
fdf::debug("bCurrentPowerMode 0x{:x}", power_mode.value());
current_power_condition_ = power_mode_map_[current_power_mode_].first;
current_link_state_ = power_mode_map_[current_power_mode_].second;
// TODO(https://fxbug.dev/42075643): Calculate and set the maximum ICC level. Currently, this
// value is temporarily set to 0x0F, which is the highest active ICC level.
if (auto result = WriteAttribute(Attributes::bActiveIccLevel, kHighestActiveIcclevel);
result.is_error()) {
return result.take_error();
}
// TODO(https://fxbug.dev/42075643): Enable auto hibernate
properties_.b_current_power_mode =
attributes_node.CreateUint("bCurrentPowerMode", static_cast<uint8_t>(current_power_mode_));
properties_.b_active_icc_level =
attributes_node.CreateUint("bActiveICCLevel", kHighestActiveIcclevel);
properties_.power_condition =
controller_node.CreateUint("PowerCondition", static_cast<uint8_t>(current_power_condition_));
properties_.link_state =
controller_node.CreateUint("LinkState", static_cast<uint8_t>(current_link_state_));
return zx::ok();
}
} // namespace ufs