aboutsummaryrefslogtreecommitdiffstats
path: root/gpxe/src/arch/i386/firmware/pcbios/e820mangler.S
blob: 99ca519bee37f7281ae228bafeeb3e464e4a0637 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
/*
 * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

FILE_LICENCE ( GPL2_OR_LATER )

	.text
	.arch i386
	.code16

#define SMAP 0x534d4150

/* Most documentation refers to the E820 buffer as being 20 bytes, and
 * the API makes it perfectly legitimate to pass only a 20-byte buffer
 * and expect to get valid data.  However, some morons at ACPI decided
 * to extend the data structure by adding an extra "extended
 * attributes" field and by including critical information within this
 * field, such as whether or not the region is enabled.  A caller who
 * passes in only a 20-byte buffer therefore risks getting very, very
 * misleading information.
 *
 * I have personally witnessed an HP BIOS that returns a value of
 * 0x0009 in the extended attributes field.  If we don't pass this
 * value through to the caller, 32-bit WinPE will die, usually with a
 * PAGE_FAULT_IN_NONPAGED_AREA blue screen of death.
 *
 * Allow a ridiculously large maximum value (64 bytes) for the E820
 * buffer as a guard against insufficiently creative idiots in the
 * future.
 */
#define E820MAXSIZE	64

/****************************************************************************
 *
 * Allowed memory windows
 *
 * There are two ways to view this list.  The first is as a list of
 * (non-overlapping) allowed memory regions, sorted by increasing
 * address.  The second is as a list of (non-overlapping) hidden
 * memory regions, again sorted by increasing address.  The second
 * view is offset by half an entry from the first: think about this
 * for a moment and it should make sense.
 *
 * xxx_memory_window is used to indicate an "allowed region"
 * structure, hidden_xxx_memory is used to indicate a "hidden region"
 * structure.  Each structure is 16 bytes in length.
 *
 ****************************************************************************
 */
	.section ".data16", "aw", @progbits
	.align 16
	.globl hidemem_base
	.globl hidemem_umalloc
	.globl hidemem_textdata
memory_windows:
base_memory_window:	.long 0x00000000, 0x00000000 /* Start of memory */

hidemem_base:		.long 0x000a0000, 0x00000000 /* Changes at runtime */
ext_memory_window:	.long 0x000a0000, 0x00000000 /* 640kB mark */

hidemem_umalloc:	.long 0xffffffff, 0xffffffff /* Changes at runtime */
			.long 0xffffffff, 0xffffffff /* Changes at runtime */

hidemem_textdata:	.long 0xffffffff, 0xffffffff /* Changes at runtime */
			.long 0xffffffff, 0xffffffff /* Changes at runtime */

			.long 0xffffffff, 0xffffffff /* End of memory */
memory_windows_end:

/****************************************************************************
 * Truncate region to memory window
 *
 * Parameters:
 *  %edx:%eax	Start of region
 *  %ecx:%ebx	Length of region
 *  %si		Memory window
 * Returns:
 *  %edx:%eax	Start of windowed region
 *  %ecx:%ebx	Length of windowed region
 ****************************************************************************
 */
	.section ".text16", "ax", @progbits
window_region:
	/* Convert (start,len) to (start, end) */
	addl	%eax, %ebx
	adcl	%edx, %ecx
	/* Truncate to window start */
	cmpl	4(%si), %edx
	jne	1f
	cmpl	0(%si), %eax
1:	jae	2f
	movl	4(%si), %edx
	movl	0(%si), %eax
2:	/* Truncate to window end */
	cmpl	12(%si), %ecx
	jne	1f
	cmpl	8(%si), %ebx
1:	jbe	2f
	movl	12(%si), %ecx
	movl	8(%si), %ebx
2:	/* Convert (start, end) back to (start, len) */
	subl	%eax, %ebx
	sbbl	%edx, %ecx
	/* If length is <0, set length to 0 */
	jae	1f
	xorl	%ebx, %ebx
	xorl	%ecx, %ecx
	ret
	.size	window_region, . - window_region

/****************************************************************************
 * Patch "memory above 1MB" figure
 *
 * Parameters:
 *  %ax		Memory above 1MB, in 1kB blocks
 * Returns:
 *  %ax		Modified memory above 1M in 1kB blocks
 ****************************************************************************
 */
	.section ".text16", "ax", @progbits
