/* * 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. * * You can also choose to distribute this program under the terms of * the Unmodified Binary Distribution Licence (as given in the file * COPYING.UBDL), provided that you have satisfied its requirements. */ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include #include #include #include #include #include #include #include /** @file * * PXE API entry point */ /* Disambiguate the various error causes */ #define EINFO_EPXENBP \ __einfo_uniqify ( EINFO_EPLATFORM, 0x01, \ "External PXE NBP error" ) #define EPXENBP( status ) EPLATFORM ( EINFO_EPXENBP, status ) /** 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; /** Real-mode code segment size */ extern char _text16_memsz[]; #define _text16_memsz ( ( size_t ) _text16_memsz ) /** Real-mode data segment size */ extern char _data16_memsz[]; #define _data16_memsz ( ( size_t ) _data16_memsz ) /** PXENV_UNDI_TRANSMIT API call profiler */ static struct profiler pxe_api_tx_profiler __profiler = { .name = "pxeapi.tx" }; /** PXENV_UNDI_ISR API call profiler */ static struct profiler pxe_api_isr_profiler __profiler = { .name = "pxeapi.isr" }; /** PXE unknown API call profiler * * This profiler can be used to measure the overhead of a dummy PXE * API call. */ static struct profiler pxe_api_unknown_profiler __profiler = { .name = "pxeapi.unknown" }; /** Miscellaneous PXE API call profiler */ static struct profiler pxe_api_misc_profiler __profiler = { .name = "pxeapi.misc" }; /** * 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; } /** * Determine applicable profiler (for debugging) * * @v opcode PXE opcode * @ret profiler Profiler */ static struct profiler * pxe_api_profiler ( unsigned int opcode ) { /* Determine applicable profiler */ switch ( opcode ) { case PXENV_UNDI_TRANSMIT: return &pxe_api_tx_profiler; case PXENV_UNDI_ISR: return &pxe_api_isr_profiler; case PXENV_UNKNOWN: return &pxe_api_unknown_profiler; default: return &pxe_api_misc_profiler; } } /** * 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 profiler *profiler = pxe_api_profiler ( opcode ); struct pxe_api_call *call; union u_PXENV_ANY params; PXENV_EXIT_t ret; /* Start profiling */ profile_start ( profiler ); /* 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; /* Stop profiling, if applicable */ profile_stop ( profiler ); } /** * 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 ) { uint32_t discard_a; uint32_t discard_b; uint32_t discard_d; /* Ensure INT 1A is hooked */ if ( ! int_1a_hooked ) { hook_bios_interrupt ( 0x1a, ( intptr_t ) pxe_int_1a, &pxe_int_1a_vector ); devices_get(); int_1a_hooked = 1; } /* Set PXE network device */ pxe_set_netdev ( netdev ); /* Notify BIOS of installation */ __asm__ __volatile__ ( REAL_CODE ( "pushw %%cs\n\t" "popw %%es\n\t" "int $0x1a\n\t" ) : "=a" ( discard_a ), "=b" ( discard_b ), "=d" ( discard_d ) : "0" ( 0x564e ), "1" ( __from_text16 ( &pxenv ) ) ); } /** * 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, ( intptr_t ) pxe_int_1a, &pxe_int_1a_vector ))!= 0){ DBGC ( &pxe_netdev, "PXE 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 status; DBGC ( &pxe_netdev, "PXE NBP starting with netdev %s, code %04x:%04zx, " "data %04x:%04zx\n", ( pxe_netdev ? pxe_netdev->name : ""), rm_cs, _text16_memsz, rm_ds, _data16_memsz ); /* Allow restarting NBP via PXENV_RESTART_TFTP */ jmp = rmsetjmp ( pxe_restart_nbp ); if ( jmp ) DBGC ( &pxe_netdev, "PXE NBP restarting (%x)\n", jmp ); /* Far call to PXE NBP */ __asm__ __volatile__ ( REAL_CODE ( "pushl %%ebp\n\t" /* gcc bug */ "movw %%cx, %%es\n\t" "pushw %%es\n\t" "pushw %%di\n\t" "sti\n\t" "lcall $0, $0x7c00\n\t" "popl %%ebp\n\t" /* discard */ "popl %%ebp\n\t" /* gcc bug */ ) : "=a" ( status ), "=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", "memory" ); if ( status ) return -EPXENBP ( status ); return 0; } /** * Notify BIOS of existence of network device * * @v netdev Network device * @ret rc Return status code */ static int pxe_notify ( struct net_device *netdev ) { /* Do nothing if we already have a network device */ if ( pxe_netdev ) return 0; /* Activate (and deactivate) PXE stack to notify BIOS */ pxe_activate ( netdev ); pxe_deactivate(); return 0; } /** PXE BIOS notification driver */ struct net_driver pxe_driver __net_driver = { .name = "PXE", .probe = pxe_notify, }; REQUIRING_SYMBOL ( pxe_api_call ); REQUIRE_OBJECT ( pxe_preboot ); REQUIRE_OBJECT ( pxe_undi ); REQUIRE_OBJECT ( pxe_udp ); REQUIRE_OBJECT ( pxe_tftp ); REQUIRE_OBJECT ( pxe_file );