1 | /* |
---|
2 | * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>. |
---|
3 | * |
---|
4 | * This program is free software; you can redistribute it and/or |
---|
5 | * modify it under the terms of the GNU General Public License as |
---|
6 | * published by the Free Software Foundation; either version 2 of the |
---|
7 | * License, or any later version. |
---|
8 | * |
---|
9 | * This program is distributed in the hope that it will be useful, but |
---|
10 | * WITHOUT ANY WARRANTY; without even the implied warranty of |
---|
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
---|
12 | * General Public License for more details. |
---|
13 | * |
---|
14 | * You should have received a copy of the GNU General Public License |
---|
15 | * along with this program; if not, write to the Free Software |
---|
16 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
---|
17 | */ |
---|
18 | |
---|
19 | FILE_LICENCE ( GPL2_OR_LATER ); |
---|
20 | |
---|
21 | #include <stdint.h> |
---|
22 | #include <errno.h> |
---|
23 | #include <realmode.h> |
---|
24 | #include <bios.h> |
---|
25 | #include <memsizes.h> |
---|
26 | #include <gpxe/memmap.h> |
---|
27 | |
---|
28 | /** |
---|
29 | * @file |
---|
30 | * |
---|
31 | * Memory mapping |
---|
32 | * |
---|
33 | */ |
---|
34 | |
---|
35 | /** Magic value for INT 15,e820 calls */ |
---|
36 | #define SMAP ( 0x534d4150 ) |
---|
37 | |
---|
38 | /** An INT 15,e820 memory map entry */ |
---|
39 | struct e820_entry { |
---|
40 | /** Start of region */ |
---|
41 | uint64_t start; |
---|
42 | /** Length of region */ |
---|
43 | uint64_t len; |
---|
44 | /** Type of region */ |
---|
45 | uint32_t type; |
---|
46 | /** Extended attributes (optional) */ |
---|
47 | uint32_t attrs; |
---|
48 | } __attribute__ (( packed )); |
---|
49 | |
---|
50 | #define E820_TYPE_RAM 1 /**< Normal memory */ |
---|
51 | #define E820_TYPE_RESERVED 2 /**< Reserved and unavailable */ |
---|
52 | #define E820_TYPE_ACPI 3 /**< ACPI reclaim memory */ |
---|
53 | #define E820_TYPE_NVS 4 /**< ACPI NVS memory */ |
---|
54 | |
---|
55 | #define E820_ATTR_ENABLED 0x00000001UL |
---|
56 | #define E820_ATTR_NONVOLATILE 0x00000002UL |
---|
57 | #define E820_ATTR_UNKNOWN 0xfffffffcUL |
---|
58 | |
---|
59 | #define E820_MIN_SIZE 20 |
---|
60 | |
---|
61 | /** Buffer for INT 15,e820 calls */ |
---|
62 | static struct e820_entry __bss16 ( e820buf ); |
---|
63 | #define e820buf __use_data16 ( e820buf ) |
---|
64 | |
---|
65 | /** |
---|
66 | * Get size of extended memory via INT 15,e801 |
---|
67 | * |
---|
68 | * @ret extmem Extended memory size, in kB, or 0 |
---|
69 | */ |
---|
70 | static unsigned int extmemsize_e801 ( void ) { |
---|
71 | uint16_t extmem_1m_to_16m_k, extmem_16m_plus_64k; |
---|
72 | uint16_t confmem_1m_to_16m_k, confmem_16m_plus_64k; |
---|
73 | unsigned int flags; |
---|
74 | unsigned int extmem; |
---|
75 | |
---|
76 | __asm__ __volatile__ ( REAL_CODE ( "stc\n\t" |
---|
77 | "int $0x15\n\t" |
---|
78 | "pushfw\n\t" |
---|
79 | "popw %w0\n\t" ) |
---|
80 | : "=r" ( flags ), |
---|
81 | "=a" ( extmem_1m_to_16m_k ), |
---|
82 | "=b" ( extmem_16m_plus_64k ), |
---|
83 | "=c" ( confmem_1m_to_16m_k ), |
---|
84 | "=d" ( confmem_16m_plus_64k ) |
---|
85 | : "a" ( 0xe801 ) ); |
---|
86 | |
---|
87 | if ( flags & CF ) { |
---|
88 | DBG ( "INT 15,e801 failed with CF set\n" ); |
---|
89 | return 0; |
---|
90 | } |
---|
91 | |
---|
92 | if ( ! ( extmem_1m_to_16m_k | extmem_16m_plus_64k ) ) { |
---|
93 | DBG ( "INT 15,e801 extmem=0, using confmem\n" ); |
---|
94 | extmem_1m_to_16m_k = confmem_1m_to_16m_k; |
---|
95 | extmem_16m_plus_64k = confmem_16m_plus_64k; |
---|
96 | } |
---|
97 | |
---|
98 | extmem = ( extmem_1m_to_16m_k + ( extmem_16m_plus_64k * 64 ) ); |
---|
99 | DBG ( "INT 15,e801 extended memory size %d+64*%d=%d kB " |
---|
100 | "[100000,%llx)\n", extmem_1m_to_16m_k, extmem_16m_plus_64k, |
---|
101 | extmem, ( 0x100000 + ( ( ( uint64_t ) extmem ) * 1024 ) ) ); |
---|
102 | |
---|
103 | /* Sanity check. Some BIOSes report the entire 4GB address |
---|
104 | * space as available, which cannot be correct (since that |
---|
105 | * would leave no address space available for 32-bit PCI |
---|
106 | * BARs). |
---|
107 | */ |
---|
108 | if ( extmem == ( 0x400000 - 0x400 ) ) { |
---|
109 | DBG ( "INT 15,e801 reported whole 4GB; assuming insane\n" ); |
---|
110 | return 0; |
---|
111 | } |
---|
112 | |
---|
113 | return extmem; |
---|
114 | } |
---|
115 | |
---|
116 | /** |
---|
117 | * Get size of extended memory via INT 15,88 |
---|
118 | * |
---|
119 | * @ret extmem Extended memory size, in kB |
---|
120 | */ |
---|
121 | static unsigned int extmemsize_88 ( void ) { |
---|
122 | uint16_t extmem; |
---|
123 | |
---|
124 | /* Ignore CF; it is not reliable for this call */ |
---|
125 | __asm__ __volatile__ ( REAL_CODE ( "int $0x15" ) |
---|
126 | : "=a" ( extmem ) : "a" ( 0x8800 ) ); |
---|
127 | |
---|
128 | DBG ( "INT 15,88 extended memory size %d kB [100000, %x)\n", |
---|
129 | extmem, ( 0x100000 + ( extmem * 1024 ) ) ); |
---|
130 | return extmem; |
---|
131 | } |
---|
132 | |
---|
133 | /** |
---|
134 | * Get size of extended memory |
---|
135 | * |
---|
136 | * @ret extmem Extended memory size, in kB |
---|
137 | * |
---|
138 | * Note that this is only an approximation; for an accurate picture, |
---|
139 | * use the E820 memory map obtained via get_memmap(); |
---|
140 | */ |
---|
141 | unsigned int extmemsize ( void ) { |
---|
142 | unsigned int extmem; |
---|
143 | |
---|
144 | /* Try INT 15,e801 first, then fall back to INT 15,88 */ |
---|
145 | extmem = extmemsize_e801(); |
---|
146 | if ( ! extmem ) |
---|
147 | extmem = extmemsize_88(); |
---|
148 | return extmem; |
---|
149 | } |
---|
150 | |
---|
151 | /** |
---|
152 | * Get e820 memory map |
---|
153 | * |
---|
154 | * @v memmap Memory map to fill in |
---|
155 | * @ret rc Return status code |
---|
156 | */ |
---|
157 | static int meme820 ( struct memory_map *memmap ) { |
---|
158 | struct memory_region *region = memmap->regions; |
---|
159 | uint32_t next = 0; |
---|
160 | uint32_t smap; |
---|
161 | size_t size; |
---|
162 | unsigned int flags; |
---|
163 | unsigned int discard_D; |
---|
164 | |
---|
165 | /* Clear the E820 buffer. Do this once before starting, |
---|
166 | * rather than on each call; some BIOSes rely on the contents |
---|
167 | * being preserved between calls. |
---|
168 | */ |
---|
169 | memset ( &e820buf, 0, sizeof ( e820buf ) ); |
---|
170 | |
---|
171 | do { |
---|
172 | /* Some BIOSes corrupt %esi for fun. Guard against |
---|
173 | * this by telling gcc that all non-output registers |
---|
174 | * may be corrupted. |
---|
175 | */ |
---|
176 | __asm__ __volatile__ ( REAL_CODE ( "pushl %%ebp\n\t" |
---|
177 | "stc\n\t" |
---|
178 | "int $0x15\n\t" |
---|
179 | "pushfw\n\t" |
---|
180 | "popw %%dx\n\t" |
---|
181 | "popl %%ebp\n\t" ) |
---|
182 | : "=a" ( smap ), "=b" ( next ), |
---|
183 | "=c" ( size ), "=d" ( flags ), |
---|
184 | "=D" ( discard_D ) |
---|
185 | : "a" ( 0xe820 ), "b" ( next ), |
---|
186 | "D" ( __from_data16 ( &e820buf ) ), |
---|
187 | "c" ( sizeof ( e820buf ) ), |
---|
188 | "d" ( SMAP ) |
---|
189 | : "esi", "memory" ); |
---|
190 | |
---|
191 | if ( smap != SMAP ) { |
---|
192 | DBG ( "INT 15,e820 failed SMAP signature check\n" ); |
---|
193 | return -ENOTSUP; |
---|
194 | } |
---|
195 | |
---|
196 | if ( size < E820_MIN_SIZE ) { |
---|
197 | DBG ( "INT 15,e820 returned only %zd bytes\n", size ); |
---|
198 | return -EINVAL; |
---|
199 | } |
---|
200 | |
---|
201 | if ( flags & CF ) { |
---|
202 | DBG ( "INT 15,e820 terminated on CF set\n" ); |
---|
203 | break; |
---|
204 | } |
---|
205 | |
---|
206 | /* If first region is not RAM, assume map is invalid */ |
---|
207 | if ( ( memmap->count == 0 ) && |
---|
208 | ( e820buf.type != E820_TYPE_RAM ) ) { |
---|
209 | DBG ( "INT 15,e820 failed, first entry not RAM\n" ); |
---|
210 | return -EINVAL; |
---|
211 | } |
---|
212 | |
---|
213 | DBG ( "INT 15,e820 region [%llx,%llx) type %d", |
---|
214 | e820buf.start, ( e820buf.start + e820buf.len ), |
---|
215 | ( int ) e820buf.type ); |
---|
216 | if ( size > offsetof ( typeof ( e820buf ), attrs ) ) { |
---|
217 | DBG ( " (%s", ( ( e820buf.attrs & E820_ATTR_ENABLED ) |
---|
218 | ? "enabled" : "disabled" ) ); |
---|
219 | if ( e820buf.attrs & E820_ATTR_NONVOLATILE ) |
---|
220 | DBG ( ", non-volatile" ); |
---|
221 | if ( e820buf.attrs & E820_ATTR_UNKNOWN ) |
---|
222 | DBG ( ", other [%08x]", e820buf.attrs ); |
---|
223 | DBG ( ")" ); |
---|
224 | } |
---|
225 | DBG ( "\n" ); |
---|
226 | |
---|
227 | /* Discard non-RAM regions */ |
---|
228 | if ( e820buf.type != E820_TYPE_RAM ) |
---|
229 | continue; |
---|
230 | |
---|
231 | /* Check extended attributes, if present */ |
---|
232 | if ( size > offsetof ( typeof ( e820buf ), attrs ) ) { |
---|
233 | if ( ! ( e820buf.attrs & E820_ATTR_ENABLED ) ) |
---|
234 | continue; |
---|
235 | if ( e820buf.attrs & E820_ATTR_NONVOLATILE ) |
---|
236 | continue; |
---|
237 | } |
---|
238 | |
---|
239 | region->start = e820buf.start; |
---|
240 | region->end = e820buf.start + e820buf.len; |
---|
241 | region++; |
---|
242 | memmap->count++; |
---|
243 | |
---|
244 | if ( memmap->count >= ( sizeof ( memmap->regions ) / |
---|
245 | sizeof ( memmap->regions[0] ) ) ) { |
---|
246 | DBG ( "INT 15,e820 too many regions returned\n" ); |
---|
247 | /* Not a fatal error; what we've got so far at |
---|
248 | * least represents valid regions of memory, |
---|
249 | * even if we couldn't get them all. |
---|
250 | */ |
---|
251 | break; |
---|
252 | } |
---|
253 | } while ( next != 0 ); |
---|
254 | |
---|
255 | /* Sanity checks. Some BIOSes report complete garbage via INT |
---|
256 | * 15,e820 (especially at POST time), despite passing the |
---|
257 | * signature checks. We currently check for a base memory |
---|
258 | * region (starting at 0) and at least one high memory region |
---|
259 | * (starting at 0x100000). |
---|
260 | */ |
---|
261 | if ( memmap->count < 2 ) { |
---|
262 | DBG ( "INT 15,e820 returned only %d regions; assuming " |
---|
263 | "insane\n", memmap->count ); |
---|
264 | return -EINVAL; |
---|
265 | } |
---|
266 | if ( memmap->regions[0].start != 0 ) { |
---|
267 | DBG ( "INT 15,e820 region 0 starts at %llx (expected 0); " |
---|
268 | "assuming insane\n", memmap->regions[0].start ); |
---|
269 | return -EINVAL; |
---|
270 | } |
---|
271 | if ( memmap->regions[1].start != 0x100000 ) { |
---|
272 | DBG ( "INT 15,e820 region 1 starts at %llx (expected 100000); " |
---|
273 | "assuming insane\n", memmap->regions[0].start ); |
---|
274 | return -EINVAL; |
---|
275 | } |
---|
276 | |
---|
277 | return 0; |
---|
278 | } |
---|
279 | |
---|
280 | /** |
---|
281 | * Get memory map |
---|
282 | * |
---|
283 | * @v memmap Memory map to fill in |
---|
284 | */ |
---|
285 | void get_memmap ( struct memory_map *memmap ) { |
---|
286 | unsigned int basemem, extmem; |
---|
287 | int rc; |
---|
288 | |
---|
289 | DBG ( "Fetching system memory map\n" ); |
---|
290 | |
---|
291 | /* Clear memory map */ |
---|
292 | memset ( memmap, 0, sizeof ( *memmap ) ); |
---|
293 | |
---|
294 | /* Get base and extended memory sizes */ |
---|
295 | basemem = basememsize(); |
---|
296 | DBG ( "FBMS base memory size %d kB [0,%x)\n", |
---|
297 | basemem, ( basemem * 1024 ) ); |
---|
298 | extmem = extmemsize(); |
---|
299 | |
---|
300 | /* Try INT 15,e820 first */ |
---|
301 | if ( ( rc = meme820 ( memmap ) ) == 0 ) { |
---|
302 | DBG ( "Obtained system memory map via INT 15,e820\n" ); |
---|
303 | return; |
---|
304 | } |
---|
305 | |
---|
306 | /* Fall back to constructing a map from basemem and extmem sizes */ |
---|
307 | DBG ( "INT 15,e820 failed; constructing map\n" ); |
---|
308 | memmap->regions[0].end = ( basemem * 1024 ); |
---|
309 | memmap->regions[1].start = 0x100000; |
---|
310 | memmap->regions[1].end = 0x100000 + ( extmem * 1024 ); |
---|
311 | memmap->count = 2; |
---|
312 | } |
---|