patch_1m:
	pushal
	/* Convert to (start,len) format and call truncate */
	xorl	%ecx, %ecx
	movzwl	%ax, %ebx
	shll	$10, %ebx
	xorl	%edx, %edx
	movl	$0x100000, %eax
	movw	$ext_memory_window, %si
	call	window_region
	/* Convert back to "memory above 1MB" format and return via %ax */
	pushfw
	shrl	$10, %ebx
	popfw
	movw	%sp, %bp
	movw	%bx, 28(%bp)
	popal
	ret
	.size patch_1m, . - patch_1m

/****************************************************************************
 * Patch "memory above 16MB" figure
 *
 * Parameters:
 *  %bx		Memory above 16MB, in 64kB blocks
 * Returns:
 *  %bx		Modified memory above 16M in 64kB blocks
 ****************************************************************************
 */
	.section ".text16", "ax", @progbits
patch_16m:
	pushal
	/* Convert to (start,len) format and call truncate */
	xorl	%ecx, %ecx
	shll	$16, %ebx
	xorl	%edx, %edx
	movl	$0x1000000, %eax
	movw	$ext_memory_window, %si
	call	window_region
	/* Convert back to "memory above 16MB" format and return via %bx */
	pushfw
	shrl	$16, %ebx
	popfw
	movw	%sp, %bp
	movw	%bx, 16(%bp)
	popal
	ret
	.size patch_16m, . - patch_16m

/****************************************************************************
 * Patch "memory between 1MB and 16MB" and "memory above 16MB" figures
 *
 * Parameters:
 *  %ax		Memory between 1MB and 16MB, in 1kB blocks
 *  %bx		Memory above 16MB, in 64kB blocks
 * Returns:
 *  %ax		Modified memory between 1MB and 16MB, in 1kB blocks
 *  %bx		Modified memory above 16MB, in 64kB blocks
 ****************************************************************************
 */
	.section ".text16", "ax", @progbits
patch_1m_16m:
	call	patch_1m
	call	patch_16m
	/* If 1M region is no longer full-length, kill off the 16M region */
	cmpw	$( 15 * 1024 ), %ax
	je	1f
	xorw	%bx, %bx
1:	ret
	.size patch_1m_16m, . - patch_1m_16m

/****************************************************************************
 * Get underlying e820 memory region to underlying_e820 buffer
 *
 * Parameters:
 *   As for INT 15,e820
 * Returns:
 *   As for INT 15,e820
 *
 * Wraps the underlying INT 15,e820 call so that the continuation
 * value (%ebx) is a 16-bit simple sequence counter (with the high 16
 * bits ignored), and termination is always via CF=1 rather than
 * %ebx=0.
 *
 ****************************************************************************
 */
	.section ".text16", "ax", @progbits
get_underlying_e820:

	/* If the requested region is in the cache, return it */
	cmpw	%bx, underlying_e820_index
	jne	2f
	pushw	%di
	pushw	%si
	movw	$underlying_e820_cache, %si
	cmpl	underlying_e820_cache_size, %ecx
	jbe	1f
	movl	underlying_e820_cache_size, %ecx
1:	pushl	%ecx
	rep movsb
	popl	%ecx
	popw	%si
	popw	%di
	incw	%bx
	movl	%edx, %eax
	clc
	ret
2:	
	/* If the requested region is earlier than the cached region,
	 * invalidate the cache.
	 */
	cmpw	%bx, underlying_e820_index
	jbe	1f
	movw	$0xffff, underlying_e820_index
1:
	/* If the cache is invalid, reset the underlying %ebx */
	cmpw	$0xffff, underlying_e820_index
	jne	1f
	andl	$0, underlying_e820_ebx
1:	
	/* If the cache is valid but the continuation value is zero,
	 * this means that the previous underlying call returned with
	 * %ebx=0.  Return with CF=1 in this case.
	 */
	cmpw	$0xffff, underlying_e820_index
	je	1f
	cmpl	$0, underlying_e820_ebx
	jne	1f
	stc
	ret
1:	
	/* Get the next region into the cache */
	pushl	%eax
	pushl	%ebx
	pushl	%ecx
	pushl	%edx
	pushl	%esi	/* Some implementations corrupt %esi, so we	*/
	pushl	%edi	/* preserve %esi, %edi and %ebp to be paranoid	*/
	pushl	%ebp
	pushw	%es
	pushw	%ds
	popw	%es
	movw	$underlying_e820_cache, %di
	cmpl	$E820MAXSIZE, %ecx
	jbe	1f
	movl	$E820MAXSIZE, %ecx
