blob: 2b69e1c7fc616ea4574f13fd62bd286248869430 [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.
#ifndef ZIRCON_SYSTEM_UTEST_CORE_PAGER_USERPAGER_H_
#define ZIRCON_SYSTEM_UTEST_CORE_PAGER_USERPAGER_H_
#include <lib/fit/function.h>
#include <lib/zx/event.h>
#include <lib/zx/pager.h>
#include <lib/zx/port.h>
#include <lib/zx/vmar.h>
#include <lib/zx/vmo.h>
#include <zircon/syscalls-next.h>
#include <zircon/syscalls/port.h>
#include <zircon/time.h>
#include <zircon/types.h>
#include <memory>
#include <fbl/intrusive_double_list.h>
#include "test_thread.h"
namespace pager_tests {
class UserPager;
class Vmo : public fbl::DoublyLinkedListable<std::unique_ptr<Vmo>> {
public:
~Vmo() = default;
// Generates this vmo contents at the specified offset.
void GenerateBufferContents(void* dest_buffer, uint64_t page_count,
uint64_t paged_vmo_page_offset);
// Validates this vmo's content in the specified pages using a mapped vmar.
bool CheckVmar(uint64_t page_offset, uint64_t page_count, const void* expected = nullptr);
// Validates this vmo's content in the specified pages using vmo_read.
bool CheckVmo(uint64_t page_offset, uint64_t page_count, const void* expected = nullptr);
// Resizes the vmo. This changes the |Vmo| internal state, so another thread should not be trying
// to access it or perform operations on it concurrently.
bool Resize(uint64_t new_page_count);
// Commits the specified pages in this vmo.
bool Commit(uint64_t page_offset, uint64_t page_count) {
return OpRange(ZX_VMO_OP_COMMIT, page_offset, page_count);
}
uint64_t GetKey() const { return base_val_; }
uintptr_t GetBaseAddr() const { return base_addr_; }
const zx::vmo& vmo() const { return vmo_; }
std::unique_ptr<Vmo> Clone();
std::unique_ptr<Vmo> Clone(uint64_t offset, uint64_t size);
private:
Vmo(zx::vmo vmo, uint64_t size, uint64_t* base, uint64_t base_addr, uint64_t base_val)
: size_(size),
base_(base),
base_addr_(base_addr),
vmo_(std::move(vmo)),
base_val_(base_val) {}
bool OpRange(uint32_t op, uint64_t page_offset, uint64_t page_count);
// These are set in the ctor, but can be changed by Vmo::Resize.
uint64_t size_;
uint64_t* base_;
uintptr_t base_addr_;
// These are set in the ctor, but can be changed by UserPager::ReplaceVmo.
zx::vmo vmo_;
uint64_t base_val_; // == packet key
friend UserPager;
};
class UserPager {
public:
UserPager();
~UserPager();
// Initialzies the UserPager.
bool Init();
// Closes the pager handle.
void ClosePagerHandle() { pager_.reset(); }
// Closes the pager's port handle.
void ClosePortHandle() { port_.reset(); }
// Creates a new paged vmo.
bool CreateVmo(uint64_t size, Vmo** vmo_out);
// Creates a new paged vmo with the provided create |options|.
bool CreateVmoWithOptions(uint64_t size, uint32_t options, Vmo** vmo_out);
// Detaches the paged vmo.
bool DetachVmo(Vmo* vmo);
// Destroyes the paged vmo.
void ReleaseVmo(Vmo* vmo);
// Unmaps the paged vmo.
bool UnmapVmo(Vmo* vmo);
// Replaces the paged vmo's mapping with new content. This changes the |Vmo| internal state, so
// another thread should not be trying to access it or perform operations on it concurrently.
bool ReplaceVmo(Vmo* vmo, zx::vmo* old_vmo);
// Populates the specified pages with autogenerated content. |src_page_offset| is used
// to offset where in the temporary vmo the content is generated.
bool SupplyPages(Vmo* vmo, uint64_t page_offset, uint64_t page_count,
uint64_t src_page_offset = 0);
// Populates the specified pages with the content in |src| starting at |src_page_offset|.
bool SupplyPages(Vmo* vmo, uint64_t page_offset, uint64_t page_count, zx::vmo src,
uint64_t src_page_offset = 0);
// Signals failure to populate pages in the specified range.
bool FailPages(Vmo* vmo, uint64_t page_offset, uint64_t page_count,
zx_status_t error_status = ZX_ERR_IO);
// Signals that pages in the specified range can be marked dirty.
bool DirtyPages(Vmo* vmo, uint64_t page_offset, uint64_t page_count);
// Queries dirty ranges of pages in the specified range and verifies that they match the ones
// provided in |dirty_ranges_to_verify|. The number of entries in |dirty_ranges_to_verify| is
// passed in with |num_dirty_ranges_to_verify|.
bool VerifyDirtyRanges(Vmo* paged_vmo, zx_vmo_dirty_range_t* dirty_ranges_to_verify,
size_t num_dirty_ranges_to_verify);
// Queries pager vmo stats, and returns whether the |paged_vmo| has been modified since the last
// query.
bool VerifyModified(Vmo* paged_vmo);
// Begins and ends writeback on pages in the specified range.
bool WritebackBeginPages(Vmo* vmo, uint64_t page_offset, uint64_t page_count);
bool WritebackEndPages(Vmo* vmo, uint64_t page_offset, uint64_t page_count);
// Checks if there is a request for the range [page_offset, length). Will
// wait until |deadline|.
bool WaitForPageRead(Vmo* vmo, uint64_t page_offset, uint64_t page_count, zx_time_t deadline);
bool WaitForPageDirty(Vmo* vmo, uint64_t page_offset, uint64_t page_count, zx_time_t deadline);
bool WaitForPageComplete(uint64_t key, zx_time_t deadline);
// Gets the first page read request. Blocks until |deadline|.
bool GetPageReadRequest(Vmo* vmo, zx_time_t deadline, uint64_t* page_offset,
uint64_t* page_count);
// Gets the first page dirty request. Blocks until |deadline|.
bool GetPageDirtyRequest(Vmo* vmo, zx_time_t deadline, uint64_t* page_offset,
uint64_t* page_count);
// Gets the first page request with |command|. Blocks until |deadline|.
bool GetPageRequest(Vmo* vmo, uint16_t command, zx_time_t deadline, uint64_t* page_offset,
uint64_t* page_count);
// Starts a thread to handle any page faults. Faulted in pages are initialized with the default
// page tagged data as per SupplyPages. This function is not thread safe, and should only be
// called once. After starting a pager thread it is an error to create or destroy VMOs, as this
// could lead to data races.
bool StartTaggedPageFaultHandler();
const zx::pager& pager() const { return pager_; }
private:
bool WaitForPageRequest(uint16_t command, Vmo* vmo, uint64_t page_offset, uint64_t page_count,
zx_time_t deadline);
bool WaitForRequest(uint64_t key, const zx_packet_page_request_t& request, zx_time_t deadline);
bool WaitForRequest(fit::function<bool(const zx_port_packet_t& packet)> cmp_fn,
zx_time_t deadline);
void PageFaultHandler();
bool VerifyDirtyRangesHelper(Vmo* paged_vmo, zx_vmo_dirty_range_t* dirty_ranges_to_verify,
size_t num_dirty_ranges_to_verify, zx_vmo_dirty_range_t* ranges_buf,
size_t ranges_buf_size, uint64_t* num_ranges_buf);
zx::pager pager_;
zx::port port_;
static constexpr uint64_t kShutdownKey = 1;
uint64_t next_base_ = kShutdownKey + 1;
fbl::DoublyLinkedList<std::unique_ptr<Vmo>> vmos_;
typedef struct request : fbl::DoublyLinkedListable<std::unique_ptr<struct request>> {
zx_port_packet_t req;
} request_t;
fbl::DoublyLinkedList<std::unique_ptr<request_t>> requests_;
zx::event shutdown_event_;
TestThread pager_thread_;
};
inline bool check_buffer_data(Vmo* vmo, uint64_t offset, uint64_t len, const void* data,
bool check_vmar) {
return check_vmar ? vmo->CheckVmar(offset, len, data) : vmo->CheckVmo(offset, len, data);
}
inline bool check_buffer(Vmo* vmo, uint64_t offset, uint64_t len, bool check_vmar) {
return check_vmar ? vmo->CheckVmar(offset, len) : vmo->CheckVmo(offset, len);
}
#define VMO_VMAR_TEST(test_name, fn_name) \
void fn_name(bool); \
TEST(test_name, fn_name##Vmar) { fn_name(true); } \
TEST(test_name, fn_name##Vmo) { fn_name(false); } \
void fn_name(bool check_vmar)
} // namespace pager_tests
#endif // ZIRCON_SYSTEM_UTEST_CORE_PAGER_USERPAGER_H_