| 1 | #include "mem/tests/test_internal.h" |
| 2 | |
| 3 | TEST_GROUP_DECLARE(page_table); |
| 4 | |
| 5 | /* Tagged PTEs: |
| 6 | * [ payload high 53 ][ AVAIL2 ][ LOCK ][ payload low 6 ][ type 2 ][ P=0 ] |
| 7 | */ |
| 8 | |
| 9 | #define PTE_PAYLOAD_MAX ((1ULL << PTE_TAGGED_PAYLOAD_BITS) - 1) |
| 10 | |
| 11 | TEST_DECLARE_UNIT(page_table, tagged_roundtrip) { |
| 12 | static const uint64_t payloads[] = { |
| 13 | 0, 1, 0x3F, /* fills the low chunk */ |
| 14 | 0x40, /* first bit of the high chunk */ |
| 15 | 0x3FF, 0xDEADBEEF, 1ULL << 32, PTE_PAYLOAD_MAX - 1, PTE_PAYLOAD_MAX, |
| 16 | }; |
| 17 | |
| 18 | for (size_t i = 0; i < TEST_ARRAY_LEN(payloads); i++) { |
| 19 | struct pte_tagged in = {.type = PTE_TAG_TYPE_DEMAND_PAGED, |
| 20 | .payload = payloads[i]}; |
| 21 | |
| 22 | struct pte_tagged out = pte_tagged_unpack(pte: pte_tagged_pack(pt: &in)); |
| 23 | |
| 24 | TEST_ASSERT_EQ(out.type, in.type); |
| 25 | TEST_ASSERT_EQ(out.payload, in.payload); |
| 26 | } |
| 27 | |
| 28 | return TEST_SUCCESS; |
| 29 | } |
| 30 | |
| 31 | /* Walking a single bit across the whole payload width catches shifts |
| 32 | * that are short or long, which some values can step over */ |
| 33 | TEST_DECLARE_UNIT(page_table, tagged_payload_walk) { |
| 34 | for (unsigned bit = 0; bit < PTE_TAGGED_PAYLOAD_BITS; bit++) { |
| 35 | struct pte_tagged in = {.type = PTE_TAG_TYPE_DEMAND_PAGED, |
| 36 | .payload = 1ULL << bit}; |
| 37 | |
| 38 | struct pte_tagged out = pte_tagged_unpack(pte: pte_tagged_pack(pt: &in)); |
| 39 | TEST_ASSERT_EQ(out.payload, in.payload); |
| 40 | } |
| 41 | |
| 42 | return TEST_SUCCESS; |
| 43 | } |
| 44 | |
| 45 | /* Packed format must be safe to OR into live PTE, so it cannot be PRESENT, |
| 46 | * LOCK or AVAIL2 no matter what */ |
| 47 | TEST_DECLARE_UNIT(page_table, tagged_pack_reserved_bits) { |
| 48 | const uint64_t reserved = PAGE_PRESENT | PTE_LOCK_BIT | PTE_AVAIL2_BIT; |
| 49 | |
| 50 | for (unsigned bit = 0; bit < PTE_TAGGED_PAYLOAD_BITS; bit++) { |
| 51 | struct pte_tagged in = {.type = PTE_TAG_TYPE_DEMAND_PAGED, |
| 52 | .payload = 1ULL << bit}; |
| 53 | TEST_ASSERT_EQ((pte_tagged_pack(&in) & reserved), 0); |
| 54 | } |
| 55 | |
| 56 | struct pte_tagged full = {.type = PTE_TAG_TYPE_DEMAND_PAGED, |
| 57 | .payload = PTE_PAYLOAD_MAX}; |
| 58 | TEST_ASSERT_EQ((pte_tagged_pack(&full) & reserved), 0); |
| 59 | |
| 60 | return TEST_SUCCESS; |
| 61 | } |
| 62 | |
| 63 | /* Unpack ignores */ |
| 64 | TEST_DECLARE_UNIT(page_table, tagged_unpack_reserved_bits) { |
| 65 | struct pte_tagged in = {.type = PTE_TAG_TYPE_DEMAND_PAGED, |
| 66 | .payload = 0x1234567}; |
| 67 | |
| 68 | uint64_t packed = pte_tagged_pack(pt: &in); |
| 69 | |
| 70 | struct pte_tagged plain = pte_tagged_unpack(pte: packed); |
| 71 | struct pte_tagged locked = |
| 72 | pte_tagged_unpack(pte: packed | PTE_LOCK_BIT | PTE_AVAIL2_BIT); |
| 73 | |
| 74 | TEST_ASSERT_EQ(locked.type, plain.type); |
| 75 | TEST_ASSERT_EQ(locked.payload, plain.payload); |
| 76 | |
| 77 | return TEST_SUCCESS; |
| 78 | } |
| 79 | |
| 80 | /* Type sits above PRESENT */ |
| 81 | TEST_DECLARE_UNIT(page_table, tagged_type_field) { |
| 82 | struct pte_tagged in = {.type = PTE_TAG_TYPE_DEMAND_PAGED, .payload = 0}; |
| 83 | uint64_t packed = pte_tagged_pack(pt: &in); |
| 84 | |
| 85 | TEST_ASSERT_EQ(PTE_TAGGED_GET_TYPE(packed), PTE_TAG_TYPE_DEMAND_PAGED); |
| 86 | TEST_ASSERT_EQ(pte_tagged_unpack(packed).type, PTE_TAG_TYPE_DEMAND_PAGED); |
| 87 | |
| 88 | /* Untyped empty tags pack to nothing */ |
| 89 | struct pte_tagged none = {.type = PTE_TAG_TYPE_NONE, .payload = 0}; |
| 90 | TEST_ASSERT_EQ(pte_tagged_pack(&none), 0); |
| 91 | |
| 92 | return TEST_SUCCESS; |
| 93 | } |
| 94 | |
| 95 | /* Two chunk widths and positions they are stored at have to add up to |
| 96 | * payload width */ |
| 97 | TEST_DECLARE_UNIT(page_table, tagged_layout_consistency) { |
| 98 | TEST_ASSERT_EQ(PTE_TAGGED_PAYLOAD_BITS, |
| 99 | PTE_TAGGED_PAYLOAD_LOW_BITS + |
| 100 | (64 - PTE_TAGGED_PAYLOAD_HIGH_SHIFT)); |
| 101 | |
| 102 | /* low chunk must end exactly where LOCK begins */ |
| 103 | TEST_ASSERT_EQ(PTE_TAGGED_PAYLOAD_LOW_SHIFT + PTE_TAGGED_PAYLOAD_LOW_BITS, |
| 104 | PTE_LOCK_SHIFT); |
| 105 | |
| 106 | /* high chunk must start exactly above AVAIL2 */ |
| 107 | TEST_ASSERT_EQ(PTE_TAGGED_PAYLOAD_HIGH_SHIFT, PTE_AVAIL2_SHIFT + 1); |
| 108 | |
| 109 | /* type must fit below low chunk without touching PRESENT */ |
| 110 | TEST_ASSERT_EQ(PTE_TAGGED_TYPE_SHIFT, PAGE_PRESENT_SHIFT + 1); |
| 111 | TEST_ASSERT_LE(PTE_TAGGED_TYPE_SHIFT + 2, PTE_TAGGED_PAYLOAD_LOW_SHIFT); |
| 112 | |
| 113 | return TEST_SUCCESS; |
| 114 | } |
| 115 | |
| 116 | /* pte_is_shared only means something on present entries, SHARED |
| 117 | * is used as payload space when tagged encoding is used */ |
| 118 | TEST_DECLARE_UNIT(page_table, is_shared_requires_present) { |
| 119 | TEST_ASSERT(!pte_is_shared(0)); |
| 120 | TEST_ASSERT(!pte_is_shared(PTE_SHARED_BIT)); |
| 121 | TEST_ASSERT(!pte_is_shared(PAGE_PRESENT)); |
| 122 | TEST_ASSERT(pte_is_shared(PAGE_PRESENT | PTE_SHARED_BIT)); |
| 123 | |
| 124 | return TEST_SUCCESS; |
| 125 | } |
| 126 | |