iPXE
Data Structures | Defines | Functions | Variables
undinet.c File Reference

UNDI network device driver. More...

#include <string.h>
#include <unistd.h>
#include <byteswap.h>
#include <pxe.h>
#include <realmode.h>
#include <pic8259.h>
#include <biosint.h>
#include <pnpbios.h>
#include <basemem_packet.h>
#include <ipxe/io.h>
#include <ipxe/iobuf.h>
#include <ipxe/netdevice.h>
#include <ipxe/if_ether.h>
#include <ipxe/ethernet.h>
#include <ipxe/pci.h>
#include <ipxe/profile.h>
#include <undi.h>
#include <undinet.h>

Go to the source code of this file.

Data Structures

struct  undi_nic
 An UNDI NIC. More...
struct  undinet_profiler
 A PXE API call breakdown profiler. More...
struct  undinet_irq_broken
 A device with broken support for generating interrupts. More...

Defines

#define EINFO_EPXECALL
#define EPXECALL(status)   EPLATFORM ( EINFO_EPXECALL, status )
#define UNDI_HACK_EB54   0x0001
 Work around Etherboot 5.4 bugs.
#define UNDI_INITIALIZE_RETRY_MAX   10
 Maximum number of times to retry PXENV_UNDI_INITIALIZE.
#define UNDI_INITIALIZE_RETRY_DELAY_MS   200
 Delay between retries of PXENV_UNDI_INITIALIZE.
#define UNDI_RX_QUOTA   4
 Maximum number of received packets per poll.
#define UNDI_RX_ALIGN   16
 Alignment of received frame payload.
#define undinet_params   __use_data16 ( undinet_params )
#define undinet_entry_point   __use_data16 ( undinet_entry_point )
#define undiisr_irq   __use_data16 ( undiisr_irq )
#define undiisr_next_handler   __use_data16 ( undiisr_next_handler )
#define undiisr_trigger_count   __use_data16 ( undiisr_trigger_count )
#define undinet_tbd   __use_data16 ( undinet_tbd )
#define undinet_destaddr   __use_data16 ( undinet_destaddr )

Functions

 FILE_LICENCE (GPL2_OR_LATER)
static void undinet_close (struct net_device *netdev)
 Close NIC.
static union u_PXENV_ANY __bss16 (undinet_params)
 UNDI parameter block.
SEGOFF16_t __bss16 (undinet_entry_point)
 UNDI entry point.
static const char * undinet_function_name (unsigned int function)
 Name PXE API call.
static struct undinet_profilerundinet_profiler (unsigned int function)
 Determine applicable profiler pair (for debugging)
static int undinet_call (struct undi_nic *undinic, unsigned int function, void *params, size_t params_len)
 Issue UNDI API call.
void undiisr (void)
 UNDI interrupt service routine.
uint8_t __data16 (undiisr_irq)
 IRQ number.
struct segoff __data16 (undiisr_next_handler)
 IRQ chain vector.
volatile uint8_t __data16 (undiisr_trigger_count)=0
 IRQ trigger count.
static void undinet_hook_isr (unsigned int irq)
 Hook UNDI interrupt service routine.
static void undinet_unhook_isr (unsigned int irq)
 Unhook UNDI interrupt service routine.
static int undinet_isr_triggered (void)
 Test to see if UNDI ISR has been triggered.
static struct s_PXENV_UNDI_TBD __data16 (undinet_tbd)
 UNDI transmit buffer descriptor.
static uint8_t __data16_array (undinet_destaddr,[ETH_ALEN])
 UNDI transmit destination address.
static int undinet_transmit (struct net_device *netdev, struct io_buffer *iobuf)
 Transmit packet.
static void undinet_poll (struct net_device *netdev)
 Poll for received packets.
static int undinet_open (struct net_device *netdev)
 Open NIC.
static void undinet_irq (struct net_device *netdev, int enable)
 Enable/disable interrupts.
static int undinet_irq_is_broken (struct device_description *desc)
 Check for devices with broken support for generating interrupts.
int undinet_probe (struct undi_device *undi, struct device *dev)
 Probe UNDI device.
void undinet_remove (struct undi_device *undi)
 Remove UNDI device.

Variables

static struct profiler
undinet_irq_profiler 
__profiler
 IRQ profiler.
static unsigned int last_trigger_count = 0
 Last observed trigger count.
static struct net_device_operations undinet_operations
 UNDI network device operations.
static struct undinet_irq_broken undinet_irq_broken_list []
 List of devices with broken support for generating interrupts.

Detailed Description

UNDI network device driver.

Definition in file undinet.c.


Define Documentation

#define EINFO_EPXECALL
Value:
__einfo_uniqify ( EINFO_EPLATFORM, 0x01,                        \
                          "External PXE API error" )

Definition at line 60 of file undinet.c.

#define EPXECALL (   status)    EPLATFORM ( EINFO_EPXECALL, status )

Definition at line 63 of file undinet.c.