1:	movl	underlying_e820_ebx, %ebx
	stc
	pushfw
	lcall	*%cs:int15_vector
	popw	%es
	popl	%ebp
	popl	%edi
	popl	%esi
	/* Check for error return from underlying e820 call */
	jc	2f /* CF set: error */
	cmpl	$SMAP, %eax
	je	3f /* 'SMAP' missing: error */
2:	/* An error occurred: return values returned by underlying e820 call */
	stc	/* Force CF set if SMAP was missing */
	addr32 leal 16(%esp), %esp /* avoid changing other flags */
	ret
3:	/* No error occurred */
	movl	%ebx, underlying_e820_ebx
	movl	%ecx, underlying_e820_cache_size
	popl	%edx
	popl	%ecx
	popl	%ebx
	popl	%eax
	/* Mark cache as containing this result */
	incw	underlying_e820_index

	/* Loop until found */
	jmp	get_underlying_e820
	.size	get_underlying_e820, . - get_underlying_e820

	.section ".data16", "aw", @progbits
underlying_e820_index:
	.word	0xffff /* Initialise to an invalid value */
	.size underlying_e820_index, . - underlying_e820_index

	.section ".bss16", "aw", @nobits
underlying_e820_ebx:
	.long	0
	.size underlying_e820_ebx, . - underlying_e820_ebx

	.section ".bss16", "aw", @nobits
underlying_e820_cache:
	.space	E820MAXSIZE
	.size underlying_e820_cache, . - underlying_e820_cache

	.section ".bss16", "aw", @nobits
underlying_e820_cache_size:
	.long	0
	.size	underlying_e820_cache_size, . - underlying_e820_cache_size

/****************************************************************************
 * Get windowed e820 region, without empty region stripping
 *
 * Parameters:
 *   As for INT 15,e820
 * Returns:
 *   As for INT 15,e820
 *
 * Wraps the underlying INT 15,e820 call so that each underlying
 * region is returned N times, windowed to fit within N visible-memory
 * windows.  Termination is always via CF=1.
 *
 ****************************************************************************
 */
	.section ".text16", "ax", @progbits
get_windowed_e820:

	/* Preserve registers */
	pushl	%esi
	pushw	%bp

	/* Split %ebx into %si:%bx, store original %bx in %bp */
	pushl	%ebx
	popw	%bp
	popw	%si

	/* %si == 0 => start of memory_windows list */
	testw	%si, %si
	jne	1f
	movw	$memory_windows, %si
1:	
	/* Get (cached) underlying e820 region to buffer */
	call	get_underlying_e820
	jc	99f /* Abort on error */

	/* Preserve registers */
	pushal
	/* start => %edx:%eax, len => %ecx:%ebx */
	movl	%es:0(%di), %eax
	movl	%es:4(%di), %edx
	movl	%es:8(%di), %ebx
	movl	%es:12(%di), %ecx
	/* Truncate region to current window */
	call	window_region
1:	/* Store modified values in e820 map entry */
	movl	%eax, %es:0(%di)
	movl	%edx, %es:4(%di)
	movl	%ebx, %es:8(%di)
	movl	%ecx, %es:12(%di)
	/* Restore registers */
	popal

	/* Derive continuation value for next call */
	addw	$16, %si
	cmpw	$memory_windows_end, %si
	jne	1f
	/* End of memory windows: reset %si and allow %bx to continue */
	xorw	%si, %si
	jmp	2f
1:	/* More memory windows to go: restore original %bx */
	movw	%bp, %bx
2:	/* Construct %ebx from %si:%bx */
	pushw	%si
	pushw	%bx
	popl	%ebx

98:	/* Clear CF */
	clc
99:	/* Restore registers and return */
	popw	%bp
	popl	%esi
	ret
	.size get_windowed_e820, . - get_windowed_e820

/****************************************************************************
 * Get windowed e820 region, with empty region stripping
 *
 * Parameters:
 *   As for INT 15,e820
 * Returns:
 *   As for INT 15,e820
 *
 * Wraps the underlying INT 15,e820 call so that each underlying
 * region is returned up to N times, windowed to fit within N
 * visible-memory windows.  Empty windows are never returned.
 * Termination is always via CF=1.
 *
 ****************************************************************************
 */
	.section ".text16", "ax", @progbits
