1261643Sian/*-
2261643Sian * Copyright (c) 2014 Ian Lepore <ian@freebsd.org>
3297682Sian * All rights reserved.
4261643Sian *
5261643Sian * Redistribution and use in source and binary forms, with or without
6261643Sian * modification, are permitted provided that the following conditions
7261643Sian * are met:
8261643Sian * 1. Redistributions of source code must retain the above copyright
9261643Sian *    notice, this list of conditions and the following disclaimer.
10261643Sian * 2. Redistributions in binary form must reproduce the above copyright
11261643Sian *    notice, this list of conditions and the following disclaimer in the
12261643Sian *    documentation and/or other materials provided with the distribution.
13261643Sian *
14261643Sian * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15261643Sian * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16261643Sian * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17261643Sian * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18261643Sian * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19261643Sian * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20261643Sian * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21261643Sian * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22261643Sian * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23261643Sian * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24261643Sian * SUCH DAMAGE.
25261643Sian */
26261643Sian
27261643Sian#include <sys/cdefs.h>
28261643Sian__FBSDID("$FreeBSD: stable/11/sys/arm/arm/physmem.c 338694 2018-09-15 18:01:15Z markj $");
29261643Sian
30261643Sian#include "opt_ddb.h"
31261643Sian
32261643Sian/*
33261643Sian * Routines for describing and initializing anything related to physical memory.
34261643Sian */
35261643Sian
36261643Sian#include <sys/param.h>
37261643Sian#include <sys/systm.h>
38261643Sian#include <vm/vm.h>
39277532Sian#include <machine/md_var.h>
40261643Sian#include <machine/physmem.h>
41261643Sian
42261643Sian/*
43261643Sian * These structures are used internally to keep track of regions of physical
44261643Sian * ram, and regions within the physical ram that need to be excluded.  An
45261643Sian * exclusion region can be excluded from crash dumps, from the vm pool of pages
46261643Sian * that can be allocated, or both, depending on the exclusion flags associated
47261643Sian * with the region.
48261643Sian */
49338694Smarkj#define	MAX_HWCNT	16
50338694Smarkj#define	MAX_EXCNT	16
51261643Sian
52294754Sandrew#define	MAX_PHYS_ADDR	0xFFFFFFFFull
53294754Sandrew
54261643Sianstruct region {
55261656Sian	vm_paddr_t	addr;
56261643Sian	vm_size_t	size;
57261643Sian	uint32_t	flags;
58261643Sian};
59261643Sian
60261643Sianstatic struct region hwregions[MAX_HWCNT];
61261643Sianstatic struct region exregions[MAX_EXCNT];
62261643Sian
63261643Sianstatic size_t hwcnt;
64261643Sianstatic size_t excnt;
65261643Sian
66261643Sian/*
67283366Sandrew * These "avail lists" are globals used to communicate physical memory layout to
68261643Sian * other parts of the kernel.  Within the arrays, each value is the starting
69261643Sian * address of a contiguous area of physical address space.  The values at even
70261643Sian * indexes are areas that contain usable memory and the values at odd indexes
71261643Sian * are areas that aren't usable.  Each list is terminated by a pair of zero
72261643Sian * entries.
73261643Sian *
74261643Sian * dump_avail tells the dump code what regions to include in a crash dump, and
75261643Sian * phys_avail is the way we hand all the remaining physical ram we haven't used
76261643Sian * in early kernel init over to the vm system for allocation management.
77261643Sian *
78261643Sian * We size these arrays to hold twice as many available regions as we allow for
79261643Sian * hardware memory regions, to allow for the fact that exclusions can split a
80261643Sian * hardware region into two or more available regions.  In the real world there
81261643Sian * will typically be one or two hardware regions and two or three exclusions.
82261643Sian *
83261643Sian * Each available region in this list occupies two array slots (the start of the
84261643Sian * available region and the start of the unavailable region that follows it).
85261643Sian */
86261643Sian#define	MAX_AVAIL_REGIONS	(MAX_HWCNT * 2)
87261643Sian#define	MAX_AVAIL_ENTRIES	(MAX_AVAIL_REGIONS * 2)
88261643Sian
89261643Sianvm_paddr_t phys_avail[MAX_AVAIL_ENTRIES + 2]; /* +2 to allow for a pair  */
90261643Sianvm_paddr_t dump_avail[MAX_AVAIL_ENTRIES + 2]; /* of zeroes to terminate. */
91261643Sian
92277532Sian/*
93277532Sian * realmem is the total number of hardware pages, excluded or not.
94277532Sian * Maxmem is one greater than the last physical page number.
95277532Sian */
96261643Sianlong realmem;
97277532Sianlong Maxmem;
98261643Sian
99261649Sian/* The address at which the kernel was loaded.  Set early in initarm(). */
100261656Sianvm_paddr_t arm_physmem_kernaddr;
101261649Sian
102261643Sian/*
103261643Sian * Print the contents of the physical and excluded region tables using the
104261643Sian * provided printf-like output function (which will be either printf or
105261643Sian * db_printf).
106261643Sian */
107261643Sianstatic void
108261643Sianphysmem_dump_tables(int (*prfunc)(const char *, ...))
109261643Sian{
110261643Sian	int flags, i;
111261643Sian	uintmax_t addr, size;
112261643Sian	const unsigned int mbyte = 1024 * 1024;
113261643Sian
114261643Sian	prfunc("Physical memory chunk(s):\n");
115261643Sian	for (i = 0; i < hwcnt; ++i) {
116261643Sian		addr = hwregions[i].addr;
117261643Sian		size = hwregions[i].size;
118261643Sian		prfunc("  0x%08jx - 0x%08jx, %5ju MB (%7ju pages)\n", addr,
119261643Sian		    addr + size - 1, size / mbyte, size / PAGE_SIZE);
120261643Sian	}
121261643Sian
122261643Sian	prfunc("Excluded memory regions:\n");
123261643Sian	for (i = 0; i < excnt; ++i) {
124261643Sian		addr  = exregions[i].addr;
125261643Sian		size  = exregions[i].size;
126261643Sian		flags = exregions[i].flags;
127261643Sian		prfunc("  0x%08jx - 0x%08jx, %5ju MB (%7ju pages) %s %s\n",
128261643Sian		    addr, addr + size - 1, size / mbyte, size / PAGE_SIZE,
129261643Sian		    (flags & EXFLAG_NOALLOC) ? "NoAlloc" : "",
130261643Sian		    (flags & EXFLAG_NODUMP)  ? "NoDump" : "");
131261643Sian	}
132261677Sian
133261677Sian#ifdef DEBUG
134261677Sian	prfunc("Avail lists:\n");
135261677Sian	for (i = 0; phys_avail[i] != 0; ++i) {
136261677Sian		prfunc("  phys_avail[%d] 0x%08x\n", i, phys_avail[i]);
137261677Sian	}
138261677Sian	for (i = 0; dump_avail[i] != 0; ++i) {
139261677Sian		prfunc("  dump_avail[%d] 0x%08x\n", i, dump_avail[i]);
140261677Sian	}
141261677Sian#endif
142261643Sian}
143261643Sian
144261643Sian/*
145261643Sian * Print the contents of the static mapping table.  Used for bootverbose.
146261643Sian */
147261643Sianvoid
148314506Sianarm_physmem_print_tables(void)
149261643Sian{
150261643Sian
151261643Sian	physmem_dump_tables(printf);
152261643Sian}
153261643Sian
154261643Sian/*
155261643Sian * Walk the list of hardware regions, processing it against the list of
156261643Sian * exclusions that contain the given exflags, and generating an "avail list".
157261643Sian *
158279702Sian * Updates the value at *pavail with the sum of all pages in all hw regions.
159261643Sian *
160261643Sian * Returns the number of pages of non-excluded memory added to the avail list.
161261643Sian */
162277532Sianstatic size_t
163277532Sianregions_to_avail(vm_paddr_t *avail, uint32_t exflags, long *pavail)
164261643Sian{
165261643Sian	size_t acnt, exi, hwi;
166293061Sian	uint64_t end, start, xend, xstart;
167261643Sian	long availmem;
168261643Sian	const struct region *exp, *hwp;
169261643Sian
170261643Sian	realmem = 0;
171261643Sian	availmem = 0;
172261643Sian	acnt = 0;
173261643Sian	for (hwi = 0, hwp = hwregions; hwi < hwcnt; ++hwi, ++hwp) {
174261643Sian		start = hwp->addr;
175261643Sian		end   = hwp->size + start;
176293061Sian		realmem += arm32_btop((vm_offset_t)(end - start));
177261643Sian		for (exi = 0, exp = exregions; exi < excnt; ++exi, ++exp) {
178272333Sian			/*
179272333Sian			 * If the excluded region does not match given flags,
180272333Sian			 * continue checking with the next excluded region.
181272333Sian			 */
182272333Sian			if ((exp->flags & exflags) == 0)
183272333Sian				continue;
184261643Sian			xstart = exp->addr;
185261643Sian			xend   = exp->size + xstart;
186261643Sian			/*
187261643Sian			 * If the excluded region ends before this hw region,
188261643Sian			 * continue checking with the next excluded region.
189261643Sian			 */
190261643Sian			if (xend <= start)
191261643Sian				continue;
192261643Sian			/*
193261643Sian			 * If the excluded region begins after this hw region
194261643Sian			 * we're done because both lists are sorted.
195261643Sian			 */
196261643Sian			if (xstart >= end)
197261643Sian				break;
198261643Sian			/*
199261643Sian			 * If the excluded region completely covers this hw
200261643Sian			 * region, shrink this hw region to zero size.
201261643Sian			 */
202261643Sian			if ((start >= xstart) && (end <= xend)) {
203261643Sian				start = xend;
204261643Sian				end = xend;
205261643Sian				break;
206261643Sian			}
207261643Sian			/*
208261643Sian			 * If the excluded region falls wholly within this hw
209261643Sian			 * region without abutting or overlapping the beginning
210261643Sian			 * or end, create an available entry from the leading
211261643Sian			 * fragment, then adjust the start of this hw region to
212261643Sian			 * the end of the excluded region, and continue checking
213261643Sian			 * the next excluded region because another exclusion
214261643Sian			 * could affect the remainder of this hw region.
215261643Sian			 */
216261643Sian			if ((xstart > start) && (xend < end)) {
217293061Sian				avail[acnt++] = (vm_paddr_t)start;
218293061Sian				avail[acnt++] = (vm_paddr_t)xstart;
219293061Sian				availmem +=
220293061Sian				    arm32_btop((vm_offset_t)(xstart - start));
221261643Sian				start = xend;
222261643Sian				continue;
223261643Sian			}
224261643Sian			/*
225261676Sian			 * We know the excluded region overlaps either the start
226261676Sian			 * or end of this hardware region (but not both), trim
227261676Sian			 * the excluded portion off the appropriate end.
228261643Sian			 */
229261676Sian			if (xstart <= start)
230261676Sian				start = xend;
231261676Sian			else
232261643Sian				end = xstart;
233261643Sian		}
234261643Sian		/*
235261643Sian		 * If the trimming actions above left a non-zero size, create an
236261643Sian		 * available entry for it.
237261643Sian		 */
238261643Sian		if (end > start) {
239293061Sian			avail[acnt++] = (vm_paddr_t)start;
240293061Sian			avail[acnt++] = (vm_paddr_t)end;
241293061Sian			availmem += arm32_btop((vm_offset_t)(end - start));
242261643Sian		}
243261643Sian		if (acnt >= MAX_AVAIL_ENTRIES)
244261643Sian			panic("Not enough space in the dump/phys_avail arrays");
245261643Sian	}
246261643Sian
247277532Sian	if (pavail)
248277532Sian		*pavail = availmem;
249277532Sian	return (acnt);
250261643Sian}
251261643Sian
252261643Sian/*
253261643Sian * Insertion-sort a new entry into a regions list; sorted by start address.
254261643Sian */
255261643Sianstatic void
256261656Sianinsert_region(struct region *regions, size_t rcnt, vm_paddr_t addr,
257261643Sian    vm_size_t size, uint32_t flags)
258261643Sian{
259261643Sian	size_t i;
260261643Sian	struct region *ep, *rp;
261261643Sian
262261643Sian	ep = regions + rcnt;
263261643Sian	for (i = 0, rp = regions; i < rcnt; ++i, ++rp) {
264261643Sian		if (addr < rp->addr) {
265261643Sian			bcopy(rp, rp + 1, (ep - rp) * sizeof(*rp));
266261643Sian			break;
267261643Sian		}
268261643Sian	}
269261643Sian	rp->addr  = addr;
270261643Sian	rp->size  = size;
271261643Sian	rp->flags = flags;
272261643Sian}
273261643Sian
274261643Sian/*
275261643Sian * Add a hardware memory region.
276261643Sian */
277261643Sianvoid
278294754Sandrewarm_physmem_hardware_region(uint64_t pa, uint64_t sz)
279261643Sian{
280261643Sian	vm_offset_t adj;
281261643Sian
282261643Sian	/*
283261643Sian	 * Filter out the page at PA 0x00000000.  The VM can't handle it, as
284261643Sian	 * pmap_extract() == 0 means failure.
285294754Sandrew	 */
286294754Sandrew	if (pa == 0) {
287294754Sandrew		if (sz <= PAGE_SIZE)
288294754Sandrew			return;
289294754Sandrew		pa  = PAGE_SIZE;
290294754Sandrew		sz -= PAGE_SIZE;
291294754Sandrew	} else if (pa > MAX_PHYS_ADDR) {
292294754Sandrew		/* This range is past usable memory, ignore it */
293294754Sandrew		return;
294294754Sandrew	}
295294754Sandrew
296294754Sandrew	/*
297293063Sian	 * Also filter out the page at the end of the physical address space --
298293065Sian	 * if addr is non-zero and addr+size is zero we wrapped to the next byte
299293065Sian	 * beyond what vm_paddr_t can express.  That leads to a NULL pointer
300293065Sian	 * deref early in startup; work around it by leaving the last page out.
301293063Sian	 *
302293063Sian	 * XXX This just in:  subtract out a whole megabyte, not just 1 page.
303293065Sian	 * Reducing the size by anything less than 1MB results in the NULL
304293065Sian	 * pointer deref in _vm_map_lock_read().  Better to give up a megabyte
305293065Sian	 * than leave some folks with an unusable system while we investigate.
306261643Sian	 */
307294754Sandrew	if ((pa + sz) > (MAX_PHYS_ADDR - 1024 * 1024)) {
308294754Sandrew		sz = MAX_PHYS_ADDR - pa + 1;
309294238Sandrew		if (sz <= 1024 * 1024)
310294238Sandrew			return;
311293063Sian		sz -= 1024 * 1024;
312261643Sian	}
313261643Sian
314261643Sian	/*
315261643Sian	 * Round the starting address up to a page boundary, and truncate the
316261643Sian	 * ending page down to a page boundary.
317261643Sian	 */
318261643Sian	adj = round_page(pa) - pa;
319261643Sian	pa  = round_page(pa);
320261643Sian	sz  = trunc_page(sz - adj);
321261643Sian
322294238Sandrew	if (sz > 0 && hwcnt < nitems(hwregions))
323261643Sian		insert_region(hwregions, hwcnt++, pa, sz, 0);
324261643Sian}
325261643Sian
326261643Sian/*
327261643Sian * Add an exclusion region.
328261643Sian */
329338694Smarkjvoid
330338694Smarkjarm_physmem_exclude_region(vm_paddr_t pa, vm_size_t sz, uint32_t exflags)
331261643Sian{
332261643Sian	vm_offset_t adj;
333261643Sian
334261643Sian	/*
335261643Sian	 * Truncate the starting address down to a page boundary, and round the
336261643Sian	 * ending page up to a page boundary.
337261643Sian	 */
338261643Sian	adj = pa - trunc_page(pa);
339261643Sian	pa  = trunc_page(pa);
340261643Sian	sz  = round_page(sz + adj);
341261643Sian
342338694Smarkj	if (excnt >= nitems(exregions))
343338694Smarkj		panic("failed to exclude region %#jx-%#jx", (uintmax_t)pa,
344338694Smarkj		    (uintmax_t)(pa + sz));
345338694Smarkj	insert_region(exregions, excnt++, pa, sz, exflags);
346261643Sian}
347261643Sian
348261643Sian/*
349261643Sian * Process all the regions added earlier into the global avail lists.
350277532Sian *
351277532Sian * Updates the kernel global 'physmem' with the number of physical pages
352277532Sian * available for use (all pages not in any exclusion region).
353277532Sian *
354277532Sian * Updates the kernel global 'Maxmem' with the page number one greater then the
355277532Sian * last page of physical memory in the system.
356261643Sian */
357261643Sianvoid
358261643Sianarm_physmem_init_kernel_globals(void)
359261643Sian{
360277532Sian	size_t nextidx;
361261643Sian
362277532Sian	regions_to_avail(dump_avail, EXFLAG_NODUMP, NULL);
363277532Sian	nextidx = regions_to_avail(phys_avail, EXFLAG_NOALLOC, &physmem);
364277532Sian	if (nextidx == 0)
365277532Sian		panic("No memory entries in phys_avail");
366277532Sian	Maxmem = atop(phys_avail[nextidx - 1]);
367261643Sian}
368261643Sian
369261643Sian#ifdef DDB
370261643Sian#include <ddb/ddb.h>
371261643Sian
372261643SianDB_SHOW_COMMAND(physmem, db_show_physmem)
373261643Sian{
374261643Sian
375261643Sian	physmem_dump_tables(db_printf);
376261643Sian}
377261643Sian
378261643Sian#endif /* DDB */
379261643Sian
380