1// SPDX-License-Identifier: GPL-3.0-or-later
2// Public API for the Physical Memory Manager
3
4#include "mos/mm/physical/pmm.h"
5
6#include "mos/assert.h"
7#include "mos/mm/mm.h"
8#include "mos/mm/physical/buddy.h"
9#include "mos/platform/platform.h"
10#include "mos/syslog/printk.h"
11
12#include <mos_stdlib.h>
13#include <mos_string.h>
14
15phyframe_t *phyframes = NULL;
16size_t pmm_total_frames = 0; // system pfn <= pfn_max
17size_t pmm_allocated_frames = 0;
18size_t pmm_reserved_frames = 0;
19
20void pmm_init(void)
21{
22 pr_dinfo2(pmm, "setting up physical memory manager...");
23 MOS_ASSERT_ONCE("pmm_init should only be called once");
24
25 const size_t phyframes_npages = ALIGN_UP_TO_PAGE(platform_info->max_pfn * sizeof(phyframe_t)) / MOS_PAGE_SIZE;
26 pr_dinfo2(pmm, "%zu pages required for the phyframes array with %llu pages in total", phyframes_npages, platform_info->max_pfn);
27
28 const pmm_region_t *phyframes_region = NULL; // the region that will hold the phyframes array
29 pfn_t phyframes_pfn = 0;
30
31 // now we need to find contiguous memory for the phyframes array
32 for (u32 i = 0; i < platform_info->num_pmm_regions; i++)
33 {
34 const pmm_region_t *const r = &platform_info->pmm_regions[i];
35
36 if (r->reserved)
37 {
38 pr_dinfo2(pmm, "skipping reserved region " PFNADDR_RANGE, PFNADDR(r->pfn_start, r->pfn_start + r->nframes));
39 continue;
40 }
41
42 if (r->nframes < phyframes_npages)
43 {
44 pr_dinfo2(pmm, "skipping region " PFNADDR_RANGE " because it's too small", PFNADDR(r->pfn_start, r->pfn_start + r->nframes));
45 continue; // early out if this region is too small
46 }
47
48 phyframes_pfn = r->pfn_start;
49 phyframes_region = r;
50
51 pr_dinfo2(pmm, "using " PFNADDR_RANGE " for the phyframes array", PFNADDR(phyframes_pfn, phyframes_pfn + phyframes_npages));
52
53 // ! initialise phyframes array
54 phyframes = (void *) pfn_va(phyframes_pfn);
55 memzero(s: phyframes, n: phyframes_npages * MOS_PAGE_SIZE);
56 pmm_total_frames = platform_info->max_pfn;
57 buddy_init(max_nframes: pmm_total_frames);
58 pmm_reserve_frames(pfn: phyframes_pfn, npages: phyframes_npages);
59 break;
60 }
61
62 MOS_ASSERT_X(phyframes && phyframes_region, "failed to find a region for the phyframes array");
63
64 // add all the other regions
65 for (u32 i = 0; i < platform_info->num_pmm_regions; i++)
66 {
67 pmm_region_t *r = &platform_info->pmm_regions[i];
68 if (r == phyframes_region)
69 continue;
70 if (r->nframes == 0) // ???
71 mos_warn_once("region " PFN_FMT " has 0 frames", r->pfn_start);
72 if (r->reserved)
73 {
74 if (r->pfn_start >= platform_info->max_pfn)
75 continue; // we ignore any reserved regions that are outside of the max_pfn
76 pmm_reserve_frames(pfn: r->pfn_start, npages: r->nframes);
77 }
78 }
79
80 MOS_ASSERT_X(phyframes[0].state == PHYFRAME_RESERVED, "phyframe 0 isn't reserved, things have gone horribly wrong");
81 pr_dinfo2(pmm, "initialised");
82}
83
84void pmm_dump_lists(void)
85{
86 pr_info("Physical Memory Manager dump:");
87 buddy_dump_all();
88}
89
90MOS_PANIC_HOOK_FEAT(pmm, pmm_dump_lists, "dump physical allocator lists");
91
92phyframe_t *pmm_allocate_frames(size_t n_frames, pmm_allocation_flags_t flags)
93{
94 MOS_ASSERT(flags == PMM_ALLOC_NORMAL);
95 phyframe_t *frame = buddy_alloc_n_exact(nframes: n_frames);
96 if (!frame)
97 return NULL;
98 const pfn_t pfn = phyframe_pfn(frame);
99 pr_dinfo2(pmm, "allocated " PFN_RANGE ", %zu pages", pfn, pfn + n_frames, n_frames);
100
101 for (size_t i = 0; i < n_frames; i++)
102 pfn_phyframe(pfn + i)->allocated_refcount = 0;
103
104 pmm_allocated_frames += n_frames;
105 return frame;
106}
107
108void pmm_free_frames(phyframe_t *start_frame, size_t n_pages)
109{
110 const pfn_t start = phyframe_pfn(start_frame);
111 pr_dinfo2(pmm, "freeing " PFN_RANGE ", %zu pages", start, start + n_pages - 1, n_pages);
112 for (pfn_t pfn = start; pfn < start + n_pages; pfn++)
113 {
114 phyframe_t *frame = pfn_phyframe(pfn);
115 linked_list_init(list_node(frame)); // sanitize the list node
116 buddy_free_n(phyframe_pfn(frame), nframes: 1);
117 }
118
119 pmm_allocated_frames -= n_pages;
120}
121
122pfn_t pmm_reserve_frames(pfn_t pfn_start, size_t npages)
123{
124 MOS_ASSERT_X(pfn_start + npages <= pmm_total_frames, "out of bounds: " PFN_RANGE ", %zu pages", pfn_start, pfn_start + npages - 1, npages);
125 pr_dinfo2(pmm, "reserving " PFN_RANGE ", %zu pages", pfn_start, pfn_start + npages - 1, npages);
126 buddy_reserve_n(pfn: pfn_start, nframes: npages);
127 pmm_reserved_frames += npages;
128 return pfn_start;
129}
130
131pmm_region_t *pmm_find_reserved_region(ptr_t needle)
132{
133 pr_dinfo2(pmm, "looking for block containing " PTR_FMT, needle);
134
135 const pfn_t needle_pfn = needle / MOS_PAGE_SIZE;
136
137 for (size_t i = 0; i < platform_info->num_pmm_regions; i++)
138 {
139 pmm_region_t *r = &platform_info->pmm_regions[i];
140 if (r->reserved && r->pfn_start <= needle_pfn && needle_pfn < r->pfn_start + r->nframes)
141 {
142 pr_dinfo2(pmm, "found block: " PFN_RANGE ", %zu pages", r->pfn_start, r->pfn_start + r->nframes - 1, r->nframes);
143 return r;
144 }
145 }
146
147 pr_dinfo2(pmm, "no block found");
148 return NULL;
149}
150
151phyframe_t *_pmm_ref_phyframes(phyframe_t *frame, size_t n_pages)
152{
153 const pfn_t start = phyframe_pfn(frame);
154 MOS_ASSERT_X(start + n_pages <= pmm_total_frames, "out of bounds");
155 pr_dinfo2(pmm, "ref range: " PFN_RANGE ", %zu pages", start, start + n_pages, n_pages);
156
157 for (size_t i = start; i < start + n_pages; i++)
158 pfn_phyframe(i)->allocated_refcount++;
159 return frame;
160}
161
162void _pmm_unref_phyframes(phyframe_t *frame, size_t n_pages)
163{
164 const pfn_t start = phyframe_pfn(frame);
165 MOS_ASSERT_X(start + n_pages <= pmm_total_frames, "out of bounds");
166 pr_dinfo2(pmm, "unref range: " PFN_RANGE ", %zu pages", start, start + n_pages, n_pages);
167
168 for (pfn_t pfn = start; pfn < start + n_pages; pfn++)
169 {
170 phyframe_t *frame = pfn_phyframe(pfn);
171 MOS_ASSERT(frame->allocated_refcount > 0);
172
173 if (--frame->allocated_refcount == 0)
174 pmm_free_frames(start_frame: frame, n_pages: 1);
175 }
176}
177