get_nonempty_e820:

	/* Record entry parameters */
	pushl	%eax
	pushl	%ecx
	pushl	%edx

	/* Get next windowed region */
	call	get_windowed_e820
	jc	99f /* abort on error */

	/* If region is non-empty, finish here */
	cmpl	$0, %es:8(%di)
	jne	98f
	cmpl	$0, %es:12(%di)
	jne	98f

	/* Region was empty: restore entry parameters and go to next region */
	popl	%edx
	popl	%ecx
	popl	%eax
	jmp	get_nonempty_e820

98:	/* Clear CF */
	clc
99:	/* Return values from underlying call */
	addr32 leal 12(%esp), %esp /* avoid changing flags */
	ret
	.size get_nonempty_e820, . - get_nonempty_e820

/****************************************************************************
 * Get mangled e820 region, with empty region stripping
 *
 * Parameters:
 *   As for INT 15,e820
 * Returns:
 *   As for INT 15,e820
 *
 * Wraps the underlying INT 15,e820 call so that underlying regions
 * are windowed to the allowed memory regions.  Empty regions are
 * stripped from the map.  Termination is always via %ebx=0.
 *
 ****************************************************************************
 */
	.section ".text16", "ax", @progbits
get_mangled_e820:

	/* Get a nonempty region */
	call	get_nonempty_e820
	jc	99f /* Abort on error */

	/* Peek ahead to see if there are any further nonempty regions */
	pushal
	pushw	%es
	movw	%sp, %bp
	subw	%cx, %sp
	movl	$0xe820, %eax
	movl	$SMAP, %edx
	pushw	%ss
	popw	%es
	movw	%sp, %di
	call	get_nonempty_e820
	movw	%bp, %sp
	popw	%es
	popal
	jnc	99f /* There are further nonempty regions */

	/* No futher nonempty regions: zero %ebx and clear CF */
	xorl	%ebx, %ebx
	
99:	/* Return */
	ret
	.size get_mangled_e820, . - get_mangled_e820

/****************************************************************************
 * Set/clear CF on the stack as appropriate, assumes stack is as it should
 * be immediately before IRET
 ****************************************************************************
 */
patch_cf:
	pushw	%bp
	movw	%sp, %bp
	setc	8(%bp)	/* Set/reset CF; clears PF, AF, ZF, SF */
	popw	%bp
	ret

/****************************************************************************
 * INT 15,e820 handler
 ****************************************************************************
 */
	.section ".text16", "ax", @progbits
int15_e820:
	pushw	%ds
	pushw	%cs:rm_ds
	popw	%ds
	call	get_mangled_e820
	popw	%ds
	call	patch_cf
	iret
	.size int15_e820, . - int15_e820
	
/****************************************************************************
 * INT 15,e801 handler
 ****************************************************************************
 */
	.section ".text16", "ax", @progbits
int15_e801:
	/* Call previous handler */
	pushfw
	lcall	*%cs:int15_vector
	call	patch_cf
	/* Edit result */
	pushw	%ds
	pushw	%cs:rm_ds
	popw	%ds
	call	patch_1m_16m
	xchgw	%ax, %cx
	xchgw	%bx, %dx
	call	patch_1m_16m
	xchgw	%ax, %cx
	xchgw	%bx, %dx
	popw	%ds
	iret
	.size int15_e801, . - int15_e801
	
/****************************************************************************
 * INT 15,88 handler
 ****************************************************************************
 */
	.section ".text16", "ax", @progbits
int15_88:
	/* Call previous handler */
	pushfw
	lcall	*%cs:int15_vector
	call	patch_cf
	/* Edit result */
	pushw	%ds
	pushw	%cs:rm_ds
	popw	%ds
	call	patch_1m
	popw	%ds
	iret
	.size int15_88, . - int15_88
		
/****************************************************************************
 * INT 15 handler
 ****************************************************************************
 */
	.section ".text16", "ax", @progbits
	.globl int15
int15:
	/* See if we want to intercept this call */
	pushfw
	cmpw	$0xe820, %ax
	jne	1f
	cmpl	$SMAP, %edx
	jne	1f
	popfw
	jmp	int15_e820
1:	cmpw	$0xe801, %ax
	jne	2f
	popfw
	jmp	int15_e801
2:	cmpb	$0x88, %ah
	jne	3f
	popfw
	jmp	int15_88
3:	popfw
	ljmp	*%cs:int15_vector
	.size int15, . - int15
	
	.section ".text16.data", "aw", @progbits
	.globl int15_vector
int15_vector:
	.long 0
	.size int15_vector, . - int15_vector