diff --git a/src/arch/i386/drivers/net/undinet.c b/src/arch/i386/drivers/net/undinet.c index 6bc0fc36..46a759cc 100644 --- a/src/arch/i386/drivers/net/undinet.c +++ b/src/arch/i386/drivers/net/undinet.c @@ -45,8 +45,20 @@ struct undi_nic { 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 ); /***************************************************************************** @@ -245,6 +257,9 @@ static struct segoff prev_handler[ IRQ_MAX + 1 ]; static volatile uint8_t __text16 ( trigger_count ) = 0; #define trigger_count __use_text16 ( trigger_count ) +/** Last observed trigger count */ +static unsigned int last_trigger_count = 0; + /** * Hook UNDI interrupt service routine * @@ -292,7 +307,6 @@ static void undinet_unhook_isr ( unsigned int irq ) { * @ret triggered ISR has been triggered since last check */ static int undinet_isr_triggered ( void ) { - static unsigned int last_trigger_count = 0; unsigned int this_trigger_count; /* Read trigger_count. Do this only once; it is volatile */ @@ -333,6 +347,16 @@ static int undinet_transmit ( struct net_device *netdev, 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 ); @@ -460,9 +484,15 @@ static void undinet_poll ( struct net_device *netdev, unsigned int rx_quota ) { undi_isr.Frame.segment, undi_isr.Frame.offset, frag_len ); if ( iob_len ( iobuf ) == len ) { + /* Whole packet received; deliver it */ netdev_rx ( netdev, iobuf ); iobuf = NULL; --rx_quota; + /* 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: @@ -582,6 +612,7 @@ int undinet_probe ( struct undi_device *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; @@ -639,6 +670,21 @@ int undinet_probe ( struct undi_device *undi ) { DBGC ( undinic, "UNDINIC %p is %s on IRQ %d\n", undinic, eth_ntoa ( netdev->ll_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 and link speed %ld\n", + undinic, undi_iface.IfaceType, undi_iface.LinkSpeed ); + 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; + } + /* Point to NIC specific routines */ netdev->open = undinet_open; netdev->close = undinet_close; @@ -653,6 +699,7 @@ int undinet_probe ( struct undi_device *undi ) { return 0; err_register: + err_undi_get_iface_info: err_bad_irq: err_undi_get_information: err_undi_initialize: diff --git a/src/drivers/bus/isapnp.c b/src/drivers/bus/isapnp.c index 79268d44..b34108c7 100644 --- a/src/drivers/bus/isapnp.c +++ b/src/drivers/bus/isapnp.c @@ -529,7 +529,7 @@ static int isapnp_try_isolate ( void ) { * */ static void isapnp_isolate ( void ) { - for ( isapnp_read_port = ISAPNP_READ_PORT_MIN ; + for ( isapnp_read_port = ISAPNP_READ_PORT_START ; isapnp_read_port <= ISAPNP_READ_PORT_MAX ; isapnp_read_port += ISAPNP_READ_PORT_STEP ) { /* Avoid problematic locations such as the NE2000 diff --git a/src/drivers/net/legacy.c b/src/drivers/net/legacy.c index 22ddfe66..2d4633d4 100644 --- a/src/drivers/net/legacy.c +++ b/src/drivers/net/legacy.c @@ -57,12 +57,17 @@ static void legacy_poll ( struct net_device *netdev, unsigned int rx_quota ) { } } -static int legacy_open ( struct net_device *netdev __unused ) { +static int legacy_open ( struct net_device *netdev ) { + struct nic *nic = netdev->priv; + + nic->nic_op->irq ( nic, ENABLE ); return 0; } -static void legacy_close ( struct net_device *netdev __unused ) { - /* Nothing to do */ +static void legacy_close ( struct net_device *netdev ) { + struct nic *nic = netdev->priv; + + nic->nic_op->irq ( nic, DISABLE ); } int legacy_probe ( void *hwdev, diff --git a/src/drivers/net/pnic.c b/src/drivers/net/pnic.c index 63b4195f..f614a073 100644 --- a/src/drivers/net/pnic.c +++ b/src/drivers/net/pnic.c @@ -160,29 +160,6 @@ static int pnic_transmit ( struct net_device *netdev, struct io_buffer *iobuf ) return 0; } -/************************************************************************** -IRQ - Handle card interrupt status -***************************************************************************/ -#if 0 -static void pnic_irq ( struct net_device *netdev, irq_action_t action ) { - struct pnic *pnic = netdev->priv; - uint8_t enabled; - - switch ( action ) { - case DISABLE : - case ENABLE : - enabled = ( action == ENABLE ? 1 : 0 ); - pnic_command ( pnic, PNIC_CMD_MASK_IRQ, - &enabled, sizeof ( enabled ), NULL, 0, NULL ); - break; - case FORCE : - pnic_command ( pnic, PNIC_CMD_FORCE_IRQ, - NULL, 0, NULL, 0, NULL ); - break; - } -} -#endif - /************************************************************************** OPEN - Open network device ***************************************************************************/ diff --git a/src/drivers/net/rtl8139.c b/src/drivers/net/rtl8139.c index 6acffd9b..90a7182b 100644 --- a/src/drivers/net/rtl8139.c +++ b/src/drivers/net/rtl8139.c @@ -380,8 +380,7 @@ static int rtl_transmit ( struct net_device *netdev, struct io_buffer *iobuf ) { /* Check for space in TX ring */ if ( rtl->tx.iobuf[rtl->tx.next] != NULL ) { - printf ( "TX overflow\n" ); - free_iob ( iobuf ); + DBG ( "TX overflow\n" ); return -ENOBUFS; } @@ -472,34 +471,6 @@ static void rtl_poll ( struct net_device *netdev, unsigned int rx_quota ) { } } -#if 0 -static void rtl_irq(struct nic *nic, irq_action_t action) -{ - unsigned int mask; - /* Bit of a guess as to which interrupts we should allow */ - unsigned int interested = ROK | RER | RXOVW | FOVW | SERR; - - switch ( action ) { - case DISABLE : - case ENABLE : - mask = inw(rtl->ioaddr + IntrMask); - mask = mask & ~interested; - if ( action == ENABLE ) mask = mask | interested; - outw(mask, rtl->ioaddr + IntrMask); - break; - case FORCE : - /* Apparently writing a 1 to this read-only bit of a - * read-only and otherwise unrelated register will - * force an interrupt. If you ever want to see how - * not to write a datasheet, read the one for the - * RTL8139... - */ - outb(EROK, rtl->ioaddr + RxEarlyStatus); - break; - } -} -#endif - /** * Probe PCI device * diff --git a/src/include/gpxe/isapnp.h b/src/include/gpxe/isapnp.h index 72ce0a6f..07797a99 100644 --- a/src/include/gpxe/isapnp.h +++ b/src/include/gpxe/isapnp.h @@ -49,7 +49,8 @@ /* Port addresses */ #define ISAPNP_ADDRESS 0x279 #define ISAPNP_WRITE_DATA 0xa79 -#define ISAPNP_READ_PORT_MIN 0x213 /* ISAPnP spec says 0x203, but +#define ISAPNP_READ_PORT_MIN 0x203 +#define ISAPNP_READ_PORT_START 0x213 /* ISAPnP spec says 0x203, but * Linux ISAPnP starts at * 0x213 with no explanatory * comment. 0x203 probably @@ -63,6 +64,10 @@ * any value less than 16. */ +/* Card select numbers */ +#define ISAPNP_CSN_MIN 0x01 +#define ISAPNP_CSN_MAX 0x0f + /* Registers */ #define ISAPNP_READPORT 0x00 #define ISAPNP_SERIALISOLATION 0x01 diff --git a/src/include/gpxe/netdevice.h b/src/include/gpxe/netdevice.h index c0df7c96..0bc5311c 100644 --- a/src/include/gpxe/netdevice.h +++ b/src/include/gpxe/netdevice.h @@ -289,8 +289,9 @@ netdev_put ( struct net_device *netdev ) { } extern int netdev_tx ( struct net_device *netdev, struct io_buffer *iobuf ); -void netdev_tx_complete ( struct net_device *netdev, struct io_buffer *iobuf ); -void netdev_tx_complete_next ( struct net_device *netdev ); +extern void netdev_tx_complete ( struct net_device *netdev, + struct io_buffer *iobuf ); +extern void netdev_tx_complete_next ( struct net_device *netdev ); extern void netdev_rx ( struct net_device *netdev, struct io_buffer *iobuf ); extern int netdev_poll ( struct net_device *netdev, unsigned int rx_quota ); extern struct io_buffer * netdev_rx_dequeue ( struct net_device *netdev ); @@ -299,9 +300,9 @@ extern int register_netdev ( struct net_device *netdev ); extern int netdev_open ( struct net_device *netdev ); extern void netdev_close ( struct net_device *netdev ); extern void unregister_netdev ( struct net_device *netdev ); -struct net_device * find_netdev ( const char *name ); -struct net_device * find_pci_netdev ( unsigned int busdevfn ); - +extern struct net_device * find_netdev ( const char *name ); +extern struct net_device * find_netdev_by_location ( unsigned int bus_type, + unsigned int location ); extern int net_tx ( struct io_buffer *iobuf, struct net_device *netdev, struct net_protocol *net_protocol, const void *ll_dest ); extern int net_rx ( struct io_buffer *iobuf, struct net_device *netdev, diff --git a/src/interface/pxe/pxe_preboot.c b/src/interface/pxe/pxe_preboot.c index 15752b23..a74e58af 100644 --- a/src/interface/pxe/pxe_preboot.c +++ b/src/interface/pxe/pxe_preboot.c @@ -28,6 +28,9 @@ #include #include #include +#include +#include +#include #include #include "pxe.h" #include "pxe_call.h" @@ -196,41 +199,47 @@ PXENV_EXIT_t pxenv_restart_tftp ( struct s_PXENV_TFTP_READ_FILE * Status: working */ PXENV_EXIT_t pxenv_start_undi ( struct s_PXENV_START_UNDI *start_undi ) { + unsigned int isapnp_read_port; + unsigned int isapnp_csn; + unsigned int pci_busdevfn; + unsigned int bus_type; + unsigned int location; + struct net_device *netdev; - DBG ( "PXENV_START_UNDI" ); + DBG ( "PXENV_START_UNDI %04x:%04x:%04x", + start_undi->AX, start_undi->BX, start_undi->DX ); -#if 0 - /* Record PCI bus & devfn passed by caller, so we know which - * NIC they want to use. - * - * If they don't match our already-existing NIC structure, set - * values to ensure that the specified NIC is used at the next - * call to pxe_intialise_nic(). - */ - bus = ( start_undi->AX >> 8 ) & 0xff; - devfn = start_undi->AX & 0xff; + /* Determine bus type and location */ + isapnp_read_port = start_undi->DX; + isapnp_csn = start_undi->BX; + pci_busdevfn = start_undi->AX; -#warning "device probing mechanism has completely changed" -#if 0 - if ( ( pci->dev.driver == NULL ) || - ( pci->dev.bus != bus ) || ( pci->dev.devfn != devfn ) ) { - /* This is quite a bit of a hack and relies on - * knowledge of the internal operation of Etherboot's - * probe mechanism. - */ - DBG ( " set PCI %hhx:%hhx.%hhx", - bus, PCI_SLOT(devfn), PCI_FUNC(devfn) ); - dev->type = BOOT_NIC; - dev->to_probe = PROBE_PCI; - memset ( &dev->state, 0, sizeof(dev->state) ); - pci->advance = 1; - pci->dev.use_specified = 1; - pci->dev.bus = bus; - pci->dev.devfn = devfn; + /* Use a heuristic to decide whether we are PCI or ISAPnP */ + if ( ( isapnp_read_port >= ISAPNP_READ_PORT_MIN ) && + ( isapnp_read_port <= ISAPNP_READ_PORT_MAX ) && + ( isapnp_csn >= ISAPNP_CSN_MIN ) && + ( isapnp_csn <= ISAPNP_CSN_MAX ) ) { + bus_type = BUS_TYPE_ISAPNP; + location = isapnp_csn; + } else { + bus_type = BUS_TYPE_PCI; + location = pci_busdevfn; } -#endif -#endif + /* Look for a matching net device */ + netdev = find_netdev_by_location ( bus_type, location ); + if ( ! netdev ) { + DBG ( " no net device found" ); + start_undi->Status = PXENV_STATUS_UNDI_CANNOT_INITIALIZE_NIC; + return PXENV_EXIT_FAILURE; + } + DBG ( " using netdev %s", netdev->name ); + + /* Save as PXE net device */ + pxe_set_netdev ( netdev ); + + /* Hook INT 1A */ + pxe_hook_int1a(); start_undi->Status = PXENV_STATUS_SUCCESS; return PXENV_EXIT_SUCCESS; diff --git a/src/interface/pxe/pxe_undi.c b/src/interface/pxe/pxe_undi.c index 0ce4b290..33de8117 100644 --- a/src/interface/pxe/pxe_undi.c +++ b/src/interface/pxe/pxe_undi.c @@ -394,18 +394,15 @@ PXENV_EXIT_t pxenv_undi_initiate_diags ( struct s_PXENV_UNDI_INITIATE_DIAGS /* PXENV_UNDI_FORCE_INTERRUPT * - * Status: working + * Status: won't implement (would require driver API changes for no + * perceptible benefit) */ PXENV_EXIT_t pxenv_undi_force_interrupt ( struct s_PXENV_UNDI_FORCE_INTERRUPT *undi_force_interrupt ) { DBG ( "PXENV_UNDI_FORCE_INTERRUPT" ); -#if 0 - eth_irq ( FORCE ); -#endif - - undi_force_interrupt->Status = PXENV_STATUS_SUCCESS; - return PXENV_EXIT_SUCCESS; + undi_force_interrupt->Status = PXENV_STATUS_UNSUPPORTED; + return PXENV_EXIT_FAILURE; } /* PXENV_UNDI_GET_MCAST_ADDRESS @@ -483,7 +480,7 @@ PXENV_EXIT_t pxenv_undi_get_iface_info ( struct s_PXENV_UNDI_GET_IFACE_INFO * Most PXE stacks seem to take this approach. */ snprintf ( ( char * ) undi_get_iface_info->IfaceType, - sizeof ( undi_get_iface_info->IfaceType ), "Etherboot" ); + sizeof ( undi_get_iface_info->IfaceType ), "gPXE" ); undi_get_iface_info->LinkSpeed = 10000000; /* 10 Mbps */ undi_get_iface_info->ServiceFlags = 0; memset ( undi_get_iface_info->Reserved, 0, diff --git a/src/net/ipv4.c b/src/net/ipv4.c index e1219edb..35341b3d 100644 --- a/src/net/ipv4.c +++ b/src/net/ipv4.c @@ -141,12 +141,20 @@ void del_ipv4_address ( struct net_device *netdev ) { * @v dest Final destination address * @ret dest Next hop destination address * @ret miniroute Routing table entry to use, or NULL if no route + * + * If the route requires use of a gateway, the next hop destination + * address will be overwritten with the gateway address. */ static struct ipv4_miniroute * ipv4_route ( struct in_addr *dest ) { struct ipv4_miniroute *miniroute; int local; int has_gw; + /* Never attempt to route the broadcast address */ + if ( dest->s_addr == INADDR_BROADCAST ) + return NULL; + + /* Find first usable route in routing table */ list_for_each_entry ( miniroute, &ipv4_miniroutes, list ) { local = ( ( ( dest->s_addr ^ miniroute->address.s_addr ) & miniroute->netmask.s_addr ) == 0 ); diff --git a/src/net/netdevice.c b/src/net/netdevice.c index 8a099107..971830d9 100644 --- a/src/net/netdevice.c +++ b/src/net/netdevice.c @@ -356,15 +356,17 @@ struct net_device * find_netdev ( const char *name ) { /** * Get network device by PCI bus:dev.fn address * - * @v busdevfn PCI bus:dev.fn address + * @v bus_type Bus type + * @v location Bus location * @ret netdev Network device, or NULL */ -struct net_device * find_pci_netdev ( unsigned int busdevfn ) { +struct net_device * find_netdev_by_location ( unsigned int bus_type, + unsigned int location ) { struct net_device *netdev; list_for_each_entry ( netdev, &net_devices, list ) { - if ( ( netdev->dev->desc.bus_type == BUS_TYPE_PCI ) && - ( netdev->dev->desc.location == busdevfn ) ) + if ( ( netdev->dev->desc.bus_type == bus_type ) && + ( netdev->dev->desc.location == location ) ) return netdev; }