| /* | |
| * Copyright (C) 2010 Piotr JaroszyĆski <p.jaroszynski@gmail.com> | |
| * | |
| * 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 St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| */ | |
| #include <errno.h> | |
| #include <string.h> | |
| #include <stdio.h> | |
| #include <ipxe/linux_api.h> | |
| #include <ipxe/list.h> | |
| #include <ipxe/linux.h> | |
| #include <ipxe/malloc.h> | |
| #include <ipxe/device.h> | |
| #include <ipxe/netdevice.h> | |
| #include <ipxe/iobuf.h> | |
| #include <ipxe/ethernet.h> | |
| #include <ipxe/settings.h> | |
| #include <ipxe/socket.h> | |
| /* This hack prevents pre-2.6.32 headers from redefining struct sockaddr */ | |
| #define _SYS_SOCKET_H | |
| #define __GLIBC__ 2 | |
| #include <linux/socket.h> | |
| #undef __GLIBC__ | |
| #include <linux/if.h> | |
| #include <linux/if_ether.h> | |
| #include <linux/if_tun.h> | |
| #define RX_BUF_SIZE 1536 | |
| #define RX_QUOTA 4 | |
| /** @file | |
| * | |
| * The TAP driver. | |
| * | |
| * The TAP is a Virtual Ethernet network device. | |
| */ | |
| struct tap_nic { | |
| /** Tap interface name */ | |
| char * interface; | |
| /** File descriptor of the opened tap device */ | |
| int fd; | |
| }; | |
| /** Default MAC address */ | |
| static const uint8_t tap_default_mac[ETH_ALEN] = | |
| { 0x52, 0x54, 0x00, 0x12, 0x34, 0x56 }; | |
| /** Open the TAP device */ | |
| static int tap_open(struct net_device * netdev) | |
| { | |
| struct tap_nic * nic = netdev->priv; | |
| struct ifreq ifr; | |
| int ret; | |
| nic->fd = linux_open("/dev/net/tun", O_RDWR); | |
| if (nic->fd < 0) { | |
| DBGC(nic, "tap %p open('/dev/net/tun') = %d (%s)\n", nic, nic->fd, linux_strerror(linux_errno)); | |
| return nic->fd; | |
| } | |
| memset(&ifr, 0, sizeof(ifr)); | |
| /* IFF_NO_PI for no extra packet information */ | |
| ifr.ifr_flags = IFF_TAP | IFF_NO_PI; | |
| strncpy(ifr.ifr_name, nic->interface, IFNAMSIZ); | |
| DBGC(nic, "tap %p interface = '%s'\n", nic, nic->interface); | |
| ret = linux_ioctl(nic->fd, TUNSETIFF, &ifr); | |
| if (ret != 0) { | |
| DBGC(nic, "tap %p ioctl(%d, ...) = %d (%s)\n", nic, nic->fd, ret, linux_strerror(linux_errno)); | |
| linux_close(nic->fd); | |
| return ret; | |
| } | |
| /* Set nonblocking mode to make tap_poll easier */ | |
| ret = linux_fcntl(nic->fd, F_SETFL, O_NONBLOCK); | |
| if (ret != 0) { | |
| DBGC(nic, "tap %p fcntl(%d, ...) = %d (%s)\n", nic, nic->fd, ret, linux_strerror(linux_errno)); | |
| linux_close(nic->fd); | |
| return ret; | |
| } | |
| return 0; | |
| } | |
| /** Close the TAP device */ | |
| static void tap_close(struct net_device *netdev) | |
| { | |
| struct tap_nic * nic = netdev->priv; | |
| linux_close(nic->fd); | |
| } | |
| /** | |
| * Transmit an ethernet packet. | |
| * | |
| * The packet can be written to the TAP device and marked as complete immediately. | |
| */ | |
| static int tap_transmit(struct net_device *netdev, struct io_buffer *iobuf) | |
| { | |
| struct tap_nic * nic = netdev->priv; | |
| int rc; | |
| /* Pad and align packet */ | |
| iob_pad(iobuf, ETH_ZLEN); | |
| rc = linux_write(nic->fd, iobuf->data, iobuf->tail - iobuf->data); | |
| DBGC2(nic, "tap %p wrote %d bytes\n", nic, rc); | |
| netdev_tx_complete(netdev, iobuf); | |
| return 0; | |
| } | |
| /** Poll for new packets */ | |
| static void tap_poll(struct net_device *netdev) | |
| { | |
| struct tap_nic * nic = netdev->priv; | |
| struct pollfd pfd; | |
| struct io_buffer * iobuf; | |
| unsigned int quota = RX_QUOTA; | |
| int r; | |
| pfd.fd = nic->fd; | |
| pfd.events = POLLIN; | |
| if (linux_poll(&pfd, 1, 0) == -1) { | |
| DBGC(nic, "tap %p poll failed (%s)\n", nic, linux_strerror(linux_errno)); | |
| return; | |
| } | |
| if ((pfd.revents & POLLIN) == 0) | |
| return; | |
| /* At this point we know there is at least one new packet to be read */ | |
| iobuf = alloc_iob(RX_BUF_SIZE); | |
| if (! iobuf) | |
| goto allocfail; | |
| while (quota-- && | |
| ((r = linux_read(nic->fd, iobuf->data, RX_BUF_SIZE)) > 0)) { | |
| DBGC2(nic, "tap %p read %d bytes\n", nic, r); | |
| iob_put(iobuf, r); | |
| netdev_rx(netdev, iobuf); | |
| iobuf = alloc_iob(RX_BUF_SIZE); | |
| if (! iobuf) | |
| goto allocfail; | |
| } | |
| free_iob(iobuf); | |
| return; | |
| allocfail: | |
| DBGC(nic, "tap %p alloc_iob failed\n", nic); | |
| } | |
| /** | |
| * Set irq. | |
| * | |
| * Not used on linux, provide a dummy implementation. | |
| */ | |
| static void tap_irq(struct net_device *netdev, int enable) | |
| { | |
| struct tap_nic *nic = netdev->priv; | |
| DBGC(nic, "tap %p irq enable = %d\n", nic, enable); | |
| } | |
| /** Tap operations */ | |
| static struct net_device_operations tap_operations = { | |
| .open = tap_open, | |
| .close = tap_close, | |
| .transmit = tap_transmit, | |
| .poll = tap_poll, | |
| .irq = tap_irq, | |
| }; | |
| /** Handle a device request for the tap driver */ | |
| static int tap_probe(struct linux_device *device, struct linux_device_request *request) | |
| { | |
| struct linux_setting *if_setting; | |
| struct net_device *netdev; | |
| struct tap_nic *nic; | |
| int rc; | |
| netdev = alloc_etherdev(sizeof(*nic)); | |
| if (! netdev) | |
| return -ENOMEM; | |
| netdev_init(netdev, &tap_operations); | |
| nic = netdev->priv; | |
| linux_set_drvdata(device, netdev); | |
| netdev->dev = &device->dev; | |
| memcpy ( netdev->hw_addr, tap_default_mac, ETH_ALEN ); | |
| memset(nic, 0, sizeof(*nic)); | |
| /* Look for the mandatory if setting */ | |
| if_setting = linux_find_setting("if", &request->settings); | |
| /* No if setting */ | |
| if (! if_setting) { | |
| printf("tap missing a mandatory if setting\n"); | |
| rc = -EINVAL; | |
| goto err_settings; | |
| } | |
| nic->interface = if_setting->value; | |
| snprintf ( device->dev.name, sizeof ( device->dev.name ), "%s", | |
| nic->interface ); | |
| device->dev.desc.bus_type = BUS_TYPE_TAP; | |
| if_setting->applied = 1; | |
| /* Apply rest of the settings */ | |
| linux_apply_settings(&request->settings, &netdev->settings.settings); | |
| /* Register network device */ | |
| if ((rc = register_netdev(netdev)) != 0) | |
| goto err_register; | |
| netdev_link_up(netdev); | |
| return 0; | |
| unregister_netdev(netdev); | |
| err_register: | |
| err_settings: | |
| netdev_nullify(netdev); | |
| netdev_put(netdev); | |
| return rc; | |
| } | |
| /** Remove the device */ | |
| static void tap_remove(struct linux_device *device) | |
| { | |
| struct net_device *netdev = linux_get_drvdata(device); | |
| unregister_netdev(netdev); | |
| netdev_nullify(netdev); | |
| netdev_put(netdev); | |
| } | |
| /** Tap linux_driver */ | |
| struct linux_driver tap_driver __linux_driver = { | |
| .name = "tap", | |
| .probe = tap_probe, | |
| .remove = tap_remove, | |
| }; | |
| /* Generate build rules */ | |
| LINUX_ROM ( "tap", "Linux TUN/TAP NIC" ); |