Referenced by undinet_call().

#define UNDI_INITIALIZE_RETRY_MAX   10

Maximum number of times to retry PXENV_UNDI_INITIALIZE.

Definition at line 76 of file undinet.c.

Referenced by undinet_probe().

Delay between retries of PXENV_UNDI_INITIALIZE.

Definition at line 79 of file undinet.c.

Referenced by undinet_probe().

#define UNDI_RX_QUOTA   4

Maximum number of received packets per poll.

Definition at line 82 of file undinet.c.

Referenced by undinet_poll().

#define UNDI_RX_ALIGN   16

Alignment of received frame payload.

Definition at line 85 of file undinet.c.

Referenced by undinet_poll().

Definition at line 96 of file undinet.c.

Referenced by undinet_call().

Definition at line 105 of file undinet.c.

Referenced by undinet_call(), undinet_probe(), and undinet_remove().

Definition at line 367 of file undinet.c.

Referenced by undinet_hook_isr(), and undinet_unhook_isr().

Definition at line 371 of file undinet.c.

Referenced by undinet_hook_isr(), and undinet_unhook_isr().

Definition at line 375 of file undinet.c.

Referenced by undinet_isr_triggered().

Definition at line 439 of file undinet.c.

Referenced by undinet_transmit().

Definition at line 443 of file undinet.c.

Referenced by undinet_transmit().


Function Documentation

FILE_LICENCE ( GPL2_OR_LATER  )
static void undinet_close ( struct net_device netdev) [static]

Close NIC.

Parameters:
netdevNet device

Definition at line 745 of file undinet.c.

References DBGC, s_PXENV_UNDI_ISR::FuncFlag, undi_nic::irq, undi_nic::isr_processing, net_device::priv, PXENV_UNDI_CLOSE, PXENV_UNDI_ISR, PXENV_UNDI_ISR_IN_GET_NEXT, PXENV_UNDI_ISR_OUT_RECEIVE, PXENV_UNDI_ISR_OUT_TRANSMIT, rc, undinet_call(), and undinet_unhook_isr().

