| // 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 <lib/fake-bti/bti.h> |
| #include <lib/fake-object/object.h> |
| #include <lib/zircon-internal/thread_annotations.h> |
| #include <lib/zx/status.h> |
| #include <lib/zx/vmo.h> |
| #include <zircon/assert.h> |
| #include <zircon/status.h> |
| #include <zircon/syscalls.h> |
| |
| #include <utility> |
| |
| #include <fbl/auto_lock.h> |
| #include <fbl/mutex.h> |
| #include <fbl/ref_counted.h> |
| #include <fbl/ref_ptr.h> |
| #include <fbl/span.h> |
| #include <fbl/vector.h> |
| |
| #include "zircon/errors.h" |
| |
| // Normally just defined in the kernel: |
| #define PAGE_SIZE_SHIFT 12 |
| |
| namespace { |
| class Bti final : public fake_object::Object { |
| public: |
| virtual ~Bti() = default; |
| |
| static zx_status_t Create(fbl::Span<const zx_paddr_t> paddrs, |
| fbl::RefPtr<fake_object::Object>* out) { |
| *out = fbl::AdoptRef(new Bti(paddrs)); |
| return ZX_OK; |
| } |
| |
| zx_status_t get_info(zx_handle_t handle, uint32_t topic, void* buffer, size_t buffer_size, |
| size_t* actual_count, size_t* avail_count) override; |
| |
| fake_object::HandleType type() const final { return fake_object::HandleType::BTI; } |
| |
| uint64_t& pmo_count() { return pmo_count_; } |
| |
| bool PopulatePaddrs(zx_paddr_t* paddrs, size_t paddrs_count) { |
| for (size_t i = 0; i < paddrs_count; i++) { |
| if (paddrs_.empty()) { |
| paddrs[i] = FAKE_BTI_PHYS_ADDR; |
| } else { |
| if (paddrs_index_ >= paddrs_.size()) { |
| return false; |
| } |
| paddrs[i] = paddrs_[paddrs_index_++]; |
| } |
| } |
| return true; |
| } |
| |
| private: |
| Bti() = default; |
| explicit Bti(fbl::Span<const zx_paddr_t> paddrs) : paddrs_(paddrs) {} |
| |
| fbl::Span<const zx_paddr_t> paddrs_ = {}; |
| size_t paddrs_index_ = 0; |
| uint64_t pmo_count_ = 0; |
| }; |
| |
| class Pmt final : public fake_object::Object { |
| public: |
| virtual ~Pmt() = default; |
| |
| static zx_status_t Create(zx::vmo vmo, uint64_t offset, uint64_t size, fbl::RefPtr<Bti> bti, |
| fbl::RefPtr<fake_object::Object>* out) { |
| fbl::RefPtr<Pmt> pmt(fbl::AdoptRef(new Pmt(std::move(vmo), offset, size, std::move(bti)))); |
| // These lines exist because currently offset_ and size_ are unused, and |
| // GCC and Clang disagree about whether or not marking them as unused is acceptable. |
| (void)pmt->offset_; |
| (void)pmt->size_; |
| *out = std::move(pmt); |
| return ZX_OK; |
| } |
| |
| fake_object::HandleType type() const final { return fake_object::HandleType::PMT; } |
| Bti& bti() { return *bti_; } |
| |
| private: |
| Pmt(zx::vmo vmo, uint64_t offset, uint64_t size, fbl::RefPtr<Bti> bti) |
| : vmo_(std::move(vmo)), offset_(offset), size_(size), bti_(std::move(bti)) {} |
| |
| zx::vmo vmo_; |
| uint64_t offset_; |
| uint64_t size_; |
| fbl::RefPtr<Bti> bti_; |
| }; |
| } // namespace |
| // Fake BTI API |
| |
| // Implements fake-bti's version of |zx_object_get_info|. |
| zx_status_t Bti::get_info(zx_handle_t handle, uint32_t topic, void* buffer, size_t buffer_size, |
| size_t* actual_count, size_t* avail_count) { |
| zx::status status = fake_object::FakeHandleTable().Get(handle); |
| if (!status.is_ok()) { |
| printf("fake_bti_get_info: Failed to find handle %u\n", handle); |
| return status.status_value(); |
| } |
| fbl::RefPtr<fake_object::Object> obj = std::move(status.value()); |
| if (obj->type() == fake_object::HandleType::BTI) { |
| auto bti_obj = fbl::RefPtr<Bti>::Downcast(std::move(obj)); |
| switch (topic) { |
| case ZX_INFO_BTI: { |
| if (avail_count) { |
| *avail_count = 1; |
| } |
| if (actual_count) { |
| *actual_count = 0; |
| } |
| if (buffer_size < sizeof(zx_info_bti_t)) { |
| return ZX_ERR_BUFFER_TOO_SMALL; |
| } |
| zx_info_bti_t info = { |
| .minimum_contiguity = ZX_PAGE_SIZE, |
| .aspace_size = UINT64_MAX, |
| .pmo_count = bti_obj->pmo_count(), |
| .quarantine_count = 0, |
| }; |
| memcpy(buffer, &info, sizeof(info)); |
| if (actual_count) { |
| *actual_count = 1; |
| } |
| return ZX_OK; |
| } |
| default: |
| ZX_ASSERT_MSG(false, "fake object_get_info: Unsupported BTI topic %u\n", topic); |
| } |
| } |
| ZX_ASSERT_MSG(false, "fake object_get_info: Unsupported PMT topic %u\n", topic); |
| } |
| |
| __EXPORT |
| zx_status_t fake_bti_create(zx_handle_t* out) { |
| return fake_bti_create_with_paddrs(nullptr, 0, out); |
| } |
| |
| __EXPORT |
| zx_status_t fake_bti_create_with_paddrs(const zx_paddr_t* paddrs, size_t paddr_count, |
| zx_handle_t* out) { |
| fbl::RefPtr<fake_object::Object> new_bti; |
| zx_status_t status = Bti::Create(fbl::Span(paddrs, paddr_count), &new_bti); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| zx::status add_status = fake_object::FakeHandleTable().Add(std::move(new_bti)); |
| if (add_status.is_ok()) { |
| *out = add_status.value(); |
| } |
| |
| return add_status.status_value(); |
| } |
| |
| // Fake syscall implementations |
| __EXPORT |
| zx_status_t zx_bti_pin(zx_handle_t bti_handle, uint32_t options, zx_handle_t vmo, uint64_t offset, |
| uint64_t size, zx_paddr_t* addrs, size_t addrs_count, zx_handle_t* out) { |
| zx::status status = fake_object::FakeHandleTable().Get(bti_handle); |
| ZX_ASSERT_MSG(status.is_ok() && status.value()->type() == fake_object::HandleType::BTI, |
| "fake bti_pin: Bad handle %u\n", bti_handle); |
| fbl::RefPtr<Bti> bti_obj = fbl::RefPtr<Bti>::Downcast(std::move(status.value())); |
| |
| zx::vmo vmo_clone; |
| zx_status_t vmo_status = zx::unowned_vmo(vmo)->duplicate(ZX_RIGHT_SAME_RIGHTS, &vmo_clone); |
| if (vmo_status != ZX_OK) { |
| return vmo_status; |
| } |
| |
| zx_info_handle_basic_t handle_info; |
| vmo_status = |
| vmo_clone.get_info(ZX_INFO_HANDLE_BASIC, &handle_info, sizeof(handle_info), nullptr, nullptr); |
| ZX_ASSERT_MSG(vmo_status == ZX_OK, "fake bti_pin: Failed to get VMO info\n"); |
| const zx_rights_t vmo_rights = handle_info.rights; |
| if (!(vmo_rights & ZX_RIGHT_MAP)) { |
| return ZX_ERR_ACCESS_DENIED; |
| } |
| |
| // Check argument validity |
| if (offset % ZX_PAGE_SIZE != 0) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| if (size % ZX_PAGE_SIZE != 0) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| // Validate options |
| bool compress_results = false; |
| bool contiguous = false; |
| if (options & ZX_BTI_PERM_READ) { |
| if (!(vmo_rights & ZX_RIGHT_READ)) { |
| return ZX_ERR_ACCESS_DENIED; |
| } |
| options &= ~ZX_BTI_PERM_READ; |
| } |
| if (options & ZX_BTI_PERM_WRITE) { |
| if (!(vmo_rights & ZX_RIGHT_WRITE)) { |
| return ZX_ERR_ACCESS_DENIED; |
| } |
| options &= ~ZX_BTI_PERM_WRITE; |
| } |
| if (options & ZX_BTI_PERM_EXECUTE) { |
| // Note: We check ZX_RIGHT_READ instead of ZX_RIGHT_EXECUTE |
| // here because the latter applies to execute permission of |
| // the host CPU, whereas ZX_BTI_PERM_EXECUTE applies to |
| // transactions initiated by the bus device. |
| if (!(vmo_rights & ZX_RIGHT_READ)) { |
| return ZX_ERR_ACCESS_DENIED; |
| } |
| options &= ~ZX_BTI_PERM_EXECUTE; |
| } |
| if (!((options & ZX_BTI_COMPRESS) && (options & ZX_BTI_CONTIGUOUS))) { |
| if (options & ZX_BTI_COMPRESS) { |
| compress_results = true; |
| options &= ~ZX_BTI_COMPRESS; |
| } |
| if (options & ZX_BTI_CONTIGUOUS) { |
| contiguous = true; |
| options &= ~ZX_BTI_CONTIGUOUS; |
| } |
| } |
| if (options) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| if (compress_results || !contiguous) { |
| if (addrs_count != size / ZX_PAGE_SIZE) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| } else { |
| if (addrs_count != 1) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| } |
| // Fill |addrs| with the fake physical address. |
| if (!bti_obj->PopulatePaddrs(addrs, addrs_count)) { |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| |
| fbl::RefPtr<fake_object::Object> new_pmt; |
| zx_status_t pmt_status = Pmt::Create(std::move(vmo_clone), offset, size, bti_obj, &new_pmt); |
| if (pmt_status != ZX_OK) { |
| return pmt_status; |
| } |
| |
| zx::status add_status = fake_object::FakeHandleTable().Add(std::move(new_pmt)); |
| if (add_status.is_ok()) { |
| *out = add_status.value(); |
| ++bti_obj->pmo_count(); |
| } |
| |
| return add_status.status_value(); |
| } |
| |
| __EXPORT |
| zx_status_t zx_bti_release_quarantine(zx_handle_t handle) { |
| zx::status status = fake_object::FakeHandleTable().Get(handle); |
| ZX_ASSERT_MSG(status.is_ok() && status.value()->type() == fake_object::HandleType::BTI, |
| "fake bti_release_quarantine: Bad handle %u\n", handle); |
| return ZX_OK; |
| } |
| |
| __EXPORT |
| zx_status_t zx_pmt_unpin(zx_handle_t handle) { |
| zx::status get_status = fake_object::FakeHandleTable().Get(handle); |
| ZX_ASSERT_MSG(get_status.is_ok() && get_status.value()->type() == fake_object::HandleType::PMT, |
| "fake pmt_unpin: Bad handle %u\n", handle); |
| fbl::RefPtr<Pmt> pmt = fbl::RefPtr<Pmt>::Downcast(std::move(get_status.value())); |
| --pmt->bti().pmo_count(); |
| zx::status remove_status = fake_object::FakeHandleTable().Remove(handle); |
| ZX_ASSERT_MSG(remove_status.is_ok(), "fake pmt_unpin: Failed to remove handle %u: %s\n", handle, |
| zx_status_get_string(remove_status.status_value())); |
| return ZX_OK; |
| } |
| |
| // A fake version of zx_vmo_create_contiguous. This version just creates a normal vmo. |
| __EXPORT |
| zx_status_t zx_vmo_create_contiguous(zx_handle_t bti_handle, size_t size, uint32_t alignment_log2, |
| zx_handle_t* out) { |
| if (size == 0) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| if (alignment_log2 == 0) { |
| alignment_log2 = PAGE_SIZE_SHIFT; |
| } |
| // catch obviously wrong values |
| if (alignment_log2 < PAGE_SIZE_SHIFT || alignment_log2 >= (8 * sizeof(uint64_t))) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| // Make sure this is a valid fake bti: |
| fbl::RefPtr<fake_object::Object> bti_obj; |
| zx::status get_status = fake_object::FakeHandleTable().Get(bti_handle); |
| ZX_ASSERT_MSG(get_status.is_ok() && get_status.value()->type() == fake_object::HandleType::BTI, |
| "fake bti_pin: Bad handle %u\n", bti_handle); |
| |
| // For this fake implementation, just create a normal vmo: |
| return zx_vmo_create(size, 0, out); |
| } |