blob: 8341c967617fceeef77d67e3834a6d2924172704 [file] [log] [blame]
/*
* Copyright (C) 2021 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.
*/
#include <stdio.h>
#include <errno.h>
#include <ipxe/netdevice.h>
#include <ipxe/ethernet.h>
#include <ipxe/if_ether.h>
#include <ipxe/in.h>
#include <ipxe/timer.h>
#include <ipxe/retry.h>
#include <ipxe/linux.h>
#include <ipxe/linux_api.h>
#include <ipxe/slirp.h>
/** @file
*
* Linux Slirp network driver
*
*/
/** Maximum number of open file descriptors */
#define SLIRP_MAX_FDS 128
/** A Slirp network interface */
struct slirp_nic {
/** The libslirp device object */
struct Slirp *slirp;
/** Polling file descriptor list */
struct pollfd pollfds[SLIRP_MAX_FDS];
/** Number of file descriptors */
unsigned int numfds;
};
/** A Slirp alarm timer */
struct slirp_alarm {
/** Slirp network interface */
struct slirp_nic *slirp;
/** Retry timer */
struct retry_timer timer;
/** Callback function */
void ( __asmcall * callback ) ( void *opaque );
/** Opaque value for callback function */
void *opaque;
};
/** Default MAC address */
static const uint8_t slirp_default_mac[ETH_ALEN] =
{ 0x52, 0x54, 0x00, 0x12, 0x34, 0x56 };
/******************************************************************************
*
* Slirp interface
*
******************************************************************************
*/
/**
* Send packet
*
* @v buf Data buffer
* @v len Length of data
* @v device Device opaque pointer
* @ret len Consumed length (or negative on error)
*/
static ssize_t __asmcall slirp_send_packet ( const void *buf, size_t len,
void *device ) {
struct net_device *netdev = device;
struct io_buffer *iobuf;
/* Allocate I/O buffer */
iobuf = alloc_iob ( len );
if ( ! iobuf )
return -1;
/* Populate I/O buffer */
memcpy ( iob_put ( iobuf, len ), buf, len );
/* Hand off to network stack */
netdev_rx ( netdev, iobuf );
return len;
}
/**
* Print an error message
*
* @v msg Error message
* @v device Device opaque pointer
*/
static void __asmcall slirp_guest_error ( const char *msg, void *device ) {
struct net_device *netdev = device;
struct slirp_nic *slirp = netdev->priv;
DBGC ( slirp, "SLIRP %p error: %s\n", slirp, msg );
}
/**
* Get virtual clock
*
* @v device Device opaque pointer
* @ret clock_ns Clock time in nanoseconds
*/
static int64_t __asmcall slirp_clock_get_ns ( void *device __unused ) {
int64_t time;
time = currticks();
return ( time * ( 1000000 / TICKS_PER_MS ) );
}
/**
* Handle timer expiry
*
* @v timer Retry timer
* @v over Failure indicator
*/
static void slirp_expired ( struct retry_timer *timer, int over __unused ) {
struct slirp_alarm *alarm =
container_of ( timer, struct slirp_alarm, timer );
struct slirp_nic *slirp = alarm->slirp;
/* Notify callback */
DBGC ( slirp, "SLIRP %p timer fired\n", slirp );
alarm->callback ( alarm->opaque );
}
/**
* Create a new timer
*
* @v callback Timer callback
* @v opaque Timer opaque pointer
* @v device Device opaque pointer
* @ret timer Timer
*/
static void * __asmcall
slirp_timer_new ( void ( __asmcall * callback ) ( void *opaque ),
void *opaque, void *device ) {
struct net_device *netdev = device;
struct slirp_nic *slirp = netdev->priv;
struct slirp_alarm *alarm;
/* Allocate timer */
alarm = malloc ( sizeof ( *alarm ) );
if ( ! alarm ) {
DBGC ( slirp, "SLIRP %p could not allocate timer\n", slirp );
return NULL;
}
/* Initialise timer */
memset ( alarm, 0, sizeof ( *alarm ) );
alarm->slirp = slirp;
timer_init ( &alarm->timer, slirp_expired, NULL );
alarm->callback = callback;
alarm->opaque = opaque;
DBGC ( slirp, "SLIRP %p timer %p has callback %p (%p)\n",
slirp, alarm, alarm->callback, alarm->opaque );
return alarm;
}
/**
* Delete a timer
*
* @v timer Timer
* @v device Device opaque pointer
*/
static void __asmcall slirp_timer_free ( void *timer, void *device ) {
struct net_device *netdev = device;
struct slirp_nic *slirp = netdev->priv;
struct slirp_alarm *alarm = timer;
/* Ignore timers that failed to allocate */
if ( ! alarm )
return;
/* Stop timer */
stop_timer ( &alarm->timer );
/* Free timer */
free ( alarm );
DBGC ( slirp, "SLIRP %p timer %p freed\n", slirp, alarm );
}
/**
* Set timer expiry time
*
* @v timer Timer
* @v expire Expiry time
* @v device Device opaque pointer
*/
static void __asmcall slirp_timer_mod ( void *timer, int64_t expire,
void *device ) {
struct net_device *netdev = device;
struct slirp_nic *slirp = netdev->priv;
struct slirp_alarm *alarm = timer;
int64_t timeout_ms;
unsigned long timeout;
/* Ignore timers that failed to allocate */
if ( ! alarm )
return;
/* (Re)start timer */
timeout_ms = ( expire - ( currticks() / TICKS_PER_MS ) );
if ( timeout_ms < 0 )
timeout_ms = 0;
timeout = ( timeout_ms * TICKS_PER_MS );
start_timer_fixed ( &alarm->timer, timeout );
DBGC ( slirp, "SLIRP %p timer %p set for %ld ticks\n",
slirp, alarm, timeout );
}
/**
* Register file descriptor for polling
*
* @v fd File descriptor
* @v device Device opaque pointer
*/
static void __asmcall slirp_register_poll_fd ( int fd, void *device ) {
struct net_device *netdev = device;
struct slirp_nic *slirp = netdev->priv;
DBGC ( slirp, "SLIRP %p registered FD %d\n", slirp, fd );
}
/**
* Unregister file descriptor
*
* @v fd File descriptor
* @v device Device opaque pointer
*/
static void __asmcall slirp_unregister_poll_fd ( int fd, void *device ) {
struct net_device *netdev = device;
struct slirp_nic *slirp = netdev->priv;
DBGC ( slirp, "SLIRP %p unregistered FD %d\n", slirp, fd );
}
/**
* Notify that new events are ready
*
* @v device Device opaque pointer
*/
static void __asmcall slirp_notify ( void *device ) {
struct net_device *netdev = device;
struct slirp_nic *slirp = netdev->priv;
DBGC2 ( slirp, "SLIRP %p notified\n", slirp );
}
/** Slirp callbacks */
static struct slirp_callbacks slirp_callbacks = {
.send_packet = slirp_send_packet,
.guest_error = slirp_guest_error,
.clock_get_ns = slirp_clock_get_ns,
.timer_new = slirp_timer_new,
.timer_free = slirp_timer_free,
.timer_mod = slirp_timer_mod,
.register_poll_fd = slirp_register_poll_fd,
.unregister_poll_fd = slirp_unregister_poll_fd,
.notify = slirp_notify,
};
/******************************************************************************
*
* Network device interface
*
******************************************************************************
*/
/**
* Open network device
*
* @v netdev Network device
* @ret rc Return status code
*/
static int slirp_open ( struct net_device *netdev ) {
struct slirp_nic *slirp = netdev->priv;
/* Nothing to do */
DBGC ( slirp, "SLIRP %p opened\n", slirp );
return 0;
}
/**
* Close network device
*
* @v netdev Network device
*/
static void slirp_close ( struct net_device *netdev ) {
struct slirp_nic *slirp = netdev->priv;
/* Nothing to do */
DBGC ( slirp, "SLIRP %p closed\n", slirp );
}
/**
* Transmit packet
*
* @v netdev Network device
* @v iobuf I/O buffer
* @ret rc Return status code
*/
static int slirp_transmit ( struct net_device *netdev,
struct io_buffer *iobuf ) {
struct slirp_nic *slirp = netdev->priv;
/* Transmit packet */
linux_slirp_input ( slirp->slirp, iobuf->data, iob_len ( iobuf ) );
netdev_tx_complete ( netdev, iobuf );
return 0;
}
/**
* Add polling file descriptor
*
* @v fd File descriptor
* @v events Events of interest
* @v device Device opaque pointer
* @ret index File descriptor index
*/
static int __asmcall slirp_add_poll ( int fd, int events, void *device ) {
struct net_device *netdev = device;
struct slirp_nic *slirp = netdev->priv;
struct pollfd *pollfd;
unsigned int index;
/* Fail if too many descriptors are registered */
if ( slirp->numfds >= SLIRP_MAX_FDS ) {
DBGC ( slirp, "SLIRP %p too many file descriptors\n", slirp );
return -1;
}
/* Populate polling file descriptor */
index = slirp->numfds++;
pollfd = &slirp->pollfds[index];
pollfd->fd = fd;
pollfd->events = 0;
if ( events & SLIRP_EVENT_IN )
pollfd->events |= POLLIN;
if ( events & SLIRP_EVENT_OUT )
pollfd->events |= POLLOUT;
if ( events & SLIRP_EVENT_PRI )
pollfd->events |= POLLPRI;
if ( events & SLIRP_EVENT_ERR )
pollfd->events |= POLLERR;
if ( events & SLIRP_EVENT_HUP )
pollfd->events |= ( POLLHUP | POLLRDHUP );
DBGCP ( slirp, "SLIRP %p polling FD %d event mask %#04x(%#04x)\n",
slirp, fd, events, pollfd->events );
return index;
}
/**
* Get returned events for a file descriptor
*
* @v index File descriptor index
* @v device Device opaque pointer
* @ret events Returned events
*/
static int __asmcall slirp_get_revents ( int index, void *device ) {
struct net_device *netdev = device;
struct slirp_nic *slirp = netdev->priv;
int revents;
int events;
/* Ignore failed descriptors */
if ( index < 0 )
return 0;
/* Collect events */
revents = slirp->pollfds[index].revents;
events = 0;
if ( revents & POLLIN )
events |= SLIRP_EVENT_IN;
if ( revents & POLLOUT )
events |= SLIRP_EVENT_OUT;
if ( revents & POLLPRI )
events |= SLIRP_EVENT_PRI;
if ( revents & POLLERR )
events |= SLIRP_EVENT_ERR;
if ( revents & ( POLLHUP | POLLRDHUP ) )
events |= SLIRP_EVENT_HUP;
if ( events ) {
DBGC2 ( slirp, "SLIRP %p polled FD %d events %#04x(%#04x)\n",
slirp, slirp->pollfds[index].fd, events, revents );
}
return events;
}
/**
* Poll for completed and received packets
*
* @v netdev Network device
*/
static void slirp_poll ( struct net_device *netdev ) {
struct slirp_nic *slirp = netdev->priv;
uint32_t timeout = 0;
int ready;
int error;
/* Rebuild polling file descriptor list */
slirp->numfds = 0;
linux_slirp_pollfds_fill ( slirp->slirp, &timeout,
slirp_add_poll, netdev );
/* Poll descriptors */
ready = linux_poll ( slirp->pollfds, slirp->numfds, 0 );
error = ( ready == -1 );
linux_slirp_pollfds_poll ( slirp->slirp, error, slirp_get_revents,
netdev );
/* Record polling errors */
if ( error ) {
DBGC ( slirp, "SLIRP %p poll failed: %s\n",
slirp, linux_strerror ( linux_errno ) );
netdev_rx_err ( netdev, NULL, -ELINUX ( linux_errno ) );
}
}
/** Network device operations */
static struct net_device_operations slirp_operations = {
.open = slirp_open,
.close = slirp_close,
.transmit = slirp_transmit,
.poll = slirp_poll,
};
/******************************************************************************
*
* Linux driver interface
*
******************************************************************************
*/
/**
* Probe device
*
* @v linux Linux device
* @v request Device creation request
* @ret rc Return status code
*/
static int slirp_probe ( struct linux_device *linux,
struct linux_device_request *request ) {
struct net_device *netdev;
struct slirp_nic *slirp;
struct slirp_config config;
int rc;
/* Allocate device */
netdev = alloc_etherdev ( sizeof ( *slirp ) );
if ( ! netdev ) {
rc = -ENOMEM;
goto err_alloc;
}
netdev_init ( netdev, &slirp_operations );
linux_set_drvdata ( linux, netdev );
snprintf ( linux->dev.name, sizeof ( linux->dev.name ), "host" );
netdev->dev = &linux->dev;
memcpy ( netdev->hw_addr, slirp_default_mac, ETH_ALEN );
slirp = netdev->priv;
memset ( slirp, 0, sizeof ( *slirp ) );
/* Apply requested settings */
linux_apply_settings ( &request->settings,
netdev_settings ( netdev ) );
/* Initialise default configuration (matching qemu) */
memset ( &config, 0, sizeof ( config ) );
config.version = 1;
config.in_enabled = true;
config.vnetwork.s_addr = htonl ( 0x0a000200 ); /* 10.0.2.0 */
config.vnetmask.s_addr = htonl ( 0xffffff00 ); /* 255.255.255.0 */
config.vhost.s_addr = htonl ( 0x0a000202 ); /* 10.0.2.2 */
config.in6_enabled = true;
config.vdhcp_start.s_addr = htonl ( 0x0a00020f ); /* 10.0.2.15 */
config.vnameserver.s_addr = htonl ( 0x0a000203 ); /* 10.0.2.3 */
/* Instantiate device */
slirp->slirp = linux_slirp_new ( &config, &slirp_callbacks, netdev );
if ( ! slirp->slirp ) {
DBGC ( slirp, "SLIRP could not instantiate\n" );
rc = -ENODEV;
goto err_new;
}
/* Register network device */
if ( ( rc = register_netdev ( netdev ) ) != 0 )
goto err_register;
/* Set link up since there is no concept of link state */
netdev_link_up ( netdev );
return 0;
unregister_netdev ( netdev );
err_register:
linux_slirp_cleanup ( slirp->slirp );
err_new:
netdev_nullify ( netdev );
netdev_put ( netdev );
err_alloc:
return rc;
}
/**
* Remove device
*
* @v linux Linux device
*/
static void slirp_remove ( struct linux_device *linux ) {
struct net_device *netdev = linux_get_drvdata ( linux );
struct slirp_nic *slirp = netdev->priv;
/* Unregister network device */
unregister_netdev ( netdev );
/* Shut down device */
linux_slirp_cleanup ( slirp->slirp );
/* Free network device */
netdev_nullify ( netdev );
netdev_put ( netdev );
}
/** Slirp driver */
struct linux_driver slirp_driver __linux_driver = {
.name = "slirp",
.probe = slirp_probe,
.remove = slirp_remove,
.can_probe = 1,
};