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$");
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_paddr_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/* The address at which the kernel was loaded.  Set early in initarm(). */
93vm_paddr_t arm_physmem_kernaddr;
94
95/*
96 * Print the contents of the physical and excluded region tables using the
97 * provided printf-like output function (which will be either printf or
98 * db_printf).
99 */
100static void
101physmem_dump_tables(int (*prfunc)(const char *, ...))
102{
103	int flags, i;
104	uintmax_t addr, size;
105	const unsigned int mbyte = 1024 * 1024;
106
107	prfunc("Physical memory chunk(s):\n");
108	for (i = 0; i < hwcnt; ++i) {
109		addr = hwregions[i].addr;
110		size = hwregions[i].size;
111		prfunc("  0x%08jx - 0x%08jx, %5ju MB (%7ju pages)\n", addr,
112		    addr + size - 1, size / mbyte, size / PAGE_SIZE);
113	}
114
115	prfunc("Excluded memory regions:\n");
116	for (i = 0; i < excnt; ++i) {
117		addr  = exregions[i].addr;
118		size  = exregions[i].size;
119		flags = exregions[i].flags;
120		prfunc("  0x%08jx - 0x%08jx, %5ju MB (%7ju pages) %s %s\n",
121		    addr, addr + size - 1, size / mbyte, size / PAGE_SIZE,
122		    (flags & EXFLAG_NOALLOC) ? "NoAlloc" : "",
123		    (flags & EXFLAG_NODUMP)  ? "NoDump" : "");
124	}
125
126#ifdef DEBUG
127	prfunc("Avail lists:\n");
128	for (i = 0; phys_avail[i] != 0; ++i) {
129		prfunc("  phys_avail[%d] 0x%08x\n", i, phys_avail[i]);
130	}
131	for (i = 0; dump_avail[i] != 0; ++i) {
132		prfunc("  dump_avail[%d] 0x%08x\n", i, dump_avail[i]);
133	}
134#endif
135}
136
137/*
138 * Print the contents of the static mapping table.  Used for bootverbose.
139 */
140void
141arm_physmem_print_tables()
142{
143
144	physmem_dump_tables(printf);
145}
146
147/*
148 * Walk the list of hardware regions, processing it against the list of
149 * exclusions that contain the given exflags, and generating an "avail list".
150 *
151 * Updates the kernel global 'realmem' with the sum of all pages in hw regions.
152 *
153 * Returns the number of pages of non-excluded memory added to the avail list.
154 */
155static long
156regions_to_avail(vm_paddr_t *avail, uint32_t exflags)
157{
158	size_t acnt, exi, hwi;
159	vm_paddr_t end, start, xend, xstart;
160	long availmem;
161	const struct region *exp, *hwp;
162
163	realmem = 0;
164	availmem = 0;
165	acnt = 0;
166	for (hwi = 0, hwp = hwregions; hwi < hwcnt; ++hwi, ++hwp) {
167		start = hwp->addr;
168		end   = hwp->size + start;
169		realmem += arm32_btop(end - start);
170		for (exi = 0, exp = exregions; exi < excnt; ++exi, ++exp) {
171			xstart = exp->addr;
172			xend   = exp->size + xstart;
173			/*
174			 * If the excluded region ends before this hw region,
175			 * continue checking with the next excluded region.
176			 */
177			if (xend <= start)
178				continue;
179			/*
180			 * If the excluded region begins after this hw region
181			 * we're done because both lists are sorted.
182			 */
183			if (xstart >= end)
184				break;
185			/*
186			 * If the excluded region completely covers this hw
187			 * region, shrink this hw region to zero size.
188			 */
189			if ((start >= xstart) && (end <= xend)) {
190				start = xend;
191				end = xend;
192				break;
193			}
194			/*
195			 * If the excluded region falls wholly within this hw
196			 * region without abutting or overlapping the beginning
197			 * or end, create an available entry from the leading
198			 * fragment, then adjust the start of this hw region to
199			 * the end of the excluded region, and continue checking
200			 * the next excluded region because another exclusion
201			 * could affect the remainder of this hw region.
202			 */
203			if ((xstart > start) && (xend < end)) {
204				avail[acnt++] = start;
205				avail[acnt++] = xstart;
206				availmem += arm32_btop(xstart - start);
207				start = xend;
208				continue;
209			}
210			/*
211			 * We know the excluded region overlaps either the start
212			 * or end of this hardware region (but not both), trim
213			 * the excluded portion off the appropriate end.
214			 */
215			if (xstart <= start)
216				start = xend;
217			else
218				end = xstart;
219		}
220		/*
221		 * If the trimming actions above left a non-zero size, create an
222		 * available entry for it.
223		 */
224		if (end > start) {
225			avail[acnt++] = start;
226			avail[acnt++] = end;
227			availmem += arm32_btop(end - start);
228		}
229		if (acnt >= MAX_AVAIL_ENTRIES)
230			panic("Not enough space in the dump/phys_avail arrays");
231	}
232
233	return (availmem);
234}
235
236/*
237 * Insertion-sort a new entry into a regions list; sorted by start address.
238 */
239static void
240insert_region(struct region *regions, size_t rcnt, vm_paddr_t addr,
241    vm_size_t size, uint32_t flags)
242{
243	size_t i;
244	struct region *ep, *rp;
245
246	ep = regions + rcnt;
247	for (i = 0, rp = regions; i < rcnt; ++i, ++rp) {
248		if (addr < rp->addr) {
249			bcopy(rp, rp + 1, (ep - rp) * sizeof(*rp));
250			break;
251		}
252	}
253	rp->addr  = addr;
254	rp->size  = size;
255	rp->flags = flags;
256}
257
258/*
259 * Add a hardware memory region.
260 */
261void
262arm_physmem_hardware_region(vm_paddr_t pa, vm_size_t sz)
263{
264	vm_offset_t adj;
265
266	/*
267	 * Filter out the page at PA 0x00000000.  The VM can't handle it, as
268	 * pmap_extract() == 0 means failure.
269	 */
270	if (pa == 0) {
271		pa  = PAGE_SIZE;
272		sz -= PAGE_SIZE;
273	}
274
275	/*
276	 * Round the starting address up to a page boundary, and truncate the
277	 * ending page down to a page boundary.
278	 */
279	adj = round_page(pa) - pa;
280	pa  = round_page(pa);
281	sz  = trunc_page(sz - adj);
282
283	if (hwcnt < nitems(hwregions))
284		insert_region(hwregions, hwcnt++, pa, sz, 0);
285}
286
287/*
288 * Add an exclusion region.
289 */
290void arm_physmem_exclude_region(vm_paddr_t pa, vm_size_t sz, uint32_t exflags)
291{
292	vm_offset_t adj;
293
294	/*
295	 * Truncate the starting address down to a page boundary, and round the
296	 * ending page up to a page boundary.
297	 */
298	adj = pa - trunc_page(pa);
299	pa  = trunc_page(pa);
300	sz  = round_page(sz + adj);
301
302	if (excnt < nitems(exregions))
303		insert_region(exregions, excnt++, pa, sz, exflags);
304}
305
306/*
307 * Process all the regions added earlier into the global avail lists.
308 */
309void
310arm_physmem_init_kernel_globals(void)
311{
312
313	regions_to_avail(dump_avail, EXFLAG_NODUMP);
314	physmem = regions_to_avail(phys_avail, EXFLAG_NOALLOC);
315}
316
317#ifdef DDB
318#include <ddb/ddb.h>
319
320DB_SHOW_COMMAND(physmem, db_show_physmem)
321{
322
323	physmem_dump_tables(db_printf);
324}
325
326#endif /* DDB */
327
328