k_page_heap.cpp 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
  2. // SPDX-License-Identifier: GPL-2.0-or-later
  3. #include "core/core.h"
  4. #include "core/hle/kernel/k_page_heap.h"
  5. namespace Kernel {
  6. void KPageHeap::Initialize(PAddr address, size_t size, VAddr management_address,
  7. size_t management_size, const size_t* block_shifts,
  8. size_t num_block_shifts) {
  9. // Check our assumptions.
  10. ASSERT(Common::IsAligned(address, PageSize));
  11. ASSERT(Common::IsAligned(size, PageSize));
  12. ASSERT(0 < num_block_shifts && num_block_shifts <= NumMemoryBlockPageShifts);
  13. const VAddr management_end = management_address + management_size;
  14. // Set our members.
  15. m_heap_address = address;
  16. m_heap_size = size;
  17. m_num_blocks = num_block_shifts;
  18. // Setup bitmaps.
  19. m_management_data.resize(management_size / sizeof(u64));
  20. u64* cur_bitmap_storage{m_management_data.data()};
  21. for (size_t i = 0; i < num_block_shifts; i++) {
  22. const size_t cur_block_shift = block_shifts[i];
  23. const size_t next_block_shift = (i != num_block_shifts - 1) ? block_shifts[i + 1] : 0;
  24. cur_bitmap_storage = m_blocks[i].Initialize(m_heap_address, m_heap_size, cur_block_shift,
  25. next_block_shift, cur_bitmap_storage);
  26. }
  27. // Ensure we didn't overextend our bounds.
  28. ASSERT(VAddr(cur_bitmap_storage) <= management_end);
  29. }
  30. size_t KPageHeap::GetNumFreePages() const {
  31. size_t num_free = 0;
  32. for (size_t i = 0; i < m_num_blocks; i++) {
  33. num_free += m_blocks[i].GetNumFreePages();
  34. }
  35. return num_free;
  36. }
  37. PAddr KPageHeap::AllocateByLinearSearch(s32 index) {
  38. const size_t needed_size = m_blocks[index].GetSize();
  39. for (s32 i = index; i < static_cast<s32>(m_num_blocks); i++) {
  40. if (const PAddr addr = m_blocks[i].PopBlock(false); addr != 0) {
  41. if (const size_t allocated_size = m_blocks[i].GetSize(); allocated_size > needed_size) {
  42. this->Free(addr + needed_size, (allocated_size - needed_size) / PageSize);
  43. }
  44. return addr;
  45. }
  46. }
  47. return 0;
  48. }
  49. PAddr KPageHeap::AllocateByRandom(s32 index, size_t num_pages, size_t align_pages) {
  50. // Get the size and required alignment.
  51. const size_t needed_size = num_pages * PageSize;
  52. const size_t align_size = align_pages * PageSize;
  53. // Determine meta-alignment of our desired alignment size.
  54. const size_t align_shift = std::countr_zero(align_size);
  55. // Decide on a block to allocate from.
  56. constexpr static size_t MinimumPossibleAlignmentsForRandomAllocation = 4;
  57. {
  58. // By default, we'll want to look at all blocks larger than our current one.
  59. s32 max_blocks = static_cast<s32>(m_num_blocks);
  60. // Determine the maximum block we should try to allocate from.
  61. size_t possible_alignments = 0;
  62. for (s32 i = index; i < max_blocks; ++i) {
  63. // Add the possible alignments from blocks at the current size.
  64. possible_alignments += (1 + ((m_blocks[i].GetSize() - needed_size) >> align_shift)) *
  65. m_blocks[i].GetNumFreeBlocks();
  66. // If there are enough possible alignments, we don't need to look at larger blocks.
  67. if (possible_alignments >= MinimumPossibleAlignmentsForRandomAllocation) {
  68. max_blocks = i + 1;
  69. break;
  70. }
  71. }
  72. // If we have any possible alignments which require a larger block, we need to pick one.
  73. if (possible_alignments > 0 && index + 1 < max_blocks) {
  74. // Select a random alignment from the possibilities.
  75. const size_t rnd = m_rng.GenerateRandom(possible_alignments);
  76. // Determine which block corresponds to the random alignment we chose.
  77. possible_alignments = 0;
  78. for (s32 i = index; i < max_blocks; ++i) {
  79. // Add the possible alignments from blocks at the current size.
  80. possible_alignments +=
  81. (1 + ((m_blocks[i].GetSize() - needed_size) >> align_shift)) *
  82. m_blocks[i].GetNumFreeBlocks();
  83. // If the current block gets us to our random choice, use the current block.
  84. if (rnd < possible_alignments) {
  85. index = i;
  86. break;
  87. }
  88. }
  89. }
  90. }
  91. // Pop a block from the index we selected.
  92. if (PAddr addr = m_blocks[index].PopBlock(true); addr != 0) {
  93. // Determine how much size we have left over.
  94. if (const size_t leftover_size = m_blocks[index].GetSize() - needed_size;
  95. leftover_size > 0) {
  96. // Determine how many valid alignments we can have.
  97. const size_t possible_alignments = 1 + (leftover_size >> align_shift);
  98. // Select a random valid alignment.
  99. const size_t random_offset = m_rng.GenerateRandom(possible_alignments) << align_shift;
  100. // Free memory before the random offset.
  101. if (random_offset != 0) {
  102. this->Free(addr, random_offset / PageSize);
  103. }
  104. // Advance our block by the random offset.
  105. addr += random_offset;
  106. // Free memory after our allocated block.
  107. if (random_offset != leftover_size) {
  108. this->Free(addr + needed_size, (leftover_size - random_offset) / PageSize);
  109. }
  110. }
  111. // Return the block we allocated.
  112. return addr;
  113. }
  114. return 0;
  115. }
  116. void KPageHeap::FreeBlock(PAddr block, s32 index) {
  117. do {
  118. block = m_blocks[index++].PushBlock(block);
  119. } while (block != 0);
  120. }
  121. void KPageHeap::Free(PAddr addr, size_t num_pages) {
  122. // Freeing no pages is a no-op.
  123. if (num_pages == 0) {
  124. return;
  125. }
  126. // Find the largest block size that we can free, and free as many as possible.
  127. s32 big_index = static_cast<s32>(m_num_blocks) - 1;
  128. const PAddr start = addr;
  129. const PAddr end = addr + num_pages * PageSize;
  130. PAddr before_start = start;
  131. PAddr before_end = start;
  132. PAddr after_start = end;
  133. PAddr after_end = end;
  134. while (big_index >= 0) {
  135. const size_t block_size = m_blocks[big_index].GetSize();
  136. const PAddr big_start = Common::AlignUp(start, block_size);
  137. const PAddr big_end = Common::AlignDown(end, block_size);
  138. if (big_start < big_end) {
  139. // Free as many big blocks as we can.
  140. for (auto block = big_start; block < big_end; block += block_size) {
  141. this->FreeBlock(block, big_index);
  142. }
  143. before_end = big_start;
  144. after_start = big_end;
  145. break;
  146. }
  147. big_index--;
  148. }
  149. ASSERT(big_index >= 0);
  150. // Free space before the big blocks.
  151. for (s32 i = big_index - 1; i >= 0; i--) {
  152. const size_t block_size = m_blocks[i].GetSize();
  153. while (before_start + block_size <= before_end) {
  154. before_end -= block_size;
  155. this->FreeBlock(before_end, i);
  156. }
  157. }
  158. // Free space after the big blocks.
  159. for (s32 i = big_index - 1; i >= 0; i--) {
  160. const size_t block_size = m_blocks[i].GetSize();
  161. while (after_start + block_size <= after_end) {
  162. this->FreeBlock(after_start, i);
  163. after_start += block_size;
  164. }
  165. }
  166. }
  167. size_t KPageHeap::CalculateManagementOverheadSize(size_t region_size, const size_t* block_shifts,
  168. size_t num_block_shifts) {
  169. size_t overhead_size = 0;
  170. for (size_t i = 0; i < num_block_shifts; i++) {
  171. const size_t cur_block_shift = block_shifts[i];
  172. const size_t next_block_shift = (i != num_block_shifts - 1) ? block_shifts[i + 1] : 0;
  173. overhead_size += KPageHeap::Block::CalculateManagementOverheadSize(
  174. region_size, cur_block_shift, next_block_shift);
  175. }
  176. return Common::AlignUp(overhead_size, PageSize);
  177. }
  178. } // namespace Kernel