blob: 71be5ee7668f411163e4fd5a96ae0467c158bcfd [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 <fuchsia/hardware/block/cpp/banjo.h>
#include <fuchsia/hardware/block/partition/cpp/banjo.h>
#include <fuchsia/hardware/rpmb/cpp/banjo.h>
#include <fuchsia/hardware/rpmb/llcpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/operation/block.h>
#include <lib/zircon-internal/thread_annotations.h>
#include <threads.h>
#include <array>
#include <atomic>
#include <deque>
#include <memory>
#include <ddk/trace/event.h>
#include <ddktl/device.h>
#include <ddktl/protocol/empty-protocol.h>
#include <fbl/auto_lock.h>
#include <fbl/condition_variable.h>
#include <hw/sdmmc.h>
#include "sdmmc-device.h"
namespace sdmmc {
// See the eMMC specification section 7.4.69 for these constants.
enum EmmcPartition : uint8_t {
USER_DATA_PARTITION = 0x0,
BOOT_PARTITION_1 = 0x1,
BOOT_PARTITION_2 = 0x2,
RPMB_PARTITION = 0x3,
PARTITION_COUNT,
};
struct PartitionInfo {
enum EmmcPartition partition;
uint64_t block_count;
};
struct RpmbRequestInfo {
::llcpp::fuchsia::mem::Range tx_frames = {};
::llcpp::fuchsia::mem::Range rx_frames = {};
::llcpp::fuchsia::hardware::rpmb::Rpmb::Interface::RequestCompleter::Async completer;
};
using BlockOperation = block::BorrowedOperation<PartitionInfo>;
class SdmmcBlockDevice;
class PartitionDevice;
using PartitionDeviceType = ddk::Device<PartitionDevice, ddk::GetSizable, ddk::GetProtocolable>;
class PartitionDevice : public PartitionDeviceType,
public ddk::BlockImplProtocol<PartitionDevice, ddk::base_protocol>,
public ddk::BlockPartitionProtocol<PartitionDevice> {
public:
PartitionDevice(zx_device_t* parent, SdmmcBlockDevice* sdmmc_parent,
const block_info_t& block_info, EmmcPartition partition)
: PartitionDeviceType(parent),
sdmmc_parent_(sdmmc_parent),
block_info_(block_info),
partition_(partition) {}
zx_status_t AddDevice();
void DdkRelease() { delete this; }
zx_off_t DdkGetSize();
zx_status_t DdkGetProtocol(uint32_t proto_id, void* out);
void BlockImplQuery(block_info_t* info_out, size_t* block_op_size_out);
void BlockImplQueue(block_op_t* btxn, block_impl_queue_callback completion_cb, void* cookie);
zx_status_t BlockPartitionGetGuid(guidtype_t guid_type, guid_t* out_guid);
zx_status_t BlockPartitionGetName(char* out_name, size_t capacity);
private:
SdmmcBlockDevice* const sdmmc_parent_;
const block_info_t block_info_;
const EmmcPartition partition_;
};
class RpmbDevice;
using RpmbDeviceType = ddk::Device<RpmbDevice, ddk::Messageable>;
class RpmbDevice : public RpmbDeviceType,
public ddk::RpmbProtocol<RpmbDevice, ddk::base_protocol>,
public ::llcpp::fuchsia::hardware::rpmb::Rpmb::Interface {
public:
// sdmmc_parent is owned by the SDMMC root device when the RpmbDevice object is created. Ownership
// is transferred to devmgr shortly after, meaning it will outlive this object due to the
// parent/child device relationship.
RpmbDevice(zx_device_t* parent, SdmmcBlockDevice* sdmmc_parent,
const std::array<uint8_t, SDMMC_CID_SIZE>& cid,
const std::array<uint8_t, MMC_EXT_CSD_SIZE>& ext_csd)
: RpmbDeviceType(parent),
sdmmc_parent_(sdmmc_parent),
cid_(cid),
rpmb_size_(ext_csd[MMC_EXT_CSD_RPMB_SIZE_MULT]),
reliable_write_sector_count_(ext_csd[MMC_EXT_CSD_REL_WR_SEC_C]),
loop_(&kAsyncLoopConfigNoAttachToCurrentThread) {}
void DdkRelease() { delete this; }
zx_status_t DdkMessage(fidl_incoming_msg_t* msg, fidl_txn_t* txn);
void RpmbConnectServer(zx::channel server);
void GetDeviceInfo(GetDeviceInfoCompleter::Sync& completer) override;
void Request(::llcpp::fuchsia::hardware::rpmb::Request request,
RequestCompleter::Sync& completer) override;
private:
SdmmcBlockDevice* const sdmmc_parent_;
const std::array<uint8_t, SDMMC_CID_SIZE> cid_;
const uint8_t rpmb_size_;
const uint8_t reliable_write_sector_count_;
async::Loop loop_;
bool loop_started_ = false;
};
class SdmmcBlockDevice;
using SdmmcBlockDeviceType = ddk::Device<SdmmcBlockDevice, ddk::Unbindable, ddk::Suspendable>;
class SdmmcBlockDevice : public SdmmcBlockDeviceType {
public:
SdmmcBlockDevice(zx_device_t* parent, const SdmmcDevice& sdmmc)
: SdmmcBlockDeviceType(parent), sdmmc_(sdmmc) {
block_info_.max_transfer_size = static_cast<uint32_t>(sdmmc_.host_info().max_transfer_size);
}
~SdmmcBlockDevice() { txn_list_.CompleteAll(ZX_ERR_INTERNAL); }
static zx_status_t Create(zx_device_t* parent, const SdmmcDevice& sdmmc,
std::unique_ptr<SdmmcBlockDevice>* out_dev);
zx_status_t ProbeSd();
zx_status_t ProbeMmc();
zx_status_t AddDevice() TA_EXCL(lock_);
void DdkUnbind(ddk::UnbindTxn txn);
void DdkSuspend(ddk::SuspendTxn txn);
void DdkRelease() { delete this; }
// Called by children of this device.
void Queue(BlockOperation txn) TA_EXCL(lock_);
void RpmbQueue(RpmbRequestInfo info) TA_EXCL(lock_);
// Visible for testing.
zx_status_t Init() { return sdmmc_.Init(); }
void StopWorkerThread() TA_EXCL(lock_);
void SetBlockInfo(uint32_t block_size, uint64_t block_count);
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 ReadWrite(const block_read_write_t& txn, const EmmcPartition partition);
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);
int WorkerThread();
zx_status_t WaitForTran();
zx_status_t MmcDoSwitch(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();
zx_status_t MmcSwitchTiming(sdmmc_timing_t new_timing);
zx_status_t MmcSwitchFreq(uint32_t new_freq);
zx_status_t MmcDecodeExtCsd();
bool MmcSupportsHs();
bool MmcSupportsHsDdr();
bool MmcSupportsHs200();
bool MmcSupportsHs400();
SdmmcDevice sdmmc_; // Only accessed by ProbeSd, ProbeMmc, and WorkerThread.
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_;
fbl::ConditionVariable worker_event_ TA_GUARDED(lock_);
// blockio requests
block::BorrowedOperationQueue<PartitionInfo> txn_list_ TA_GUARDED(lock_);
std::deque<RpmbRequestInfo> rpmb_list_ TA_GUARDED(lock_);
// outstanding request (1 right now)
sdmmc_req_t req_;
thrd_t worker_thread_ = 0;
std::atomic<bool> dead_ = false;
block_info_t block_info_{};
bool is_sd_ = false;
};
} // namespace sdmmc
#endif // SRC_DEVICES_BLOCK_DRIVERS_SDMMC_SDMMC_BLOCK_DEVICE_H_