iPXE - Open Source Boot Firmware

[pxe] Introduce PXE exit hook for NBP chaining
[ipxe.git] / src / arch / i386 / prefix / pxeprefix.S
1 FILE_LICENCE ( GPL2_OR_LATER )
2
3 #define PXENV_UNDI_SHUTDOWN             0x0005
4 #define PXENV_UNDI_GET_NIC_TYPE         0x0012
5 #define PXENV_UNDI_GET_IFACE_INFO       0x0013
6 #define PXENV_STOP_UNDI                 0x0015
7 #define PXENV_UNLOAD_STACK              0x0070
8
9 #define PXE_HACK_EB54                   0x0001
10
11         .text
12         .arch i386
13         .org 0
14         .code16
15
16 #include <undi.h>
17
18 #define STACK_MAGIC ( 'L' + ( 'R' << 8 ) + ( 'E' << 16 ) + ( 'T' << 24 ) )
19 #define EB_MAGIC_1 ( 'E' + ( 't' << 8 ) + ( 'h' << 16 ) + ( 'e' << 24 ) )
20 #define EB_MAGIC_2 ( 'r' + ( 'b' << 8 ) + ( 'o' << 16 ) + ( 'o' << 24 ) )
21
22 /*****************************************************************************
23  * Entry point: set operating context, print welcome message
24  *****************************************************************************
25  */
26         .section ".prefix", "ax", @progbits
27         jmp     $0x7c0, $1f
28 1:
29         /* Preserve registers for possible return to PXE */
30         pushfl
31         pushal
32         pushw   %gs
33         pushw   %fs
34         pushw   %es
35         pushw   %ds
36
37         /* Store magic word on PXE stack and remember PXE %ss:esp */
38         pushl   $STACK_MAGIC
39         movw    %ss, %cs:pxe_ss
40         movl    %esp, %cs:pxe_esp
41
42         /* Set up segments */
43         movw    %cs, %ax
44         movw    %ax, %ds
45         movw    $0x40, %ax              /* BIOS data segment access */
46         movw    %ax, %fs
47         /* Set up stack just below 0x7c00 */
48         xorw    %ax, %ax
49         movw    %ax, %ss
50         movl    $0x7c00, %esp
51         /* Clear direction flag, for the sake of sanity */
52         cld
53         /* Print welcome message */
54         movw    $10f, %si
55         xorw    %di, %di
56         call    print_message
57         .section ".prefix.data", "aw", @progbits
58 10:     .asciz  "PXE->EB:"
59         .previous
60
61 /*****************************************************************************
62  * Find us a usable !PXE or PXENV+ entry point
63  *****************************************************************************
64  */
65 detect_pxe:
66         /* Plan A: !PXE pointer from the stack */
67         lgsl    pxe_esp, %ebp           /* %gs:%bp -> original stack */
68         lesw    %gs:52(%bp), %bx
69         call    is_valid_ppxe
70         je      have_ppxe
71
72         /* Plan B: PXENV+ pointer from initial ES:BX */
73         movw    %gs:32(%bp),%bx
74         movw    %gs:8(%bp),%es
75         call    is_valid_pxenv
76         je      have_pxenv
77
78         /* Plan C: PXENV+ structure via INT 1Ah */
79         movw    $0x5650, %ax
80         int     $0x1a
81         jc      1f
82         cmpw    $0x564e, %ax
83         jne     1f
84         call    is_valid_pxenv
85         je      have_pxenv
86 1:
87         /* Plan D: scan base memory for !PXE */
88         call    memory_scan_ppxe
89         je      have_ppxe
90
91         /* Plan E: scan base memory for PXENV+ */
92         call    memory_scan_pxenv
93         jne     stack_not_found
94         
95 have_pxenv:
96         movw    %bx, pxenv_offset
97         movw    %es, pxenv_segment
98
99         cmpw    $0x201, %es:6(%bx)      /* API version >= 2.01 */
100         jb      1f
101         cmpb    $0x2c, %es:8(%bx)       /* ... and structure long enough */
102         jb      2f
103
104         lesw    %es:0x28(%bx), %bx      /* Find !PXE from PXENV+ */
105         call    is_valid_ppxe
106         je      have_ppxe
107 2:
108         call    memory_scan_ppxe        /* We are *supposed* to have !PXE... */
109         je      have_ppxe
110 1:
111         lesw    pxenv_segoff, %bx       /* Nope, we're stuck with PXENV+ */
112
113         /* Record entry point and UNDI segments */
114         pushl   %es:0x0a(%bx)           /* Entry point */
115         pushw   %es:0x24(%bx)           /* UNDI code segment */
116         pushw   %es:0x26(%bx)           /* UNDI code size */
117         pushw   %es:0x20(%bx)           /* UNDI data segment */
118         pushw   %es:0x22(%bx)           /* UNDI data size */
119
120         /* Print "PXENV+ at <address>" */
121         movw    $10f, %si
122         jmp     check_have_stack
123         .section ".prefix.data", "aw", @progbits
124 10:     .asciz  " PXENV+ at "
125         .previous
126
127 have_ppxe:
128         movw    %bx, ppxe_offset
129         movw    %es, ppxe_segment
130         
131         pushl   %es:0x10(%bx)           /* Entry point */
132         pushw   %es:0x30(%bx)           /* UNDI code segment */
133         pushw   %es:0x36(%bx)           /* UNDI code size */
134         pushw   %es:0x28(%bx)           /* UNDI data segment */
135         pushw   %es:0x2e(%bx)           /* UNDI data size */
136
137         /* Print "!PXE at <address>" */
138         movw    $10f, %si
139         jmp     check_have_stack
140         .section ".prefix.data", "aw", @progbits
141 10:     .asciz  " !PXE at "
142         .previous
143
144 is_valid_ppxe:
145         cmpl    $0x45585021, %es:(%bx)
146         jne     1f
147         movzbw  %es:4(%bx), %cx
148         cmpw    $0x58, %cx
149         jae     is_valid_checksum
150 1:
151         ret
152         
153 is_valid_pxenv:
154         cmpl    $0x4e455850, %es:(%bx)
155         jne     1b
156         cmpw    $0x2b56, %es:4(%bx)
157         jne     1b
158         movzbw  %es:8(%bx), %cx
159         cmpw    $0x28, %cx
160         jb      1b
161         
162 is_valid_checksum:
163         pushw   %ax
164         movw    %bx, %si
165         xorw    %ax, %ax
166 2:
167         es lodsb
168         addb    %al, %ah
169         loopw   2b
170         popw    %ax
171         ret
172
173 memory_scan_ppxe:
174         movw    $is_valid_ppxe, %dx
175         jmp     memory_scan_common
176
177 memory_scan_pxenv:
178         movw    $is_valid_pxenv, %dx
179
180 memory_scan_common:
181         movw    %fs:(0x13), %ax
182         shlw    $6, %ax
183         decw    %ax
184 1:      incw    %ax
185         cmpw    $( 0xa000 - 1 ), %ax
186         ja      2f
187         movw    %ax, %es
188         xorw    %bx, %bx
189         call    *%dx
190         jne     1b
191 2:      ret
192         
193 /*****************************************************************************
194  * Sanity check: we must have an entry point
195  *****************************************************************************
196  */
197 check_have_stack:
198         /* Save common values pushed onto the stack */
199         popl    undi_data_segoff
200         popl    undi_code_segoff
201         popl    entry_segoff
202
203         /* Print have !PXE/PXENV+ message; structure pointer in %es:%bx */
204         call    print_message
205         call    print_segoff
206         movb    $( ',' ), %al
207         call    print_character
208
209         /* Check for entry point */
210         movl    entry_segoff, %eax
211         testl   %eax, %eax
212         jnz     99f
213         /* No entry point: print message and skip everything else */
214 stack_not_found:
215         movw    $10f, %si
216         call    print_message
217         jmp     finished
218         .section ".prefix.data", "aw", @progbits
219 10:     .asciz  " No PXE stack found!\n"
220         .previous
221 99:     
222
223 /*****************************************************************************
224  * Calculate base memory usage by UNDI
225  *****************************************************************************
226  */
227 find_undi_basemem_usage:
228         movw    undi_code_segment, %ax
229         movw    undi_code_size, %bx
230         movw    undi_data_segment, %cx
231         movw    undi_data_size, %dx
232         cmpw    %ax, %cx
233         ja      1f
234         xchgw   %ax, %cx
235         xchgw   %bx, %dx
236 1:      /* %ax:%bx now describes the lower region, %cx:%dx the higher */
237         shrw    $6, %ax                 /* Round down to nearest kB */
238         movw    %ax, undi_fbms_start
239         addw    $0x0f, %dx              /* Round up to next segment */
240         shrw    $4, %dx
241         addw    %dx, %cx
242         addw    $((1024 / 16) - 1), %cx /* Round up to next kB */
243         shrw    $6, %cx
244         movw    %cx, undi_fbms_end
245
246 /*****************************************************************************
247  * Print information about detected PXE stack
248  *****************************************************************************
249  */
250 print_structure_information:
251         /* Print entry point */
252         movw    $10f, %si
253         call    print_message
254         les     entry_segoff, %bx
255         call    print_segoff
256         .section ".prefix.data", "aw", @progbits
257 10:     .asciz  " entry point at "
258         .previous
259         /* Print UNDI code segment */
260         movw    $10f, %si
261         call    print_message
262         les     undi_code_segoff, %bx
263         call    print_segoff
264         .section ".prefix.data", "aw", @progbits
265 10:     .asciz  "\n         UNDI code segment "
266         .previous
267         /* Print UNDI data segment */
268         movw    $10f, %si
269         call    print_message
270         les     undi_data_segoff, %bx
271         call    print_segoff
272         .section ".prefix.data", "aw", @progbits
273 10:     .asciz  ", data segment "
274         .previous
275         /* Print UNDI memory usage */
276         movw    $10f, %si
277         call    print_message
278         movw    undi_fbms_start, %ax
279         call    print_word
280         movb    $( '-' ), %al
281         call    print_character
282         movw    undi_fbms_end, %ax
283         call    print_word
284         movw    $20f, %si
285         call    print_message
286         .section ".prefix.data", "aw", @progbits
287 10:     .asciz  " ("
288 20:     .asciz  "kB)\n"
289         .previous
290
291 /*****************************************************************************
292  * Determine physical device
293  *****************************************************************************
294  */
295 get_physical_device:
296         /* Issue PXENV_UNDI_GET_NIC_TYPE */
297         movw    $PXENV_UNDI_GET_NIC_TYPE, %bx
298         call    pxe_call
299         jnc     1f
300         call    print_pxe_error
301         jmp     no_physical_device
302 1:      /* Determine physical device type */
303         movb    ( pxe_parameter_structure + 0x02 ), %al
304         cmpb    $2, %al
305         je      pci_physical_device
306         jmp     no_physical_device
307
308 pci_physical_device:
309         /* Record PCI bus:dev.fn and vendor/device IDs */
310         movl    ( pxe_parameter_structure + 0x03 ), %eax
311         movl    %eax, pci_vendor
312         movw    ( pxe_parameter_structure + 0x0b ), %ax
313         movw    %ax, pci_busdevfn
314         movw    $10f, %si
315         call    print_message
316         call    print_pci_busdevfn
317         jmp     99f
318         .section ".prefix.data", "aw", @progbits
319 10:     .asciz  "         UNDI device is PCI "
320         .previous
321
322 no_physical_device:
323         /* No device found, or device type not understood */
324         movw    $10f, %si
325         call    print_message
326         .section ".prefix.data", "aw", @progbits
327 10:     .asciz  "         Unable to determine UNDI physical device"
328         .previous
329
330 99:
331
332 /*****************************************************************************
333  * Determine interface type
334  *****************************************************************************
335  */
336 get_iface_type:
337         /* Issue PXENV_UNDI_GET_IFACE_INFO */
338         movw    $PXENV_UNDI_GET_IFACE_INFO, %bx
339         call    pxe_call
340         jnc     1f
341         call    print_pxe_error
342         jmp     99f
343 1:      /* Print interface type */
344         movw    $10f, %si
345         call    print_message
346         leaw    ( pxe_parameter_structure + 0x02 ), %si
347         call    print_message
348         .section ".prefix.data", "aw", @progbits
349 10:     .asciz  ", type "
350         .previous
351         /* Check for "Etherboot" interface type */
352         cmpl    $EB_MAGIC_1, ( pxe_parameter_structure + 0x02 )
353         jne     99f
354         cmpl    $EB_MAGIC_2, ( pxe_parameter_structure + 0x06 )
355         jne     99f
356         movw    $10f, %si
357         call    print_message
358         .section ".prefix.data", "aw", @progbits
359 10:     .asciz  " (workaround enabled)"
360         .previous
361         /* Flag Etherboot workarounds as required */
362         orw     $PXE_HACK_EB54, pxe_hacks
363
364 99:     movb    $0x0a, %al
365         call    print_character
366
367 /*****************************************************************************
368  * Leave NIC in a safe state
369  *****************************************************************************
370  */
371 #ifndef PXELOADER_KEEP_PXE
372 shutdown_nic:
373         /* Issue PXENV_UNDI_SHUTDOWN */
374         movw    $PXENV_UNDI_SHUTDOWN, %bx
375         call    pxe_call
376         jnc     1f
377         call    print_pxe_error
378 1:
379 unload_base_code:
380         /* Etherboot treats PXENV_UNLOAD_STACK as PXENV_STOP_UNDI, so
381          * we must not issue this call if the underlying stack is
382          * Etherboot and we were not intending to issue a PXENV_STOP_UNDI.
383          */
384 #ifdef PXELOADER_KEEP_UNDI
385         testw   $PXE_HACK_EB54, pxe_hacks
386         jnz     99f
387 #endif /* PXELOADER_KEEP_UNDI */
388         /* Issue PXENV_UNLOAD_STACK */
389         movw    $PXENV_UNLOAD_STACK, %bx
390         call    pxe_call
391         jnc     1f
392         call    print_pxe_error
393         jmp     99f
394 1:      /* Free base memory used by PXE base code */
395         movw    undi_fbms_start, %ax
396         movw    %fs:(0x13), %bx
397         call    free_basemem
398 99:
399         andw    $~( UNDI_FL_INITIALIZED | UNDI_FL_KEEP_ALL ), flags
400 #endif /* PXELOADER_KEEP_PXE */
401
402 /*****************************************************************************
403  * Unload UNDI driver
404  *****************************************************************************
405  */
406 #ifndef PXELOADER_KEEP_UNDI
407 unload_undi:
408         /* Issue PXENV_STOP_UNDI */
409         movw    $PXENV_STOP_UNDI, %bx
410         call    pxe_call
411         jnc     1f
412         call    print_pxe_error
413         jmp     99f
414 1:      /* Free base memory used by UNDI */
415         movw    undi_fbms_end, %ax
416         movw    undi_fbms_start, %bx
417         call    free_basemem
418         /* Clear UNDI_FL_STARTED */
419         andw    $~UNDI_FL_STARTED, flags
420 99:     
421 #endif /* PXELOADER_KEEP_UNDI */
422
423 /*****************************************************************************
424  * Print remaining free base memory
425  *****************************************************************************
426  */
427 print_free_basemem:
428         movw    $10f, %si
429         call    print_message
430         movw    %fs:(0x13), %ax
431         call    print_word
432         movw    $20f, %si
433         call    print_message
434         .section ".prefix.data", "aw", @progbits
435 10:     .asciz  "         "
436 20:     .asciz  "kB free base memory after PXE unload\n"
437         .previous
438         
439 /*****************************************************************************
440  * Exit point
441  *****************************************************************************
442  */     
443 finished:
444         jmp     run_gpxe
445
446 /*****************************************************************************
447  * Subroutine: print segment:offset address
448  *
449  * Parameters:
450  *   %es:%bx : segment:offset address to print
451  *   %ds:di : output buffer (or %di=0 to print to console)
452  * Returns:
453  *   %ds:di : next character in output buffer (if applicable)
454  *****************************************************************************
455  */
456 print_segoff:
457         /* Preserve registers */
458         pushw   %ax
459         /* Print "<segment>:offset" */
460         movw    %es, %ax
461         call    print_hex_word
462         movb    $( ':' ), %al
463         call    print_character
464         movw    %bx, %ax
465         call    print_hex_word
466         /* Restore registers and return */
467         popw    %ax
468         ret
469
470 /*****************************************************************************
471  * Subroutine: print decimal word
472  *
473  * Parameters:
474  *   %ax : word to print
475  *   %ds:di : output buffer (or %di=0 to print to console)
476  * Returns:
477  *   %ds:di : next character in output buffer (if applicable)
478  *****************************************************************************
479  */
480 print_word:
481         /* Preserve registers */
482         pushw   %ax
483         pushw   %bx
484         pushw   %cx
485         pushw   %dx
486         /* Build up digit sequence on stack */
487         movw    $10, %bx
488         xorw    %cx, %cx
489 1:      xorw    %dx, %dx
490         divw    %bx, %ax
491         pushw   %dx
492         incw    %cx
493         testw   %ax, %ax
494         jnz     1b
495         /* Print digit sequence */
496 1:      popw    %ax
497         call    print_hex_nibble
498         loop    1b
499         /* Restore registers and return */
500         popw    %dx
501         popw    %cx
502         popw    %bx
503         popw    %ax
504         ret
505         
506 /*****************************************************************************
507  * Subroutine: zero 1kB block of base memory
508  *
509  * Parameters:
510  *   %bx : block to zero (in kB)
511  * Returns:
512  *   Nothing
513  *****************************************************************************
514  */
515 zero_kb:
516         /* Preserve registers */
517         pushw   %ax
518         pushw   %cx
519         pushw   %di
520         pushw   %es
521         /* Zero block */
522         movw    %bx, %ax
523         shlw    $6, %ax
524         movw    %ax, %es
525         movw    $0x400, %cx
526         xorw    %di, %di
527         xorw    %ax, %ax
528         rep stosb
529         /* Restore registers and return */
530         popw    %es
531         popw    %di
532         popw    %cx
533         popw    %ax
534         ret
535         
536 /*****************************************************************************
537  * Subroutine: free and zero base memory
538  *
539  * Parameters:
540  *   %ax : Desired new free base memory counter (in kB)
541  *   %bx : Expected current free base memory counter (in kB)
542  *   %fs : BIOS data segment (0x40)
543  * Returns:
544  *   None
545  *
546  * The base memory from %bx kB to %ax kB is unconditionally zeroed.
547  * It will be freed if and only if the expected current free base
548  * memory counter (%bx) matches the actual current free base memory
549  * counter in 0x40:0x13; if this does not match then the memory will
550  * be leaked.
551  *****************************************************************************
552  */
553 free_basemem:
554         /* Zero base memory */
555         pushw   %bx
556 1:      cmpw    %bx, %ax
557         je      2f
558         call    zero_kb
559         incw    %bx
560         jmp     1b
561 2:      popw    %bx
562         /* Free base memory */
563         cmpw    %fs:(0x13), %bx         /* Update FBMS only if "old" value  */
564         jne     1f                      /* is correct                       */
565 1:      movw    %ax, %fs:(0x13)
566         ret
567
568 /*****************************************************************************
569  * Subroutine: make a PXE API call.  Works with either !PXE or PXENV+ API.
570  *
571  * Parameters:
572  *   %bx : PXE API call number
573  *   %ds:pxe_parameter_structure : Parameters for PXE API call
574  * Returns:
575  *   %ax : PXE status code (not exit code)
576  *   CF set if %ax is non-zero
577  *****************************************************************************
578  */
579 pxe_call:
580         /* Preserve registers */
581         pushw   %di
582         pushw   %es
583         /* Set up registers for PXENV+ API.  %bx already set up */
584         pushw   %ds
585         popw    %es
586         movw    $pxe_parameter_structure, %di
587         /* Set up stack for !PXE API */
588         pushw   %es
589         pushw   %di
590         pushw   %bx
591         /* Make the API call */
592         lcall   *entry_segoff
593         /* Reset the stack */
594         addw    $6, %sp
595         movw    pxe_parameter_structure, %ax
596         clc
597         testw   %ax, %ax
598         jz      1f
599         stc
600 1:      /* Clear direction flag, for the sake of sanity */
601         cld
602         /* Restore registers and return */
603         popw    %es
604         popw    %di
605         ret
606
607 /*****************************************************************************
608  * Subroutine: print PXE API call error message
609  *
610  * Parameters:
611  *   %ax : PXE status code
612  *   %bx : PXE API call number
613  * Returns:
614  *   Nothing
615  *****************************************************************************
616  */
617 print_pxe_error:
618         pushw   %si
619         movw    $10f, %si
620         call    print_message
621         xchgw   %ax, %bx
622         call    print_hex_word
623         movw    $20f, %si
624         call    print_message
625         xchgw   %ax, %bx
626         call    print_hex_word
627         movw    $30f, %si
628         call    print_message
629         popw    %si
630         ret
631         .section ".prefix.data", "aw", @progbits
632 10:     .asciz  "         UNDI API call "
633 20:     .asciz  " failed: status code "
634 30:     .asciz  "\n"
635         .previous
636
637 /*****************************************************************************
638  * PXE data structures
639  *****************************************************************************
640  */
641         .section ".prefix.data"
642
643 pxe_esp:                .long 0
644 pxe_ss:                 .word 0
645
646 pxe_parameter_structure: .fill 64
647
648 undi_code_segoff:
649 undi_code_size:         .word 0
650 undi_code_segment:      .word 0
651
652 undi_data_segoff:
653 undi_data_size:         .word 0
654 undi_data_segment:      .word 0
655
656 pxe_hacks:              .word 0
657
658 /* The following fields are part of a struct undi_device */
659
660 undi_device:
661
662 pxenv_segoff:
663 pxenv_offset:           .word 0
664 pxenv_segment:          .word 0
665
666 ppxe_segoff:
667 ppxe_offset:            .word 0
668 ppxe_segment:           .word 0
669         
670 entry_segoff:
671 entry_offset:           .word 0
672 entry_segment:          .word 0
673
674 undi_fbms_start:        .word 0
675 undi_fbms_end:          .word 0
676
677 pci_busdevfn:           .word UNDI_NO_PCI_BUSDEVFN
678 isapnp_csn:             .word UNDI_NO_ISAPNP_CSN
679 isapnp_read_port:       .word UNDI_NO_ISAPNP_READ_PORT
680
681 pci_vendor:             .word 0
682 pci_device:             .word 0
683 flags:
684         .word ( UNDI_FL_INITIALIZED | UNDI_FL_STARTED | UNDI_FL_KEEP_ALL )
685
686         .equ undi_device_size, ( . - undi_device )
687
688 /*****************************************************************************
689  * Run gPXE main code
690  *****************************************************************************
691  */
692         .section ".prefix"
693 run_gpxe:
694         /* Install gPXE */
695         call    install
696
697         /* Set up real-mode stack */
698         movw    %bx, %ss
699         movw    $_estack16, %sp
700
701 #ifdef PXELOADER_KEEP_UNDI
702         /* Copy our undi_device structure to the preloaded_undi variable */
703         movw    %bx, %es
704         movw    $preloaded_undi, %di
705         movw    $undi_device, %si
706         movw    $undi_device_size, %cx
707         rep movsb
708 #endif
709
710         /* Retrieve PXE %ss:esp */
711         movw    pxe_ss, %di
712         movl    pxe_esp, %ebp
713
714         /* Jump to .text16 segment with %ds pointing to .data16 */
715         movw    %bx, %ds
716         pushw   %ax
717         pushw   $1f
718         lret
719         .section ".text16", "ax", @progbits
720 1:
721         /* Update the exit hook */
722         movw    %cs,pxe_exit_hook+2
723         push    %ax
724         mov     $2f,%ax
725         mov     %ax,pxe_exit_hook
726         pop     %ax
727
728         /* Run main program */
729         pushl   $main
730         pushw   %cs
731         call    prot_call
732         popl    %ecx /* discard */
733
734         /* Uninstall gPXE */
735         call    uninstall
736
737         /* Restore PXE stack */
738         movw    %di, %ss
739         movl    %ebp, %esp
740
741         /* Jump to hook if applicable */
742         ljmpw   *pxe_exit_hook
743
744 2:      /* Check PXE stack magic */
745         popl    %eax
746         cmpl    $STACK_MAGIC, %eax
747         jne     1f
748
749         /* PXE stack OK: return to caller */
750         popw    %ds
751         popw    %es
752         popw    %fs
753         popw    %gs
754         popal
755         popfl
756         xorw    %ax, %ax        /* Return success */
757         lret
758
759 1:      /* PXE stack corrupt or removed: use INT 18 */
760         int     $0x18
761         .previous