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