Referenced by undinet_open().

                                                        {
        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 = undinet_call ( undinic, 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 */
        undinet_call ( undinic, PXENV_UNDI_CLOSE, &undi_close,
                       sizeof ( undi_close ) );

        /* Disable interrupt and unhook ISR if applicable */
        if ( undinic->irq ) {
                disable_irq ( undinic->irq );
                undinet_unhook_isr ( undinic->irq );
        }

        DBGC ( undinic, "UNDINIC %p closed\n", undinic );
}
static union u_PXENV_ANY __bss16 ( undinet_params  ) [static, write]

UNDI parameter block.

Used as the parameter block for all UNDI API calls. Resides in base memory.

SEGOFF16_t __bss16 ( undinet_entry_point  )

UNDI entry point.

Used as the indirection vector for all UNDI API calls. Resides in base memory.

static const char* undinet_function_name ( unsigned int  function) [inline, static]

Name PXE API call.

Parameters:
functionAPI call number
Return values:
nameAPI call name

Definition at line 182 of file undinet.c.

References PXENV_GET_CACHED_INFO, PXENV_START_UNDI, PXENV_STOP_UNDI, PXENV_UNDI_CLEANUP, PXENV_UNDI_CLEAR_STATISTICS, PXENV_UNDI_CLOSE, PXENV_UNDI_FORCE_INTERRUPT, PXENV_UNDI_GET_IFACE_INFO, PXENV_UNDI_GET_INFORMATION, PXENV_UNDI_GET_MCAST_ADDRESS, PXENV_UNDI_GET_NIC_TYPE, PXENV_UNDI_GET_STATISTICS, PXENV_UNDI_INITIALIZE, PXENV_UNDI_INITIATE_DIAGS, PXENV_UNDI_ISR, PXENV_UNDI_OPEN, PXENV_UNDI_RESET_ADAPTER, PXENV_UNDI_SET_MCAST_ADDRESS, PXENV_UNDI_SET_PACKET_FILTER, PXENV_UNDI_SET_STATION_ADDRESS, PXENV_UNDI_SHUTDOWN, PXENV_UNDI_STARTUP, and PXENV_UNDI_TRANSMIT.

Referenced by undinet_call().

                                                {
        switch ( function ) {
        case PXENV_START_UNDI:
                return "PXENV_START_UNDI";
        case PXENV_STOP_UNDI:
                return "PXENV_STOP_UNDI";
        case PXENV_UNDI_STARTUP:
                return "PXENV_UNDI_STARTUP";
        case PXENV_UNDI_CLEANUP:
                return "PXENV_UNDI_CLEANUP";
        case PXENV_UNDI_INITIALIZE:
                return "PXENV_UNDI_INITIALIZE";
        case PXENV_UNDI_RESET_ADAPTER:
                return "PXENV_UNDI_RESET_ADAPTER";
        case PXENV_UNDI_SHUTDOWN:
                return "PXENV_UNDI_SHUTDOWN";
        case PXENV_UNDI_OPEN:
                return "PXENV_UNDI_OPEN";
        case PXENV_UNDI_CLOSE:
                return "PXENV_UNDI_CLOSE";
        case PXENV_UNDI_TRANSMIT:
                return "PXENV_UNDI_TRANSMIT";
        case PXENV_UNDI_SET_MCAST_ADDRESS:
                return "PXENV_UNDI_SET_MCAST_ADDRESS";
        case PXENV_UNDI_SET_STATION_ADDRESS:
                return "PXENV_UNDI_SET_STATION_ADDRESS";
        case PXENV_UNDI_SET_PACKET_FILTER:
                return "PXENV_UNDI_SET_PACKET_FILTER";
        case PXENV_UNDI_GET_INFORMATION:
                return "PXENV_UNDI_GET_INFORMATION";
        case PXENV_UNDI_GET_STATISTICS:
                return "PXENV_UNDI_GET_STATISTICS";
        case PXENV_UNDI_CLEAR_STATISTICS:
                return "PXENV_UNDI_CLEAR_STATISTICS";
        case PXENV_UNDI_INITIATE_DIAGS:
                return "PXENV_UNDI_INITIATE_DIAGS";
        case PXENV_UNDI_FORCE_INTERRUPT:
                return "PXENV_UNDI_FORCE_INTERRUPT";
        case PXENV_UNDI_GET_MCAST_ADDRESS:
                return "PXENV_UNDI_GET_MCAST_ADDRESS";
        case PXENV_UNDI_GET_NIC_TYPE:
                return "PXENV_UNDI_GET_NIC_TYPE";
        case PXENV_UNDI_GET_IFACE_INFO:
                return "PXENV_UNDI_GET_IFACE_INFO";
        /*
         * Duplicate case value; this is a bug in the PXE specification.
         *
         *      case PXENV_UNDI_GET_STATE:
         *              return "PXENV_UNDI_GET_STATE";
         */
        case PXENV_UNDI_ISR:
                return "PXENV_UNDI_ISR";
        case PXENV_GET_CACHED_INFO:
                return "PXENV_GET_CACHED_INFO";
        default:
                return "UNKNOWN API CALL";
        }
}
static struct undinet_profiler* undinet_profiler ( unsigned int  function) [static, read]

Determine applicable profiler pair (for debugging)

Parameters:
functionAPI call number
Return values:
profilerProfiler

Definition at line 247 of file undinet.c.

References PXENV_UNDI_ISR, PXENV_UNDI_TRANSMIT, and PXENV_UNKNOWN.

Referenced by undinet_call().

                                                                            {

        /* Determine applicable profiler */
        switch ( function ) {
        case PXENV_UNDI_TRANSMIT:
                return &undinet_tx_profiler;
        case PXENV_UNDI_ISR:
                return &undinet_isr_profiler;
        case PXENV_UNKNOWN:
                return &undinet_unknown_profiler;
        default:
                return &undinet_misc_profiler;
        }
}
static int undinet_call ( struct undi_nic undinic,
unsigned int  function,
void *  params,
size_t  params_len 
) [static]

Issue UNDI API call.

Parameters:
undinicUNDI NIC
functionAPI call number
paramsPXE parameter block
params_lenLength of PXE parameter block
Return values:
rcReturn status code

Definition at line 271 of file undinet.c.

References __asm__(), __from_data16, assert, DBGC, DBGC_HDA, EPXECALL, undinet_profiler::ext, memcpy(), undinet_profiler::p2r, profile_start(), profile_start_at(), profile_started(), profile_stop(), profile_stop_at(), profile_stopped(), PXENV_EXIT_SUCCESS, undinet_profiler::r2p, rc, REAL_CODE, rm_ds, started, strerror(), undinet_profiler::total, undinet_entry_point, undinet_function_name(), undinet_params, and undinet_profiler().

Referenced by undinet_close(), undinet_open(), undinet_poll(), undinet_probe(), undinet_remove(), and undinet_transmit().

                                                            {
        struct undinet_profiler *profiler = undinet_profiler ( function );
        PXENV_EXIT_t exit;
        uint32_t before;
        uint32_t started;
        uint32_t stopped;
        uint32_t after;
        int discard_D;
        int rc;

        /* Copy parameter block and entry point */
        assert ( params_len <= sizeof ( undinet_params ) );
        memcpy ( &undinet_params, params, params_len );

        /* Call real-mode entry point.  This calling convention will
         * work with both the !PXE and the PXENV+ entry points.
         */
        profile_start ( &profiler->total );
        __asm__ __volatile__ ( REAL_CODE ( "pushl %%ebp\n\t" /* gcc bug */
                                           "rdtsc\n\t"
                                           "pushl %%eax\n\t"
                                           "pushw %%es\n\t"
                                           "pushw %%di\n\t"
                                           "pushw %%bx\n\t"
                                           "lcall *undinet_entry_point\n\t"
                                           "movw %%ax, %%bx\n\t"
                                           "rdtsc\n\t"
                                           "addw $6, %%sp\n\t"
                                           "popl %%edx\n\t"
                                           "popl %%ebp\n\t" /* gcc bug */ )
                               : "=a" ( stopped ), "=d" ( started ),
                                 "=b" ( exit ), "=D" ( discard_D )
                               : "b" ( function ),
                                 "D" ( __from_data16 ( &undinet_params ) )
                               : "ecx", "esi" );
        profile_stop ( &profiler->total );
        before = profile_started ( &profiler->total );
        after = profile_stopped ( &profiler->total );
        profile_start_at ( &profiler->p2r, before );
        profile_stop_at ( &profiler->p2r, started );
        profile_start_at ( &profiler->ext, started );
        profile_stop_at ( &profiler->ext, stopped );
        profile_start_at ( &profiler->r2p, stopped );
        profile_stop_at ( &profiler->r2p, after );

        /* Determine return status code based on PXENV_EXIT and
         * PXENV_STATUS
         */
        rc = ( ( exit == PXENV_EXIT_SUCCESS ) ?
               0 : -EPXECALL ( undinet_params.Status ) );

        /* If anything goes wrong, print as much debug information as
         * it's possible to give.
         */
        if ( rc != 0 ) {
                SEGOFF16_t rm_params = {
                        .segment = rm_ds,
                        .offset = __from_data16 ( &undinet_params ),
                };

                DBGC ( undinic, "UNDINIC %p %s failed: %s\n", undinic,
                       undinet_function_name ( function ), strerror ( rc ) );
                DBGC ( undinic, "UNDINIC %p parameters at %04x:%04x length "
                       "%#02zx, entry point at %04x:%04x\n", undinic,
                       rm_params.segment, rm_params.offset, params_len,
                       undinet_entry_point.segment,
                       undinet_entry_point.offset );
                DBGC ( undinic, "UNDINIC %p parameters provided:\n", undinic );
                DBGC_HDA ( undinic, rm_params, params, params_len );
                DBGC ( undinic, "UNDINIC %p parameters returned:\n", undinic );
                DBGC_HDA ( undinic, rm_params, &undinet_params, params_len );
        }

        /* Copy parameter block back */
        memcpy ( params, &undinet_params, params_len );

        return rc;
}
void undiisr ( void  )

UNDI interrupt service routine.

The UNDI ISR increments a counter (trigger_count) and exits.

Referenced by undinet_hook_isr(), and undinet_unhook_isr().

IRQ number.

struct segoff __data16 ( undiisr_next_handler  ) [read]

IRQ chain vector.

volatile uint8_t __data16 ( undiisr_trigger_count  ) [pure virtual]

IRQ trigger count.

static void undinet_hook_isr ( unsigned int  irq) [static]

Hook UNDI interrupt service routine.

Parameters:
irqIRQ number

Definition at line 385 of file undinet.c.

References assert, hook_bios_interrupt(), undiisr(), undiisr_irq, and undiisr_next_handler.

Referenced by undinet_open().

                                                  {

        assert ( irq <= IRQ_MAX );
        assert ( undiisr_irq == 0 );

        undiisr_irq = irq;
        hook_bios_interrupt ( IRQ_INT ( irq ), ( ( intptr_t ) undiisr ),
                              &undiisr_next_handler );
}
static void undinet_unhook_isr ( unsigned int  irq) [static]

Unhook UNDI interrupt service routine.

Parameters:
irqIRQ number

Definition at line 400 of file undinet.c.

References assert, undiisr(), undiisr_irq, undiisr_next_handler, and unhook_bios_interrupt().

Referenced by undinet_close().

                                                    {

        assert ( irq <= IRQ_MAX );

        unhook_bios_interrupt ( IRQ_INT ( irq ), ( ( intptr_t ) undiisr ),
                                &undiisr_next_handler );
        undiisr_irq = 0;
}
static int undinet_isr_triggered ( void  ) [static]

Test to see if UNDI ISR has been triggered.

Return values:
triggeredISR has been triggered since last check

Definition at line 414 of file undinet.c.

References undiisr_trigger_count.

Referenced by undinet_poll().

                                          {
        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;
        }
}
static struct s_PXENV_UNDI_TBD __data16 ( undinet_tbd  ) [static, read]

