diff options
Diffstat (limited to 'src/drivers')
-rw-r--r-- | src/drivers/net/efi/snp.h | 49 | ||||
-rw-r--r-- | src/drivers/net/efi/snpnet.c | 362 | ||||
-rw-r--r-- | src/drivers/net/efi/snpnet.h | 35 | ||||
-rw-r--r-- | src/drivers/net/efi/snponly.c | 129 |
4 files changed, 575 insertions, 0 deletions
diff --git a/src/drivers/net/efi/snp.h b/src/drivers/net/efi/snp.h new file mode 100644 index 0000000..4d6b101 --- /dev/null +++ b/src/drivers/net/efi/snp.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2010 VMware, Inc. All Rights Reserved. + * + * 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. + */ + +#ifndef _SNP_H +#define _SNP_H + +/** @file + * + * SNP driver + * + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include <ipxe/device.h> +#include <ipxe/netdevice.h> +#include <ipxe/efi/Protocol/SimpleNetwork.h> + +/** A network device that consumes the EFI Simple Network Protocol */ +struct snp_device { + /** Underlying simple network protocol instance */ + EFI_SIMPLE_NETWORK_PROTOCOL *snp; + + /** Generic device */ + struct device dev; + + /** Network device */ + struct net_device *netdev; + + /** State to put the snp in when removing the device */ + uint32 removal_state; +}; + +#endif /* _SNP_H */ diff --git a/src/drivers/net/efi/snpnet.c b/src/drivers/net/efi/snpnet.c new file mode 100644 index 0000000..ba63a01 --- /dev/null +++ b/src/drivers/net/efi/snpnet.c @@ -0,0 +1,362 @@ +/* + * Copyright (C) 2010 VMware, Inc. All Rights Reserved. + * + * 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. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include <errno.h> +#include <string.h> +#include <ipxe/io.h> +#include <ipxe/iobuf.h> +#include <ipxe/netdevice.h> +#include <ipxe/if_ether.h> +#include <ipxe/ethernet.h> +#include <ipxe/efi/efi.h> +#include <ipxe/efi/Protocol/SimpleNetwork.h> +#include "snp.h" +#include "snpnet.h" + +/** @file + * + * SNP network device driver + * + */ + +/** SNP net device structure */ +struct snpnet_device { + /** The underlying simple network protocol */ + EFI_SIMPLE_NETWORK_PROTOCOL *snp; + + /** State that the SNP should be in after close */ + UINT32 close_state; +}; + +/** + * Transmit packet + * + * @v netdev Network device + * @v iobuf I/O buffer + * @ret rc Return status code + */ +static int snpnet_transmit ( struct net_device *netdev, + struct io_buffer *iobuf ) { + struct snpnet_device *snpnetdev = netdev->priv; + EFI_SIMPLE_NETWORK_PROTOCOL *snp = snpnetdev->snp; + EFI_STATUS efirc; + size_t len = iob_len ( iobuf ); + + efirc = snp->Transmit ( snp, 0, len, iobuf->data, NULL, NULL, NULL ); + return EFIRC_TO_RC ( efirc ); +} + +/** + * Find a I/O buffer on the list of outstanding Tx buffers and complete it. + * + * @v snpnetdev SNP network device + * @v txbuf Buffer address + */ +static void snpnet_complete ( struct net_device *netdev, void *txbuf ) { + struct io_buffer *tmp; + struct io_buffer *iobuf; + + list_for_each_entry_safe ( iobuf, tmp, &netdev->tx_queue, list ) { + if ( iobuf->data == txbuf ) { + netdev_tx_complete ( netdev, iobuf ); + break; + } + } +} + +/** + * Poll for received packets + * + * @v netdev Network device + */ +static void snpnet_poll ( struct net_device *netdev ) { + struct snpnet_device *snpnetdev = netdev->priv; + EFI_SIMPLE_NETWORK_PROTOCOL *snp = snpnetdev->snp; + EFI_STATUS efirc; + struct io_buffer *iobuf = NULL; + UINTN len; + void *txbuf; + + /* Process Tx completions */ + while ( 1 ) { + efirc = snp->GetStatus ( snp, NULL, &txbuf ); + if ( efirc ) { + DBGC ( snp, "SNP %p could not get status %s\n", snp, + efi_strerror ( efirc ) ); + break; + } + + if ( txbuf == NULL ) + break; + + snpnet_complete ( netdev, txbuf ); + } + + /* Process received packets */ + while ( 1 ) { + /* The spec is not clear if the max packet size refers to the + * payload or the entire packet including headers. The Receive + * function needs a buffer large enough to contain the headers, + * and potentially a 4-byte CRC and 4-byte VLAN tag (?), so add + * some breathing room. + */ + len = snp->Mode->MaxPacketSize + ETH_HLEN + 8; + iobuf = alloc_iob ( len ); + if ( iobuf == NULL ) { + netdev_rx_err ( netdev, NULL, -ENOMEM ); + break; + } + + efirc = snp->Receive ( snp, NULL, &len, iobuf->data, + NULL, NULL, NULL ); + + /* No packets left? */ + if ( efirc == EFI_NOT_READY ) { + free_iob ( iobuf ); + break; + } + + /* Other error? */ + if ( efirc ) { + DBGC ( snp, "SNP %p receive packet error: %s " + "(len was %zd, is now %zd)\n", + snp, efi_strerror ( efirc ), iob_len(iobuf), + (size_t)len ); + netdev_rx_err ( netdev, iobuf, efirc ); + break; + } + + /* Packet is valid, deliver it */ + iob_put ( iobuf, len ); + netdev_rx ( netdev, iob_disown ( iobuf ) ); + } +} + +/** + * Open NIC + * + * @v netdev Net device + * @ret rc Return status code + */ +static int snpnet_open ( struct net_device *netdev ) { + struct snpnet_device *snpnetdev = netdev->priv; + EFI_SIMPLE_NETWORK_PROTOCOL *snp = snpnetdev->snp; + EFI_STATUS efirc; + UINT32 enableFlags, disableFlags; + + snpnetdev->close_state = snp->Mode->State; + if ( snp->Mode->State != EfiSimpleNetworkInitialized ) { + efirc = snp->Initialize ( snp, 0, 0 ); + if ( efirc ) { + DBGC ( snp, "SNP %p could not initialize: %s\n", + snp, efi_strerror ( efirc ) ); + return EFIRC_TO_RC ( efirc ); + } + } + + /* Use the default MAC address */ + efirc = snp->StationAddress ( snp, FALSE, + (EFI_MAC_ADDRESS *)netdev->ll_addr ); + if ( efirc ) { + DBGC ( snp, "SNP %p could not reset station address: %s\n", + snp, efi_strerror ( efirc ) ); + } + + /* Set up receive filters to receive unicast and broadcast packets + * always. Also, enable either promiscuous multicast (if possible) or + * promiscuous operation, in order to catch all multicast packets. + */ + enableFlags = snp->Mode->ReceiveFilterMask & + ( EFI_SIMPLE_NETWORK_RECEIVE_UNICAST | + EFI_SIMPLE_NETWORK_RECEIVE_BROADCAST ); + disableFlags = snp->Mode->ReceiveFilterMask & + ( EFI_SIMPLE_NETWORK_RECEIVE_MULTICAST | + EFI_SIMPLE_NETWORK_RECEIVE_PROMISCUOUS | + EFI_SIMPLE_NETWORK_RECEIVE_PROMISCUOUS_MULTICAST ); + if ( snp->Mode->ReceiveFilterMask & + EFI_SIMPLE_NETWORK_RECEIVE_PROMISCUOUS_MULTICAST ) { + enableFlags |= EFI_SIMPLE_NETWORK_RECEIVE_PROMISCUOUS_MULTICAST; + } else if ( snp->Mode->ReceiveFilterMask & + EFI_SIMPLE_NETWORK_RECEIVE_PROMISCUOUS ) { + enableFlags |= EFI_SIMPLE_NETWORK_RECEIVE_PROMISCUOUS; + } + disableFlags &= ~enableFlags; + efirc = snp->ReceiveFilters ( snp, enableFlags, disableFlags, + FALSE, 0, NULL ); + if ( efirc ) { + DBGC ( snp, "SNP %p could not set receive filters: %s\n", + snp, efi_strerror ( efirc ) ); + } + + DBGC ( snp, "SNP %p opened\n", snp ); + return 0; +} + +/** + * Close NIC + * + * @v netdev Net device + */ +static void snpnet_close ( struct net_device *netdev ) { + struct snpnet_device *snpnetdev = netdev->priv; + EFI_SIMPLE_NETWORK_PROTOCOL *snp = snpnetdev->snp; + EFI_STATUS efirc; + + if ( snpnetdev->close_state != EfiSimpleNetworkInitialized ) { + efirc = snp->Shutdown ( snp ); + if ( efirc ) { + DBGC ( snp, "SNP %p could not shut down: %s\n", + snp, efi_strerror ( efirc ) ); + } + } +} + +/** + * Enable/disable interrupts + * + * @v netdev Net device + * @v enable Interrupts should be enabled + */ +static void snpnet_irq ( struct net_device *netdev, int enable ) { + struct snpnet_device *snpnetdev = netdev->priv; + EFI_SIMPLE_NETWORK_PROTOCOL *snp = snpnetdev->snp; + + /* On EFI, interrupts are never necessary. (This function is only + * required for BIOS PXE.) If interrupts were required, they could be + * simulated using a fast timer. + */ + DBGC ( snp, "SNP %p cannot %s interrupts\n", + snp, ( enable ? "enable" : "disable" ) ); +} + +/** SNP network device operations */ +static struct net_device_operations snpnet_operations = { + .open = snpnet_open, + .close = snpnet_close, + .transmit = snpnet_transmit, + .poll = snpnet_poll, + .irq = snpnet_irq, +}; + +/** + * Probe SNP device + * + * @v snpdev SNP device + * @ret rc Return status code + */ +int snpnet_probe ( struct snp_device *snpdev ) { + EFI_SIMPLE_NETWORK_PROTOCOL *snp = snpdev->snp; + EFI_STATUS efirc; + struct net_device *netdev; + struct snpnet_device *snpnetdev; + int rc; + + DBGC ( snp, "SNP %p probing...\n", snp ); + + /* Allocate net device */ + netdev = alloc_etherdev ( sizeof ( struct snpnet_device ) ); + if ( ! netdev ) + return -ENOMEM; + netdev_init ( netdev, &snpnet_operations ); + netdev->dev = &snpdev->dev; + snpdev->netdev = netdev; + snpnetdev = netdev->priv; + snpnetdev->snp = snp; + snpdev->removal_state = snp->Mode->State; + + /* Start the interface */ + if ( snp->Mode->State == EfiSimpleNetworkStopped ) { + efirc = snp->Start ( snp ); + if ( efirc ) { + DBGC ( snp, "SNP %p could not start: %s\n", snp, + efi_strerror ( efirc ) ); + rc = EFIRC_TO_RC ( efirc ); + goto err_start; + } + } + + if ( snp->Mode->HwAddressSize > sizeof ( netdev->hw_addr ) ) { + DBGC ( snp, "SNP %p hardware address is too large\n", snp ); + rc = -EINVAL; + goto err_hwaddr; + } + memcpy ( netdev->hw_addr, snp->Mode->PermanentAddress.Addr, + snp->Mode->HwAddressSize ); + + /* Mark as link up; we don't handle link state */ + netdev_link_up ( netdev ); + + /* Register network device */ + if ( ( rc = register_netdev ( netdev ) ) != 0 ) + goto err_register; + + DBGC ( snp, "SNP %p added\n", snp ); + return 0; + +err_register: +err_hwaddr: + if ( snpdev->removal_state == EfiSimpleNetworkStopped ) + snp->Stop ( snp ); + +err_start: + netdev_nullify ( netdev ); + netdev_put ( netdev ); + snpdev->netdev = NULL; + return rc; +} + +/** + * Remove SNP device + * + * @v snpdev SNP device + */ +void snpnet_remove ( struct snp_device *snpdev ) { + EFI_SIMPLE_NETWORK_PROTOCOL *snp = snpdev->snp; + EFI_STATUS efirc; + struct net_device *netdev = snpdev->netdev; + + if ( snp->Mode->State == EfiSimpleNetworkInitialized && + snpdev->removal_state != EfiSimpleNetworkInitialized ) { + DBGC ( snp, "SNP %p shutting down\n", snp ); + efirc = snp->Shutdown ( snp ); + if ( efirc ) { + DBGC ( snp, "SNP %p could not shut down: %s\n", + snp, efi_strerror ( efirc ) ); + } + } + + if ( snp->Mode->State == EfiSimpleNetworkStarted && + snpdev->removal_state == EfiSimpleNetworkStopped ) { + DBGC ( snp, "SNP %p stopping\n", snp ); + efirc = snp->Stop ( snp ); + if ( efirc ) { + DBGC ( snp, "SNP %p could not be stopped\n", snp ); + } + } + + /* Unregister net device */ + unregister_netdev ( netdev ); + + /* Free network device */ + netdev_nullify ( netdev ); + netdev_put ( netdev ); + + DBGC ( snp, "SNP %p removed\n", snp ); +} diff --git a/src/drivers/net/efi/snpnet.h b/src/drivers/net/efi/snpnet.h new file mode 100644 index 0000000..72b4a7d --- /dev/null +++ b/src/drivers/net/efi/snpnet.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2010 VMware, Inc. All Rights Reserved. + * + * 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. + */ + +#ifndef _SNPNET_H +#define _SNPNET_H + +/** @file + * + * EFI Simple Network Protocol network device driver + * + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +struct snp_device; + +extern int snpnet_probe ( struct snp_device *snpdev ); +extern void snpnet_remove ( struct snp_device *snpdev ); + +#endif /* _SNPNET_H */ diff --git a/src/drivers/net/efi/snponly.c b/src/drivers/net/efi/snponly.c new file mode 100644 index 0000000..435ed4f --- /dev/null +++ b/src/drivers/net/efi/snponly.c @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2010 VMware, Inc. All Rights Reserved. + * + * 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. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include <string.h> +#include <errno.h> +#include <ipxe/device.h> +#include <ipxe/init.h> +#include <ipxe/efi/efi.h> +#include <ipxe/efi/Protocol/SimpleNetwork.h> +#include "snp.h" +#include "snpnet.h" + +/** @file + * + * Chain-loading Simple Network Protocol Bus Driver + * + * This bus driver allows iPXE to use the EFI Simple Network Protocol provided + * by the platform to transmit and receive packets. It attaches to only the + * device handle that iPXE was loaded from, that is, it will only use the + * Simple Network Protocol on the current loaded image's device handle. + * + * Eseentially, this driver provides the EFI equivalent of the "undionly" + * driver. + */ + +/** The one and only SNP network device */ +static struct snp_device snponly_dev; + +/** EFI simple network protocol GUID */ +static EFI_GUID efi_simple_network_protocol_guid + = EFI_SIMPLE_NETWORK_PROTOCOL_GUID; + +/** + * Probe SNP root bus + * + * @v rootdev SNP bus root device + * + * Look at the loaded image's device handle and see if the simple network + * protocol exists. If so, register a driver for it. + */ +static int snpbus_probe ( struct root_device *rootdev ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + EFI_STATUS efirc; + int rc; + void *snp; + + efirc = bs->OpenProtocol ( efi_loaded_image->DeviceHandle, + &efi_simple_network_protocol_guid, + &snp, efi_image_handle, NULL, + EFI_OPEN_PROTOCOL_GET_PROTOCOL ); + if ( efirc ) { + DBG ( "Could not find Simple Network Protocol!\n" ); + return -ENODEV; + } + snponly_dev.snp = snp; + + /* Add to device hierarchy */ + strncpy ( snponly_dev.dev.name, "EFI SNP", + ( sizeof ( snponly_dev.dev.name ) - 1 ) ); + snponly_dev.dev.parent = &rootdev->dev; + list_add ( &snponly_dev.dev.siblings, &rootdev->dev.children); + INIT_LIST_HEAD ( &snponly_dev.dev.children ); + + /* Create network device */ + if ( ( rc = snpnet_probe ( &snponly_dev ) ) != 0 ) + goto err; + + return 0; + +err: + list_del ( &snponly_dev.dev.siblings ); + return rc; +} + +/** + * Remove SNP root bus + * + * @v rootdev SNP bus root device + */ +static void snpbus_remove ( struct root_device *rootdev __unused ) { + snpnet_remove ( &snponly_dev ); + list_del ( &snponly_dev.dev.siblings ); +} + +/** SNP bus root device driver */ +static struct root_driver snp_root_driver = { + .probe = snpbus_probe, + .remove = snpbus_remove, +}; + +/** SNP bus root device */ +struct root_device snp_root_device __root_device = { + .dev = { .name = "EFI SNP" }, + .driver = &snp_root_driver, +}; + +/** + * Prepare for exit + * + * @v flags Shutdown flags + */ +static void snponly_shutdown ( int flags ) { + /* If we are shutting down to boot an OS, make sure the SNP does not + * stay active. + */ + if ( flags & SHUTDOWN_BOOT ) + snponly_dev.removal_state = EfiSimpleNetworkStopped; +} + +struct startup_fn startup_snponly __startup_fn ( STARTUP_LATE ) = { + .shutdown = snponly_shutdown, +}; |