blob: 012f26cbc62cfe5e4a9b4d76f9b5bf0b40a38482 [file] [log] [blame]
// Copyright 2022 The Chromium 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 "ipcz/node_link_memory.h"
#include <atomic>
#include <cstddef>
#include <cstdint>
#include <utility>
#include "ipcz/buffer_id.h"
#include "ipcz/driver_memory.h"
#include "ipcz/ipcz.h"
#include "ipcz/node.h"
#include "ipcz/node_link.h"
#include "util/ref_counted.h"
namespace ipcz {
namespace {
constexpr BufferId kPrimaryBufferId{0};
// Fixed allocation size for each NodeLink's primary shared buffer.
constexpr size_t kPrimaryBufferSize = 65536;
// The front of the primary buffer is reserved for special current and future
// uses which require synchronous availability throughout a link's lifetime.
constexpr size_t kPrimaryBufferReservedHeaderSize = 256;
struct IPCZ_ALIGN(8) PrimaryBufferHeader {
// Atomic generator for new unique BufferIds to use across the associated
// NodeLink. This allows each side of a NodeLink to generate new BufferIds
// spontaneously without synchronization or risk of collisions.
std::atomic<uint64_t> next_buffer_id;
// Atomic generator for new unique SublinkIds to use across the associated
// NodeLink. This allows each side of a NodeLink to generate new SublinkIds
// spontaneously without synchronization or risk of collisions.
std::atomic<uint64_t> next_sublink_id;
};
static_assert(sizeof(PrimaryBufferHeader) < kPrimaryBufferReservedHeaderSize);
constexpr size_t kPrimaryBufferHeaderPaddingSize =
kPrimaryBufferReservedHeaderSize - sizeof(PrimaryBufferHeader);
} // namespace
// This structure always sits at offset 0 in the primary buffer and has a fixed
// layout according to the NodeLink's agreed upon protocol version. This is the
// layout for version 0 (currently the only version.)
struct IPCZ_ALIGN(8) NodeLinkMemory::PrimaryBuffer {
// Header + padding occupies the first 256 bytes.
PrimaryBufferHeader header;
uint8_t reserved_header_padding[kPrimaryBufferHeaderPaddingSize];
// Reserved memory for a series of fixed block allocators. Additional
// allocators may be adopted by a NodeLinkMemory over its lifetime, but these
// ones remain fixed within the primary buffer.
std::array<uint8_t, 4096> mem_for_64_byte_blocks;
std::array<uint8_t, 12288> mem_for_256_byte_blocks;
std::array<uint8_t, 15360> mem_for_512_byte_blocks;
std::array<uint8_t, 11264> mem_for_1024_byte_blocks;
std::array<uint8_t, 16384> mem_for_2048_byte_blocks;
BlockAllocator block_allocator_64() {
return BlockAllocator(absl::MakeSpan(mem_for_64_byte_blocks), 64);
}
BlockAllocator block_allocator_256() {
return BlockAllocator(absl::MakeSpan(mem_for_256_byte_blocks), 256);
}
BlockAllocator block_allocator_512() {
return BlockAllocator(absl::MakeSpan(mem_for_512_byte_blocks), 512);
}
BlockAllocator block_allocator_1024() {
return BlockAllocator(absl::MakeSpan(mem_for_1024_byte_blocks), 1024);
}
BlockAllocator block_allocator_2048() {
return BlockAllocator(absl::MakeSpan(mem_for_2048_byte_blocks), 2048);
}
};
NodeLinkMemory::NodeLinkMemory(Ref<Node> node,
DriverMemoryMapping primary_buffer_memory)
: node_(std::move(node)),
primary_buffer_memory_(primary_buffer_memory.bytes()),
primary_buffer_(
*reinterpret_cast<PrimaryBuffer*>(primary_buffer_memory_.data())) {
// Consistency check here, because PrimaryBuffer is private to NodeLinkMemory.
static_assert(sizeof(PrimaryBuffer) <= kPrimaryBufferSize,
"PrimaryBuffer structure is too large.");
buffer_pool_.AddBuffer(kPrimaryBufferId, std::move(primary_buffer_memory));
buffer_pool_.RegisterBlockAllocator(kPrimaryBufferId,
primary_buffer_.block_allocator_64());
buffer_pool_.RegisterBlockAllocator(kPrimaryBufferId,
primary_buffer_.block_allocator_256());
buffer_pool_.RegisterBlockAllocator(kPrimaryBufferId,
primary_buffer_.block_allocator_512());
buffer_pool_.RegisterBlockAllocator(kPrimaryBufferId,
primary_buffer_.block_allocator_1024());
buffer_pool_.RegisterBlockAllocator(kPrimaryBufferId,
primary_buffer_.block_allocator_2048());
}
NodeLinkMemory::~NodeLinkMemory() = default;
// static
NodeLinkMemory::Allocation NodeLinkMemory::Allocate(Ref<Node> node) {
DriverMemory primary_buffer_memory(node->driver(), sizeof(PrimaryBuffer));
if (!primary_buffer_memory.is_valid()) {
return {.node_link_memory = nullptr, .primary_buffer_memory = {}};
}
auto memory = AdoptRef(
new NodeLinkMemory(std::move(node), primary_buffer_memory.Map()));
PrimaryBuffer& primary_buffer = memory->primary_buffer_;
// The first allocable BufferId is 1, because the primary buffer uses 0.
primary_buffer.header.next_buffer_id.store(1, std::memory_order_relaxed);
// The first allocable SublinkId is kMaxInitialPortals. This way it doesn't
// matter whether the two ends of a NodeLink initiate their connection with a
// different initial portal count: neither can request more than
// kMaxInitialPortals, so neither will be assuming initial ownership of any
// SublinkIds at or above this value.
primary_buffer.header.next_sublink_id.store(kMaxInitialPortals,
std::memory_order_release);
primary_buffer.block_allocator_64().InitializeRegion();
primary_buffer.block_allocator_256().InitializeRegion();
primary_buffer.block_allocator_512().InitializeRegion();
primary_buffer.block_allocator_1024().InitializeRegion();
primary_buffer.block_allocator_2048().InitializeRegion();
return {
.node_link_memory = std::move(memory),
.primary_buffer_memory = std::move(primary_buffer_memory),
};
}
// static
Ref<NodeLinkMemory> NodeLinkMemory::Adopt(Ref<Node> node,
DriverMemory primary_buffer_memory) {
return AdoptRef(
new NodeLinkMemory(std::move(node), primary_buffer_memory.Map()));
}
BufferId NodeLinkMemory::AllocateNewBufferId() {
return BufferId{primary_buffer_.header.next_buffer_id.fetch_add(
1, std::memory_order_relaxed)};
}
SublinkId NodeLinkMemory::AllocateSublinkIds(size_t count) {
return SublinkId{primary_buffer_.header.next_sublink_id.fetch_add(
count, std::memory_order_relaxed)};
}
} // namespace ipcz