blob: b33c20dac90b6d584e871c88fa177e3aacb2a5c8 [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.
// clang-format off
#include <lib/sysconfig/sync-client.h>
// clang-format on
#include <dirent.h>
#include <fcntl.h>
#include <fbl/algorithm.h>
#include <fuchsia/sysinfo/llcpp/fidl.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/watcher.h>
#include <lib/fzl/fdio.h>
#include <lib/zx/channel.h>
#include <lib/zx/time.h>
#include <string.h>
#include <zircon/hw/gpt.h>
namespace sysconfig {
namespace skipblock = ::llcpp::fuchsia::hardware::skipblock;
namespace {
constexpr size_t kKilobyte = 1 << 10;
constexpr size_t kSysconfigSize = 60 * kKilobyte;
constexpr size_t kABRMetadtaSize = 4 * kKilobyte;
constexpr size_t kVerifiedBootMetadataSize = 64 * kKilobyte;
struct AstroSysconfigPartition {
uint8_t sysconfig[kSysconfigSize];
uint8_t abr_metadata[kABRMetadtaSize];
uint8_t vb_metadata_a[kVerifiedBootMetadataSize];
uint8_t vb_metadata_b[kVerifiedBootMetadataSize];
uint8_t vb_metadata_r[kVerifiedBootMetadataSize];
} __PACKED;
static_assert(sizeof(AstroSysconfigPartition) == 256 * kKilobyte);
zx_status_t FindSysconfigPartition(const fbl::unique_fd& devfs_root,
std::optional<skipblock::SkipBlock::SyncClient>* out) {
fbl::unique_fd dir_fd(openat(devfs_root.get(), "class/skip-block/", O_RDONLY));
if (!dir_fd) {
return ZX_ERR_IO;
}
DIR* dir = fdopendir(dir_fd.release());
if (dir == nullptr) {
return ZX_ERR_IO;
}
const auto closer = fbl::MakeAutoCall([&dir]() { closedir(dir); });
auto watch_dir_event_cb = [](int dirfd, int event, const char* filename, void* cookie) {
if (event != WATCH_EVENT_ADD_FILE) {
return ZX_OK;
}
if ((strcmp(filename, ".") == 0) || strcmp(filename, "..") == 0) {
return ZX_OK;
}
zx::channel local, remote;
zx_status_t status = zx::channel::create(0, &local, &remote);
if (status != ZX_OK) {
return status;
}
fzl::UnownedFdioCaller caller(dirfd);
status = fdio_service_connect_at(caller.borrow_channel(), filename, remote.release());
if (status != ZX_OK) {
return ZX_OK;
}
skipblock::SkipBlock::SyncClient skip_block(std::move(local));
auto result = skip_block.GetPartitionInfo();
status = result.ok() ? result.value().status : result.status();
if (status != ZX_OK) {
return ZX_OK;
}
const auto& response = result.value();
const uint8_t type[] = GUID_SYS_CONFIG_VALUE;
if (memcmp(response.partition_info.partition_guid.data(), type, skipblock::GUID_LEN) != 0) {
return ZX_OK;
}
auto* out = static_cast<std::optional<skipblock::SkipBlock::SyncClient>*>(cookie);
*out = std::move(skip_block);
return ZX_ERR_STOP;
};
const zx::time deadline = zx::deadline_after(zx::sec(5));
if (fdio_watch_directory(dirfd(dir), watch_dir_event_cb, deadline.get(), out) != ZX_ERR_STOP) {
return ZX_ERR_NOT_FOUND;
}
return ZX_OK;
}
zx_status_t CheckIfAstro(const fbl::unique_fd& devfs_root) {
fzl::UnownedFdioCaller caller(devfs_root.get());
zx::channel local, remote;
zx_status_t status = zx::channel::create(0, &local, &remote);
if (status != ZX_OK) {
return status;
}
status = fdio_service_connect_at(caller.borrow_channel(),"misc/sysinfo", remote.release());
if (status != ZX_OK) {
return ZX_OK;
}
auto result = ::llcpp::fuchsia::sysinfo::Device::Call::GetBoardName(zx::unowned(local));
status = result.ok() ? result->status : result.status();
if (status != ZX_OK) {
return status;
}
if (strncmp(result->name.data(), "astro", result->name.size()) == 0) {
return ZX_OK;
}
return ZX_ERR_NOT_SUPPORTED;
}
} // namespace
zx_status_t SyncClient::Create(std::optional<SyncClient>* out) {
fbl::unique_fd devfs_root(open("/dev", O_RDWR));
if (!devfs_root) {
return ZX_ERR_IO;
}
return Create(devfs_root, out);
}
zx_status_t SyncClient::Create(const fbl::unique_fd& devfs_root, std::optional<SyncClient>* out) {
// TODO(surajmalhotra): This is just a temporary measure to allow us to hardcode constants into
// this library safely. For future products, the library should be updated to use some sort of
// configuration file to determine partition layout.
auto status = CheckIfAstro(devfs_root);
if (status != ZX_OK) {
return status;
}
std::optional<skipblock::SkipBlock::SyncClient> skip_block;
status = FindSysconfigPartition(devfs_root, &skip_block);
if (status != ZX_OK) {
return status;
}
*out = SyncClient(*std::move(skip_block));
return ZX_OK;
}
zx_status_t SyncClient::WritePartition(PartitionType partition, const zx::vmo& vmo,
zx_off_t vmo_offset) {
zx::vmo dup;
if (zx_status_t status = vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &dup); status != ZX_OK) {
return status;
}
skipblock::WriteBytesOperation operation = {
.vmo = std::move(dup),
.vmo_offset = vmo_offset,
.offset = GetPartitionOffset(partition),
.size = GetPartitionSize(partition),
};
auto result = skip_block_.WriteBytes(std::move(operation));
return result.ok() ? result.value().status : result.status();
}
zx_status_t SyncClient::InitializeReadMapper() {
auto result = skip_block_.GetPartitionInfo();
zx_status_t status = result.ok() ? result.value().status : result.status();
if (status != ZX_OK) {
return status;
}
const uint64_t block_size = result.value().partition_info.block_size_bytes;
return read_mapper_.CreateAndMap(fbl::round_up(block_size, ZX_PAGE_SIZE), "sysconfig read");
}
zx_status_t SyncClient::ReadPartition(PartitionType partition, const zx::vmo& vmo,
zx_off_t vmo_offset) {
// Lazily create read mapper.
if (read_mapper_.start() == nullptr) {
zx_status_t status = InitializeReadMapper();
if (status != ZX_OK) {
return status;
}
}
zx::vmo dup;
if (zx_status_t status = read_mapper_.vmo().duplicate(ZX_RIGHT_SAME_RIGHTS, &dup);
status != ZX_OK) {
return status;
}
skipblock::ReadWriteOperation operation = {
.vmo = std::move(dup),
.vmo_offset = 0,
.block = 0,
.block_count = 1,
};
auto result = skip_block_.Read(std::move(operation));
zx_status_t status = result.ok() ? result.value().status : result.status();
if (status != ZX_OK) {
return status;
}
return vmo.write(reinterpret_cast<uint8_t*>(read_mapper_.start()) + GetPartitionOffset(partition),
vmo_offset, GetPartitionSize(partition));
}
size_t SyncClient::GetPartitionSize(PartitionType partition) {
switch (partition) {
case PartitionType::kSysconfig:
return kSysconfigSize;
case PartitionType::kABRMetadata:
return kABRMetadtaSize;
case PartitionType::kVerifiedBootMetadataA:
case PartitionType::kVerifiedBootMetadataB:
case PartitionType::kVerifiedBootMetadataR:
return kVerifiedBootMetadataSize;
}
ZX_ASSERT(false); // Unreachable.
}
size_t SyncClient::GetPartitionOffset(PartitionType partition) {
switch (partition) {
case PartitionType::kSysconfig:
return offsetof(AstroSysconfigPartition, sysconfig);
case PartitionType::kABRMetadata:
return offsetof(AstroSysconfigPartition, abr_metadata);
case PartitionType::kVerifiedBootMetadataA:
return offsetof(AstroSysconfigPartition, vb_metadata_a);
case PartitionType::kVerifiedBootMetadataB:
return offsetof(AstroSysconfigPartition, vb_metadata_b);
case PartitionType::kVerifiedBootMetadataR:
return offsetof(AstroSysconfigPartition, vb_metadata_r);
}
ZX_ASSERT(false); // Unreachable.
}
} // namespace sysconfig