iPXE
pxe_call.c
Go to the documentation of this file.
00001 /*
00002  * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>.
00003  *
00004  * This program is free software; you can redistribute it and/or
00005  * modify it under the terms of the GNU General Public License as
00006  * published by the Free Software Foundation; either version 2 of the
00007  * License, or any later version.
00008  *
00009  * This program is distributed in the hope that it will be useful, but
00010  * WITHOUT ANY WARRANTY; without even the implied warranty of
00011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012  * General Public License for more details.
00013  *
00014  * You should have received a copy of the GNU General Public License
00015  * along with this program; if not, write to the Free Software
00016  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
00017  * 02110-1301, USA.
00018  *
00019  * You can also choose to distribute this program under the terms of
00020  * the Unmodified Binary Distribution Licence (as given in the file
00021  * COPYING.UBDL), provided that you have satisfied its requirements.
00022  */
00023 
00024 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
00025 
00026 #include <ipxe/uaccess.h>
00027 #include <ipxe/init.h>
00028 #include <ipxe/profile.h>
00029 #include <ipxe/netdevice.h>
00030 #include <rmsetjmp.h>
00031 #include <registers.h>
00032 #include <biosint.h>
00033 #include <pxe.h>
00034 #include <pxe_call.h>
00035 
00036 /** @file
00037  *
00038  * PXE API entry point
00039  */
00040 
00041 /* Disambiguate the various error causes */
00042 #define EINFO_EPXENBP                                                   \
00043         __einfo_uniqify ( EINFO_EPLATFORM, 0x01,                        \
00044                           "External PXE NBP error" )
00045 #define EPXENBP( status ) EPLATFORM ( EINFO_EPXENBP, status )
00046 
00047 /** Vector for chaining INT 1A */
00048 extern struct segoff __text16 ( pxe_int_1a_vector );
00049 #define pxe_int_1a_vector __use_text16 ( pxe_int_1a_vector )
00050 
00051 /** INT 1A handler */
00052 extern void pxe_int_1a ( void );
00053 
00054 /** INT 1A hooked flag */
00055 static int int_1a_hooked = 0;
00056 
00057 /** Real-mode code segment size */
00058 extern char _text16_memsz[];
00059 #define _text16_memsz ( ( size_t ) _text16_memsz )
00060 
00061 /** Real-mode data segment size */
00062 extern char _data16_memsz[];
00063 #define _data16_memsz ( ( size_t ) _data16_memsz )
00064 
00065 /** PXENV_UNDI_TRANSMIT API call profiler */
00066 static struct profiler pxe_api_tx_profiler __profiler =
00067         { .name = "pxeapi.tx" };
00068 
00069 /** PXENV_UNDI_ISR API call profiler */
00070 static struct profiler pxe_api_isr_profiler __profiler =
00071         { .name = "pxeapi.isr" };
00072 
00073 /** PXE unknown API call profiler
00074  *
00075  * This profiler can be used to measure the overhead of a dummy PXE
00076  * API call.
00077  */
00078 static struct profiler pxe_api_unknown_profiler __profiler =
00079         { .name = "pxeapi.unknown" };
00080 
00081 /** Miscellaneous PXE API call profiler */
00082 static struct profiler pxe_api_misc_profiler __profiler =
00083         { .name = "pxeapi.misc" };
00084 
00085 /**
00086  * Handle an unknown PXE API call
00087  *
00088  * @v pxenv_unknown                     Pointer to a struct s_PXENV_UNKNOWN
00089  * @ret #PXENV_EXIT_FAILURE             Always
00090  * @err #PXENV_STATUS_UNSUPPORTED       Always
00091  */
00092 static PXENV_EXIT_t pxenv_unknown ( struct s_PXENV_UNKNOWN *pxenv_unknown ) {
00093         pxenv_unknown->Status = PXENV_STATUS_UNSUPPORTED;
00094         return PXENV_EXIT_FAILURE;
00095 }
00096 
00097 /** Unknown PXE API call list */
00098 struct pxe_api_call pxenv_unknown_api __pxe_api_call =
00099         PXE_API_CALL ( PXENV_UNKNOWN, pxenv_unknown, struct s_PXENV_UNKNOWN );
00100 
00101 /**
00102  * Locate PXE API call
00103  *
00104  * @v opcode            Opcode
00105  * @ret call            PXE API call, or NULL
00106  */
00107 static struct pxe_api_call * find_pxe_api_call ( uint16_t opcode ) {
00108         struct pxe_api_call *call;
00109 
00110         for_each_table_entry ( call, PXE_API_CALLS ) {
00111                 if ( call->opcode == opcode )
00112                         return call;
00113         }
00114         return NULL;
00115 }
00116 
00117 /**
00118  * Determine applicable profiler (for debugging)
00119  *
00120  * @v opcode            PXE opcode
00121  * @ret profiler        Profiler
00122  */
00123 static struct profiler * pxe_api_profiler ( unsigned int opcode ) {
00124 
00125         /* Determine applicable profiler */
00126         switch ( opcode ) {
00127         case PXENV_UNDI_TRANSMIT:
00128                 return &pxe_api_tx_profiler;
00129         case PXENV_UNDI_ISR:
00130                 return &pxe_api_isr_profiler;
00131         case PXENV_UNKNOWN:
00132                 return &pxe_api_unknown_profiler;
00133         default:
00134                 return &pxe_api_misc_profiler;
00135         }
00136 }
00137 
00138 /**
00139  * Dispatch PXE API call
00140  *
00141  * @v bx                PXE opcode
00142  * @v es:di             Address of PXE parameter block
00143  * @ret ax              PXE exit code
00144  */
00145 __asmcall void pxe_api_call ( struct i386_all_regs *ix86 ) {
00146         uint16_t opcode = ix86->regs.bx;
00147         userptr_t uparams = real_to_user ( ix86->segs.es, ix86->regs.di );
00148         struct profiler *profiler = pxe_api_profiler ( opcode );
00149         struct pxe_api_call *call;
00150         union u_PXENV_ANY params;
00151         PXENV_EXIT_t ret;
00152 
00153         /* Start profiling */
00154         profile_start ( profiler );
00155 
00156         /* Locate API call */
00157         call = find_pxe_api_call ( opcode );
00158         if ( ! call ) {
00159                 DBGC ( &pxe_netdev, "PXENV_UNKNOWN_%04x\n", opcode );
00160                 call = &pxenv_unknown_api;
00161         }
00162 
00163         /* Copy parameter block from caller */
00164         copy_from_user ( &params, uparams, 0, call->params_len );
00165 
00166         /* Set default status in case child routine fails to do so */
00167         params.Status = PXENV_STATUS_FAILURE;
00168 
00169         /* Hand off to relevant API routine */
00170         ret = call->entry ( &params );
00171 
00172         /* Copy modified parameter block back to caller and return */
00173         copy_to_user ( uparams, 0, &params, call->params_len );
00174         ix86->regs.ax = ret;
00175 
00176         /* Stop profiling, if applicable */
00177         profile_stop ( profiler );
00178 }
00179 
00180 /**
00181  * Dispatch weak PXE API call with PXE stack available
00182  *
00183  * @v ix86              Registers for PXE call
00184  * @ret present         Zero (PXE stack present)
00185  */
00186 int pxe_api_call_weak ( struct i386_all_regs *ix86 ) {
00187         pxe_api_call ( ix86 );
00188         return 0;
00189 }
00190 
00191 /**
00192  * Dispatch PXE loader call
00193  *
00194  * @v es:di             Address of PXE parameter block
00195  * @ret ax              PXE exit code
00196  */
00197 __asmcall void pxe_loader_call ( struct i386_all_regs *ix86 ) {
00198         userptr_t uparams = real_to_user ( ix86->segs.es, ix86->regs.di );
00199         struct s_UNDI_LOADER params;
00200         PXENV_EXIT_t ret;
00201 
00202         /* Copy parameter block from caller */
00203         copy_from_user ( &params, uparams, 0, sizeof ( params ) );
00204 
00205         /* Fill in ROM segment address */
00206         ppxe.UNDIROMID.segment = ix86->segs.ds;
00207 
00208         /* Set default status in case child routine fails to do so */
00209         params.Status = PXENV_STATUS_FAILURE;
00210 
00211         /* Call UNDI loader */
00212         ret = undi_loader ( &params );
00213 
00214         /* Copy modified parameter block back to caller and return */
00215         copy_to_user ( uparams, 0, &params, sizeof ( params ) );
00216         ix86->regs.ax = ret;
00217 }
00218 
00219 /**
00220  * Calculate byte checksum as used by PXE
00221  *
00222  * @v data              Data
00223  * @v size              Length of data
00224  * @ret sum             Checksum
00225  */
00226 static uint8_t pxe_checksum ( void *data, size_t size ) {
00227         uint8_t *bytes = data;
00228         uint8_t sum = 0;
00229 
00230         while ( size-- ) {
00231                 sum += *bytes++;
00232         }
00233         return sum;
00234 }
00235 
00236 /**
00237  * Initialise !PXE and PXENV+ structures
00238  *
00239  */
00240 static void pxe_init_structures ( void ) {
00241         uint32_t rm_cs_phys = ( rm_cs << 4 );
00242         uint32_t rm_ds_phys = ( rm_ds << 4 );
00243 
00244         /* Fill in missing segment fields */
00245         ppxe.EntryPointSP.segment = rm_cs;
00246         ppxe.EntryPointESP.segment = rm_cs;
00247         ppxe.Stack.segment_address = rm_ds;
00248         ppxe.Stack.Physical_address = rm_ds_phys;
00249         ppxe.UNDIData.segment_address = rm_ds;
00250         ppxe.UNDIData.Physical_address = rm_ds_phys;
00251         ppxe.UNDICode.segment_address = rm_cs;
00252         ppxe.UNDICode.Physical_address = rm_cs_phys;
00253         ppxe.UNDICodeWrite.segment_address = rm_cs;
00254         ppxe.UNDICodeWrite.Physical_address = rm_cs_phys;
00255         pxenv.RMEntry.segment = rm_cs;
00256         pxenv.StackSeg = rm_ds;
00257         pxenv.UNDIDataSeg = rm_ds;
00258         pxenv.UNDICodeSeg = rm_cs;
00259         pxenv.PXEPtr.segment = rm_cs;
00260 
00261         /* Update checksums */
00262         ppxe.StructCksum -= pxe_checksum ( &ppxe, sizeof ( ppxe ) );
00263         pxenv.Checksum -= pxe_checksum ( &pxenv, sizeof ( pxenv ) );
00264 }
00265 
00266 /** PXE structure initialiser */
00267 struct init_fn pxe_init_fn __init_fn ( INIT_NORMAL ) = {
00268         .initialise = pxe_init_structures,
00269 };
00270 
00271 /**
00272  * Activate PXE stack
00273  *
00274  * @v netdev            Net device to use as PXE net device
00275  */
00276 void pxe_activate ( struct net_device *netdev ) {
00277         uint32_t discard_a;
00278         uint32_t discard_b;
00279         uint32_t discard_d;
00280 
00281         /* Ensure INT 1A is hooked */
00282         if ( ! int_1a_hooked ) {
00283                 hook_bios_interrupt ( 0x1a, ( intptr_t ) pxe_int_1a,
00284                                       &pxe_int_1a_vector );
00285                 devices_get();
00286                 int_1a_hooked = 1;
00287         }
00288 
00289         /* Set PXE network device */
00290         pxe_set_netdev ( netdev );
00291 
00292         /* Notify BIOS of installation */
00293         __asm__ __volatile__ ( REAL_CODE ( "pushw %%cs\n\t"
00294                                            "popw %%es\n\t"
00295                                            "int $0x1a\n\t" )
00296                                : "=a" ( discard_a ), "=b" ( discard_b ),
00297                                  "=d" ( discard_d )
00298                                : "0" ( 0x564e ),
00299                                  "1" ( __from_text16 ( &pxenv ) ) );
00300 }
00301 
00302 /**
00303  * Deactivate PXE stack
00304  *
00305  * @ret rc              Return status code
00306  */
00307 int pxe_deactivate ( void ) {
00308         int rc;
00309 
00310         /* Clear PXE network device */
00311         pxe_set_netdev ( NULL );
00312 
00313         /* Ensure INT 1A is unhooked, if possible */
00314         if ( int_1a_hooked ) {
00315                 if ( ( rc = unhook_bios_interrupt ( 0x1a,
00316                                                     ( intptr_t ) pxe_int_1a,
00317                                                     &pxe_int_1a_vector ))!= 0){
00318                         DBGC ( &pxe_netdev, "PXE could not unhook INT 1A: %s\n",
00319                                strerror ( rc ) );
00320                         return rc;
00321                 }
00322                 devices_put();
00323                 int_1a_hooked = 0;
00324         }
00325 
00326         return 0;
00327 }
00328 
00329 /** Jump buffer for PXENV_RESTART_TFTP */
00330 rmjmp_buf pxe_restart_nbp;
00331 
00332 /**
00333  * Start PXE NBP at 0000:7c00
00334  *
00335  * @ret rc              Return status code
00336  */
00337 int pxe_start_nbp ( void ) {
00338         int jmp;
00339         int discard_b, discard_c, discard_d, discard_D;
00340         uint16_t status;
00341 
00342         DBGC ( &pxe_netdev, "PXE NBP starting with netdev %s, code %04x:%04zx, "
00343                "data %04x:%04zx\n", ( pxe_netdev ? pxe_netdev->name : "<none>"),
00344                rm_cs, _text16_memsz, rm_ds, _data16_memsz );
00345 
00346         /* Allow restarting NBP via PXENV_RESTART_TFTP */
00347         jmp = rmsetjmp ( pxe_restart_nbp );
00348         if ( jmp )
00349                 DBGC ( &pxe_netdev, "PXE NBP restarting (%x)\n", jmp );
00350 
00351         /* Far call to PXE NBP */
00352         __asm__ __volatile__ ( REAL_CODE ( "pushl %%ebp\n\t" /* gcc bug */
00353                                            "movw %%cx, %%es\n\t"
00354                                            "pushw %%es\n\t"
00355                                            "pushw %%di\n\t"
00356                                            "sti\n\t"
00357                                            "lcall $0, $0x7c00\n\t"
00358                                            "popl %%ebp\n\t" /* discard */
00359                                            "popl %%ebp\n\t" /* gcc bug */ )
00360                                : "=a" ( status ), "=b" ( discard_b ),
00361                                  "=c" ( discard_c ), "=d" ( discard_d ),
00362                                  "=D" ( discard_D )
00363                                : "a" ( 0 ), "b" ( __from_text16 ( &pxenv ) ),
00364                                  "c" ( rm_cs ),
00365                                  "d" ( virt_to_phys ( &pxenv ) ),
00366                                  "D" ( __from_text16 ( &ppxe ) )
00367                                : "esi", "memory" );
00368         if ( status )
00369                 return -EPXENBP ( status );
00370 
00371         return 0;
00372 }
00373 
00374 /**
00375  * Notify BIOS of existence of network device
00376  *
00377  * @v netdev            Network device
00378  * @ret rc              Return status code
00379  */
00380 static int pxe_notify ( struct net_device *netdev ) {
00381 
00382         /* Do nothing if we already have a network device */
00383         if ( pxe_netdev )
00384                 return 0;
00385 
00386         /* Activate (and deactivate) PXE stack to notify BIOS */
00387         pxe_activate ( netdev );
00388         pxe_deactivate();
00389 
00390         return 0;
00391 }
00392 
00393 /** PXE BIOS notification driver */
00394 struct net_driver pxe_driver __net_driver = {
00395         .name = "PXE",
00396         .probe = pxe_notify,
00397 };
00398 
00399 REQUIRING_SYMBOL ( pxe_api_call );
00400 REQUIRE_OBJECT ( pxe_preboot );
00401 REQUIRE_OBJECT ( pxe_undi );
00402 REQUIRE_OBJECT ( pxe_udp );
00403 REQUIRE_OBJECT ( pxe_tftp );
00404 REQUIRE_OBJECT ( pxe_file );