physmem.c revision 261643
1/*-
2 * Copyright (c) 2014 Ian Lepore <ian@freebsd.org>
3 * All rights excluded.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 */
26
27#include <sys/cdefs.h>
28__FBSDID("$FreeBSD: head/sys/arm/arm/physmem.c 261643 2014-02-08 23:54:16Z ian $");
29
30#include "opt_ddb.h"
31
32/*
33 * Routines for describing and initializing anything related to physical memory.
34 */
35
36#include <sys/param.h>
37#include <sys/systm.h>
38#include <vm/vm.h>
39#include <machine/physmem.h>
40
41/*
42 * These structures are used internally to keep track of regions of physical
43 * ram, and regions within the physical ram that need to be excluded.  An
44 * exclusion region can be excluded from crash dumps, from the vm pool of pages
45 * that can be allocated, or both, depending on the exclusion flags associated
46 * with the region.
47 */
48#define	MAX_HWCNT	10
49#define	MAX_EXCNT	10
50
51struct region {
52	vm_offset_t	addr;
53	vm_size_t	size;
54	uint32_t	flags;
55};
56
57static struct region hwregions[MAX_HWCNT];
58static struct region exregions[MAX_EXCNT];
59
60static size_t hwcnt;
61static size_t excnt;
62
63/*
64 * These "avail lists" are globals used to communicate physical memory layout to
65 * other parts of the kernel.  Within the arrays, each value is the starting
66 * address of a contiguous area of physical address space.  The values at even
67 * indexes are areas that contain usable memory and the values at odd indexes
68 * are areas that aren't usable.  Each list is terminated by a pair of zero
69 * entries.
70 *
71 * dump_avail tells the dump code what regions to include in a crash dump, and
72 * phys_avail is the way we hand all the remaining physical ram we haven't used
73 * in early kernel init over to the vm system for allocation management.
74 *
75 * We size these arrays to hold twice as many available regions as we allow for
76 * hardware memory regions, to allow for the fact that exclusions can split a
77 * hardware region into two or more available regions.  In the real world there
78 * will typically be one or two hardware regions and two or three exclusions.
79 *
80 * Each available region in this list occupies two array slots (the start of the
81 * available region and the start of the unavailable region that follows it).
82 */
83#define	MAX_AVAIL_REGIONS	(MAX_HWCNT * 2)
84#define	MAX_AVAIL_ENTRIES	(MAX_AVAIL_REGIONS * 2)
85
86vm_paddr_t phys_avail[MAX_AVAIL_ENTRIES + 2]; /* +2 to allow for a pair  */
87vm_paddr_t dump_avail[MAX_AVAIL_ENTRIES + 2]; /* of zeroes to terminate. */
88
89/* This is the total number of hardware pages, excluded or not. */
90long realmem;
91
92/*
93 * Print the contents of the physical and excluded region tables using the
94 * provided printf-like output function (which will be either printf or
95 * db_printf).
96 */
97static void
98physmem_dump_tables(int (*prfunc)(const char *, ...))
99{
100	int flags, i;
101	uintmax_t addr, size;
102	const unsigned int mbyte = 1024 * 1024;
103
104	prfunc("Physical memory chunk(s):\n");
105	for (i = 0; i < hwcnt; ++i) {
106		addr = hwregions[i].addr;
107		size = hwregions[i].size;
108		prfunc("  0x%08jx - 0x%08jx, %5ju MB (%7ju pages)\n", addr,
109		    addr + size - 1, size / mbyte, size / PAGE_SIZE);
110	}
111
112	prfunc("Excluded memory regions:\n");
113	for (i = 0; i < excnt; ++i) {
114		addr  = exregions[i].addr;
115		size  = exregions[i].size;
116		flags = exregions[i].flags;
117		prfunc("  0x%08jx - 0x%08jx, %5ju MB (%7ju pages) %s %s\n",
118		    addr, addr + size - 1, size / mbyte, size / PAGE_SIZE,
119		    (flags & EXFLAG_NOALLOC) ? "NoAlloc" : "",
120		    (flags & EXFLAG_NODUMP)  ? "NoDump" : "");
121	}
122}
123
124/*
125 * Print the contents of the static mapping table.  Used for bootverbose.
126 */
127void
128arm_physmem_print_tables()
129{
130
131	physmem_dump_tables(printf);
132}
133
134/*
135 * Walk the list of hardware regions, processing it against the list of
136 * exclusions that contain the given exflags, and generating an "avail list".
137 *
138 * Updates the kernel global 'realmem' with the sum of all pages in hw regions.
139 *
140 * Returns the number of pages of non-excluded memory added to the avail list.
141 */
142static long
143regions_to_avail(vm_paddr_t *avail, uint32_t exflags)
144{
145	size_t acnt, exi, hwi;
146	vm_paddr_t end, start, xend, xstart;
147	long availmem;
148	const struct region *exp, *hwp;
149
150	realmem = 0;
151	availmem = 0;
152	acnt = 0;
153	for (hwi = 0, hwp = hwregions; hwi < hwcnt; ++hwi, ++hwp) {
154		start = hwp->addr;
155		end   = hwp->size + start;
156		realmem += arm32_btop(end - start);
157		for (exi = 0, exp = exregions; exi < excnt; ++exi, ++exp) {
158			xstart = exp->addr;
159			xend   = exp->size + xstart;
160			/*
161			 * If the excluded region ends before this hw region,
162			 * continue checking with the next excluded region.
163			 */
164			if (xend <= start)
165				continue;
166			/*
167			 * If the excluded region begins after this hw region
168			 * we're done because both lists are sorted.
169			 */
170			if (xstart >= end)
171				break;
172			/*
173			 * If the excluded region completely covers this hw
174			 * region, shrink this hw region to zero size.
175			 */
176			if ((start >= xstart) && (end <= xend)) {
177				start = xend;
178				end = xend;
179				break;
180			}
181			/*
182			 * If the excluded region falls wholly within this hw
183			 * region without abutting or overlapping the beginning
184			 * or end, create an available entry from the leading
185			 * fragment, then adjust the start of this hw region to
186			 * the end of the excluded region, and continue checking
187			 * the next excluded region because another exclusion
188			 * could affect the remainder of this hw region.
189			 */
190			if ((xstart > start) && (xend < end)) {
191				avail[acnt++] = start;
192				avail[acnt++] = xstart;
193				availmem += arm32_btop(xstart - start);
194				start = xend;
195				continue;
196			}
197			/*
198			 * If excluded region partially overlaps this region,
199			 * trim the excluded portion off the appropriate end.
200			 */
201			if ((xstart >= start) && (xstart <= end)) {
202				end = xstart;
203			} else if ((xend >= start) && (xend <= end)) {
204				start = xend;
205			}
206		}
207		/*
208		 * If the trimming actions above left a non-zero size, create an
209		 * available entry for it.
210		 */
211		if (end > start) {
212			avail[acnt++] = start;
213			avail[acnt++] = end;
214			availmem += arm32_btop(end - start);
215		}
216		if (acnt >= MAX_AVAIL_ENTRIES)
217			panic("Not enough space in the dump/phys_avail arrays");
218	}
219
220	return (availmem);
221}
222
223/*
224 * Insertion-sort a new entry into a regions list; sorted by start address.
225 */
226static void
227insert_region(struct region *regions, size_t rcnt, vm_offset_t addr,
228    vm_size_t size, uint32_t flags)
229{
230	size_t i;
231	struct region *ep, *rp;
232
233	ep = regions + rcnt;
234	for (i = 0, rp = regions; i < rcnt; ++i, ++rp) {
235		if (addr < rp->addr) {
236			bcopy(rp, rp + 1, (ep - rp) * sizeof(*rp));
237			break;
238		}
239	}
240	rp->addr  = addr;
241	rp->size  = size;
242	rp->flags = flags;
243}
244
245/*
246 * Add a hardware memory region.
247 */
248void
249arm_physmem_hardware_region(vm_offset_t pa, vm_size_t sz)
250{
251	vm_offset_t adj;
252
253	/*
254	 * Filter out the page at PA 0x00000000.  The VM can't handle it, as
255	 * pmap_extract() == 0 means failure.
256	 */
257	if (pa == 0) {
258		pa  = PAGE_SIZE;
259		sz -= PAGE_SIZE;
260	}
261
262	/*
263	 * Round the starting address up to a page boundary, and truncate the
264	 * ending page down to a page boundary.
265	 */
266	adj = round_page(pa) - pa;
267	pa  = round_page(pa);
268	sz  = trunc_page(sz - adj);
269
270	if (hwcnt < nitems(hwregions))
271		insert_region(hwregions, hwcnt++, pa, sz, 0);
272}
273
274/*
275 * Add an exclusion region.
276 */
277void arm_physmem_exclude_region(vm_offset_t pa, vm_size_t sz, uint32_t exflags)
278{
279	vm_offset_t adj;
280
281	/*
282	 * Truncate the starting address down to a page boundary, and round the
283	 * ending page up to a page boundary.
284	 */
285	adj = pa - trunc_page(pa);
286	pa  = trunc_page(pa);
287	sz  = round_page(sz + adj);
288
289	if (excnt < nitems(exregions))
290		insert_region(exregions, excnt++, pa, sz, exflags);
291}
292
293/*
294 * Process all the regions added earlier into the global avail lists.
295 */
296void
297arm_physmem_init_kernel_globals(void)
298{
299
300	regions_to_avail(dump_avail, EXFLAG_NODUMP);
301	physmem = regions_to_avail(phys_avail, EXFLAG_NOALLOC);
302}
303
304#ifdef DDB
305#include <ddb/ddb.h>
306
307DB_SHOW_COMMAND(physmem, db_show_physmem)
308{
309
310	physmem_dump_tables(db_printf);
311}
312
313#endif /* DDB */
314
315