Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include <AK/BuiltinWrappers.h>
8#include <Kernel/Assertions.h>
9#include <Kernel/Memory/MemoryManager.h>
10#include <Kernel/Memory/PhysicalRegion.h>
11#include <Kernel/Memory/PhysicalZone.h>
12#include <Kernel/Random.h>
13
14namespace Kernel::Memory {
15
16static constexpr u32 next_power_of_two(u32 value)
17{
18 value--;
19 value |= value >> 1;
20 value |= value >> 2;
21 value |= value >> 4;
22 value |= value >> 8;
23 value |= value >> 16;
24 value++;
25 return value;
26}
27
28PhysicalRegion::~PhysicalRegion() = default;
29
30PhysicalRegion::PhysicalRegion(PhysicalAddress lower, PhysicalAddress upper)
31 : m_lower(lower)
32 , m_upper(upper)
33{
34 m_pages = (m_upper.get() - m_lower.get()) / PAGE_SIZE;
35}
36
37void PhysicalRegion::initialize_zones()
38{
39 size_t remaining_pages = m_pages;
40 auto base_address = m_lower;
41
42 auto make_zones = [&](size_t zone_size) -> size_t {
43 size_t pages_per_zone = zone_size / PAGE_SIZE;
44 size_t zone_count = 0;
45 auto first_address = base_address;
46 while (remaining_pages >= pages_per_zone) {
47 m_zones.append(adopt_nonnull_own_or_enomem(new (nothrow) PhysicalZone(base_address, pages_per_zone)).release_value_but_fixme_should_propagate_errors());
48 base_address = base_address.offset(pages_per_zone * PAGE_SIZE);
49 m_usable_zones.append(*m_zones.last());
50 remaining_pages -= pages_per_zone;
51 ++zone_count;
52 }
53 if (zone_count)
54 dmesgln(" * {}x PhysicalZone ({} MiB) @ {:016x}-{:016x}", zone_count, pages_per_zone / 256, first_address.get(), base_address.get() - pages_per_zone * PAGE_SIZE - 1);
55 return zone_count;
56 };
57
58 // First make 16 MiB zones (with 4096 pages each)
59 m_large_zones = make_zones(large_zone_size);
60
61 // Then divide any remaining space into 1 MiB zones (with 256 pages each)
62 make_zones(small_zone_size);
63}
64
65OwnPtr<PhysicalRegion> PhysicalRegion::try_take_pages_from_beginning(size_t page_count)
66{
67 VERIFY(page_count > 0);
68 VERIFY(page_count < m_pages);
69 auto taken_lower = m_lower;
70 auto taken_upper = taken_lower.offset((PhysicalPtr)page_count * PAGE_SIZE);
71 m_lower = m_lower.offset((PhysicalPtr)page_count * PAGE_SIZE);
72 m_pages = (m_upper.get() - m_lower.get()) / PAGE_SIZE;
73
74 return try_create(taken_lower, taken_upper);
75}
76
77Vector<NonnullRefPtr<PhysicalPage>> PhysicalRegion::take_contiguous_free_pages(size_t count)
78{
79 auto rounded_page_count = next_power_of_two(count);
80 auto order = count_trailing_zeroes(rounded_page_count);
81
82 Optional<PhysicalAddress> page_base;
83 for (auto& zone : m_usable_zones) {
84 page_base = zone.allocate_block(order);
85 if (page_base.has_value()) {
86 if (zone.is_empty()) {
87 // We've exhausted this zone, move it to the full zones list.
88 m_full_zones.append(zone);
89 }
90 break;
91 }
92 }
93
94 if (!page_base.has_value())
95 return {};
96
97 Vector<NonnullRefPtr<PhysicalPage>> physical_pages;
98 physical_pages.ensure_capacity(count);
99
100 for (size_t i = 0; i < count; ++i)
101 physical_pages.append(PhysicalPage::create(page_base.value().offset(i * PAGE_SIZE)));
102 return physical_pages;
103}
104
105RefPtr<PhysicalPage> PhysicalRegion::take_free_page()
106{
107 if (m_usable_zones.is_empty())
108 return nullptr;
109
110 auto& zone = *m_usable_zones.first();
111 auto page = zone.allocate_block(0);
112 VERIFY(page.has_value());
113
114 if (zone.is_empty()) {
115 // We've exhausted this zone, move it to the full zones list.
116 m_full_zones.append(zone);
117 }
118
119 return PhysicalPage::create(page.value());
120}
121
122void PhysicalRegion::return_page(PhysicalAddress paddr)
123{
124 auto large_zone_base = lower().get();
125 auto small_zone_base = lower().get() + (m_large_zones * large_zone_size);
126
127 size_t zone_index;
128 if (paddr.get() < small_zone_base)
129 zone_index = (paddr.get() - large_zone_base) / large_zone_size;
130 else
131 zone_index = m_large_zones + (paddr.get() - small_zone_base) / small_zone_size;
132
133 auto& zone = m_zones[zone_index];
134 VERIFY(zone->contains(paddr));
135 zone->deallocate_block(paddr, 0);
136 if (m_full_zones.contains(*zone))
137 m_usable_zones.append(*zone);
138}
139
140}