Files
ipxe/src/usr/ifmgmt.c
Michael Brown 9f0b7f428a [ifmgmt] Do not sleep CPU while configuring network devices
iPXE currently calls cpu_nap() while performing DHCP, in order to
reduce CPU utilisation on virtual machines.  Under mild broadcast load
(~100 packets per second), this can cause received packets to be
dropped because the receive descriptor ring is overrun before the next
18Hz timer interrupt wakes up the CPU.  The result is that DHCP is
likely to intermittently fail on networks with appreciable amounts of
broadcast (or multicast) traffic.

This behaviour was introduced in the series of commits which
generalised the "dhcp" command to the "ifconf" command.  The earlier
code (which did not handle IPv6 configuration) had no call to
cpu_nap() and so did not suffer from this problem.

Fix by removing the call to cpu_nap() in ifpoller_progress().  This
has the undesirable side effect that CPU utilisation will remain at
100% while waiting for DHCP to complete (which can take several
seconds, if we have to wait around for potential ProxyDHCP offers to
arrive).

Reported-by: Alex Davies <adavies@jumptrading.com>
Reported-by: Christoffer Stokbæk <christoffers@easyspeedy.com>
Signed-off-by: Michael Brown <mcb30@ipxe.org>
2014-06-01 19:54:23 +01:00

290 lines
7.7 KiB
C

