blob: 46e2592fd66d438482d85af1928f41c0887c0389 [file] [log] [blame]
// Copyright 2018 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.
#include <libzbi/zbi-zx.h>
#include <lib/zx/vmar.h>
#include <lib/zx/vmo.h>
#include <limits.h>
#include <string.h>
#include <zircon/assert.h>
#include <utility>
namespace zbi {
namespace {
size_t PageRound(size_t size) {
return (size + PAGE_SIZE) & -(size_t)PAGE_SIZE;
}
}
zx_status_t ZbiVMO::Init(zx::vmo vmo) {
vmo_ = std::move(vmo);
auto status = vmo_.get_size(&capacity_);
if (status == ZX_OK && capacity_ > 0) {
status = Map();
}
return status;
}
zx::vmo ZbiVMO::Release() {
Unmap();
capacity_= 0;
return std::move(vmo_);
}
ZbiVMO::~ZbiVMO() {
Unmap();
}
zx_status_t ZbiVMO::Map() {
uintptr_t mapping;
auto status = zx::vmar::root_self()->map(
0, vmo_, 0, capacity_, ZX_VM_PERM_READ | ZX_VM_PERM_WRITE,
&mapping);
if (status == ZX_OK) {
base_ = reinterpret_cast<uint8_t*>(mapping);
}
return status;
}
void ZbiVMO::Unmap() {
if (base_) {
[[maybe_unused]] auto status =
zx::vmar::root_self()->unmap(reinterpret_cast<uintptr_t>(base_),
capacity_);
ZX_DEBUG_ASSERT(status == ZX_OK);
base_ = nullptr;
}
}
zbi_result_t ZbiVMO::AppendSection(uint32_t length, uint32_t type,
uint32_t extra, uint32_t flags,
const void* payload) {
void* dest;
auto result = CreateSection(length, type, extra, flags, &dest);
if (result == ZBI_RESULT_OK) {
memcpy(dest, payload, length);
}
return result;
}
zbi_result_t ZbiVMO::CreateSection(uint32_t length, uint32_t type,
uint32_t extra, uint32_t flags,
void** payload) {
auto result = Zbi::CreateSection(length, type, extra, flags, payload);
if (result == ZBI_RESULT_TOO_BIG) {
const size_t new_capacity =
PageRound(Length() + sizeof(zbi_header_t) + length);
ZX_DEBUG_ASSERT(new_capacity > capacity_);
auto status = vmo_.set_size(new_capacity);
if (status == ZX_OK) {
Unmap();
capacity_ = new_capacity;
status = Map();
}
if (status == ZX_OK) {
result = Zbi::CreateSection(length, type, extra, flags, payload);
}
}
return result;
}
zbi_result_t ZbiVMO::SplitComplete(ZbiVMO* kernel, ZbiVMO* data) const {
// First check that it's a proper complete ZBI. After this it should be
// safe to trust the headers (modulo racing modification of the original
// VMO, which we can't help).
auto result = CheckComplete();
if (result != ZBI_RESULT_OK) {
return result;
}
// First clone a VMO covering just the leading kernel portion of the ZBI.
auto kernel_hdr = Header() + 1;
const uint32_t kernel_size =
static_cast<uint32_t>(sizeof(zbi_header_t) * 2) + kernel_hdr->length;
const size_t kernel_vmo_size = PageRound(kernel_size);
auto status = vmo_.create_child(ZX_VMO_CHILD_COPY_ON_WRITE,
0, kernel_vmo_size, &kernel->vmo_);
if (status != ZX_OK) {
return ZBI_RESULT_TOO_BIG;
}
// Map it in.
kernel->Unmap(); // Just in case.
kernel->capacity_ = kernel_vmo_size;
status = kernel->Map();
if (status != ZX_OK) {
return ZBI_RESULT_TOO_BIG;
}
// Update the size in the copied container header.
kernel->Header()->length =
kernel_size - static_cast<uint32_t>(sizeof(zbi_header_t));
// Now create (or clone if possible) a VMO for the remainder.
const uint32_t data_payload_size = Length() - kernel_size;
const size_t data_vmo_size = PageRound(
data_payload_size + static_cast<uint32_t>(sizeof(zbi_header_t)));
// If by some miracle the remainder is aligned exactly right, then
// we can clone the trailing portion as well.
bool clone = (kernel_size - sizeof(zbi_header_t)) % PAGE_SIZE == 0;
status = clone ?
vmo_.create_child(ZX_VMO_CHILD_COPY_ON_WRITE,
kernel_size - sizeof(zbi_header_t),
data_vmo_size, &data->vmo_) :
vmo_.create(data_vmo_size, 0, &data->vmo_);
if (status != ZX_OK) {
return ZBI_RESULT_TOO_BIG;
}
// Map it in.
data->Unmap(); // Just in case.
data->capacity_ = data_vmo_size;
status = data->Map();
if (status != ZX_OK) {
return ZBI_RESULT_TOO_BIG;
}
// Fill in the header and data (if not already virtually copied).
*data->Header() = (zbi_header_t)ZBI_CONTAINER_HEADER(data_payload_size);
if (!clone) {
memcpy(data->Payload(), Base() + kernel_size, data_payload_size);
}
return ZBI_RESULT_OK;
}
// C API wrapper.
zbi_result_t SplitCompleteWrapper(zx_handle_t zbi_vmo,
zx_handle_t* kernel_vmo,
zx_handle_t* data_vmo) {
ZbiVMO zbi, kernel, data;
auto status = zbi.Init(zx::vmo(zbi_vmo));
if (status != ZX_OK) {
return ZBI_RESULT_TOO_BIG;
}
auto result = zbi.SplitComplete(&kernel, &data);
if (result == ZBI_RESULT_OK) {
*kernel_vmo = kernel.vmo_.release();
*data_vmo = data.vmo_.release();
}
return result;
}
zbi_result_t zbi_split_complete(zx_handle_t zbi_vmo,
zx_handle_t* kernel_vmo,
zx_handle_t* data_vmo) {
return SplitCompleteWrapper(zbi_vmo, kernel_vmo, data_vmo);
}
} // namespace zbi