blob: a3775fae019856b0672ac2e3d8d9ce44b630e543 [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.
#include <lib/fake-object/object.h>
#include <lib/fake-resource/resource.h>
#include <lib/zx/vmo.h>
#include <zircon/assert.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/resource.h>
#include <array>
#include <utility>
#include <fbl/auto_lock.h>
#include <fbl/mutex.h>
#include <fbl/ref_counted.h>
#include <fbl/ref_ptr.h>
#include <fbl/vector.h>
#include "zircon/errors.h"
#include "zircon/syscalls/object.h"
namespace {
// Implement a basic fake Resource object to use with accompanying syscalls.
// This object will only be to spec in regards to having a |kind| and inclusive
// range. Only shared resources are permitted at this time to reduce complexity
// as exclusive resources are not needed for most test purposes. It is not permitted
// to create a |root| resource through this interface.
class Resource final : public Object {
public:
virtual ~Resource() = default;
static zx_status_t Create(zx_paddr_t base, size_t size, zx_rsrc_kind_t kind,
zx_rsrc_flags_t flags, const char* name, size_t name_len,
fbl::RefPtr<Object>* out) {
*out = fbl::AdoptRef(new Resource(base, size, kind, flags, name, name_len));
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;
HandleType type() const final { return HandleType::RESOURCE; }
zx_paddr_t base() const { return base_; }
size_t size() const { return size_; }
zx_rsrc_kind_t kind() const { return kind_; }
bool is_exclusive() const { return is_exclusive_; }
private:
zx_paddr_t base_;
size_t size_;
zx_rsrc_kind_t kind_;
const bool is_exclusive_;
std::array<char, ZX_MAX_NAME_LEN> name_;
Resource(zx_paddr_t base, size_t size, zx_rsrc_kind_t kind, zx_rsrc_flags_t flags,
const char* name, size_t name_len)
: base_(base), size_(size), kind_(kind), is_exclusive_(flags & ZX_RSRC_FLAG_EXCLUSIVE) {
ZX_ASSERT_MSG(kind_ != ZX_RSRC_KIND_IRQ && kind_ != ZX_RSRC_KIND_HYPERVISOR &&
kind_ != ZX_RSRC_KIND_VMEX && kind_ != ZX_RSRC_KIND_SMC,
"fake-resource: unsupported kind: %u\n", kind);
memcpy(name_.data(), name, name_len);
}
};
// Returns true if r2 is valid within r1
bool is_valid_range(zx_paddr_t r1_base, size_t r1_size, zx_paddr_t r2_base, size_t r2_size) {
return (r2_base >= r1_base && r2_base + r2_size <= r1_base + r1_size);
}
zx_status_t get_resource_from_handle(const char* caller, zx_handle_t handle,
fbl::RefPtr<Object>* obj) {
zx_status_t st = FakeHandleTable().Get(handle, obj);
ZX_ASSERT_MSG(st == ZX_OK, "fake_%s: failed to find handle\n", caller);
ZX_ASSERT_MSG((*obj)->type() == HandleType::RESOURCE, "fake_%s: wrong object type: %u\n", caller,
static_cast<uint32_t>((*obj)->type()));
return st;
}
bool exclusive_region_overlaps(zx_rsrc_kind_t kind, zx_paddr_t new_rsrc_base,
size_t new_rsrc_size) {
bool overlaps = false;
zx_paddr_t new_rsrc_end = new_rsrc_base + new_rsrc_size;
FakeHandleTable().ForEach(HandleType::RESOURCE, [&](Object* obj) -> bool {
auto* rsrc = static_cast<Resource*>(obj);
// In the case of exclusive resources we need to ensure the new resource does not
// overlap with existing exclusive ranges.
if (rsrc->kind() == kind && rsrc->is_exclusive()) {
zx_paddr_t rsrc_end = rsrc->base() + rsrc->size();
// If we overlap from the base side of the resource
if ((new_rsrc_base <= rsrc->base() && new_rsrc_end > rsrc->base()) ||
// If we start inside the existing resource
(new_rsrc_base >= rsrc->base() && new_rsrc_base <= rsrc_end)) {
overlaps = true;
return false;
}
}
return true;
});
return overlaps;
}
} // namespace
// Implements fake-resources version of |zx_object_get_info|.
zx_status_t Resource::get_info(zx_handle_t handle, uint32_t topic, void* buffer, size_t buffer_size,
size_t* actual_count, size_t* avail_count) {
ZX_ASSERT_MSG(topic == ZX_INFO_RESOURCE, "fake_resource_get_info: wrong topic type: %u\n", topic);
ZX_ASSERT_MSG(buffer_size >= sizeof(zx_info_resource_t),
"fake_resource_get_info: info buffer is too small (actual: %zu, needed: %zu)\n",
buffer_size, sizeof(zx_info_resource_t));
fbl::RefPtr<Object> obj;
ZX_ASSERT(get_resource_from_handle(__func__, handle, &obj) == ZX_OK);
auto* info = static_cast<zx_info_resource_t*>(buffer);
info->base = base_;
info->size = size_;
info->kind = kind_;
memcpy(info->name, name_.data(), name_.size());
info->flags = 0;
if (actual_count) {
*actual_count = 1;
}
if (avail_count) {
*avail_count = 1;
}
return ZX_OK;
}
__BEGIN_CDECLS
__EXPORT
zx_status_t zx_resource_create(zx_handle_t parent_rsrc, uint32_t options, uint64_t base,
size_t size, const char* name, size_t name_size,
zx_handle_t* resource_out) {
fbl::RefPtr<Object> parent_base;
ZX_ASSERT(get_resource_from_handle(__func__, parent_rsrc, &parent_base) == ZX_OK);
fbl::RefPtr<Resource> parent(static_cast<Resource*>(parent_base.get()));
zx_rsrc_kind_t kind = ZX_RSRC_EXTRACT_KIND(options);
zx_rsrc_flags_t flags = ZX_RSRC_EXTRACT_FLAGS(options);
// Fake root resources have no range or kind verification necessary.
if (parent->kind() != ZX_RSRC_KIND_ROOT) {
if (kind != parent->kind()) {
return ZX_ERR_WRONG_TYPE;
}
// Ensure the child range fits within the parent.
if (!is_valid_range(parent->base(), parent->size(), base, size)) {
return ZX_ERR_ACCESS_DENIED;
}
}
// Ensure that if this region is exclusive it does not overlap with an exclusive region.
if ((flags & ZX_RSRC_FLAG_EXCLUSIVE) && exclusive_region_overlaps(kind, base, size)) {
return ZX_ERR_ACCESS_DENIED;
}
fbl::RefPtr<Object> new_res;
ZX_ASSERT(Resource::Create(base, size, kind, flags, name, name_size, &new_res) == ZX_OK);
return FakeHandleTable().Add(std::move(new_res), resource_out);
}
// Create a paged VMO to stand in for a physical one for tests. The real
// zx_vmo_set_cache_policy can still be called on a paged VMO so there's no need
// to replace that syscall with a fake.
__EXPORT
zx_status_t zx_vmo_create_physical(zx_handle_t resource, zx_paddr_t paddr, size_t size,
zx_handle_t* out) {
fbl::RefPtr<Object> rsrc_base;
ZX_ASSERT(get_resource_from_handle(__func__, resource, &rsrc_base) == ZX_OK);
fbl::RefPtr<Resource> rsrc(static_cast<Resource*>(rsrc_base.get()));
if (!is_valid_range(rsrc->base(), rsrc->size(), paddr, size)) {
return ZX_ERR_ACCESS_DENIED;
}
return zx_vmo_create(size, 0, out);
}
// Validate the syscall but otherwise don't take action. If a test actually
// needs IO permissions then more work will need to be done getting them real
// resources to allwow it.
zx_status_t ioport_syscall_common(zx_handle_t resource, uint16_t io_addr, uint32_t len) {
fbl::RefPtr<Object> rsrc_base;
zx_status_t st = get_resource_from_handle(__func__, resource, &rsrc_base);
if (st != ZX_OK) {
return ZX_OK;
}
fbl::RefPtr<Resource> rsrc(static_cast<Resource*>(rsrc_base.get()));
if (rsrc->kind() != ZX_RSRC_KIND_IOPORT) {
return ZX_ERR_WRONG_TYPE;
}
if (!is_valid_range(rsrc->base(), rsrc->size(), io_addr, len)) {
return ZX_ERR_ACCESS_DENIED;
}
return ZX_OK;
}
__EXPORT
zx_status_t zx_ioports_request(zx_handle_t resource, uint16_t io_addr, uint32_t len) {
return ioport_syscall_common(resource, io_addr, len);
}
// Same as zx_ioports_request
__EXPORT
zx_status_t zx_ioports_release(zx_handle_t resource, uint16_t io_addr, uint32_t len) {
return ioport_syscall_common(resource, io_addr, len);
}
// The root resource is handed off to userboot by the kernel and is not something that can be
// created in userspace normally. This allows a test to bootstrap a resource chain by creating
// a fake root resoure.
__EXPORT
zx_status_t fake_root_resource_create(zx_handle_t* out) {
std::array<char, ZX_MAX_NAME_LEN> name = {"FAKE ROOT"};
fbl::RefPtr<Object> new_res;
ZX_ASSERT(Resource::Create(0, 0, ZX_RSRC_KIND_ROOT, 0, name.data(), name.size(), &new_res) ==
ZX_OK);
return FakeHandleTable().Add(std::move(new_res), out);
}
__END_CDECLS