| /* |
| * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>. |
| * |
| * 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 <ipxe/uaccess.h> |
| #include <ipxe/init.h> |
| #include <ipxe/profile.h> |
| #include <ipxe/netdevice.h> |
| #include <rmsetjmp.h> |
| #include <registers.h> |
| #include <biosint.h> |
| #include <pxe.h> |
| #include <pxe_call.h> |
| |
| /** @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 : "<none>"), |
| 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 ); |