blob: 85c38ebcba2844f9f2d435a8bb9913873be3f46a [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 LIB_SYSCONFIG_SYNC_CLIENT_H_
#define LIB_SYSCONFIG_SYNC_CLIENT_H_
#include <fidl/fuchsia.hardware.skipblock/cpp/wire.h>
#include <fidl/fuchsia.io/cpp/wire.h>
#include <lib/fzl/owned-vmo-mapper.h>
#include <lib/zx/channel.h>
#include <zircon/types.h>
#include <optional>
#include "abr-wear-leveling.h"
#include "sysconfig-header.h"
namespace sysconfig {
namespace skipblock = fuchsia_hardware_skipblock;
// This class provides a synchronous read and write interface into sub-partitions of the sysconfig
// skip-block partition.
//
// The class takes into account differences that may appear in partition layout between various
// device's sysconfig partitions.
class __EXPORT SyncClient {
public:
// The sub partitions of the sysconfig partition.
enum class PartitionType {
kSysconfig,
// Used to determine which partition to boot into on boot.
kABRMetadata,
// The follow are used to store verified boot metadata.
kVerifiedBootMetadataA,
kVerifiedBootMetadataB,
kVerifiedBootMetadataR,
};
// Looks for a skip-block device of type sysconfig. If found, returns a client capable of reading
// and writing to sub-partitions of the sysconfig device.
static zx::result<SyncClient> Create();
// Variation on `Create` with devfs (/dev) injected.
static zx::result<SyncClient> Create(fidl::UnownedClientEnd<fuchsia_io::Directory> dev);
// Provides write access for the partition specified. Always writes full partition.
//
// |vmo| must have a size greater than or equal to the partitions size + |vmo_offset|.
zx_status_t WritePartition(PartitionType partition, const zx::vmo& vmo, zx_off_t vmo_offset);
// Write pages without first erasing.
zx_status_t WriteBytesWithoutErase(size_t offset, size_t len, const zx::vmo& vmo,
zx_off_t vmo_offset);
// Provides read access for the partition specified. Always reads full partition.
//
// |vmo| must have a size greater than or equal to the partitions size + |vmo_offset|.
zx_status_t ReadPartition(PartitionType partition, const zx::vmo& vmo, zx_off_t vmo_offset);
// Returns the size of the partition specified.
zx_status_t GetPartitionSize(PartitionType partition, size_t* out);
zx_status_t GetPartitionOffset(PartitionType partition, size_t* out);
// Use caution when updating layout in multi-threaded context.
// It's dangerous to update layout and header while there are other instances of SyncClient
// in use.
// In particular, SyncClient caches header from storage after the first time it reads it. If
// layout is changed afterwards by some other instance of SyncClient, it will not be aware of it.
// Thus make sure that you only effectively update layout in a state where no other SyncClient is
// created and in use.
zx_status_t UpdateLayout(sysconfig_header target_header);
// No copy.
SyncClient(const SyncClient&) = delete;
SyncClient& operator=(const SyncClient&) = delete;
SyncClient(SyncClient&&) = default;
SyncClient& operator=(SyncClient&&) = default;
zx::result<std::reference_wrapper<const sysconfig_header>> GetHeader();
private:
explicit SyncClient(fidl::WireSyncClient<fuchsia_hardware_skipblock::SkipBlock> skip_block)
: skip_block_(std::move(skip_block)) {}
zx_status_t InitializeReadMapper();
zx_status_t Write(size_t offset, size_t len, const zx::vmo& vmo, zx_off_t vmo_offset,
skipblock::wire::WriteBytesMode mode =
skipblock::wire::WriteBytesMode::kReadModifyEraseWrite);
zx_status_t Read(size_t offset, size_t len, const zx::vmo& vmo, zx_off_t vmo_offset);
zx_status_t LoadFromStorage();
fidl::WireSyncClient<fuchsia_hardware_skipblock::SkipBlock> skip_block_;
// Lazily initialized on reads.
fzl::OwnedVmoMapper read_mapper_;
// Once loaded from storage, the header will be cached here
std::optional<sysconfig_header> header_;
// The friend declaration here is mainly for allowing SynClientBuffered to re-use internal
// resources such as read_mapper_, without having to expose them, change access modifier or
// create another one. It makes possible the use of class composition method instead of
// inheritance, which is usually preferred.
friend class SyncClientBuffered;
friend class SyncClientAbrWearLeveling;
};
// SynClientBuffered is a wrapper of SyncClient added with write-caching capability.
// It buffers all the write to sysconfig partition to an internal buffer first.
// The bufferred data is written to persistant storage by explicitly calling its Flush() method.
class SyncClientBuffered {
public:
using PartitionType = SyncClient::PartitionType;
explicit SyncClientBuffered(::sysconfig::SyncClient client) : client_(std::move(client)) {}
virtual ~SyncClientBuffered() = default;
// Similar public interfaces as synclient
// The following can be re-implemented by child class.
virtual zx_status_t WritePartition(PartitionType partition, const zx::vmo& vmo,
zx_off_t vmo_offset);
virtual zx_status_t ReadPartition(PartitionType partition, const zx::vmo& vmo,
zx_off_t vmo_offset);
virtual zx_status_t Flush();
zx_status_t GetPartitionSize(PartitionType partition, size_t* size);
zx_status_t GetPartitionOffset(PartitionType partition, size_t* size);
// used for test
const uint8_t* GetCacheBuffer(PartitionType partition);
// No copy.
SyncClientBuffered(const SyncClientBuffered&) = delete;
SyncClientBuffered& operator=(const SyncClientBuffered&) = delete;
SyncClientBuffered(SyncClientBuffered&&) = default;
SyncClientBuffered& operator=(SyncClientBuffered&&) = default;
zx_status_t UpdateLayout(sysconfig_header target_header);
protected:
enum CacheBitMask {
kSysconfig = 1 << 0,
kAbrMetadata = 1 << 1,
kVbmetaA = 1 << 2,
kVbmetaB = 1 << 3,
kVbmetaR = 1 << 4
};
// A bit mask indicating whether the cache is empty
// 0 means empty, 1 means non-empty
uint32_t cache_modified_flag_ = 0;
fzl::OwnedVmoMapper cache_;
SyncClient client_;
zx_status_t CreateCache();
void InvalidateCache();
void MarkCacheNonEmpty(PartitionType partition);
bool IsCacheEmpty(PartitionType partition);
bool IsAllCacheEmpty();
zx_status_t WriteCache(PartitionType partition, const zx::vmo& vmo, zx_off_t vmo_offset);
zx_status_t ReadCache(PartitionType partition, const zx::vmo& vmo, zx_off_t vmo_offset);
uint32_t PartitionTypeToCacheMask(PartitionType partition);
zx_status_t GetSubpartitionCacheAddrSize(PartitionType partition, uint8_t** start, size_t* size);
};
/**
* Specialized sysconfig client for astro with NAND I/O optimization
* Implement buffered write + abr wear-leveling
*/
class SyncClientAbrWearLeveling : public SyncClientBuffered {
public:
using PartitionType = SyncClient::PartitionType;
explicit SyncClientAbrWearLeveling(::sysconfig::SyncClient client)
: SyncClientBuffered(std::move(client)) {}
// No copy.
SyncClientAbrWearLeveling(const SyncClientAbrWearLeveling&) = delete;
SyncClientAbrWearLeveling& operator=(const SyncClientAbrWearLeveling&) = delete;
SyncClientAbrWearLeveling(SyncClientAbrWearLeveling&&) = default;
SyncClientAbrWearLeveling& operator=(SyncClientAbrWearLeveling&&) = default;
zx_status_t ReadPartition(PartitionType partition, const zx::vmo& vmo,
zx_off_t vmo_offset) override;
zx_status_t Flush() override;
static const sysconfig_header& GetAbrWearLevelingSupportedLayout();
// For test purpose
uint32_t GetEraseCount() const { return erase_count_; }
zx_status_t ValidateAbrMetadataInStorage(const abr_metadata_ext* expected);
private:
bool IsOnlyAbrMetadataModified();
zx_status_t ReadLatestAbrMetadataFromStorage(const zx::vmo& vmo, zx_off_t vmo_offset);
zx_status_t FindLatestAbrMetadataFromStorage(abr_metadata_ext* out);
zx_status_t FlushAppendAbrMetadata(const sysconfig_header* header);
zx_status_t FlushReset(const sysconfig_header* header);
// For test purpose
uint32_t erase_count_ = 0;
};
} // namespace sysconfig
#endif // LIB_SYSCONFIG_SYNC_CLIENT_H_