| /* |
| * Copyright (C) 2007 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 <stdint.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <pxe.h> |
| #include <realmode.h> |
| #include <bios.h> |
| #include <pnpbios.h> |
| #include <basemem.h> |
| #include <ipxe/pci.h> |
| #include <undi.h> |
| #include <undirom.h> |
| #include <undiload.h> |
| |
| /** @file |
| * |
| * UNDI load/unload |
| * |
| */ |
| |
| /* Disambiguate the various error causes */ |
| #define EINFO_EUNDILOAD \ |
| __einfo_uniqify ( EINFO_EPLATFORM, 0x01, \ |
| "UNDI loader error" ) |
| #define EUNDILOAD( status ) EPLATFORM ( EINFO_EUNDILOAD, status ) |
| |
| /** Parameter block for calling UNDI loader */ |
| static struct s_UNDI_LOADER __bss16 ( undi_loader ); |
| #define undi_loader __use_data16 ( undi_loader ) |
| |
| /** UNDI loader entry point */ |
| static SEGOFF16_t __bss16 ( undi_loader_entry ); |
| #define undi_loader_entry __use_data16 ( undi_loader_entry ) |
| |
| /** |
| * Call UNDI loader to create a pixie |
| * |
| * @v undi UNDI device |
| * @v undirom UNDI ROM |
| * @ret rc Return status code |
| */ |
| int undi_load ( struct undi_device *undi, struct undi_rom *undirom ) { |
| struct s_PXE ppxe; |
| unsigned int fbms_seg; |
| uint16_t exit; |
| int rc; |
| |
| /* Only one UNDI instance may be loaded at any given time */ |
| if ( undi_loader_entry.segment ) { |
| DBG ( "UNDI %p cannot load multiple instances\n", undi ); |
| rc = -EBUSY; |
| goto err_multiple; |
| } |
| |
| /* Set up START_UNDI parameters */ |
| memset ( &undi_loader, 0, sizeof ( undi_loader ) ); |
| undi_loader.AX = undi->pci_busdevfn; |
| undi_loader.BX = undi->isapnp_csn; |
| undi_loader.DX = undi->isapnp_read_port; |
| undi_loader.ES = BIOS_SEG; |
| undi_loader.DI = find_pnp_bios(); |
| |
| /* Allocate base memory for PXE stack */ |
| undi->restore_fbms = get_fbms(); |
| fbms_seg = ( undi->restore_fbms << 6 ); |
| fbms_seg -= ( ( undirom->code_size + 0x0f ) >> 4 ); |
| undi_loader.UNDI_CS = fbms_seg; |
| fbms_seg -= ( ( undirom->data_size + 0x0f ) >> 4 ); |
| undi_loader.UNDI_DS = fbms_seg; |
| undi->fbms = ( fbms_seg >> 6 ); |
| set_fbms ( undi->fbms ); |
| DBGC ( undi, "UNDI %p allocated [%d,%d) kB of base memory\n", |
| undi, undi->fbms, undi->restore_fbms ); |
| |
| /* Debug info */ |
| DBGC ( undi, "UNDI %p loading ROM %p to CS %04x:%04zx DS %04x:%04zx " |
| "for ", undi, undirom, undi_loader.UNDI_CS, undirom->code_size, |
| undi_loader.UNDI_DS, undirom->data_size ); |
| if ( undi->pci_busdevfn != UNDI_NO_PCI_BUSDEVFN ) { |
| unsigned int bus = ( undi->pci_busdevfn >> 8 ); |
| unsigned int devfn = ( undi->pci_busdevfn & 0xff ); |
| DBGC ( undi, "PCI %02x:%02x.%x\n", |
| bus, PCI_SLOT ( devfn ), PCI_FUNC ( devfn ) ); |
| } |
| if ( undi->isapnp_csn != UNDI_NO_ISAPNP_CSN ) { |
| DBGC ( undi, "ISAPnP(%04x) CSN %04x\n", |
| undi->isapnp_read_port, undi->isapnp_csn ); |
| } |
| |
| /* Call loader */ |
| undi_loader_entry = undirom->loader_entry; |
| __asm__ __volatile__ ( REAL_CODE ( "pushl %%ebp\n\t" /* gcc bug */ |
| "pushw %%ds\n\t" |
| "pushw %%ax\n\t" |
| "lcall *undi_loader_entry\n\t" |
| "popl %%ebp\n\t" /* discard */ |
| "popl %%ebp\n\t" /* gcc bug */ ) |
| : "=a" ( exit ) |
| : "a" ( __from_data16 ( &undi_loader ) ) |
| : "ebx", "ecx", "edx", "esi", "edi" ); |
| if ( exit != PXENV_EXIT_SUCCESS ) { |
| rc = -EUNDILOAD ( undi_loader.Status ); |
| DBGC ( undi, "UNDI %p loader failed: %s\n", |
| undi, strerror ( rc ) ); |
| goto err_loader; |
| } |
| |
| /* Populate PXE device structure */ |
| undi->pxenv = undi_loader.PXENVptr; |
| undi->ppxe = undi_loader.PXEptr; |
| copy_from_real ( &ppxe, undi->ppxe.segment, undi->ppxe.offset, |
| sizeof ( ppxe ) ); |
| undi->entry = ppxe.EntryPointSP; |
| DBGC ( undi, "UNDI %p loaded PXENV+ %04x:%04x !PXE %04x:%04x " |
| "entry %04x:%04x\n", undi, undi->pxenv.segment, |
| undi->pxenv.offset, undi->ppxe.segment, undi->ppxe.offset, |
| undi->entry.segment, undi->entry.offset ); |
| |
| return 0; |
| |
| err_loader: |
| set_fbms ( undi->restore_fbms ); |
| memset ( &undi_loader_entry, 0, sizeof ( undi_loader_entry ) ); |
| err_multiple: |
| return rc; |
| } |
| |
| /** |
| * Unload a pixie |
| * |
| * @v undi UNDI device |
| * @ret rc Return status code |
| * |
| * Erases the PXENV+ and !PXE signatures, and frees the used base |
| * memory (if possible). |
| */ |
| int undi_unload ( struct undi_device *undi ) { |
| static uint32_t dead = 0xdeaddead; |
| |
| DBGC ( undi, "UNDI %p unloading\n", undi ); |
| |
| /* Clear entry point */ |
| memset ( &undi_loader_entry, 0, sizeof ( undi_loader_entry ) ); |
| |
| /* Erase signatures */ |
| if ( undi->pxenv.segment ) |
| put_real ( dead, undi->pxenv.segment, undi->pxenv.offset ); |
| if ( undi->ppxe.segment ) |
| put_real ( dead, undi->ppxe.segment, undi->ppxe.offset ); |
| |
| /* Free base memory, if possible */ |
| if ( undi->fbms == get_fbms() ) { |
| DBGC ( undi, "UNDI %p freeing [%d,%d) kB of base memory\n", |
| undi, undi->fbms, undi->restore_fbms ); |
| set_fbms ( undi->restore_fbms ); |
| return 0; |
| } else { |
| DBGC ( undi, "UNDI %p leaking [%d,%d) kB of base memory\n", |
| undi, undi->fbms, undi->restore_fbms ); |
| return -EBUSY; |
| } |
| } |