blob: fc0bab0eb9f2209cb0938c0c20a12db7e9578918 [file]
// 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_AML_SDMMC_AML_SDMMC_H_
#define SRC_DEVICES_BLOCK_DRIVERS_AML_SDMMC_AML_SDMMC_H_
#include <fidl/fuchsia.hardware.clock/cpp/wire.h>
#include <fidl/fuchsia.hardware.gpio/cpp/wire.h>
#include <fidl/fuchsia.hardware.platform.device/cpp/wire.h>
#include <fidl/fuchsia.hardware.power/cpp/fidl.h>
#include <fidl/fuchsia.hardware.sdio/cpp/fidl.h>
#include <fidl/fuchsia.hardware.sdmmc/cpp/driver/fidl.h>
#include <fidl/fuchsia.power.broker/cpp/fidl.h>
#include <lib/ddk/metadata.h>
#include <lib/dma-buffer/buffer.h>
#include <lib/driver/compat/cpp/compat.h>
#include <lib/driver/component/cpp/driver_base2.h>
#include <lib/driver/component/cpp/driver_export2.h>
#include <lib/driver/metadata/cpp/metadata_server.h>
#include <lib/driver/mmio/cpp/mmio.h>
#include <lib/driver/platform-device/cpp/pdev.h>
#include <lib/driver/power/cpp/suspend.h>
#include <lib/inspect/component/cpp/component.h>
#include <lib/inspect/cpp/inspect.h>
#include <lib/stdcompat/span.h>
#include <lib/trace/event.h>
#include <lib/zx/interrupt.h>
#include <lib/zx/result.h>
#include <threads.h>
#include <zircon/compiler.h>
#include <array>
#include <limits>
#include <mutex>
#include <vector>
#include <soc/aml-common/aml-sdmmc.h>
#include "src/devices/block/drivers/aml-sdmmc/aml_sdmmc_config.h"
#include "src/lib/vmo_store/vmo_store.h"
namespace aml_sdmmc {
class AmlSdmmc : public fdf::DriverBase2,
public fdf::WireServer<fuchsia_hardware_sdmmc::Sdmmc>,
public fidl::Server<fuchsia_hardware_power::PowerTokenProvider>,
public fdf_power::Suspendable<AmlSdmmc> {
public:
// Note: This name can't be changed without migrating users in other repos.
static constexpr char kDriverName[] = "aml-sd-emmc";
// Limit maximum number of descriptors to 512 for now
static constexpr size_t kMaxDmaDescriptors = 512;
// Levels for hardware power element.
static constexpr fuchsia_power_broker::PowerLevel kPowerLevelOff = 0;
static constexpr fuchsia_power_broker::PowerLevel kPowerLevelOn = 1;
explicit AmlSdmmc()
: fdf::DriverBase2(kDriverName),
registered_vmos_{
// clang-format off
SdmmcVmoStore{vmo_store::Options{}},
SdmmcVmoStore{vmo_store::Options{}},
SdmmcVmoStore{vmo_store::Options{}},
SdmmcVmoStore{vmo_store::Options{}},
SdmmcVmoStore{vmo_store::Options{}},
SdmmcVmoStore{vmo_store::Options{}},
SdmmcVmoStore{vmo_store::Options{}},
SdmmcVmoStore{vmo_store::Options{}},
// clang-format on
} {}
~AmlSdmmc() override {
if (irq_.is_valid()) {
irq_.destroy();
}
}
zx::result<> Start(fdf::DriverContext context) override;
void Stop(fdf::StopCompleter completer) __TA_EXCLUDES(lock_) override;
// fuchsia_hardware_sdmmc::Sdmmc implementation
void HostInfo(fdf::Arena& arena, HostInfoCompleter::Sync& completer) override;
void SetSignalVoltage(SetSignalVoltageRequestView request, fdf::Arena& arena,
SetSignalVoltageCompleter::Sync& completer) override;
void SetBusWidth(SetBusWidthRequestView request, fdf::Arena& arena,
SetBusWidthCompleter::Sync& completer) override;
void SetBusFreq(SetBusFreqRequestView request, fdf::Arena& arena,
SetBusFreqCompleter::Sync& completer) override;
void SetTiming(SetTimingRequestView request, fdf::Arena& arena,
SetTimingCompleter::Sync& completer) override;
void HwReset(fdf::Arena& arena, HwResetCompleter::Sync& completer) override;
void PerformTuning(PerformTuningRequestView request, fdf::Arena& arena,
PerformTuningCompleter::Sync& completer) override;
void RegisterInBandInterrupt(RegisterInBandInterruptRequestView request, fdf::Arena& arena,
RegisterInBandInterruptCompleter::Sync& completer) override;
void AckInBandInterrupt(fdf::Arena& arena,
AckInBandInterruptCompleter::Sync& completer) override {}
void RegisterVmo(RegisterVmoRequestView request, fdf::Arena& arena,
RegisterVmoCompleter::Sync& completer) override;
void UnregisterVmo(UnregisterVmoRequestView request, fdf::Arena& arena,
UnregisterVmoCompleter::Sync& completer) override;
void Request(RequestRequestView request, fdf::Arena& arena,
RequestCompleter::Sync& completer) override;
void EnableCqhci(fdf::Arena& arena, EnableCqhciCompleter::Sync& completer) override;
void DisableCqhci(fdf::Arena& arena, DisableCqhciCompleter::Sync& completer) override;
void InitializeCommandQueueing(InitializeCommandQueueingRequestView request, fdf::Arena& arena,
InitializeCommandQueueingCompleter::Sync& completer) override;
// fuchsia_hardware_power::PowerTokenProvider implementation
void GetToken(GetTokenCompleter::Sync& completer) override;
void handle_unknown_method(
fidl::UnknownMethodMetadata<fuchsia_hardware_power::PowerTokenProvider> md,
fidl::UnknownMethodCompleter::Sync& completer) override {}
zx_status_t SuspendPower() __TA_REQUIRES(lock_);
zx_status_t ResumePower() __TA_REQUIRES(lock_);
// fdf_power::Suspendable implementation
void Suspend(fdf_power::SuspendCompleter completer) override;
void Resume(fdf_power::ResumeCompleter completer) override;
bool SuspendEnabled() override;
// Used by fdf_power::Suspendable.
std::optional<fidl::ServerEnd<fuchsia_power_broker::ElementRunner>> take_power_element_runner() {
return std::move(power_element_runner_);
}
// Visible for tests
zx_status_t Init(const std::string& instance_identifier) __TA_EXCLUDES(lock_);
protected:
std::optional<inspect::ComponentInspector> component_inspector_;
virtual zx_status_t WaitForInterruptImpl();
virtual void WaitForBus() const __TA_REQUIRES(lock_);
virtual std::optional<compat::DeviceServer::BanjoConfig> get_banjo_config() {
return std::nullopt;
}
zx_status_t SetBusWidthImpl(fuchsia_hardware_sdmmc::wire::SdmmcBusWidth bus_width)
__TA_REQUIRES(lock_);
zx_status_t SetBusFreqImpl(uint32_t freq) __TA_REQUIRES(lock_);
zx_status_t SetTimingImpl(fuchsia_hardware_sdmmc::wire::SdmmcTiming timing) __TA_REQUIRES(lock_);
zx_status_t HwResetImpl() __TA_REQUIRES(lock_);
zx_status_t PerformTuningImpl(uint32_t tuning_cmd_idx) __TA_REQUIRES(tuning_lock_);
zx_status_t RequestImpl(const fuchsia_hardware_sdmmc::wire::SdmmcReq& req,
uint32_t out_response[4]) __TA_REQUIRES(lock_);
zx_status_t RegisterVmoImpl(uint32_t vmo_id, uint8_t client_id, zx::vmo vmo, uint64_t offset,
uint64_t size, fuchsia_hardware_sdmmc::wire::SdmmcVmoRight rights)
__TA_EXCLUDES(lock_);
zx_status_t UnregisterVmoImpl(uint32_t vmo_id, uint8_t client_id, zx::vmo* out_vmo)
__TA_EXCLUDES(lock_);
// Visible for tests
const zx::bti& bti() const { return bti_; }
const fdf::MmioBuffer& mmio() __TA_EXCLUDES(lock_) {
std::lock_guard<std::mutex> lock(lock_);
return *mmio_;
}
void* descs_buffer() __TA_EXCLUDES(lock_) {
std::lock_guard<std::mutex> lock(lock_);
return descs_buffer_->virt();
}
fuchsia_hardware_sdmmc::wire::SdmmcHostInfo dev_info_;
bool power_suspended_ __TA_GUARDED(lock_) = false;
// TODO(https://fxbug.dev/42084501): Remove redundant locking when Banjo is removed.
std::mutex lock_ __TA_ACQUIRED_AFTER(tuning_lock_);
std::mutex tuning_lock_ __TA_ACQUIRED_BEFORE(lock_);
private:
constexpr static size_t kResponseCount = 4;
struct TuneResults {
uint64_t results = 0;
std::string ToString(const uint32_t param_max) const {
std::vector<char> string(param_max + 1);
for (uint32_t i = 0; i <= param_max; i++) {
string[i] = (results & (1ULL << i)) ? '|' : '-';
}
return std::string(string.data(), string.size());
}
};
struct TuneWindow {
uint32_t start = 0;
uint32_t size = 0;
uint32_t middle() const { return start + (size / 2); }
};
struct TuneSettings {
uint32_t adj_delay = 0;
uint32_t delay = 0;
};
// VMO metadata that needs to be stored in accordance with the SDMMC protocol.
struct OwnedVmoInfo {
uint64_t offset;
uint64_t size;
fuchsia_hardware_sdmmc::SdmmcVmoRight rights;
};
struct Inspect {
inspect::Node root;
inspect::UintProperty bus_clock_frequency;
inspect::UintProperty adj_delay;
inspect::UintProperty delay_lines;
std::vector<inspect::Node> tuning_results_nodes;
std::vector<inspect::StringProperty> tuning_results;
inspect::UintProperty max_delay;
inspect::UintProperty longest_window_start;
inspect::UintProperty longest_window_size;
inspect::UintProperty longest_window_adj_delay;
inspect::UintProperty distance_to_failing_point;
inspect::BoolProperty power_suspended;
void Init(const std::string& instance_identifier, inspect::Node& parent,
bool is_power_suspended);
};
struct TuneContext {
zx::unowned_vmo vmo;
cpp20::span<const uint8_t> expected_block;
uint32_t cmd;
TuneSettings new_settings;
TuneSettings original_settings;
};
struct SdmmcRequestInfo {
std::vector<fuchsia_hardware_sdmmc::SdmmcReq> reqs;
fdf::Arena arena;
RequestCompleter::Async completer;
};
struct SdmmcTaskInfo {
fit::function<zx_status_t()> task;
fdf::Arena arena;
std::variant<SetBusWidthCompleter::Async, SetBusFreqCompleter::Async, SetTimingCompleter::Async,
HwResetCompleter::Async, PerformTuningCompleter::Async>
completer;
};
using SdmmcVmoStore = vmo_store::VmoStore<vmo_store::HashTableStorage<uint32_t, OwnedVmoInfo>>;
// Sweeps from zero to the max delay and creates a TuneWindow representing the largest span of
// delay values that failed.
static TuneWindow GetFailingWindow(TuneResults results);
static uint32_t DistanceToFailingPoint(TuneSettings point,
cpp20::span<const TuneResults> adj_delay_results);
zx::result<> InitResources(
fidl::ClientEnd<fuchsia_hardware_platform_device::Device> pdev_client_end,
fdf::Namespace& incoming);
void Serve(fdf::ServerEnd<fuchsia_hardware_sdmmc::Sdmmc> request);
aml_sdmmc_desc_t* descs() const __TA_REQUIRES(lock_) {
return static_cast<aml_sdmmc_desc_t*>(descs_buffer_->virt());
}
template <typename T>
void DoTaskAndComplete(fit::function<zx_status_t()>, fdf::Arena& arena, T& completer);
template <typename T>
void DoRequestAndComplete(fidl::VectorView<fuchsia_hardware_sdmmc::wire::SdmmcReq> reqs,
fdf::Arena& arena, T& completer) __TA_REQUIRES(lock_);
zx::result<TuneSettings> PerformTuning(cpp20::span<const TuneResults> adj_delay_results);
zx_status_t TuningDoTransfer(const TuneContext& context) __TA_REQUIRES(tuning_lock_);
bool TuningTestSettings(const TuneContext& context) __TA_REQUIRES(tuning_lock_);
TuneResults TuneDelayLines(const TuneContext& context) __TA_REQUIRES(tuning_lock_);
void SetTuneSettings(const TuneSettings& settings) __TA_REQUIRES(lock_);
TuneSettings GetTuneSettings() __TA_REQUIRES(lock_);
void ConfigureDefaultRegs() __TA_REQUIRES(lock_);
aml_sdmmc_desc_t* SetupCmdDesc(const fuchsia_hardware_sdmmc::wire::SdmmcReq& req)
__TA_REQUIRES(lock_);
// Returns a pointer to the LAST descriptor used.
zx::result<std::pair<aml_sdmmc_desc_t*, std::vector<fzl::PinnedVmo>>> SetupDataDescs(
const fuchsia_hardware_sdmmc::wire::SdmmcReq& req, aml_sdmmc_desc_t* cur_desc)
__TA_REQUIRES(lock_);
// These return pointers to the NEXT descriptor to use.
zx::result<aml_sdmmc_desc_t*> SetupOwnedVmoDescs(
const fuchsia_hardware_sdmmc::wire::SdmmcReq& req,
const fuchsia_hardware_sdmmc::wire::SdmmcBufferRegion& buffer,
vmo_store::StoredVmo<OwnedVmoInfo>& vmo, aml_sdmmc_desc_t* cur_desc) __TA_REQUIRES(lock_);
zx::result<std::pair<aml_sdmmc_desc_t*, fzl::PinnedVmo>> SetupUnownedVmoDescs(
const fuchsia_hardware_sdmmc::wire::SdmmcReq& req,
const fuchsia_hardware_sdmmc::wire::SdmmcBufferRegion& buffer, aml_sdmmc_desc_t* cur_desc)
__TA_REQUIRES(lock_);
zx::result<aml_sdmmc_desc_t*> PopulateDescriptors(
const fuchsia_hardware_sdmmc::wire::SdmmcReq& req, aml_sdmmc_desc_t* cur_desc,
fzl::PinnedVmo::Region region) __TA_REQUIRES(lock_);
zx_status_t FinishReq(const fuchsia_hardware_sdmmc::wire::SdmmcReq& req);
void ClearStatus() __TA_REQUIRES(lock_);
zx::result<std::array<uint32_t, kResponseCount>> WaitForInterrupt(
const fuchsia_hardware_sdmmc::wire::SdmmcReq& req) __TA_REQUIRES(lock_);
zx_status_t SetLevel(fuchsia_power_broker::PowerLevel required_level);
// Serves requests that were delayed because they were received during suspended state.
void ServeDelayedRequests() __TA_REQUIRES(tuning_lock_, lock_);
std::optional<fdf::MmioBuffer> mmio_ __TA_GUARDED(lock_);
aml_sdmmc_config::Config config_;
zx::bti bti_;
fidl::WireSyncClient<fuchsia_hardware_gpio::Gpio> reset_gpio_;
fidl::WireSyncClient<fuchsia_hardware_clock::Clock> clock_gate_;
zx::interrupt irq_;
std::unique_ptr<dma_buffer::ContiguousBuffer> descs_buffer_ __TA_GUARDED(lock_);
trace_async_id_t trace_async_id_;
std::vector<std::variant<SdmmcRequestInfo, SdmmcTaskInfo>> delayed_requests_;
uint32_t clk_div_saved_ = 0;
zx::event hardware_power_assertive_token_;
std::optional<fidl::ServerEnd<fuchsia_power_broker::ElementRunner>> power_element_runner_;
bool shutdown_ __TA_GUARDED(lock_) = false;
std::array<SdmmcVmoStore, fuchsia_hardware_sdmmc::wire::kSdmmcMaxClientId + 1> registered_vmos_
__TA_GUARDED(lock_);
uint64_t consecutive_cmd_errors_ = 0;
uint64_t consecutive_data_errors_ = 0;
Inspect inspect_;
fidl::WireSyncClient<fuchsia_driver_framework::Node> parent_;
fidl::WireSyncClient<fuchsia_driver_framework::NodeController> controller_;
fidl::ServerBindingGroup<fuchsia_hardware_power::PowerTokenProvider> bindings_;
compat::SyncInitializedDeviceServer compat_server_;
// Dedicated dispatcher for inlining fuchsia_hardware_sdmmc::Sdmmc FIDL requests.
fdf::Dispatcher worker_dispatcher_;
fdf_metadata::MetadataServer<fuchsia_hardware_sdmmc::SdmmcMetadata> metadata_server_;
fdf_metadata::MetadataServer<fuchsia_hardware_sdio::Metadata> sdio_metadata_server_;
};
} // namespace aml_sdmmc
#endif // SRC_DEVICES_BLOCK_DRIVERS_AML_SDMMC_AML_SDMMC_H_