UNDI transmit buffer descriptor.

static uint8_t __data16_array ( undinet_destaddr  ) [static]

UNDI transmit destination address.

static int undinet_transmit ( struct net_device netdev,
struct io_buffer iobuf 
) [static]

Transmit packet.

Parameters:
netdevNetwork device
iobufI/O buffer
Return values:
rcReturn status code

Definition at line 452 of file undinet.c.

References __from_data16, basemem_packet, io_buffer::data, DBGC, s_PXENV_UNDI_TRANSMIT::DestAddr, done, ETH_P_ARP, ETH_P_IP, ETH_P_RARP, eth_pull(), flags, htons, iob_len(), iob_push, len, LL_BROADCAST, memcpy(), memset(), net_proto, netdev_tx_complete(), P_ARP, P_IP, P_RARP, P_UNKNOWN, net_device::priv, protocol, s_PXENV_UNDI_TRANSMIT::Protocol, PXENV_UNDI_TRANSMIT, rc, rm_ds, strerror(), s_PXENV_UNDI_TRANSMIT::TBD, undinet_call(), undinet_destaddr, undinet_tbd, s_PXENV_UNDI_TRANSMIT::XmitFlag, XMT_BROADCAST, and XMT_DESTADDR.

                                                        {
        struct undi_nic *undinic = netdev->priv;
        struct s_PXENV_UNDI_TRANSMIT undi_transmit;
        const void *ll_dest;
        const void *ll_source;
        uint16_t net_proto;
        unsigned int flags;
        uint8_t protocol;
        size_t len;
        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.
         */

        /* Some PXE stacks are unable to cope with P_UNKNOWN, and will
         * always try to prepend a link-layer header.  Work around
         * these stacks by stripping the existing link-layer header
         * and allowing the PXE stack to (re)construct the link-layer
         * header itself.
         */
        if ( ( rc = eth_pull ( netdev, iobuf, &ll_dest, &ll_source,
                               &net_proto, &flags ) ) != 0 ) {
                DBGC ( undinic, "UNDINIC %p could not strip Ethernet header: "
                       "%s\n", undinic, strerror ( rc ) );
                return rc;
        }
        memcpy ( undinet_destaddr, ll_dest, sizeof ( undinet_destaddr ) );
        switch ( net_proto ) {
        case htons ( ETH_P_IP ) :
                protocol = P_IP;
                break;
        case htons ( ETH_P_ARP ) :
                protocol = P_ARP;
                break;
        case htons ( ETH_P_RARP ) :
                protocol = P_RARP;
                break;
        default:
                /* Unknown protocol; restore the original link-layer header */
                iob_push ( iobuf, sizeof ( struct ethhdr ) );
                protocol = P_UNKNOWN;
                break;
        }

        /* Copy packet to UNDI I/O buffer */
        len = iob_len ( iobuf );
        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.Protocol = protocol;
        undi_transmit.XmitFlag = ( ( flags & LL_BROADCAST ) ?
                                   XMT_BROADCAST : XMT_DESTADDR );
        undi_transmit.DestAddr.segment = rm_ds;
        undi_transmit.DestAddr.offset = __from_data16 ( &undinet_destaddr );
        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 = undinet_call ( undinic, PXENV_UNDI_TRANSMIT, &undi_transmit,
                                   sizeof ( undi_transmit ) ) ) != 0 )
                goto done;

        /* Free I/O buffer */
        netdev_tx_complete ( netdev, iobuf );
 done:
        return rc;
}
static void undinet_poll ( struct net_device netdev) [static]