/*
* Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
FILE_LICENCE ( GPL2_OR_LATER );
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <ipxe/console.h>
#include <ipxe/netdevice.h>
#include <ipxe/device.h>
#include <ipxe/job.h>
#include <ipxe/monojob.h>
#include <ipxe/timer.h>
#include <usr/ifmgmt.h>
/** @file
*
* Network interface management
*
*/
/** Default time to wait for link-up */
#define LINK_WAIT_TIMEOUT ( 15 * TICKS_PER_SEC )
/** Default unsuccessful configuration status code */
#define EADDRNOTAVAIL_CONFIG __einfo_error ( EINFO_EADDRNOTAVAIL_CONFIG )
#define EINFO_EADDRNOTAVAIL_CONFIG \
__einfo_uniqify ( EINFO_EADDRNOTAVAIL, 0x01, \
"No configuration methods succeeded" )
/**
* Open network device
*
* @v netdev Network device
* @ret rc Return status code
*/
int ifopen ( struct net_device *netdev ) {
int rc;
if ( ( rc = netdev_open ( netdev ) ) != 0 ) {
printf ( "Could not open %s: %s\n",
netdev->name, strerror ( rc ) );
return rc;
}
return 0;
}
/**
* Close network device
*
* @v netdev Network device
*/
void ifclose ( struct net_device *netdev ) {
netdev_close ( netdev );
}
/**
* Print network device error breakdown
*
* @v stats Network device statistics
* @v prefix Message prefix
*/
static void ifstat_errors ( struct net_device_stats *stats,
const char *prefix ) {
unsigned int i;
for ( i = 0 ; i < ( sizeof ( stats->errors ) /
sizeof ( stats->errors[0] ) ) ; i++ ) {
if ( stats->errors[i].count )
printf ( " [%s: %d x \"%s\"]\n", prefix,
stats->errors[i].count,
strerror ( stats->errors[i].rc ) );
}
}
/**
* Print status of network device
*
* @v netdev Network device
*/
void ifstat ( struct net_device *netdev ) {
printf ( "%s: %s using %s on %s (%s)\n"
" [Link:%s, TX:%d TXE:%d RX:%d RXE:%d]\n",
netdev->name, netdev_addr ( netdev ),
netdev->dev->driver_name, netdev->dev->name,
( netdev_is_open ( netdev ) ? "open" : "closed" ),
( netdev_link_ok ( netdev ) ? "up" : "down" ),
netdev->tx_stats.good, netdev->tx_stats.bad,
netdev->rx_stats.good, netdev->rx_stats.bad );
if ( ! netdev_link_ok ( netdev ) ) {
printf ( " [Link status: %s]\n",
strerror ( netdev->link_rc ) );
}
ifstat_errors ( &netdev->tx_stats, "TXE" );
ifstat_errors ( &netdev->rx_stats, "RXE" );
}
/** Network device poller */
struct ifpoller {
/** Job control interface */
struct interface job;
/** Network device */
struct net_device *netdev;
/** Network device configurator (if applicable) */
struct net_device_configurator *configurator;
/**
* Check progress
*
* @v ifpoller Network device poller
* @ret ongoing_rc Ongoing job status code (if known)
*/
int ( * progress ) ( struct ifpoller *ifpoller );
};
/**
* Report network device poller progress
*
* @v ifpoller Network device poller
* @v progress Progress report to fill in
* @ret ongoing_rc Ongoing job status code (if known)
*/
static int ifpoller_progress ( struct ifpoller *ifpoller,
struct job_progress *progress __unused ) {
/* Hand off to current progress checker */
return ifpoller->progress ( ifpoller );
}
/** Network device poller operations */
static struct interface_operation ifpoller_job_op[] = {
INTF_OP ( job_progress, struct ifpoller *, ifpoller_progress ),
};
/** Network device poller descriptor */
static struct interface_descriptor ifpoller_job_desc =
INTF_DESC ( struct ifpoller, job, ifpoller_job_op );
/**
* Poll network device until completion
*
* @v netdev Network device
* @v configurator Network device configurator (if applicable)
* @v timeout Timeout period, in ticks
* @v progress Method to check progress
* @ret rc Return status code
*/
static int ifpoller_wait ( struct net_device *netdev,
struct net_device_configurator *configurator,
unsigned long timeout,
int ( * progress ) ( struct ifpoller *ifpoller ) ) {
static struct ifpoller ifpoller = {
.job = INTF_INIT ( ifpoller_job_desc ),
};
ifpoller.netdev = netdev;
ifpoller.configurator = configurator;
ifpoller.progress = progress;
intf_plug_plug ( &monojob, &ifpoller.job );
return monojob_wait ( "", timeout );
}
/**
* Check link-up progress
*
* @v ifpoller Network device poller
* @ret ongoing_rc Ongoing job status code (if known)
*/
static int iflinkwait_progress ( struct ifpoller *ifpoller ) {
struct net_device *netdev = ifpoller->netdev;
int ongoing_rc = netdev->link_rc;
/* Terminate successfully if link is up */
if ( ongoing_rc == 0 )
intf_close ( &ifpoller->job, 0 );
/* Otherwise, report link status as ongoing job status */
return ongoing_rc;
}
/**
* Wait for link-up, with status indication
*
* @v netdev Network device
* @v timeout Timeout period, in ticks
*/
int iflinkwait ( struct net_device *netdev, unsigned long timeout ) {
int rc;
/* Ensure device is open */
if ( ( rc = ifopen ( netdev ) ) != 0 )
return rc;
/* Return immediately if link is already up */
netdev_poll ( netdev );
if ( netdev_link_ok ( netdev ) )
return 0;
/* Wait for link-up */
printf ( "Waiting for link-up on %s", netdev->name );
return ifpoller_wait ( netdev, NULL, timeout, iflinkwait_progress );
}
/**
* Check configuration progress
*
* @v ifpoller Network device poller
* @ret ongoing_rc Ongoing job status code (if known)
*/
static int ifconf_progress ( struct ifpoller *ifpoller ) {
struct net_device *netdev = ifpoller->netdev;
struct net_device_configurator *configurator = ifpoller->configurator;
struct net_device_configuration *config;
int rc;
/* Do nothing unless configuration has completed */
if ( netdev_configuration_in_progress ( netdev ) )
return 0;
/* Terminate with appropriate overall return status code */
if ( configurator ) {
config = netdev_configuration ( netdev, configurator );
rc = config->rc;
} else {
rc = ( netdev_configuration_ok ( netdev ) ?
0 : -EADDRNOTAVAIL_CONFIG );
}
intf_close ( &ifpoller->job, rc );
return rc;
}
/**
* Perform network device configuration
*
* @v netdev Network device
* @v configurator Network device configurator, or NULL to use all
* @ret rc Return status code
*/
int ifconf ( struct net_device *netdev,
struct net_device_configurator *configurator ) {
int rc;
/* Ensure device is open and link is up */
if ( ( rc = iflinkwait ( netdev, LINK_WAIT_TIMEOUT ) ) != 0 )
return rc;
/* Start configuration */
if ( configurator ) {
if ( ( rc = netdev_configure ( netdev, configurator ) ) != 0 ) {
printf ( "Could not configure %s via %s: %s\n",
netdev->name, configurator->name,
strerror ( rc ) );
return rc;
}
} else {
if ( ( rc = netdev_configure_all ( netdev ) ) != 0 ) {
printf ( "Could not configure %s: %s\n",
netdev->name, strerror ( rc ) );
return rc;
}
}
/* Wait for configuration to complete */
printf ( "Configuring %s%s%s(%s %s)",
( configurator ? "[" : "" ),
( configurator ? configurator->name : "" ),
( configurator ? "] " : "" ),
netdev->name, netdev->ll_protocol->ntoa ( netdev->ll_addr ) );
return ifpoller_wait ( netdev, configurator, 0, ifconf_progress );
}