/* * Copyright (C) 2006 Michael Brown . * * 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., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ FILE_LICENCE ( GPL2_OR_LATER ); #include #include #include #include #include #include #include /** @file * * PXE API entry point */ /** Vector for chaining INT 1A */ extern struct segoff __text16 ( pxe_int_1a_vector ); #define pxe_int_1a_vector __use_text16 ( pxe_int_1a_vector ) /** INT 1A handler */ extern void pxe_int_1a ( void ); /** INT 1A hooked flag */ static int int_1a_hooked = 0; /** * Handle an unknown PXE API call * * @v pxenv_unknown Pointer to a struct s_PXENV_UNKNOWN * @ret #PXENV_EXIT_FAILURE Always * @err #PXENV_STATUS_UNSUPPORTED Always */ static PXENV_EXIT_t pxenv_unknown ( struct s_PXENV_UNKNOWN *pxenv_unknown ) { pxenv_unknown->Status = PXENV_STATUS_UNSUPPORTED; return PXENV_EXIT_FAILURE; } /** Unknown PXE API call list */ struct pxe_api_call pxenv_unknown_api __pxe_api_call = PXE_API_CALL ( PXENV_UNKNOWN, pxenv_unknown, struct s_PXENV_UNKNOWN ); /** * Locate PXE API call * * @v opcode Opcode * @ret call PXE API call, or NULL */ static struct pxe_api_call * find_pxe_api_call ( uint16_t opcode ) { struct pxe_api_call *call; for_each_table_entry ( call, PXE_API_CALLS ) { if ( call->opcode == opcode ) return call; } return NULL; } /** * Dispatch PXE API call * * @v bx PXE opcode * @v es:di Address of PXE parameter block * @ret ax PXE exit code */ __asmcall void pxe_api_call ( struct i386_all_regs *ix86 ) { uint16_t opcode = ix86->regs.bx; userptr_t uparams = real_to_user ( ix86->segs.es, ix86->regs.di ); struct pxe_api_call *call; union u_PXENV_ANY params; PXENV_EXIT_t ret; /* Locate API call */ call = find_pxe_api_call ( opcode ); if ( ! call ) { DBGC ( &pxe_netdev, "PXENV_UNKNOWN_%04x\n", opcode ); call = &pxenv_unknown_api; } /* Copy parameter block from caller */ copy_from_user ( ¶ms, uparams, 0, call->params_len ); /* Set default status in case child routine fails to do so */ params.Status = PXENV_STATUS_FAILURE; /* Hand off to relevant API routine */ ret = call->entry ( ¶ms ); /* Copy modified parameter block back to caller and return */ copy_to_user ( uparams, 0, ¶ms, call->params_len ); ix86->regs.ax = ret; } /** * Dispatch weak PXE API call with PXE stack available * * @v ix86 Registers for PXE call * @ret present Zero (PXE stack present) */ int pxe_api_call_weak ( struct i386_all_regs *ix86 ) { pxe_api_call ( ix86 ); return 0; } /** * Dispatch PXE loader call * * @v es:di Address of PXE parameter block * @ret ax PXE exit code */ __asmcall void pxe_loader_call ( struct i386_all_regs *ix86 ) { userptr_t uparams = real_to_user ( ix86->segs.es, ix86->regs.di ); struct s_UNDI_LOADER params; PXENV_EXIT_t ret; /* Copy parameter block from caller */ copy_from_user ( ¶ms, uparams, 0, sizeof ( params ) ); /* Fill in ROM segment address */ ppxe.UNDIROMID.segment = ix86->segs.ds; /* Set default status in case child routine fails to do so */ params.Status = PXENV_STATUS_FAILURE; /* Call UNDI loader */ ret = undi_loader ( ¶ms ); /* Copy modified parameter block back to caller and return */ copy_to_user ( uparams, 0, ¶ms, sizeof ( params ) ); ix86->regs.ax = ret; } /** * Calculate byte checksum as used by PXE * * @v data Data * @v size Length of data * @ret sum Checksum */ static uint8_t pxe_checksum ( void *data, size_t size ) { uint8_t *bytes = data; uint8_t sum = 0; while ( size-- ) { sum += *bytes++; } return sum; } /** * Initialise !PXE and PXENV+ structures * */ static void pxe_init_structures ( void ) { uint32_t rm_cs_phys = ( rm_cs << 4 ); uint32_t rm_ds_phys = ( rm_ds << 4 ); /* Fill in missing segment fields */ ppxe.EntryPointSP.segment = rm_cs; ppxe.EntryPointESP.segment = rm_cs; ppxe.Stack.segment_address = rm_ds; ppxe.Stack.Physical_address = rm_ds_phys; ppxe.UNDIData.segment_address = rm_ds; ppxe.UNDIData.Physical_address = rm_ds_phys; ppxe.UNDICode.segment_address = rm_cs; ppxe.UNDICode.Physical_address = rm_cs_phys; ppxe.UNDICodeWrite.segment_address = rm_cs; ppxe.UNDICodeWrite.Physical_address = rm_cs_phys; pxenv.RMEntry.segment = rm_cs; pxenv.StackSeg = rm_ds; pxenv.UNDIDataSeg = rm_ds; pxenv.UNDICodeSeg = rm_cs; pxenv.PXEPtr.segment = rm_cs; /* Update checksums */ ppxe.StructCksum -= pxe_checksum ( &ppxe, sizeof ( ppxe ) ); pxenv.Checksum -= pxe_checksum ( &pxenv, sizeof ( pxenv ) ); } /** PXE structure initialiser */ struct init_fn pxe_init_fn __init_fn ( INIT_NORMAL ) = { .initialise = pxe_init_structures, }; /** * Activate PXE stack * * @v netdev Net device to use as PXE net device */ void pxe_activate ( struct net_device *netdev ) { /* Ensure INT 1A is hooked */ if ( ! int_1a_hooked ) { hook_bios_interrupt ( 0x1a, ( unsigned int ) pxe_int_1a, &pxe_int_1a_vector ); devices_get(); int_1a_hooked = 1; } /* Set PXE network device */ pxe_set_netdev ( netdev ); } /** * Deactivate PXE stack * * @ret rc Return status code */ int pxe_deactivate ( void ) { int rc; /* Clear PXE network device */ pxe_set_netdev ( NULL ); /* Ensure INT 1A is unhooked, if possible */ if ( int_1a_hooked ) { if ( ( rc = unhook_bios_interrupt ( 0x1a, (unsigned int) pxe_int_1a, &pxe_int_1a_vector ))!= 0){ DBG ( "Could not unhook INT 1A: %s\n", strerror ( rc ) ); return rc; } devices_put(); int_1a_hooked = 0; } return 0; } /** Jump buffer for PXENV_RESTART_TFTP */ rmjmp_buf pxe_restart_nbp; /** * Start PXE NBP at 0000:7c00 * * @ret rc Return status code */ int pxe_start_nbp ( void ) { int jmp; int discard_b, discard_c, discard_d, discard_D; uint16_t rc; /* Allow restarting NBP via PXENV_RESTART_TFTP */ jmp = rmsetjmp ( pxe_restart_nbp ); if ( jmp ) DBG ( "Restarting NBP (%x)\n", jmp ); /* Far call to PXE NBP */ __asm__ __volatile__ ( REAL_CODE ( "movw %%cx, %%es\n\t" "pushw %%es\n\t" "pushw %%di\n\t" "sti\n\t" "lcall $0, $0x7c00\n\t" "addw $4, %%sp\n\t" ) : "=a" ( rc ), "=b" ( discard_b ), "=c" ( discard_c ), "=d" ( discard_d ), "=D" ( discard_D ) : "a" ( 0 ), "b" ( __from_text16 ( &pxenv ) ), "c" ( rm_cs ), "d" ( virt_to_phys ( &pxenv ) ), "D" ( __from_text16 ( &ppxe ) ) : "esi", "ebp", "memory" ); return rc; } REQUIRE_OBJECT ( pxe_preboot ); REQUIRE_OBJECT ( pxe_undi ); REQUIRE_OBJECT ( pxe_udp ); REQUIRE_OBJECT ( pxe_tftp ); REQUIRE_OBJECT ( pxe_file );