Poll for received packets.

Parameters:
netdevNetwork 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.

Definition at line 566 of file undinet.c.

References __asm__(), alloc_iob(), s_PXENV_UNDI_ISR::BufferLength, copy_from_real, DBGC, done, EINVAL, ENOMEM, s_PXENV_UNDI_ISR::Frame, s_PXENV_UNDI_ISR::FrameHeaderLength, s_PXENV_UNDI_ISR::FrameLength, s_PXENV_UNDI_ISR::FuncFlag, undi_nic::hacks, iob_disown, iob_len(), iob_put, iob_reserve, iob_tailroom(), undi_nic::irq_supported, undi_nic::isr_processing, last_trigger_count, len, netdev_rx(), netdev_rx_err(), NULL, net_device::priv, profile_start(), profile_stop(), PXENV_UNDI_ISR, PXENV_UNDI_ISR_IN_GET_NEXT, PXENV_UNDI_ISR_IN_PROCESS, PXENV_UNDI_ISR_OUT_DONE, PXENV_UNDI_ISR_OUT_RECEIVE, PXENV_UNDI_ISR_OUT_TRANSMIT, rc, UNDI_HACK_EB54, UNDI_RX_ALIGN, UNDI_RX_QUOTA, undinet_call(), and undinet_isr_triggered().

                                                       {
        struct undi_nic *undinic = netdev->priv;
        struct s_PXENV_UNDI_ISR undi_isr;
        struct io_buffer *iobuf = NULL;
        unsigned int quota = UNDI_RX_QUOTA;
        size_t len;
        size_t reserve_len;
        size_t frag_len;
        size_t max_frag_len;
        int rc;

        if ( ! undinic->isr_processing ) {
                /* Allow interrupt to occur.  Do this even if
                 * interrupts are not known to be supported, since
                 * some cards erroneously report that they do not
                 * support interrupts.
                 */
                if ( ! undinet_isr_triggered() ) {
                        /* Allow interrupt to occur */
                        profile_start ( &undinet_irq_profiler );
                        __asm__ __volatile__ ( "sti\n\t"
                                               "nop\n\t"
                                               "nop\n\t"
                                               "cli\n\t" );
                        profile_stop ( &undinet_irq_profiler );

                        /* If interrupts are known to be supported,
                         * then do nothing on this poll; wait for the
                         * interrupt to be triggered.
                         */
                        if ( undinic->irq_supported )
                                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 ( quota ) {
                if ( ( rc = undinet_call ( undinic, PXENV_UNDI_ISR, &undi_isr,
                                           sizeof ( undi_isr ) ) ) != 0 ) {
                        netdev_rx_err ( netdev, NULL, rc );
                        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 */
                        profile_start ( &undinet_rx_profiler );
                        len = undi_isr.FrameLength;
                        frag_len = undi_isr.BufferLength;
                        reserve_len = ( -undi_isr.FrameHeaderLength &
                                        ( UNDI_RX_ALIGN - 1 ) );
                        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 ( reserve_len + 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;
                                }
                                iob_reserve ( iobuf, reserve_len );
                        }
                        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 ) );
                                quota--;
                                /* Etherboot 5.4 fails to return all packets
                                 * under mild load; pretend it retriggered.
                                 */
                                if ( undinic->hacks & UNDI_HACK_EB54 )
                                        --last_trigger_count;
                        }
                        profile_stop ( &undinet_rx_profiler );
                        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 );
        }
}
static int undinet_open ( struct net_device netdev) [static]

