| // Copyright 2021 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 "src/devices/bin/driver_runtime/arena.h" |
| |
| #include <fbl/ref_ptr.h> |
| |
| // static |
| fdf_status_t fdf_arena::Create(uint32_t options, const char* tag, size_t tag_len, |
| fdf_arena** out_arena) { |
| auto arena = fbl::AdoptRef(new fdf_arena()); |
| if (!arena) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| *out_arena = fbl::ExportToRawPtr(&arena); |
| return ZX_OK; |
| } |
| |
| void* fdf_arena::Allocate(size_t bytes) { |
| fbl::AutoLock lock(&lock_); |
| |
| bytes = FIDL_ALIGN(bytes); |
| |
| if (available_size_ < bytes) { |
| // The data doesn't fit within the current block => allocate a new block. |
| // Note: the data available at the end of the current block is lost forever (until the |
| // deallocation of the arena). |
| available_size_ = (bytes > ExtraBlock::kExtraSize) ? bytes : ExtraBlock::kExtraSize; |
| size_t extra_bytes = available_size_ + FIDL_ALIGN(sizeof(ExtraBlockNode)); |
| auto extra_block = new (new uint8_t[extra_bytes]) ExtraBlock(); |
| next_data_available_ = extra_block->data(); |
| extra_blocks_.push_front(extra_block); |
| |
| uintptr_t data = reinterpret_cast<uintptr_t>(next_data_available_); |
| allocated_ranges_.insert({data, available_size_}); |
| } |
| // At this point we have enough space within the current block (either because there was enough |
| // space within the existing block or because we allocate a new block). |
| uint8_t* data = next_data_available_; |
| next_data_available_ += bytes; |
| available_size_ -= bytes; |
| return data; |
| } |
| |
| // No-op for initial implementation. |
| void* fdf_arena::Free(void* data) { return NULL; } |
| |
| namespace { |
| |
| // Returns whether the range [addr, addr + num_bytes) contains |
| // [want_addr, want_addr + want_num_bytes). |
| bool contains_range(uintptr_t addr, size_t num_bytes, uintptr_t want_addr, size_t want_num_bytes) { |
| if (addr > want_addr) { |
| return false; |
| } |
| uintptr_t range_end = addr + num_bytes; |
| uintptr_t want_end = want_addr + want_num_bytes; |
| return want_end <= range_end; |
| } |
| |
| } // namespace |
| |
| bool fdf_arena::Contains(const void* data, size_t num_bytes) { |
| fbl::AutoLock lock(&lock_); |
| |
| uintptr_t want_addr = reinterpret_cast<uintptr_t>(data); |
| |
| // Check if the requested address lies in the initial buffer, or if we have to |
| // find it in the extra_blocks map. |
| uintptr_t allocated_addr = reinterpret_cast<uintptr_t>(initial_buffer_); |
| size_t allocated_size = kInitialBufferSize; |
| if (want_addr < allocated_addr || want_addr >= allocated_addr + allocated_size) { |
| // |std::upper_bound| will return the first element greater than the requested key. |
| auto it = allocated_ranges_.upper_bound(want_addr); |
| if (it == allocated_ranges_.begin()) { |
| return false; |
| } |
| it--; // This now points to the key less than or equal to the requested key. |
| allocated_addr = it->first; |
| allocated_size = it->second; |
| } |
| |
| // If we are checking against the newest buffer, part of it might actually not have been allocated |
| // to the user yet. |
| if (allocated_addr == reinterpret_cast<uintptr_t>(NewestBufferLocked())) { |
| ZX_ASSERT(allocated_size >= available_size_); |
| allocated_size -= available_size_; |
| } |
| return contains_range(allocated_addr, allocated_size, want_addr, num_bytes); |
| } |
| |
| void fdf_arena::Destroy() { __UNUSED auto ref = fbl::ImportFromRawPtr(this); } |
| |
| fdf_arena::~fdf_arena() __TA_NO_THREAD_SAFETY_ANALYSIS { |
| // Deletes all the extra blocks. |
| while (!extra_blocks_.is_empty()) { |
| auto* to_be_deleted = extra_blocks_.pop_front(); |
| delete[] reinterpret_cast<uint8_t*>(to_be_deleted); |
| } |
| } |