summaryrefslogtreecommitdiffstats
path: root/contrib/syslinux-4.02/gpxe/src/arch/i386/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/syslinux-4.02/gpxe/src/arch/i386/drivers')
-rw-r--r--contrib/syslinux-4.02/gpxe/src/arch/i386/drivers/net/undi.c148
-rw-r--r--contrib/syslinux-4.02/gpxe/src/arch/i386/drivers/net/undiisr.S87
-rw-r--r--contrib/syslinux-4.02/gpxe/src/arch/i386/drivers/net/undiload.c171
-rw-r--r--contrib/syslinux-4.02/gpxe/src/arch/i386/drivers/net/undinet.c631
-rw-r--r--contrib/syslinux-4.02/gpxe/src/arch/i386/drivers/net/undionly.c129
-rw-r--r--contrib/syslinux-4.02/gpxe/src/arch/i386/drivers/net/undipreload.c37
-rw-r--r--contrib/syslinux-4.02/gpxe/src/arch/i386/drivers/net/undirom.c234
7 files changed, 1437 insertions, 0 deletions
diff --git a/contrib/syslinux-4.02/gpxe/src/arch/i386/drivers/net/undi.c b/contrib/syslinux-4.02/gpxe/src/arch/i386/drivers/net/undi.c
new file mode 100644
index 0000000..c6e253c
--- /dev/null
+++ b/contrib/syslinux-4.02/gpxe/src/arch/i386/drivers/net/undi.c
@@ -0,0 +1,148 @@
+/*
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <gpxe/pci.h>
+#include <undi.h>
+#include <undirom.h>
+#include <undiload.h>
+#include <undinet.h>
+#include <undipreload.h>
+
+/** @file
+ *
+ * UNDI PCI driver
+ *
+ */
+
+/**
+ * Find UNDI ROM for PCI device
+ *
+ * @v pci PCI device
+ * @ret undirom UNDI ROM, or NULL
+ *
+ * Try to find a driver for this device. Try an exact match on the
+ * ROM address first, then fall back to a vendor/device ID match only
+ */
+static struct undi_rom * undipci_find_rom ( struct pci_device *pci ) {
+ struct undi_rom *undirom;
+ unsigned long rombase;
+
+ rombase = pci_bar_start ( pci, PCI_ROM_ADDRESS );
+ undirom = undirom_find_pci ( pci->vendor, pci->device, rombase );
+ if ( ! undirom )
+ undirom = undirom_find_pci ( pci->vendor, pci->device, 0 );
+ return undirom;
+}
+
+/**
+ * Probe PCI device
+ *
+ * @v pci PCI device
+ * @v id PCI ID
+ * @ret rc Return status code
+ */
+static int undipci_probe ( struct pci_device *pci,
+ const struct pci_device_id *id __unused ) {
+ struct undi_device *undi;
+ struct undi_rom *undirom;
+ unsigned int busdevfn = PCI_BUSDEVFN ( pci->bus, pci->devfn );
+ int rc;
+
+ /* Ignore non-network devices */
+ if ( PCI_BASE_CLASS ( pci->class ) != PCI_BASE_CLASS_NETWORK )
+ return -ENOTTY;
+
+ /* Allocate UNDI device structure */
+ undi = zalloc ( sizeof ( *undi ) );
+ if ( ! undi )
+ return -ENOMEM;
+ pci_set_drvdata ( pci, undi );
+
+ /* Find/create our pixie */
+ if ( preloaded_undi.pci_busdevfn == busdevfn ) {
+ /* Claim preloaded UNDI device */
+ DBGC ( undi, "UNDI %p using preloaded UNDI device\n", undi );
+ memcpy ( undi, &preloaded_undi, sizeof ( *undi ) );
+ memset ( &preloaded_undi, 0, sizeof ( preloaded_undi ) );
+ } else {
+ /* Find UNDI ROM for PCI device */
+ if ( ! ( undirom = undipci_find_rom ( pci ) ) ) {
+ rc = -ENODEV;
+ goto err_find_rom;
+ }
+
+ /* Call UNDI ROM loader to create pixie */
+ if ( ( rc = undi_load_pci ( undi, undirom, busdevfn ) ) != 0 )
+ goto err_load_pci;
+ }
+
+ /* Add to device hierarchy */
+ snprintf ( undi->dev.name, sizeof ( undi->dev.name ),
+ "UNDI-%s", pci->dev.name );
+ memcpy ( &undi->dev.desc, &pci->dev.desc, sizeof ( undi->dev.desc ) );
+ undi->dev.parent = &pci->dev;
+ INIT_LIST_HEAD ( &undi->dev.children );
+ list_add ( &undi->dev.siblings, &pci->dev.children );
+
+ /* Create network device */
+ if ( ( rc = undinet_probe ( undi ) ) != 0 )
+ goto err_undinet_probe;
+
+ return 0;
+
+ err_undinet_probe:
+ undi_unload ( undi );
+ list_del ( &undi->dev.siblings );
+ err_find_rom:
+ err_load_pci:
+ free ( undi );
+ pci_set_drvdata ( pci, NULL );
+ return rc;
+}
+
+/**
+ * Remove PCI device
+ *
+ * @v pci PCI device
+ */
+static void undipci_remove ( struct pci_device *pci ) {
+ struct undi_device *undi = pci_get_drvdata ( pci );
+
+ undinet_remove ( undi );
+ undi_unload ( undi );
+ list_del ( &undi->dev.siblings );
+ free ( undi );
+ pci_set_drvdata ( pci, NULL );
+}
+
+static struct pci_device_id undipci_nics[] = {
+PCI_ROM ( 0xffff, 0xffff, "undipci", "UNDI (PCI)", 0 ),
+};
+
+struct pci_driver undipci_driver __pci_driver = {
+ .ids = undipci_nics,
+ .id_count = ( sizeof ( undipci_nics ) / sizeof ( undipci_nics[0] ) ),
+ .probe = undipci_probe,
+ .remove = undipci_remove,
+};
diff --git a/contrib/syslinux-4.02/gpxe/src/arch/i386/drivers/net/undiisr.S b/contrib/syslinux-4.02/gpxe/src/arch/i386/drivers/net/undiisr.S
new file mode 100644
index 0000000..b27effe
--- /dev/null
+++ b/contrib/syslinux-4.02/gpxe/src/arch/i386/drivers/net/undiisr.S
@@ -0,0 +1,87 @@
+FILE_LICENCE ( GPL2_OR_LATER )
+
+#define PXENV_UNDI_ISR 0x0014
+#define PXENV_UNDI_ISR_IN_START 1
+#define PXENV_UNDI_ISR_OUT_OURS 0
+#define PXENV_UNDI_ISR_OUT_NOT_OURS 1
+
+#define IRQ_PIC_CUTOFF 8
+#define ICR_EOI_NON_SPECIFIC 0x20
+#define PIC1_ICR 0x20
+#define PIC2_ICR 0xa0
+
+ .text
+ .arch i386
+ .code16
+
+ .section ".text16", "ax", @progbits
+ .globl undiisr
+undiisr:
+
+ /* Preserve registers */
+ pushw %ds
+ pushw %es
+ pushw %fs
+ pushw %gs
+ pushfl
+ pushal
+
+ /* Set up our segment registers */
+ movw %cs:rm_ds, %ax
+ movw %ax, %ds
+
+ /* Check that we have an UNDI entry point */
+ cmpw $0, pxeparent_entry_point
+ je chain
+
+ /* Issue UNDI API call */
+ movw %ax, %es
+ movw $undinet_params, %di
+ movw $PXENV_UNDI_ISR, %bx
+ movw $PXENV_UNDI_ISR_IN_START, funcflag
+ pushw %es
+ pushw %di
+ pushw %bx
+ lcall *pxeparent_entry_point
+ cli /* Just in case */
+ addw $6, %sp
+ cmpw $PXENV_UNDI_ISR_OUT_OURS, funcflag
+ jne eoi
+
+trig: /* Record interrupt occurence */
+ incb undiisr_trigger_count
+
+eoi: /* Send EOI */
+ movb $ICR_EOI_NON_SPECIFIC, %al
+ cmpb $IRQ_PIC_CUTOFF, undiisr_irq
+ jb 1f
+ outb %al, $PIC2_ICR
+1: outb %al, $PIC1_ICR
+ jmp exit
+
+chain: /* Chain to next handler */
+ pushfw
+ lcall *undiisr_next_handler
+
+exit: /* Restore registers and return */
+ cli
+ popal
+ movzwl %sp, %esp
+ addr32 movl -20(%esp), %esp /* %esp isn't restored by popal */
+ popfl
+ popw %gs
+ popw %fs
+ popw %es
+ popw %ds
+ iret
+
+ .section ".data16", "aw", @progbits
+undinet_params:
+status: .word 0
+funcflag: .word 0
+bufferlength: .word 0
+framelength: .word 0
+frameheaderlength: .word 0
+frame: .word 0, 0
+prottype: .byte 0
+pkttype: .byte 0
diff --git a/contrib/syslinux-4.02/gpxe/src/arch/i386/drivers/net/undiload.c b/contrib/syslinux-4.02/gpxe/src/arch/i386/drivers/net/undiload.c
new file mode 100644
index 0000000..1d4e88d
--- /dev/null
+++ b/contrib/syslinux-4.02/gpxe/src/arch/i386/drivers/net/undiload.c
@@ -0,0 +1,171 @@
+/*
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#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 <gpxe/pci.h>
+#include <undi.h>
+#include <undirom.h>
+#include <undiload.h>
+
+/** @file
+ *
+ * UNDI load/unload
+ *
+ */
+
+/** 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;
+
+ /* 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;
+
+ /* Debug info */
+ DBGC ( undi, "UNDI %p loading UNDI ROM %p to CS %04x DS %04x for ",
+ undi, undirom, undi_loader.UNDI_CS, undi_loader.UNDI_DS );
+ 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 ( "pushw %%ds\n\t"
+ "pushw %%ax\n\t"
+ "lcall *undi_loader_entry\n\t"
+ "addw $4, %%sp\n\t" )
+ : "=a" ( exit )
+ : "a" ( __from_data16 ( &undi_loader ) )
+ : "ebx", "ecx", "edx", "esi", "edi", "ebp" );
+
+ /* UNDI API calls may rudely change the status of A20 and not
+ * bother to restore it afterwards. Intel is known to be
+ * guilty of this.
+ *
+ * Note that we will return to this point even if A20 gets
+ * screwed up by the UNDI driver, because Etherboot always
+ * resides in an even megabyte of RAM.
+ */
+ gateA20_set();
+
+ if ( exit != PXENV_EXIT_SUCCESS ) {
+ rc = -undi_loader.Status;
+ if ( rc == 0 ) /* Paranoia */
+ rc = -EIO;
+ DBGC ( undi, "UNDI %p loader failed: %s\n",
+ undi, strerror ( rc ) );
+ return rc;
+ }
+
+ /* 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 );
+
+ /* Update free base memory counter */
+ undi->fbms = ( fbms_seg >> 6 );
+ set_fbms ( undi->fbms );
+ DBGC ( undi, "UNDI %p using [%d,%d) kB of base memory\n",
+ undi, undi->fbms, undi->restore_fbms );
+
+ return 0;
+}
+
+/**
+ * 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 );
+
+ /* 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;
+ }
+}
diff --git a/contrib/syslinux-4.02/gpxe/src/arch/i386/drivers/net/undinet.c b/contrib/syslinux-4.02/gpxe/src/arch/i386/drivers/net/undinet.c
new file mode 100644
index 0000000..83b79e7
--- /dev/null
+++ b/contrib/syslinux-4.02/gpxe/src/arch/i386/drivers/net/undinet.c
@@ -0,0 +1,631 @@
+/*
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <string.h>
+#include <pxe.h>
+#include <realmode.h>
+#include <pic8259.h>
+#include <biosint.h>
+#include <pnpbios.h>
+#include <basemem_packet.h>
+#include <gpxe/io.h>
+#include <gpxe/iobuf.h>
+#include <gpxe/netdevice.h>
+#include <gpxe/if_ether.h>
+#include <gpxe/ethernet.h>
+#include <undi.h>
+#include <undinet.h>
+#include <pxeparent.h>
+
+
+/** @file
+ *
+ * UNDI network device driver
+ *
+ */
+
+/** An UNDI NIC */
+struct undi_nic {
+ /** Assigned IRQ number */
+ unsigned int irq;
+ /** Currently processing ISR */
+ int isr_processing;
+ /** Bug workarounds */
+ int hacks;
+};
+
+/**
+ * @defgroup undi_hacks UNDI workarounds
+ * @{
+ */
+
+/** Work around Etherboot 5.4 bugs */
+#define UNDI_HACK_EB54 0x0001
+
+/** @} */
+
+static void undinet_close ( struct net_device *netdev );
+
+/** Address of UNDI entry point */
+static SEGOFF16_t undinet_entry;
+
+/*****************************************************************************
+ *
+ * UNDI interrupt service routine
+ *
+ *****************************************************************************
+ */
+
+/**
+ * UNDI interrupt service routine
+ *
+ * The UNDI ISR increments a counter (@c trigger_count) and exits.
+ */
+extern void undiisr ( void );
+
+/** IRQ number */
+uint8_t __data16 ( undiisr_irq );
+#define undiisr_irq __use_data16 ( undiisr_irq )
+
+/** IRQ chain vector */
+struct segoff __data16 ( undiisr_next_handler );
+#define undiisr_next_handler __use_data16 ( undiisr_next_handler )
+
+/** IRQ trigger count */
+volatile uint8_t __data16 ( undiisr_trigger_count ) = 0;
+#define undiisr_trigger_count __use_data16 ( undiisr_trigger_count )
+
+/** Last observed trigger count */
+static unsigned int last_trigger_count = 0;
+
+/**
+ * Hook UNDI interrupt service routine
+ *
+ * @v irq IRQ number
+ */
+static void undinet_hook_isr ( unsigned int irq ) {
+
+ assert ( irq <= IRQ_MAX );
+ assert ( undiisr_irq == 0 );
+
+ undiisr_irq = irq;
+ hook_bios_interrupt ( IRQ_INT ( irq ),
+ ( ( unsigned int ) undiisr ),
+ &undiisr_next_handler );
+}
+
+/**
+ * Unhook UNDI interrupt service routine
+ *
+ * @v irq IRQ number
+ */
+static void undinet_unhook_isr ( unsigned int irq ) {
+
+ assert ( irq <= IRQ_MAX );
+
+ unhook_bios_interrupt ( IRQ_INT ( irq ),
+ ( ( unsigned int ) undiisr ),
+ &undiisr_next_handler );
+ undiisr_irq = 0;
+}
+
+/**
+ * Test to see if UNDI ISR has been triggered
+ *
+ * @ret triggered ISR has been triggered since last check
+ */
+static int undinet_isr_triggered ( void ) {
+ unsigned int this_trigger_count;
+
+ /* Read trigger_count. Do this only once; it is volatile */
+ this_trigger_count = undiisr_trigger_count;
+
+ if ( this_trigger_count == last_trigger_count ) {
+ /* Not triggered */
+ return 0;
+ } else {
+ /* Triggered */
+ last_trigger_count = this_trigger_count;
+ return 1;
+ }
+}
+
+/*****************************************************************************
+ *
+ * UNDI network device interface
+ *
+ *****************************************************************************
+ */
+
+/** UNDI transmit buffer descriptor */
+static struct s_PXENV_UNDI_TBD __data16 ( undinet_tbd );
+#define undinet_tbd __use_data16 ( undinet_tbd )
+
+/**
+ * Transmit packet
+ *
+ * @v netdev Network device
+ * @v iobuf I/O buffer
+ * @ret rc Return status code
+ */
+static int undinet_transmit ( struct net_device *netdev,
+ struct io_buffer *iobuf ) {
+ struct s_PXENV_UNDI_TRANSMIT undi_transmit;
+ size_t len = iob_len ( iobuf );
+ int rc;
+
+ /* Technically, we ought to make sure that the previous
+ * transmission has completed before we re-use the buffer.
+ * However, many PXE stacks (including at least some Intel PXE
+ * stacks and Etherboot 5.4) fail to generate TX completions.
+ * In practice this won't be a problem, since our TX datapath
+ * has a very low packet volume and we can get away with
+ * assuming that a TX will be complete by the time we want to
+ * transmit the next packet.
+ */
+
+ /* Copy packet to UNDI I/O buffer */
+ if ( len > sizeof ( basemem_packet ) )
+ len = sizeof ( basemem_packet );
+ memcpy ( &basemem_packet, iobuf->data, len );
+
+ /* Create PXENV_UNDI_TRANSMIT data structure */
+ memset ( &undi_transmit, 0, sizeof ( undi_transmit ) );
+ undi_transmit.DestAddr.segment = rm_ds;
+ undi_transmit.DestAddr.offset = __from_data16 ( &undinet_tbd );
+ undi_transmit.TBD.segment = rm_ds;
+ undi_transmit.TBD.offset = __from_data16 ( &undinet_tbd );
+
+ /* Create PXENV_UNDI_TBD data structure */
+ undinet_tbd.ImmedLength = len;
+ undinet_tbd.Xmit.segment = rm_ds;
+ undinet_tbd.Xmit.offset = __from_data16 ( basemem_packet );
+
+ /* Issue PXE API call */
+ if ( ( rc = pxeparent_call ( undinet_entry, PXENV_UNDI_TRANSMIT,
+ &undi_transmit,
+ sizeof ( undi_transmit ) ) ) != 0 )
+ goto done;
+
+ /* Free I/O buffer */
+ netdev_tx_complete ( netdev, iobuf );
+
+ done:
+ return rc;
+}
+
+/**
+ * Poll for received packets
+ *
+ * @v netdev Network device
+ *
+ * Fun, fun, fun. UNDI drivers don't use polling; they use
+ * interrupts. We therefore cheat and pretend that an interrupt has
+ * occurred every time undinet_poll() is called. This isn't too much
+ * of a hack; PCI devices share IRQs and so the first thing that a
+ * proper ISR should do is call PXENV_UNDI_ISR to determine whether or
+ * not the UNDI NIC generated the interrupt; there is no harm done by
+ * spurious calls to PXENV_UNDI_ISR. Similarly, we wouldn't be
+ * handling them any more rapidly than the usual rate of
+ * undinet_poll() being called even if we did implement a full ISR.
+ * So it should work. Ha!
+ *
+ * Addendum (21/10/03). Some cards don't play nicely with this trick,
+ * so instead of doing it the easy way we have to go to all the hassle
+ * of installing a genuine interrupt service routine and dealing with
+ * the wonderful 8259 Programmable Interrupt Controller. Joy.
+ *
+ * Addendum (10/07/07). When doing things such as iSCSI boot, in
+ * which we have to co-operate with a running OS, we can't get away
+ * with the "ISR-just-increments-a-counter-and-returns" trick at all,
+ * because it involves tying up the PIC for far too long, and other
+ * interrupt-dependent components (e.g. local disks) start breaking.
+ * We therefore implement a "proper" ISR which calls PXENV_UNDI_ISR
+ * from within interrupt context in order to deassert the device
+ * interrupt, and sends EOI if applicable.
+ */
+static void undinet_poll ( struct net_device *netdev ) {
+ struct undi_nic *undinic = netdev->priv;
+ struct s_PXENV_UNDI_ISR undi_isr;
+ struct io_buffer *iobuf = NULL;
+ size_t len;
+ size_t frag_len;
+ size_t max_frag_len;
+ int rc;
+
+ if ( ! undinic->isr_processing ) {
+ /* Do nothing unless ISR has been triggered */
+ if ( ! undinet_isr_triggered() ) {
+ /* Allow interrupt to occur */
+ __asm__ __volatile__ ( REAL_CODE ( "sti\n\t"
+ "nop\n\t"
+ "nop\n\t"
+ "cli\n\t" ) : : );
+ return;
+ }
+
+ /* Start ISR processing */
+ undinic->isr_processing = 1;
+ undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_PROCESS;
+ } else {
+ /* Continue ISR processing */
+ undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_GET_NEXT;
+ }
+
+ /* Run through the ISR loop */
+ while ( 1 ) {
+ if ( ( rc = pxeparent_call ( undinet_entry, PXENV_UNDI_ISR,
+ &undi_isr,
+ sizeof ( undi_isr ) ) ) != 0 )
+ break;
+ switch ( undi_isr.FuncFlag ) {
+ case PXENV_UNDI_ISR_OUT_TRANSMIT:
+ /* We don't care about transmit completions */
+ break;
+ case PXENV_UNDI_ISR_OUT_RECEIVE:
+ /* Packet fragment received */
+ len = undi_isr.FrameLength;
+ frag_len = undi_isr.BufferLength;
+ if ( ( len == 0 ) || ( len < frag_len ) ) {
+ /* Don't laugh. VMWare does it. */
+ DBGC ( undinic, "UNDINIC %p reported insane "
+ "fragment (%zd of %zd bytes)\n",
+ undinic, frag_len, len );
+ netdev_rx_err ( netdev, NULL, -EINVAL );
+ break;
+ }
+ if ( ! iobuf )
+ iobuf = alloc_iob ( len );
+ if ( ! iobuf ) {
+ DBGC ( undinic, "UNDINIC %p could not "
+ "allocate %zd bytes for RX buffer\n",
+ undinic, len );
+ /* Fragment will be dropped */
+ netdev_rx_err ( netdev, NULL, -ENOMEM );
+ goto done;
+ }
+ max_frag_len = iob_tailroom ( iobuf );
+ if ( frag_len > max_frag_len ) {
+ DBGC ( undinic, "UNDINIC %p fragment too big "
+ "(%zd+%zd does not fit into %zd)\n",
+ undinic, iob_len ( iobuf ), frag_len,
+ ( iob_len ( iobuf ) + max_frag_len ) );
+ frag_len = max_frag_len;
+ }
+ copy_from_real ( iob_put ( iobuf, frag_len ),
+ undi_isr.Frame.segment,
+ undi_isr.Frame.offset, frag_len );
+ if ( iob_len ( iobuf ) == len ) {
+ /* Whole packet received; deliver it */
+ netdev_rx ( netdev, iob_disown ( iobuf ) );
+ /* Etherboot 5.4 fails to return all packets
+ * under mild load; pretend it retriggered.
+ */
+ if ( undinic->hacks & UNDI_HACK_EB54 )
+ --last_trigger_count;
+ }
+ break;
+ case PXENV_UNDI_ISR_OUT_DONE:
+ /* Processing complete */
+ undinic->isr_processing = 0;
+ goto done;
+ default:
+ /* Should never happen. VMWare does it routinely. */
+ DBGC ( undinic, "UNDINIC %p ISR returned invalid "
+ "FuncFlag %04x\n", undinic, undi_isr.FuncFlag );
+ undinic->isr_processing = 0;
+ goto done;
+ }
+ undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_GET_NEXT;
+ }
+
+ done:
+ if ( iobuf ) {
+ DBGC ( undinic, "UNDINIC %p returned incomplete packet "
+ "(%zd of %zd)\n", undinic, iob_len ( iobuf ),
+ ( iob_len ( iobuf ) + iob_tailroom ( iobuf ) ) );
+ netdev_rx_err ( netdev, iobuf, -EINVAL );
+ }
+}
+
+/**
+ * Open NIC
+ *
+ * @v netdev Net device
+ * @ret rc Return status code
+ */
+static int undinet_open ( struct net_device *netdev ) {
+ struct undi_nic *undinic = netdev->priv;
+ struct s_PXENV_UNDI_SET_STATION_ADDRESS undi_set_address;
+ struct s_PXENV_UNDI_OPEN undi_open;
+ int rc;
+
+ /* Hook interrupt service routine and enable interrupt */
+ undinet_hook_isr ( undinic->irq );
+ enable_irq ( undinic->irq );
+ send_eoi ( undinic->irq );
+
+ /* Set station address. Required for some PXE stacks; will
+ * spuriously fail on others. Ignore failures. We only ever
+ * use it to set the MAC address to the card's permanent value
+ * anyway.
+ */
+ memcpy ( undi_set_address.StationAddress, netdev->ll_addr,
+ sizeof ( undi_set_address.StationAddress ) );
+ pxeparent_call ( undinet_entry, PXENV_UNDI_SET_STATION_ADDRESS,
+ &undi_set_address, sizeof ( undi_set_address ) );
+
+ /* Open NIC. We ask for promiscuous operation, since it's the
+ * only way to ask for all multicast addresses. On any
+ * switched network, it shouldn't really make a difference to
+ * performance.
+ */
+ memset ( &undi_open, 0, sizeof ( undi_open ) );
+ undi_open.PktFilter = ( FLTR_DIRECTED | FLTR_BRDCST | FLTR_PRMSCS );
+ if ( ( rc = pxeparent_call ( undinet_entry, PXENV_UNDI_OPEN,
+ &undi_open, sizeof ( undi_open ) ) ) != 0 )
+ goto err;
+
+ DBGC ( undinic, "UNDINIC %p opened\n", undinic );
+ return 0;
+
+ err:
+ undinet_close ( netdev );
+ return rc;
+}
+
+/**
+ * Close NIC
+ *
+ * @v netdev Net device
+ */
+static void undinet_close ( struct net_device *netdev ) {
+ struct undi_nic *undinic = netdev->priv;
+ struct s_PXENV_UNDI_ISR undi_isr;
+ struct s_PXENV_UNDI_CLOSE undi_close;
+ int rc;
+
+ /* Ensure ISR has exited cleanly */
+ while ( undinic->isr_processing ) {
+ undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_GET_NEXT;
+ if ( ( rc = pxeparent_call ( undinet_entry, PXENV_UNDI_ISR,
+ &undi_isr,
+ sizeof ( undi_isr ) ) ) != 0 )
+ break;
+ switch ( undi_isr.FuncFlag ) {
+ case PXENV_UNDI_ISR_OUT_TRANSMIT:
+ case PXENV_UNDI_ISR_OUT_RECEIVE:
+ /* Continue draining */
+ break;
+ default:
+ /* Stop processing */
+ undinic->isr_processing = 0;
+ break;
+ }
+ }
+
+ /* Close NIC */
+ pxeparent_call ( undinet_entry, PXENV_UNDI_CLOSE,
+ &undi_close, sizeof ( undi_close ) );
+
+ /* Disable interrupt and unhook ISR */
+ disable_irq ( undinic->irq );
+ undinet_unhook_isr ( undinic->irq );
+
+ DBGC ( undinic, "UNDINIC %p closed\n", undinic );
+}
+
+/**
+ * Enable/disable interrupts
+ *
+ * @v netdev Net device
+ * @v enable Interrupts should be enabled
+ */
+static void undinet_irq ( struct net_device *netdev, int enable ) {
+ struct undi_nic *undinic = netdev->priv;
+
+ /* Cannot support interrupts yet */
+ DBGC ( undinic, "UNDINIC %p cannot %s interrupts\n",
+ undinic, ( enable ? "enable" : "disable" ) );
+}
+
+/** UNDI network device operations */
+static struct net_device_operations undinet_operations = {
+ .open = undinet_open,
+ .close = undinet_close,
+ .transmit = undinet_transmit,
+ .poll = undinet_poll,
+ .irq = undinet_irq,
+};
+
+/**
+ * Probe UNDI device
+ *
+ * @v undi UNDI device
+ * @ret rc Return status code
+ */
+int undinet_probe ( struct undi_device *undi ) {
+ struct net_device *netdev;
+ struct undi_nic *undinic;
+ struct s_PXENV_START_UNDI start_undi;
+ struct s_PXENV_UNDI_STARTUP undi_startup;
+ struct s_PXENV_UNDI_INITIALIZE undi_initialize;
+ struct s_PXENV_UNDI_GET_INFORMATION undi_info;
+ struct s_PXENV_UNDI_GET_IFACE_INFO undi_iface;
+ struct s_PXENV_UNDI_SHUTDOWN undi_shutdown;
+ struct s_PXENV_UNDI_CLEANUP undi_cleanup;
+ struct s_PXENV_STOP_UNDI stop_undi;
+ int rc;
+
+ /* Allocate net device */
+ netdev = alloc_etherdev ( sizeof ( *undinic ) );
+ if ( ! netdev )
+ return -ENOMEM;
+ netdev_init ( netdev, &undinet_operations );
+ undinic = netdev->priv;
+ undi_set_drvdata ( undi, netdev );
+ netdev->dev = &undi->dev;
+ memset ( undinic, 0, sizeof ( *undinic ) );
+ undinet_entry = undi->entry;
+ DBGC ( undinic, "UNDINIC %p using UNDI %p\n", undinic, undi );
+
+ /* Hook in UNDI stack */
+ if ( ! ( undi->flags & UNDI_FL_STARTED ) ) {
+ memset ( &start_undi, 0, sizeof ( start_undi ) );
+ start_undi.AX = undi->pci_busdevfn;
+ start_undi.BX = undi->isapnp_csn;
+ start_undi.DX = undi->isapnp_read_port;
+ start_undi.ES = BIOS_SEG;
+ start_undi.DI = find_pnp_bios();
+ if ( ( rc = pxeparent_call ( undinet_entry, PXENV_START_UNDI,
+ &start_undi,
+ sizeof ( start_undi ) ) ) != 0 )
+ goto err_start_undi;
+ }
+ undi->flags |= UNDI_FL_STARTED;
+
+ /* Bring up UNDI stack */
+ if ( ! ( undi->flags & UNDI_FL_INITIALIZED ) ) {
+ memset ( &undi_startup, 0, sizeof ( undi_startup ) );
+ if ( ( rc = pxeparent_call ( undinet_entry, PXENV_UNDI_STARTUP,
+ &undi_startup,
+ sizeof ( undi_startup ) ) ) != 0 )
+ goto err_undi_startup;
+ memset ( &undi_initialize, 0, sizeof ( undi_initialize ) );
+ if ( ( rc = pxeparent_call ( undinet_entry,
+ PXENV_UNDI_INITIALIZE,
+ &undi_initialize,
+ sizeof ( undi_initialize ))) != 0 )
+ goto err_undi_initialize;
+ }
+ undi->flags |= UNDI_FL_INITIALIZED;
+
+ /* Get device information */
+ memset ( &undi_info, 0, sizeof ( undi_info ) );
+ if ( ( rc = pxeparent_call ( undinet_entry, PXENV_UNDI_GET_INFORMATION,
+ &undi_info, sizeof ( undi_info ) ) ) != 0 )
+ goto err_undi_get_information;
+ memcpy ( netdev->hw_addr, undi_info.PermNodeAddress, ETH_ALEN );
+ undinic->irq = undi_info.IntNumber;
+ if ( undinic->irq > IRQ_MAX ) {
+ DBGC ( undinic, "UNDINIC %p invalid IRQ %d\n",
+ undinic, undinic->irq );
+ goto err_bad_irq;
+ }
+ DBGC ( undinic, "UNDINIC %p is %s on IRQ %d\n",
+ undinic, eth_ntoa ( netdev->hw_addr ), undinic->irq );
+
+ /* Get interface information */
+ memset ( &undi_iface, 0, sizeof ( undi_iface ) );
+ if ( ( rc = pxeparent_call ( undinet_entry, PXENV_UNDI_GET_IFACE_INFO,
+ &undi_iface,
+ sizeof ( undi_iface ) ) ) != 0 )
+ goto err_undi_get_iface_info;
+ DBGC ( undinic, "UNDINIC %p has type %s, speed %d, flags %08x\n",
+ undinic, undi_iface.IfaceType, undi_iface.LinkSpeed,
+ undi_iface.ServiceFlags );
+ if ( strncmp ( ( ( char * ) undi_iface.IfaceType ), "Etherboot",
+ sizeof ( undi_iface.IfaceType ) ) == 0 ) {
+ DBGC ( undinic, "UNDINIC %p Etherboot 5.4 workaround enabled\n",
+ undinic );
+ undinic->hacks |= UNDI_HACK_EB54;
+ }
+
+ /* Mark as link up; we don't handle link state */
+ netdev_link_up ( netdev );
+
+ /* Register network device */
+ if ( ( rc = register_netdev ( netdev ) ) != 0 )
+ goto err_register;
+
+ DBGC ( undinic, "UNDINIC %p added\n", undinic );
+ return 0;
+
+ err_register:
+ err_undi_get_iface_info:
+ err_bad_irq:
+ err_undi_get_information:
+ err_undi_initialize:
+ /* Shut down UNDI stack */
+ memset ( &undi_shutdown, 0, sizeof ( undi_shutdown ) );
+ pxeparent_call ( undinet_entry, PXENV_UNDI_SHUTDOWN, &undi_shutdown,
+ sizeof ( undi_shutdown ) );
+ memset ( &undi_cleanup, 0, sizeof ( undi_cleanup ) );
+ pxeparent_call ( undinet_entry, PXENV_UNDI_CLEANUP, &undi_cleanup,
+ sizeof ( undi_cleanup ) );
+ undi->flags &= ~UNDI_FL_INITIALIZED;
+ err_undi_startup:
+ /* Unhook UNDI stack */
+ memset ( &stop_undi, 0, sizeof ( stop_undi ) );
+ pxeparent_call ( undinet_entry, PXENV_STOP_UNDI, &stop_undi,
+ sizeof ( stop_undi ) );
+ undi->flags &= ~UNDI_FL_STARTED;
+ err_start_undi:
+ netdev_nullify ( netdev );
+ netdev_put ( netdev );
+ undi_set_drvdata ( undi, NULL );
+ return rc;
+}
+
+/**
+ * Remove UNDI device
+ *
+ * @v undi UNDI device
+ */
+void undinet_remove ( struct undi_device *undi ) {
+ struct net_device *netdev = undi_get_drvdata ( undi );
+ struct undi_nic *undinic = netdev->priv;
+ struct s_PXENV_UNDI_SHUTDOWN undi_shutdown;
+ struct s_PXENV_UNDI_CLEANUP undi_cleanup;
+ struct s_PXENV_STOP_UNDI stop_undi;
+
+ /* Unregister net device */
+ unregister_netdev ( netdev );
+
+ /* If we are preparing for an OS boot, or if we cannot exit
+ * via the PXE stack, then shut down the PXE stack.
+ */
+ if ( ! ( undi->flags & UNDI_FL_KEEP_ALL ) ) {
+
+ /* Shut down UNDI stack */
+ memset ( &undi_shutdown, 0, sizeof ( undi_shutdown ) );
+ pxeparent_call ( undinet_entry, PXENV_UNDI_SHUTDOWN,
+ &undi_shutdown, sizeof ( undi_shutdown ) );
+ memset ( &undi_cleanup, 0, sizeof ( undi_cleanup ) );
+ pxeparent_call ( undinet_entry, PXENV_UNDI_CLEANUP,
+ &undi_cleanup, sizeof ( undi_cleanup ) );
+ undi->flags &= ~UNDI_FL_INITIALIZED;
+
+ /* Unhook UNDI stack */
+ memset ( &stop_undi, 0, sizeof ( stop_undi ) );
+ pxeparent_call ( undinet_entry, PXENV_STOP_UNDI, &stop_undi,
+ sizeof ( stop_undi ) );
+ undi->flags &= ~UNDI_FL_STARTED;
+ }
+
+ /* Clear entry point */
+ memset ( &undinet_entry, 0, sizeof ( undinet_entry ) );
+
+ /* Free network device */
+ netdev_nullify ( netdev );
+ netdev_put ( netdev );
+
+ DBGC ( undinic, "UNDINIC %p removed\n", undinic );
+}
diff --git a/contrib/syslinux-4.02/gpxe/src/arch/i386/drivers/net/undionly.c b/contrib/syslinux-4.02/gpxe/src/arch/i386/drivers/net/undionly.c
new file mode 100644
index 0000000..7dfb5d1
--- /dev/null
+++ b/contrib/syslinux-4.02/gpxe/src/arch/i386/drivers/net/undionly.c
@@ -0,0 +1,129 @@
+/*
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <gpxe/device.h>
+#include <gpxe/init.h>
+#include <undi.h>
+#include <undinet.h>
+#include <undipreload.h>
+
+/** @file
+ *
+ * "Pure" UNDI driver
+ *
+ * This is the UNDI driver without explicit support for PCI or any
+ * other bus type. It is capable only of using the preloaded UNDI
+ * device. It must not be combined in an image with any other
+ * drivers.
+ *
+ * If you want a PXE-loadable image that contains only the UNDI
+ * driver, build "bin/undionly.kpxe".
+ *
+ * If you want any other image format, or any other drivers in
+ * addition to the UNDI driver, build e.g. "bin/undi.dsk".
+ */
+
+/**
+ * Probe UNDI root bus
+ *
+ * @v rootdev UNDI bus root device
+ *
+ * Scans the UNDI bus for devices and registers all devices it can
+ * find.
+ */
+static int undibus_probe ( struct root_device *rootdev ) {
+ struct undi_device *undi = &preloaded_undi;
+ int rc;
+
+ /* Check for a valie preloaded UNDI device */
+ if ( ! undi->entry.segment ) {
+ DBG ( "No preloaded UNDI device found!\n" );
+ return -ENODEV;
+ }
+
+ /* Add to device hierarchy */
+ strncpy ( undi->dev.name, "UNDI",
+ ( sizeof ( undi->dev.name ) - 1 ) );
+ if ( undi->pci_busdevfn != UNDI_NO_PCI_BUSDEVFN ) {
+ undi->dev.desc.bus_type = BUS_TYPE_PCI;
+ undi->dev.desc.location = undi->pci_busdevfn;
+ undi->dev.desc.vendor = undi->pci_vendor;
+ undi->dev.desc.device = undi->pci_device;
+ } else if ( undi->isapnp_csn != UNDI_NO_ISAPNP_CSN ) {
+ undi->dev.desc.bus_type = BUS_TYPE_ISAPNP;
+ }
+ undi->dev.parent = &rootdev->dev;
+ list_add ( &undi->dev.siblings, &rootdev->dev.children);
+ INIT_LIST_HEAD ( &undi->dev.children );
+
+ /* Create network device */
+ if ( ( rc = undinet_probe ( undi ) ) != 0 )
+ goto err;
+
+ return 0;
+
+ err:
+ list_del ( &undi->dev.siblings );
+ return rc;
+}
+
+/**
+ * Remove UNDI root bus
+ *
+ * @v rootdev UNDI bus root device
+ */
+static void undibus_remove ( struct root_device *rootdev __unused ) {
+ struct undi_device *undi = &preloaded_undi;
+
+ undinet_remove ( undi );
+ list_del ( &undi->dev.siblings );
+}
+
+/** UNDI bus root device driver */
+static struct root_driver undi_root_driver = {
+ .probe = undibus_probe,
+ .remove = undibus_remove,
+};
+
+/** UNDI bus root device */
+struct root_device undi_root_device __root_device = {
+ .dev = { .name = "UNDI" },
+ .driver = &undi_root_driver,
+};
+
+/**
+ * Prepare for exit
+ *
+ * @v flags Shutdown flags
+ */
+static void undionly_shutdown ( int flags ) {
+ /* If we are shutting down to boot an OS, clear the "keep PXE
+ * stack" flag.
+ */
+ if ( flags & SHUTDOWN_BOOT )
+ preloaded_undi.flags &= ~UNDI_FL_KEEP_ALL;
+}
+
+struct startup_fn startup_undionly __startup_fn ( STARTUP_LATE ) = {
+ .shutdown = undionly_shutdown,
+};
diff --git a/contrib/syslinux-4.02/gpxe/src/arch/i386/drivers/net/undipreload.c b/contrib/syslinux-4.02/gpxe/src/arch/i386/drivers/net/undipreload.c
new file mode 100644
index 0000000..a4b2f4a
--- /dev/null
+++ b/contrib/syslinux-4.02/gpxe/src/arch/i386/drivers/net/undipreload.c
@@ -0,0 +1,37 @@
+/*
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <realmode.h>
+#include <undipreload.h>
+
+/** @file
+ *
+ * Preloaded UNDI stack
+ *
+ */
+
+/**
+ * Preloaded UNDI device
+ *
+ * This is the UNDI device that was present when Etherboot started
+ * execution (i.e. when loading a .kpxe image). The first driver to
+ * claim this device must zero out this data structure.
+ */
+struct undi_device __data16 ( preloaded_undi );
diff --git a/contrib/syslinux-4.02/gpxe/src/arch/i386/drivers/net/undirom.c b/contrib/syslinux-4.02/gpxe/src/arch/i386/drivers/net/undirom.c
new file mode 100644
index 0000000..2463d96
--- /dev/null
+++ b/contrib/syslinux-4.02/gpxe/src/arch/i386/drivers/net/undirom.c
@@ -0,0 +1,234 @@
+/*
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <pxe.h>
+#include <realmode.h>
+#include <undirom.h>
+
+/** @file
+ *
+ * UNDI expansion ROMs
+ *
+ */
+
+/** List of all UNDI ROMs */
+static LIST_HEAD ( undiroms );
+
+/**
+ * Parse PXE ROM ID structure
+ *
+ * @v undirom UNDI ROM
+ * @v pxeromid Offset within ROM to PXE ROM ID structure
+ * @ret rc Return status code
+ */
+static int undirom_parse_pxeromid ( struct undi_rom *undirom,
+ unsigned int pxeromid ) {
+ struct undi_rom_id undi_rom_id;
+ unsigned int undiloader;
+
+ DBGC ( undirom, "UNDIROM %p has PXE ROM ID at %04x:%04x\n", undirom,
+ undirom->rom_segment, pxeromid );
+
+ /* Read PXE ROM ID structure and verify */
+ copy_from_real ( &undi_rom_id, undirom->rom_segment, pxeromid,
+ sizeof ( undi_rom_id ) );
+ if ( undi_rom_id.Signature != UNDI_ROM_ID_SIGNATURE ) {
+ DBGC ( undirom, "UNDIROM %p has bad PXE ROM ID signature "
+ "%08x\n", undirom, undi_rom_id.Signature );
+ return -EINVAL;
+ }
+
+ /* Check for UNDI loader */
+ undiloader = undi_rom_id.UNDILoader;
+ if ( ! undiloader ) {
+ DBGC ( undirom, "UNDIROM %p has no UNDI loader\n", undirom );
+ return -EINVAL;
+ }
+
+ /* Fill in UNDI ROM loader fields */
+ undirom->loader_entry.segment = undirom->rom_segment;
+ undirom->loader_entry.offset = undiloader;
+ undirom->code_size = undi_rom_id.CodeSize;
+ undirom->data_size = undi_rom_id.DataSize;
+
+ DBGC ( undirom, "UNDIROM %p has UNDI loader at %04x:%04x "
+ "(code %04zx data %04zx)\n", undirom,
+ undirom->loader_entry.segment, undirom->loader_entry.offset,
+ undirom->code_size, undirom->data_size );
+ return 0;
+}
+
+/**
+ * Parse PCI expansion header
+ *
+ * @v undirom UNDI ROM
+ * @v pcirheader Offset within ROM to PCI expansion header
+ */
+static int undirom_parse_pcirheader ( struct undi_rom *undirom,
+ unsigned int pcirheader ) {
+ struct pcir_header pcir_header;
+
+ DBGC ( undirom, "UNDIROM %p has PCI expansion header at %04x:%04x\n",
+ undirom, undirom->rom_segment, pcirheader );
+
+ /* Read PCI expansion header and verify */
+ copy_from_real ( &pcir_header, undirom->rom_segment, pcirheader,
+ sizeof ( pcir_header ) );
+ if ( pcir_header.signature != PCIR_SIGNATURE ) {
+ DBGC ( undirom, "UNDIROM %p has bad PCI expansion header "
+ "signature %08x\n", undirom, pcir_header.signature );
+ return -EINVAL;
+ }
+
+ /* Fill in UNDI ROM PCI device fields */
+ undirom->bus_type = PCI_NIC;
+ undirom->bus_id.pci.vendor_id = pcir_header.vendor_id;
+ undirom->bus_id.pci.device_id = pcir_header.device_id;
+
+ DBGC ( undirom, "UNDIROM %p is for PCI devices %04x:%04x\n", undirom,
+ undirom->bus_id.pci.vendor_id, undirom->bus_id.pci.device_id );
+ return 0;
+
+}
+
+/**
+ * Probe UNDI ROM
+ *
+ * @v rom_segment ROM segment address
+ * @ret rc Return status code
+ */
+static int undirom_probe ( unsigned int rom_segment ) {
+ struct undi_rom *undirom = NULL;
+ struct undi_rom_header romheader;
+ size_t rom_len;
+ unsigned int pxeromid;
+ unsigned int pcirheader;
+ int rc;
+
+ /* Read expansion ROM header and verify */
+ copy_from_real ( &romheader, rom_segment, 0, sizeof ( romheader ) );
+ if ( romheader.Signature != ROM_SIGNATURE ) {
+ rc = -EINVAL;
+ goto err;
+ }
+ rom_len = ( romheader.ROMLength * 512 );
+
+ /* Allocate memory for UNDI ROM */
+ undirom = zalloc ( sizeof ( *undirom ) );
+ if ( ! undirom ) {
+ DBG ( "Could not allocate UNDI ROM structure\n" );
+ rc = -ENOMEM;
+ goto err;
+ }
+ DBGC ( undirom, "UNDIROM %p trying expansion ROM at %04x:0000 "
+ "(%zdkB)\n", undirom, rom_segment, ( rom_len / 1024 ) );
+ undirom->rom_segment = rom_segment;
+
+ /* Check for and parse PXE ROM ID */
+ pxeromid = romheader.PXEROMID;
+ if ( ! pxeromid ) {
+ DBGC ( undirom, "UNDIROM %p has no PXE ROM ID\n", undirom );
+ rc = -EINVAL;
+ goto err;
+ }
+ if ( pxeromid > rom_len ) {
+ DBGC ( undirom, "UNDIROM %p PXE ROM ID outside ROM\n",
+ undirom );
+ rc = -EINVAL;
+ goto err;
+ }
+ if ( ( rc = undirom_parse_pxeromid ( undirom, pxeromid ) ) != 0 )
+ goto err;
+
+ /* Parse PCIR header, if present */
+ pcirheader = romheader.PCIRHeader;
+ if ( pcirheader )
+ undirom_parse_pcirheader ( undirom, pcirheader );
+
+ /* Add to UNDI ROM list and return */
+ DBGC ( undirom, "UNDIROM %p registered\n", undirom );
+ list_add ( &undirom->list, &undiroms );
+ return 0;
+
+ err:
+ free ( undirom );
+ return rc;
+}
+
+/**
+ * Create UNDI ROMs for all possible expansion ROMs
+ *
+ * @ret
+ */
+static void undirom_probe_all_roms ( void ) {
+ static int probed = 0;
+ unsigned int rom_segment;
+
+ /* Perform probe only once */
+ if ( probed )
+ return;
+
+ DBG ( "Scanning for PXE expansion ROMs\n" );
+
+ /* Scan through expansion ROM region at 512 byte intervals */
+ for ( rom_segment = 0xc000 ; rom_segment < 0x10000 ;
+ rom_segment += 0x20 ) {
+ undirom_probe ( rom_segment );
+ }
+
+ probed = 1;
+}
+
+/**
+ * Find UNDI ROM for PCI device
+ *
+ * @v vendor_id PCI vendor ID
+ * @v device_id PCI device ID
+ * @v rombase ROM base address, or 0 for any
+ * @ret undirom UNDI ROM, or NULL
+ */
+struct undi_rom * undirom_find_pci ( unsigned int vendor_id,
+ unsigned int device_id,
+ unsigned int rombase ) {
+ struct undi_rom *undirom;
+
+ undirom_probe_all_roms();
+
+ list_for_each_entry ( undirom, &undiroms, list ) {
+ if ( undirom->bus_type != PCI_NIC )
+ continue;
+ if ( undirom->bus_id.pci.vendor_id != vendor_id )
+ continue;
+ if ( undirom->bus_id.pci.device_id != device_id )
+ continue;
+ if ( rombase && ( ( undirom->rom_segment << 4 ) != rombase ) )
+ continue;
+ DBGC ( undirom, "UNDIROM %p matched PCI %04x:%04x (%08x)\n",
+ undirom, vendor_id, device_id, rombase );
+ return undirom;
+ }
+
+ DBG ( "No UNDI ROM matched PCI %04x:%04x (%08x)\n",
+ vendor_id, device_id, rombase );
+ return NULL;
+}