Open NIC.

Parameters:
netdevNet device
Return values:
rcReturn status code

Definition at line 698 of file undinet.c.

References DBGC, FLTR_BRDCST, FLTR_DIRECTED, FLTR_PRMSCS, undi_nic::irq, net_device::ll_addr, memcpy(), memset(), s_PXENV_UNDI_OPEN::PktFilter, net_device::priv, PXENV_UNDI_OPEN, PXENV_UNDI_SET_STATION_ADDRESS, rc, send_eoi(), s_PXENV_UNDI_SET_STATION_ADDRESS::StationAddress, undinet_call(), undinet_close(), and undinet_hook_isr().

                                                      {
        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 if applicable */
        if ( undinic->irq ) {
                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 ) );
        undinet_call ( undinic, 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 = undinet_call ( undinic, 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;
}
static void undinet_irq ( struct net_device netdev,
int  enable 
) [static]

Enable/disable interrupts.

Parameters:
netdevNet device
enableInterrupts should be enabled

Definition at line 788 of file undinet.c.

References DBGC, and net_device::priv.

                                                                  {
        struct undi_nic *undinic = netdev->priv;

        /* Cannot support interrupts yet */
        DBGC ( undinic, "UNDINIC %p cannot %s interrupts\n",
               undinic, ( enable ? "enable" : "disable" ) );
}
static int undinet_irq_is_broken ( struct device_description desc) [static]

Check for devices with broken support for generating interrupts.

Parameters:
descDevice description
Return values:
irq_is_brokenInterrupt support is broken; no interrupts are generated

Definition at line 842 of file undinet.c.

References device_description::bus_type, BUS_TYPE_PCI, device_description::device, device_description::location, PCI_ANY_ID, undinet_irq_broken::pci_device, pci_init(), pci_read_config_word(), undinet_irq_broken::pci_subsys, undinet_irq_broken::pci_subsys_vendor, PCI_SUBSYSTEM_ID, PCI_SUBSYSTEM_VENDOR_ID, undinet_irq_broken::pci_vendor, undinet_irq_broken_list, and device_description::vendor.

Referenced by undinet_probe().

                                                                     {
        const struct undinet_irq_broken *broken;
        struct pci_device pci;
        uint16_t subsys_vendor;
        uint16_t subsys;
        unsigned int i;

        /* Ignore non-PCI devices */
        if ( desc->bus_type != BUS_TYPE_PCI )
                return 0;

        /* Read subsystem IDs */
        pci_init ( &pci, desc->location );
        pci_read_config_word ( &pci, PCI_SUBSYSTEM_VENDOR_ID, &subsys_vendor );
        pci_read_config_word ( &pci, PCI_SUBSYSTEM_ID, &subsys );

        /* Check for a match against the broken device list */
        for ( i = 0 ; i < ( sizeof ( undinet_irq_broken_list ) /
                            sizeof ( undinet_irq_broken_list[0] ) ) ; i++ ) {
                broken = &undinet_irq_broken_list[i];
                if ( ( broken->pci_vendor == desc->vendor ) &&
                     ( broken->pci_device == desc->device ) &&
                     ( ( broken->pci_subsys_vendor == subsys_vendor ) ||
                       ( broken->pci_subsys_vendor == PCI_ANY_ID ) ) &&
                     ( ( broken->pci_subsys == subsys ) ||
                       ( broken->pci_subsys == PCI_ANY_ID ) ) ) {
                        return 1;
                }
        }
        return 0;
}
int undinet_probe ( struct undi_device undi,
struct device dev 
)

Probe UNDI device.

Parameters:
undiUNDI device
devUnderlying generic device
Return values:
rcReturn status code

Definition at line 881 of file undinet.c.

References alloc_etherdev(), s_PXENV_START_UNDI::AX, BIOS_SEG, s_PXENV_START_UNDI::BX, s_PXENV_UNDI_GET_INFORMATION::CurrentNodeAddress, DBGC, device::desc, net_device::dev, s_PXENV_START_UNDI::DI, s_PXENV_START_UNDI::DX, ENOMEM, undi_device::entry, s_PXENV_START_UNDI::ES, ETH_ALEN, eth_ntoa(), find_pnp_bios(), undi_device::flags, undi_nic::hacks, net_device::hw_addr, s_PXENV_UNDI_GET_IFACE_INFO::IfaceType, s_PXENV_UNDI_GET_INFORMATION::IntNumber, undi_nic::irq, undi_nic::irq_supported, undi_device::isapnp_csn, undi_device::isapnp_read_port, s_PXENV_UNDI_GET_IFACE_INFO::LinkSpeed, net_device::ll_addr, mdelay(), memcpy(), memset(), netdev, netdev_init(), netdev_link_up(), netdev_nullify(), netdev_put(), NULL, undi_device::pci_busdevfn, s_PXENV_UNDI_GET_INFORMATION::PermNodeAddress, net_device::priv, PXENV_START_UNDI, PXENV_STOP_UNDI, PXENV_UNDI_CLEANUP, PXENV_UNDI_GET_IFACE_INFO, PXENV_UNDI_GET_INFORMATION, PXENV_UNDI_INITIALIZE, PXENV_UNDI_SHUTDOWN, PXENV_UNDI_STARTUP, rc, register_netdev(), s_PXENV_UNDI_GET_IFACE_INFO::ServiceFlags, strncmp(), SUPPORTED_IRQ, UNDI_FL_INITIALIZED, UNDI_FL_STARTED, UNDI_HACK_EB54, UNDI_INITIALIZE_RETRY_DELAY_MS, UNDI_INITIALIZE_RETRY_MAX, undi_set_drvdata(), undinet_call(), undinet_entry_point, and undinet_irq_is_broken().

Referenced by undibus_probe(), and undipci_probe().

                                                                   {
        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_init;
        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;
        unsigned int retry;
        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 = dev;
        memset ( undinic, 0, sizeof ( *undinic ) );
        undinet_entry_point = 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 = undinet_call ( undinic, 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 = undinet_call ( undinic, PXENV_UNDI_STARTUP,
                                           &undi_startup,
                                           sizeof ( undi_startup ) ) ) != 0 )
                        goto err_undi_startup;
                /* On some PXE stacks, PXENV_UNDI_INITIALIZE may fail
                 * due to a transient condition (e.g. media test
                 * failing because the link has only just come out of
                 * reset).  We may therefore need to retry this call
                 * several times.
                 */
                for ( retry = 0 ; ; ) {
                        memset ( &undi_init, 0, sizeof ( undi_init ) );
                        if ( ( rc = undinet_call ( undinic,
                                                   PXENV_UNDI_INITIALIZE,
                                                   &undi_init,
                                                   sizeof ( undi_init ) ) ) ==0)
                                break;
                        if ( ++retry > UNDI_INITIALIZE_RETRY_MAX )
                                goto err_undi_initialize;
                        DBGC ( undinic, "UNDINIC %p retrying "
                               "PXENV_UNDI_INITIALIZE (retry %d)\n",
                               undinic, retry );
                        /* Delay to allow link to settle if necessary */
                        mdelay ( UNDI_INITIALIZE_RETRY_DELAY_MS );
                }
        }
        undi->flags |= UNDI_FL_INITIALIZED;

        /* Get device information */
        memset ( &undi_info, 0, sizeof ( undi_info ) );
        if ( ( rc = undinet_call ( undinic, PXENV_UNDI_GET_INFORMATION,
                                   &undi_info, sizeof ( undi_info ) ) ) != 0 )
                goto err_undi_get_information;
        memcpy ( netdev->hw_addr, undi_info.PermNodeAddress, ETH_ALEN );
        memcpy ( netdev->ll_addr, undi_info.CurrentNodeAddress, ETH_ALEN );
        undinic->irq = undi_info.IntNumber;
        if ( undinic->irq > IRQ_MAX ) {
                DBGC ( undinic, "UNDINIC %p ignoring invalid IRQ %d\n",
                       undinic, undinic->irq );
                undinic->irq = 0;
        }
        DBGC ( undinic, "UNDINIC %p has MAC address %s and IRQ %d\n",
               undinic, eth_ntoa ( netdev->hw_addr ), undinic->irq );

        /* Get interface information */
        memset ( &undi_iface, 0, sizeof ( undi_iface ) );
        if ( ( rc = undinet_call ( undinic, 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 ( ( undi_iface.ServiceFlags & SUPPORTED_IRQ ) &&
             ( undinic->irq != 0 ) ) {
                undinic->irq_supported = 1;
        }
        DBGC ( undinic, "UNDINIC %p using %s mode\n", undinic,
               ( undinic->irq_supported ? "interrupt" : "polling" ) );
        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;
        }
        if ( undinet_irq_is_broken ( &dev->desc ) ) {
                DBGC ( undinic, "UNDINIC %p forcing polling mode due to "
                       "broken interrupts\n", undinic );
                undinic->irq_supported = 0;
        }

        /* Register network device */
        if ( ( rc = register_netdev ( netdev ) ) != 0 )
                goto err_register;

        /* Mark as link up; we don't handle link state */
        netdev_link_up ( netdev );

        DBGC ( undinic, "UNDINIC %p added\n", undinic );
        return 0;

 err_register:
 err_undi_get_iface_info:
 err_undi_get_information:
 err_undi_initialize:
        /* Shut down UNDI stack */
        memset ( &undi_shutdown, 0, sizeof ( undi_shutdown ) );
        undinet_call ( undinic, PXENV_UNDI_SHUTDOWN, &undi_shutdown,
                       sizeof ( undi_shutdown ) );
        memset ( &undi_cleanup, 0, sizeof ( undi_cleanup ) );
        undinet_call ( undinic, 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 ) );
        undinet_call ( undinic, 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;
}
void undinet_remove ( struct undi_device undi)

Remove UNDI device.

Parameters:
undiUNDI device

Definition at line 1035 of file undinet.c.

References DBGC, undi_device::flags, memset(), netdev, netdev_nullify(), netdev_put(), net_device::priv, PXENV_STOP_UNDI, PXENV_UNDI_CLEANUP, PXENV_UNDI_SHUTDOWN, UNDI_FL_INITIALIZED, UNDI_FL_KEEP_ALL, UNDI_FL_STARTED, undi_get_drvdata(), undinet_call(), undinet_entry_point, and unregister_netdev().

Referenced by undibus_remove(), and undipci_remove().

                                                 {
        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 ) );
                undinet_call ( undinic, PXENV_UNDI_SHUTDOWN,
                               &undi_shutdown, sizeof ( undi_shutdown ) );
                memset ( &undi_cleanup, 0, sizeof ( undi_cleanup ) );
                undinet_call ( undinic, PXENV_UNDI_CLEANUP,
                               &undi_cleanup, sizeof ( undi_cleanup ) );
                undi->flags &= ~UNDI_FL_INITIALIZED;

                /* Unhook UNDI stack */
                memset ( &stop_undi, 0, sizeof ( stop_undi ) );
                undinet_call ( undinic, PXENV_STOP_UNDI, &stop_undi,
                               sizeof ( stop_undi ) );
                undi->flags &= ~UNDI_FL_STARTED;
        }

        /* Clear entry point */
        memset ( &undinet_entry_point, 0, sizeof ( undinet_entry_point ) );

        /* Free network device */
        netdev_nullify ( netdev );
        netdev_put ( netdev );

        DBGC ( undinic, "UNDINIC %p removed\n", undinic );
}

