!************************************************************************** !* !* Network driver interface for netboot bootrom !* !* Module: netdrv.S !* Purpose: Setup the data area and initialize the network driver interface, !* also contains the PXE API entry points !* Entries: Called by bootrom kernel at $0003 for initialization !* !************************************************************************** !* !* Copyright (C) 1998-2003 Gero Kuhlmann !* !* 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. !* !* $Id: netdrv.S,v 1.5 2003/01/25 23:29:41 gkminix Exp $ !* ! !************************************************************************** ! ! Load assembler macros and other include files. ! #include #include #include #include #include #include #include ! !************************************************************************** ! ! Macro to set default descriptor table ! macro defdesc .word 0 ! segment limit low .word 0 ! base low .byte 0 ! base middle .byte desc_default_?1 ! descriptor type .byte 0 ! segment limit high .byte 0 ! base high mend ! !************************************************************************** ! ! Start BSS segment: ! .bss extrn _end_of_bss ! !************************************************************************** ! ! Start data segment: ! .data extrn _end_of_data ! !************************************************************************** ! ! The initialization code starts here. At the beginning there is some ! miscellaneous data. ! .text entry start public datseg public txtseg public bustyp extrn _end_of_text extrn drv_load extrn drv_entry extrn prnstr extrn dochecksum jmp near old_pxentry ! jump to PXE entry point start: jmp near netstart ! the kernel loader will call us here .align DATAOFS ! Fill up ! These values can be used by the binary patch program to strip off the ! BSS segment from the final binary. The BSS segment just contains zeroes ! which dont have to be saved in the tight ROM. The totsiz variable will ! be filled in by the binary patch program. .word _end_of_text .word _end_of_data .word romid totsiz: .word 0 ! total size in paragraphs ! Put the version number into the final driver image. This allows the ! binary patch program to verify that its using the correct network driver ! image. .byte MAJOR_VER ! major version number .byte MINOR_VER ! minor version number ! The bus type is put here by the binary patch program. It is specified in ! the old WfM 1.1 specification. bustyp: .byte PXENV_BUS_ISA ! use ISA bus as default ! Network driver ROM ID structure as defined in the PXE specification. Note ! that this will not be placed into the ROM loader, as its not required there. ! We also dont need to define a loader offset here. romid: .ascii "UNDI" ! structure ID .byte $16 ! length of this structure .byte 0 ! checksum (filled in by makerom) .byte 0 ! revision of this structure .byte $00,$01,$02 ! API revision number .word 0 ! offset to driver loader (not used) .word 0 ! stack size required for network driver .word _end_of_bss ! required data segment size .word _end_of_text ! required code segment size .ascii "ISAR" ! bus type for this device ! PXENV structure as defined in Intels PXE specification. The descriptor ! table in the !PXE structure doesnt get initialized here. This way, the ! assembler will set it to all zeroes, and the compression routine in the ! binary patch program will be able to compress it to a minimum size lateron. .align 16 pxenv: .ascii "PXENV+" ! signature .word $0201 ! API version 2.1 .byte pxentry_size ! size of this structure .byte 0 ! checksum .word old_pxentry ! offset to real-mode old entry point .word 0 ! segment of real-mode old entry point .word 0 ! offset to protected-mode entry point .long 0 ! segment of protected-mode entry point .word 0 ! kernel stack segment .word 0 ! kernel stack size .word 0 ! kernel code segment .word 0 ! kernel code size .word 0 ! kernel data segment .word 0 ! kernel data size datseg: .word 0 ! network driver data segment .word _end_of_bss ! network driver data size txtseg: .word 0 ! network driver code segment .word _end_of_text ! network driver code size .word pxestr ! offset to !PXE structure .word 0 ! segment of !PXE structure .align 16 pxestr: .ascii "!PXE" ! !PXE structure signature .byte pxe_def_size ! size of this structure .byte 0 ! checksum .byte 0 ! revision of this structure is zero .byte 0 ! reserved uromid: .word romid ! offset to UNDI ROM ID structure .word 0 ! segment of UNDI ROM ID structure kromid: .long 0 ! far ptr to kernel ROM ID structure .word new_pxentry ! offset to real-mode new entry point .word 0 ! segment of real-mode new entry point .long 0 ! far ptr to 32-bit entry point .long 0 ! far ptr to DHCP/TFTP callout procedure .byte 0 ! reserved desnum: .byte PXE_DESC_DEF_UNDI ! number of segment descriptors .word 0 ! first protected mode selector defdesc (stack) ! stack segment defdesc (data) ! UNDI data segment defdesc (code) ! UNDI code segment defdesc (data) ! UNDI code write segment defdesc (data) ! kernel data segment defdesc (code) ! kernel code segment defdesc (data) ! kernel code write segment ! The following variables have to be accessible by CS. Since the bootrom code ! will only run after being loaded into RAM, there is no problem writing into ! any of these variables. pxekrn: .long 0 ! kernel PXE entry point oldint: .long 0 ! old PXE interrupt vector ! !************************************************************************** ! ! Start the network driver. This will load all network driver parts into ! memory reserved by the kernel loader and setup the PXENV and !PXE structures. ! Input: DX - network driver start segment ! ES:DI - base code PXE entry point ! Output: ES:DI - pointer to PXENV structure ! AX - error code ! Registers changed: AX, BX, CX, DX, SI, DI, ES ! netstart: cld push ds call netst1 netst1: pop si sub si,#netst1 ! First save all values passed to us by the kernel loader. For the ! purposes of this network driver loader, we initially use our code ! segment for DS. push cs pop ds mov [si + datseg],dx mov word ptr [si + pxekrn + 0],di mov word ptr [si + pxekrn + 2],es ! Then copy the network driver interface code into its final place. It ! has already been checked by the bootrom loader that we have enough memory. ! The network driver proper will be copied lateron. Then compute the new ! address of the data and BSS segment and call the new copy. ! Note that the PXE specification requires the data segment to come before ! the code segment. mov ax,dx add ax,[si + totsiz] mov cx,#_end_of_text + $000F and cx,#$FFF0 ! adjust size of text segment to mov bx,cx ! paragraph alignment shift (shr,bx,4) sub ax,bx ! compute start of text segment mov [si + txtseg],ax mov es,ax xor di,di shr cx,#1 ! copy code segment rep movsw jnc netst5 movsb netst5: mov es,dx xor di,di mov cx,#_end_of_data ! copy data segment shr cx,#1 rep movsw jnc netst6 movsb netst6: #ifdef IS186 push ax push #netst3 ! push address of new copy #else mov cx,#netst3 push ax push cx ! push address of new copy #endif retf ! call new copy ! Now DS:SI contains the source address of the network driver proper. Compute ! the first usable segment following our own BSS segment, switch to our data ! segment and call the network driver loader. It gets the following parameters: ! ! DX - first usable segment for network driver ! ES:SI - pointer to network driver source netst3: push ds mov ds,dx ! set new data segment mov bx,#_end_of_bss + $000F shift (shr,bx,4) ! compute first free segment add dx,bx pop es call drv_load ! call network driver loader or ax,ax ! check for error jnz netst9 ! We can now set the PXENV and !PXE structure. For this we switch DS back ! to the code segment. We dont need to compute a checksum here, as that will ! be done by the kernel loader after it has inserted its own values. ! The descriptor table will be preset so that the kernel loader only has ! to fill in the missing values and set the correct checksum. mov bx,ds mov ax,cs mov ds,ax mov word ptr [pxenv + o_pe_rm_entry_seg],ax mov word ptr [pxenv + o_pe_pxe_seg],ax mov word ptr [pxestr + o_ps_undiromid + 2],ax mov word ptr [pxestr + o_ps_pxentry16 + 2],ax mov cx,ax sub cx,bx ! compute real size of data segment push cx mov cl,#4 mov dl,bh shr dl,cl ! convert data segment base into linear shl bx,cl ! address mov dh,ah ! convert code segment base into linear shr dh,cl ! address shl ax,cl mov word ptr [pxestr + o_ps_desc + (PXE_DESC_UNDI_DATA * desc_size) + o_desc_base_low],bx mov byte ptr [pxestr + o_ps_desc + (PXE_DESC_UNDI_DATA * desc_size) + o_desc_base_mid],dl pop bx mov dl,bh shr dl,cl ! convert size of data segment shl bx,cl mov word ptr [pxestr + o_ps_desc + (PXE_DESC_UNDI_DATA * desc_size) + o_desc_limit_low],bx mov byte ptr [pxestr + o_ps_desc + (PXE_DESC_UNDI_DATA * desc_size) + o_desc_limit_high],dl mov bx,#_end_of_text + $000F and bx,#$FFF0 mov word ptr [pxestr + o_ps_desc + (PXE_DESC_UNDI_CODE * desc_size) + o_desc_base_low],ax mov byte ptr [pxestr + o_ps_desc + (PXE_DESC_UNDI_CODE * desc_size) + o_desc_base_mid],dh mov word ptr [pxestr + o_ps_desc + (PXE_DESC_UNDI_CODE * desc_size) + o_desc_limit_low],bx mov word ptr [pxestr + o_ps_desc + (PXE_DESC_UNDI_WRITE * desc_size) + o_desc_base_low],ax mov byte ptr [pxestr + o_ps_desc + (PXE_DESC_UNDI_WRITE * desc_size) + o_desc_base_mid],dh mov word ptr [pxestr + o_ps_desc + (PXE_DESC_UNDI_WRITE * desc_size) + o_desc_limit_low],bx ! Thats it, we can now return to the kernel loader. The PXE interrupt $1A will ! get redirected lateron on the UNDI_START_UNDI call. netst8: mov ax,cs mov es,ax ! set ES:DI to point to PXENV structure mov di,#pxenv xor ax,ax ! return without error netst9: pop ds retf ! !************************************************************************** ! ! New-style PXE API entry point ! Input: 1. Arg - function code ! 2. Arg - far pointer to PXE function structure ! Output: Error code ! Carry flag set if error ! Registers changed: AX ! new_pxentry: push ds push es pushall mov bp,sp mov bx,[bp + PUSHA_SIZE + 8] les di,[bp + PUSHA_SIZE + 10] call do_pxentry mov bp,sp lds di,[bp + PUSHA_SIZE + 10] npxe8: mov word ptr [di],ax or ax,ax jz npxe9 mov ax,#PXENV_EXIT_FAILURE stc npxe9: mov [bp + PUSHA_AX],ax popall pop es pop ds retf ! !************************************************************************** ! ! Old-style PXE API entry point ! Input: BX - function code ! ES:DI - far pointer to PXE function structure ! Output: AX - error code ! Carry flag set if error ! Registers changed: AX ! old_pxentry: push ds push es pushall call do_pxentry mov bp,sp mov di,[bp + PUSHA_DI] mov ds,[bp + PUSHA_SIZE] jmp npxe8 ! !************************************************************************** ! ! Actual PXE API entry point handler ! Input: BX - function code ! ES:DI - far pointer to PXE function structure ! Output: AX - error code ! Registers changed: (E)AX, (E)BX, (E)CX, (E)DX, SI, DI, DS, ES, BP ! do_pxentry: mov cx,cs ! temporarily set the data segment to mov ds,cx ! our code segment cmp bx,#UNDI_MINNUM ! check if we are dealing with a jb dpxe1 ! UNDI function code cmp bx,#UNDI_MAXNUM jbe dpxe4 ! Handle calls to the kernel PXE entry point. If the kernel code has already ! been removed, we have to return an error code. dpxe1: mov ax,word ptr [kromid + 0] or ax,word ptr [kromid + 2] jnz dpxe3 dpxe2: mov ax,#PXENV_STATUS_BAD_FUNC dpxeC: ret dpxe3: pop ax push cs ! convert near return address to far push ax jmp far ptr [pxekrn] ! call kernel PXE entry point ! Now handle UNDI API calls. First we have to check that UNDI hasnt been ! removed yet. dpxe4: mov ax,[datseg] ! UNDI API is no longer available or ax,ax ! if data segment has been cleared jz dpxe2 cmp bx,#UNDI_ISR ! handle ISR call as fast as je dpxeA ! possible ! Handle UNDI_START_UNDI call. After letting the driver set all ! interrupts we have to set interrupt $1A for compatibility with ! the old PXE specification. cmp bx,#UNDI_START_UNDI jne dpxe5 mov ax,#PXENV_STATUS_UNDI_ALREADY_INIT mov cx,word ptr [oldint + 0] or cx,word ptr [oldint + 2] jnz dpxeC call drv_entry ! let the driver handle the request #ifdef IS386 xor ax,ax mov ds,ax mov cx,cs shl ecx,#16 mov cx,#new_int1A ! set new interrupt vector xchg ecx,dword ptr [INT_PXE * 4] seg cs mov dword ptr [oldint],ecx ! save old interrupt vector #else pushf cli xor ax,ax mov ds,ax mov cx,word ptr [INT_PXE * 4 + 0] seg cs ! save old interrupt vector mov word ptr [oldint + 0],cx mov cx,word ptr [INT_PXE * 4 + 2] seg cs ! set new interrupt vector mov word ptr [oldint + 2],cx mov word ptr [INT_PXE * 4 + 0],#new_int1A mov word ptr [INT_PXE * 4 + 2],cs popf #endif ret ! Now handle UNDI_STOP_UNDI. This will restore the interrupt $1A vector ! before we let the driver handle the call. dpxe5: cmp bx,#UNDI_STOP_UNDI jne dpxe7 mov ax,#PXENV_STATUS_UNDI_NOT_INIT mov cx,word ptr [oldint + 0] or cx,word ptr [oldint + 2] jz dpxeB mov cx,cs #ifdef IS386 xor ax,ax mov ds,ax ! check that the interrupt really is shl ecx,#16 ! ours mov cx,#new_int1A cmp dword ptr [INT_PXE * 4],ecx jne dpxe6 seg cs ! restore old interrupt mov eax,dword ptr [oldint] mov dword ptr [INT_PXE * 4],eax #else xor ax,ax ! check that the interrupt really is mov ds,ax ! ours cmp word ptr [INT_PXE * 4 + 0],#new_int1A jne dpxe6 cmp word ptr [INT_PXE * 4 + 2],cx jne dpxe6 pushf cli seg cs ! restore old interrupt mov ax,word ptr [oldint + 0] mov word ptr [INT_PXE * 4 + 0],ax seg cs mov ax,word ptr [oldint + 2] mov word ptr [INT_PXE * 4 + 2],ax popf #endif dpxeA: jmp near drv_entry ! call driver PXE entry point ! If we cant unhook the interrupt $1A we have to call the driver anyway ! but have to return an error code when the driver returns without an ! error. dpxe6: call drv_entry ! now let the driver handle the request or ax,ax ! the error code of the driver takes jnz dpxeB ! precedence mov ax,#PXENV_STATUS_KEEP_UNDI dpxeB: ret ! Handle UNDI_CLEANUP. We can only prepare the UNDI driver for removal ! from memory when the kernel code has been removed, so check for this ! first. Then clear the relevant values in the PXE entry point structure. dpxe7: cmp bx,#UNDI_CLEANUP jne dpxeA mov ax,word ptr [kromid + 0] or ax,word ptr [kromid + 2] mov ax,#PXENV_STATUS_KEEP_ALL jnz dpxeB call drv_entry ! let the driver handle the request or ax,ax jnz dpxeB mov ax,cs mov ds,ax ! clear all UNDI values in the PXE mov si,#pxenv ! entry point structures xor ax,ax mov word ptr [si + o_pe_net_ds_sel],ax mov word ptr [si + o_pe_net_ds_size],ax mov word ptr [si + o_pe_net_cs_sel],ax mov word ptr [si + o_pe_net_cs_size],ax mov byte ptr [si + o_pe_checksum],al #ifdef IS386 movzx cx,byte ptr [si + o_pe_length] #else mov cl,byte ptr [si + o_pe_length] xor ch,ch #endif call dochecksum sub byte ptr [si + o_pe_checksum],al mov si,#pxestr xor ax,ax mov word ptr [si + o_ps_undiromid + 0],ax mov word ptr [si + o_ps_undiromid + 2],ax mov byte ptr [si + o_ps_descnum],al mov byte ptr [si + o_ps_checksum],al #ifdef IS386 movzx cx,byte ptr [si + o_ps_length] #else mov cl,byte ptr [si + o_ps_length] xor ch,ch #endif call dochecksum sub byte ptr [si + o_ps_checksum],al xor ax,ax ret ! !************************************************************************** ! ! PXE interrupt handler. It will just return the address of the PXENV ! structure. ! Input: AX - input magic number ! Output: AX - output magic number ! ES:BX - far pointer to PXENV structure ! EDX - linear address of the PXENV structure ! Carry flag cleared ! Registers changed: AX ! new_int1A: pushf cmp ax,#PXE_IN_MAGIC ! check for input magic number jne int1A1 mov ax,cs mov es,ax ! set ES:BX with the address of mov bx,#pxenv ! the PXENV structure #ifdef IS386 movzx edx,ax shl edx,#4 ! convert the address into a linear add edx,#pxenv ! address #endif popf push bp ! clear carry in the flag word on the mov bp,sp ! stack and word ptr [bp + 6],#$FFFE pop bp mov ax,#PXE_OUT_MAGIC ! set return magic number iret int1A1: popf ! daisy chain into old interrupt vector seg cs jmp far ptr [oldint] ! !************************************************************************** ! end