blob: 0217c08bca00d7ea89ee25ce745192a5a754b3ba [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.
#ifndef SRC_DEVICES_BLOCK_DRIVERS_SDMMC_SDMMC_BLOCK_DEVICE_H_
#define SRC_DEVICES_BLOCK_DRIVERS_SDMMC_SDMMC_BLOCK_DEVICE_H_
#include <fidl/fuchsia.hardware.sdmmc/cpp/wire.h>
#include <fidl/fuchsia.power.broker/cpp/fidl.h>
#include <fuchsia/hardware/block/driver/cpp/banjo.h>
#include <lib/driver/component/cpp/driver_base.h>
#include <lib/fzl/vmo-mapper.h>
#include <lib/inspect/component/cpp/component.h>
#include <lib/inspect/cpp/inspect.h>
#include <lib/operation/block.h>
#include <lib/sdmmc/hw.h>
#include <lib/sync/cpp/completion.h>
#include <lib/trace/event.h>
#include <lib/zircon-internal/thread_annotations.h>
#include <threads.h>
#include <zircon/types.h>
#include <array>
#include <atomic>
#include <cinttypes>
#include <deque>
#include <memory>
#include <semaphore>
#include <fbl/auto_lock.h>
#include <fbl/condition_variable.h>
#include "sdmmc-device.h"
#include "sdmmc-partition-device.h"
#include "sdmmc-rpmb-device.h"
#include "sdmmc-types.h"
namespace sdmmc {
class SdmmcRootDevice;
// This struct is used to maintain metadata for IO while the IO is in progress: When using Banjo,
// this is a hard requirement, as only object handles are passed through the call. When using FIDL,
// objects can be transferred through the call. However for performance, we avoid repeatedly
// creating and initializing objects.
struct ReadWriteMetadata {
public:
explicit ReadWriteMetadata(SdmmcBlockDevice* block_device) : block_device(block_device) {}
// This initialization is only required if packed commands are used.
zx_status_t InitForPackedCommands(uint32_t buffer_region_count, uint32_t block_size) {
buffer_regions = std::make_unique<sdmmc_buffer_region_t[]>(buffer_region_count);
memset(buffer_regions.get(), 0, sizeof(sdmmc_buffer_region_t) * buffer_region_count);
zx_status_t status = zx::vmo::create(block_size, 0, &packed_command_header_vmo);
if (status != ZX_OK) {
FDF_LOGL(ERROR, logger(), "Failed to create packed command header vmo: %s",
zx_status_get_string(status));
return status;
}
status = packed_command_header_mapper.Map(packed_command_header_vmo);
if (status != ZX_OK) {
FDF_LOGL(ERROR, logger(), "Failed to map packed command header vmo: %s",
zx_status_get_string(status));
return status;
}
packed_command_header_data = static_cast<PackedCommand*>(packed_command_header_mapper.start());
memset(packed_command_header_data, 0, block_size);
packed_command_header_data->version = 1;
return ZX_OK;
}
fdf::Logger& logger();
// For non-packed commands, only this is needed, as initialized here.
std::unique_ptr<sdmmc_buffer_region_t[]> buffer_regions =
std::make_unique<sdmmc_buffer_region_t[]>(1);
// For packed commands, the following are also needed.
zx::vmo packed_command_header_vmo;
fzl::VmoMapper packed_command_header_mapper;
PackedCommand* packed_command_header_data;
private:
SdmmcBlockDevice* const block_device;
};
class SdmmcBlockDevice {
public:
static constexpr char kHardwarePowerElementName[] = "sdmmc-hardware";
static constexpr char kSystemWakeOnRequestPowerElementName[] = "sdmmc-system-wake-on-request";
// Common to hardware power and wake-on-request power elements.
static constexpr fuchsia_power_broker::PowerLevel kPowerLevelOff = 0;
static constexpr fuchsia_power_broker::PowerLevel kPowerLevelOn = 1;
SdmmcBlockDevice(SdmmcRootDevice* parent, std::unique_ptr<SdmmcDevice> sdmmc)
: parent_(parent), sdmmc_(std::move(sdmmc)) {
block_info_.max_transfer_size = static_cast<uint32_t>(sdmmc_->host_info().max_transfer_size);
}
static zx_status_t Create(SdmmcRootDevice* parent, std::unique_ptr<SdmmcDevice> sdmmc,
std::unique_ptr<SdmmcBlockDevice>* out_dev);
// Returns the SdmmcDevice. Used if this SdmmcBlockDevice fails to probe (i.e., no eligible device
// present).
std::unique_ptr<SdmmcDevice> TakeSdmmcDevice() { return std::move(sdmmc_); }
// Probe for SD first, then MMC.
zx_status_t Probe(const fuchsia_hardware_sdmmc::wire::SdmmcMetadata& metadata) {
return ProbeSd(metadata) == ZX_OK ? ZX_OK : ProbeMmc(metadata);
}
zx_status_t ProbeSd(const fuchsia_hardware_sdmmc::wire::SdmmcMetadata& metadata);
zx_status_t ProbeMmc(const fuchsia_hardware_sdmmc::wire::SdmmcMetadata& metadata);
zx_status_t AddDevice() TA_EXCL(lock_);
void StopWorkerDispatcher(std::optional<fdf::PrepareStopCompleter> completer = std::nullopt)
TA_EXCL(lock_);
zx_status_t SuspendPower() TA_REQ(lock_);
zx_status_t ResumePower() TA_REQ(lock_);
// Called by children of this device.
void Queue(BlockOperation txn) TA_EXCL(lock_);
void RpmbQueue(RpmbRequestInfo info) TA_EXCL(lock_);
fidl::WireSyncClient<fuchsia_driver_framework::Node>& block_node() { return block_node_; }
std::string_view block_name() const { return block_name_; }
SdmmcRootDevice* parent() { return parent_; }
// Visible for testing.
void SetBlockInfo(uint32_t block_size, uint64_t block_count);
const inspect::Inspector& inspector() const { return inspector_; }
const std::vector<std::unique_ptr<PartitionDevice>>& child_partition_devices() const {
return child_partition_devices_;
}
const std::unique_ptr<RpmbDevice>& child_rpmb_device() const { return child_rpmb_device_; }
fdf::Logger& logger();
private:
// An arbitrary limit to prevent RPMB clients from flooding us with requests.
static constexpr size_t kMaxOutstandingRpmbRequests = 16;
// The worker thread will handle this many block ops then this many RPMB requests, and will repeat
// until both queues are empty.
static constexpr size_t kRoundRobinRequestCount = 16;
zx_status_t ReadWriteWithRetries(std::vector<BlockOperation>& btxns, EmmcPartition partition);
zx_status_t ReadWriteAttempt(std::vector<BlockOperation>& btxns, bool suppress_error_messages);
zx_status_t Flush();
zx_status_t Trim(const block_trim_t& txn, const EmmcPartition partition);
zx_status_t SetPartition(const EmmcPartition partition);
zx_status_t RpmbRequest(const RpmbRequestInfo& request);
void HandleBlockOps(block::BorrowedOperationQueue<PartitionInfo>& txn_list);
void HandleRpmbRequests(std::deque<RpmbRequestInfo>& rpmb_list);
void WorkerLoop();
zx_status_t WaitForTran();
zx_status_t MmcDoSwitch(uint8_t index, uint8_t value);
zx_status_t MmcWaitForSwitch(uint8_t index, uint8_t value);
zx_status_t MmcSetBusWidth(sdmmc_bus_width_t bus_width, uint8_t mmc_ext_csd_bus_width);
sdmmc_bus_width_t MmcSelectBusWidth();
// The host is expected to switch the timing from HS200 to HS as part of HS400 initialization.
// Checking the status of the switch requires special handling to avoid a temporary mismatch
// between the host and device timings.
zx_status_t MmcSwitchTiming(sdmmc_timing_t new_timing);
zx_status_t MmcSwitchTimingHs200ToHs();
zx_status_t MmcSwitchFreq(uint32_t new_freq);
zx_status_t MmcDecodeExtCsd();
bool MmcSupportsHs();
bool MmcSupportsHsDdr();
bool MmcSupportsHs200();
bool MmcSupportsHs400();
void MmcSetInspectProperties() TA_REQ(lock_);
void BlockComplete(sdmmc::BlockOperation& txn, zx_status_t status);
// TODO(b/309152899): Once fuchsia.power.SuspendEnabled config cap is available, have this method
// return failure if power management could not be configured. Use fuchsia.power.SuspendEnabled to
// ignore this failure when expected.
// Register power configs with Power Broker, and begin the continuous power level adjustment of
// hardware. For products that don't support the Power Framework, this method simply returns
// success.
zx::result<> ConfigurePowerManagement();
// Acquires a lease on a power element via the supplied |lessor_client|, returning the resulting
// lease control client end.
zx::result<fidl::ClientEnd<fuchsia_power_broker::LeaseControl>> AcquireLease(
const fidl::WireSyncClient<fuchsia_power_broker::Lessor>& lessor_client);
// Informs Power Broker of the updated |power_level| via the supplied |current_level_client|.
void UpdatePowerLevel(
const fidl::WireSyncClient<fuchsia_power_broker::CurrentLevel>& current_level_client,
fuchsia_power_broker::PowerLevel power_level);
// Watches the required hardware power level and adjusts it accordingly. Also serves requests that
// were delayed because they were received during suspended state. Communicates power level
// transitions to the Power Broker.
void WatchHardwareRequiredLevel();
// Watches the required wake-on-request power level and replies to the Power Broker accordingly.
// Does not directly effect any real power level change of storage hardware. (That happens in
// WatchHardwareRequiredLevel().)
void WatchWakeOnRequestRequiredLevel();
SdmmcRootDevice* const parent_;
// Only accessed by ProbeSd, ProbeMmc, SuspendPower, ResumePower, and WorkerLoop.
std::unique_ptr<SdmmcDevice> sdmmc_;
sdmmc_bus_width_t bus_width_;
sdmmc_timing_t timing_;
uint32_t clock_rate_; // Bus clock rate
// mmc
std::array<uint8_t, SDMMC_CID_SIZE> raw_cid_;
std::array<uint8_t, SDMMC_CSD_SIZE> raw_csd_;
std::array<uint8_t, MMC_EXT_CSD_SIZE> raw_ext_csd_;
fbl::Mutex lock_;
// Signals the worker loop to process incoming commands (or driver shutdown).
fbl::ConditionVariable worker_event_ TA_GUARDED(lock_);
// Signals that the worker loop is idle and awaiting incoming commands (or driver shutdown).
fbl::ConditionVariable idle_event_ TA_GUARDED(lock_);
// blockio requests
block::BorrowedOperationQueue<PartitionInfo> txn_list_ TA_GUARDED(lock_);
std::deque<RpmbRequestInfo> rpmb_list_ TA_GUARDED(lock_);
// Dispatcher for processing queued block requests.
fdf::Dispatcher worker_dispatcher_;
// Signaled when worker_dispatcher_ is shut down.
libsync::Completion worker_shutdown_completion_;
// Signaled when power has been resumed.
libsync::Completion wait_for_power_resumed_;
bool worker_idle_ TA_GUARDED(lock_) = false;
bool power_suspended_ TA_GUARDED(lock_) = false;
bool shutdown_ TA_GUARDED(lock_) = false;
trace_async_id_t trace_async_id_;
std::vector<zx::event> active_power_dep_tokens_;
std::vector<zx::event> passive_power_dep_tokens_;
fidl::ClientEnd<fuchsia_power_broker::ElementControl> hardware_power_element_control_client_end_;
fidl::WireSyncClient<fuchsia_power_broker::Lessor> hardware_power_lessor_client_;
fidl::WireSyncClient<fuchsia_power_broker::CurrentLevel> hardware_power_current_level_client_;
fidl::WireClient<fuchsia_power_broker::RequiredLevel> hardware_power_required_level_client_;
fidl::ClientEnd<fuchsia_power_broker::ElementControl> wake_on_request_element_control_client_end_;
fidl::WireSyncClient<fuchsia_power_broker::Lessor> wake_on_request_lessor_client_;
fidl::WireSyncClient<fuchsia_power_broker::CurrentLevel> wake_on_request_current_level_client_;
fidl::WireClient<fuchsia_power_broker::RequiredLevel> wake_on_request_required_level_client_;
fidl::ClientEnd<fuchsia_power_broker::LeaseControl> hardware_power_lease_control_client_end_;
block_info_t block_info_{};
bool is_sd_ = false;
bool cache_enabled_ = false;
uint32_t max_packed_reads_effective_ = 0; // Use command packing up to this many reads.
uint32_t max_packed_writes_effective_ = 0; // Use command packing up to this many writes.
ReadWriteMetadata readwrite_metadata_{this};
inspect::Inspector inspector_;
inspect::Node root_;
struct InspectProperties {
inspect::UintProperty io_errors_; // Only updated from the worker thread.
inspect::UintProperty io_retries_; // Only updated from the worker thread.
inspect::UintProperty type_a_lifetime_used_; // Set once by the init thread.
inspect::UintProperty type_b_lifetime_used_; // Set once by the init thread.
inspect::UintProperty max_lifetime_used_; // Set once by the init thread.
inspect::UintProperty cache_size_bits_; // Set once by the init thread.
inspect::BoolProperty cache_enabled_; // Set once by the init thread.
inspect::BoolProperty trim_enabled_; // Set once by the init thread.
inspect::UintProperty max_packed_reads_; // Set once by the init thread.
inspect::UintProperty max_packed_writes_; // Set once by the init thread.
inspect::UintProperty max_packed_reads_effective_; // Set once by the init thread.
inspect::UintProperty max_packed_writes_effective_; // Set once by the init thread.
inspect::BoolProperty using_fidl_; // Set once by the init thread.
inspect::BoolProperty power_suspended_; // Updated whenever power state changes.
inspect::UintProperty wake_on_request_count_; // Updated whenever wake-on-request occurs.
inspect::ExponentialUintHistogram
wake_on_request_latency_us_; // Updated whenever wake-on-request occurs.
} properties_;
std::optional<inspect::ComponentInspector> exposed_inspector_;
std::string_view block_name_;
fidl::WireSyncClient<fuchsia_driver_framework::Node> block_node_;
fidl::WireSyncClient<fuchsia_driver_framework::NodeController> controller_;
std::vector<std::unique_ptr<PartitionDevice>> child_partition_devices_;
std::unique_ptr<RpmbDevice> child_rpmb_device_;
};
} // namespace sdmmc
#endif // SRC_DEVICES_BLOCK_DRIVERS_SDMMC_SDMMC_BLOCK_DEVICE_H_