/* * Copyright (C) 2021 Michael Brown . * * 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 #include #include #include #include #include #include #include #include #include #include /** @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, };