Variable Documentation

struct undinet_profiler undinet_misc_profiler __profiler [static]
Initial value:
        { .name = "undinet.irq" }

IRQ profiler.

Miscellaneous PXE API call profiler.

PXE unknown API call profiler.

PXENV_UNDI_ISR profiler.

PXENV_UNDI_TRANSMIT profiler.

Receive profiler.

Note that this profiler will not see calls to PXENV_UNDI_ISR_IN_START, which are handled by the UNDI ISR and do not go via undinet_call().

This profiler can be used to measure the overhead of a dummy PXE API call.

Definition at line 108 of file undinet.c.

unsigned int last_trigger_count = 0 [static]

Last observed trigger count.

Definition at line 378 of file undinet.c.

Referenced by undinet_poll().

Initial value:
 {
        .open           = undinet_open,
        .close          = undinet_close,
        .transmit       = undinet_transmit,
        .poll           = undinet_poll,
        .irq            = undinet_irq,
}

UNDI network device operations.

Definition at line 797 of file undinet.c.

Initial value:
 {
        
        { 0x8086, 0x1502, PCI_ANY_ID, PCI_ANY_ID },
        { 0x8086, 0x1503, PCI_ANY_ID, PCI_ANY_ID },
        
        { 0x14e4, 0x1687, PCI_ANY_ID, PCI_ANY_ID },
}

List of devices with broken support for generating interrupts.

Some PXE stacks are known to claim that IRQs are supported, but then never generate interrupts. No satisfactory solution has been found to this problem; the workaround is to add the PCI vendor and device IDs to this list. This is something of a hack, since it will generate false positives for identical devices with a working PXE stack (e.g. those that have been reflashed with iPXE), but it's an improvement on the current situation.

Definition at line 828 of file undinet.c.

Referenced by undinet_irq_is_broken().