diff options
author | Yang Zhang <yang.z.zhang@intel.com> | 2015-08-28 09:58:54 +0800 |
---|---|---|
committer | Yang Zhang <yang.z.zhang@intel.com> | 2015-09-01 12:44:00 +0800 |
commit | e44e3482bdb4d0ebde2d8b41830ac2cdb07948fb (patch) | |
tree | 66b09f592c55df2878107a468a91d21506104d3f /qemu/roms/ipxe/src/drivers/net/efi | |
parent | 9ca8dbcc65cfc63d6f5ef3312a33184e1d726e00 (diff) |
Add qemu 2.4.0
Change-Id: Ic99cbad4b61f8b127b7dc74d04576c0bcbaaf4f5
Signed-off-by: Yang Zhang <yang.z.zhang@intel.com>
Diffstat (limited to 'qemu/roms/ipxe/src/drivers/net/efi')
-rw-r--r-- | qemu/roms/ipxe/src/drivers/net/efi/nii.c | 1101 | ||||
-rw-r--r-- | qemu/roms/ipxe/src/drivers/net/efi/nii.h | 17 | ||||
-rw-r--r-- | qemu/roms/ipxe/src/drivers/net/efi/snp.c | 113 | ||||
-rw-r--r-- | qemu/roms/ipxe/src/drivers/net/efi/snpnet.c | 563 | ||||
-rw-r--r-- | qemu/roms/ipxe/src/drivers/net/efi/snpnet.h | 17 | ||||
-rw-r--r-- | qemu/roms/ipxe/src/drivers/net/efi/snponly.c | 208 |
6 files changed, 2019 insertions, 0 deletions
diff --git a/qemu/roms/ipxe/src/drivers/net/efi/nii.c b/qemu/roms/ipxe/src/drivers/net/efi/nii.c new file mode 100644 index 000000000..d0d7da95a --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/net/efi/nii.c @@ -0,0 +1,1101 @@ +/* + * Copyright (C) 2014 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 <strings.h> +#include <unistd.h> +#include <errno.h> +#include <ipxe/netdevice.h> +#include <ipxe/ethernet.h> +#include <ipxe/umalloc.h> +#include <ipxe/efi/efi.h> +#include <ipxe/efi/efi_driver.h> +#include <ipxe/efi/efi_pci.h> +#include <ipxe/efi/efi_utils.h> +#include <ipxe/efi/Protocol/NetworkInterfaceIdentifier.h> +#include <ipxe/efi/IndustryStandard/Acpi10.h> +#include "nii.h" + +/** @file + * + * NII driver + * + */ + +/* Error numbers generated by NII */ +#define EIO_INVALID_CDB __einfo_error ( EINFO_EIO_INVALID_CDB ) +#define EINFO_EIO_INVALID_CDB \ + __einfo_uniqify ( EINFO_EIO, PXE_STATCODE_INVALID_CDB, \ + "Invalid CDB" ) +#define EIO_INVALID_CPB __einfo_error ( EINFO_EIO_INVALID_CPB ) +#define EINFO_EIO_INVALID_CPB \ + __einfo_uniqify ( EINFO_EIO, PXE_STATCODE_INVALID_CPB, \ + "Invalid CPB" ) +#define EIO_BUSY __einfo_error ( EINFO_EIO_BUSY ) +#define EINFO_EIO_BUSY \ + __einfo_uniqify ( EINFO_EIO, PXE_STATCODE_BUSY, \ + "Busy" ) +#define EIO_QUEUE_FULL __einfo_error ( EINFO_EIO_QUEUE_FULL ) +#define EINFO_EIO_QUEUE_FULL \ + __einfo_uniqify ( EINFO_EIO, PXE_STATCODE_QUEUE_FULL, \ + "Queue full" ) +#define EIO_ALREADY_STARTED __einfo_error ( EINFO_EIO_ALREADY_STARTED ) +#define EINFO_EIO_ALREADY_STARTED \ + __einfo_uniqify ( EINFO_EIO, PXE_STATCODE_ALREADY_STARTED, \ + "Already started" ) +#define EIO_NOT_STARTED __einfo_error ( EINFO_EIO_NOT_STARTED ) +#define EINFO_EIO_NOT_STARTED \ + __einfo_uniqify ( EINFO_EIO, PXE_STATCODE_NOT_STARTED, \ + "Not started" ) +#define EIO_NOT_SHUTDOWN __einfo_error ( EINFO_EIO_NOT_SHUTDOWN ) +#define EINFO_EIO_NOT_SHUTDOWN \ + __einfo_uniqify ( EINFO_EIO, PXE_STATCODE_NOT_SHUTDOWN, \ + "Not shutdown" ) +#define EIO_ALREADY_INITIALIZED __einfo_error ( EINFO_EIO_ALREADY_INITIALIZED ) +#define EINFO_EIO_ALREADY_INITIALIZED \ + __einfo_uniqify ( EINFO_EIO, PXE_STATCODE_ALREADY_INITIALIZED, \ + "Already initialized" ) +#define EIO_NOT_INITIALIZED __einfo_error ( EINFO_EIO_NOT_INITIALIZED ) +#define EINFO_EIO_NOT_INITIALIZED \ + __einfo_uniqify ( EINFO_EIO, PXE_STATCODE_NOT_INITIALIZED, \ + "Not initialized" ) +#define EIO_DEVICE_FAILURE __einfo_error ( EINFO_EIO_DEVICE_FAILURE ) +#define EINFO_EIO_DEVICE_FAILURE \ + __einfo_uniqify ( EINFO_EIO, PXE_STATCODE_DEVICE_FAILURE, \ + "Device failure" ) +#define EIO_NVDATA_FAILURE __einfo_error ( EINFO_EIO_NVDATA_FAILURE ) +#define EINFO_EIO_NVDATA_FAILURE \ + __einfo_uniqify ( EINFO_EIO, PXE_STATCODE_NVDATA_FAILURE, \ + "Non-volatile data failure" ) +#define EIO_UNSUPPORTED __einfo_error ( EINFO_EIO_UNSUPPORTED ) +#define EINFO_EIO_UNSUPPORTED \ + __einfo_uniqify ( EINFO_EIO, PXE_STATCODE_UNSUPPORTED, \ + "Unsupported" ) +#define EIO_BUFFER_FULL __einfo_error ( EINFO_EIO_BUFFER_FULL ) +#define EINFO_EIO_BUFFER_FULL \ + __einfo_uniqify ( EINFO_EIO, PXE_STATCODE_BUFFER_FULL, \ + "Buffer full" ) +#define EIO_INVALID_PARAMETER __einfo_error ( EINFO_EIO_INVALID_PARAMETER ) +#define EINFO_EIO_INVALID_PARAMETER \ + __einfo_uniqify ( EINFO_EIO, PXE_STATCODE_INVALID_PARAMETER, \ + "Invalid parameter" ) +#define EIO_INVALID_UNDI __einfo_error ( EINFO_EIO_INVALID_UNDI ) +#define EINFO_EIO_INVALID_UNDI \ + __einfo_uniqify ( EINFO_EIO, PXE_STATCODE_INVALID_UNDI, \ + "Invalid UNDI" ) +#define EIO_IPV4_NOT_SUPPORTED __einfo_error ( EINFO_EIO_IPV4_NOT_SUPPORTED ) +#define EINFO_EIO_IPV4_NOT_SUPPORTED \ + __einfo_uniqify ( EINFO_EIO, PXE_STATCODE_IPV4_NOT_SUPPORTED, \ + "IPv4 not supported" ) +#define EIO_IPV6_NOT_SUPPORTED __einfo_error ( EINFO_EIO_IPV6_NOT_SUPPORTED ) +#define EINFO_EIO_IPV6_NOT_SUPPORTED \ + __einfo_uniqify ( EINFO_EIO, PXE_STATCODE_IPV6_NOT_SUPPORTED, \ + "IPv6 not supported" ) +#define EIO_NOT_ENOUGH_MEMORY __einfo_error ( EINFO_EIO_NOT_ENOUGH_MEMORY ) +#define EINFO_EIO_NOT_ENOUGH_MEMORY \ + __einfo_uniqify ( EINFO_EIO, PXE_STATCODE_NOT_ENOUGH_MEMORY, \ + "Not enough memory" ) +#define EIO_NO_DATA __einfo_error ( EINFO_EIO_NO_DATA ) +#define EINFO_EIO_NO_DATA \ + __einfo_uniqify ( EINFO_EIO, PXE_STATCODE_NO_DATA, \ + "No data" ) +#define EIO_STAT( stat ) \ + EUNIQ ( EINFO_EIO, -(stat), EIO_INVALID_CDB, EIO_INVALID_CPB, \ + EIO_BUSY, EIO_QUEUE_FULL, EIO_ALREADY_STARTED, \ + EIO_NOT_STARTED, EIO_NOT_SHUTDOWN, EIO_ALREADY_INITIALIZED, \ + EIO_NOT_INITIALIZED, EIO_DEVICE_FAILURE, EIO_NVDATA_FAILURE, \ + EIO_UNSUPPORTED, EIO_BUFFER_FULL, EIO_INVALID_PARAMETER, \ + EIO_INVALID_UNDI, EIO_IPV4_NOT_SUPPORTED, \ + EIO_IPV6_NOT_SUPPORTED, EIO_NOT_ENOUGH_MEMORY, EIO_NO_DATA ) + +/** Maximum PCI BAR + * + * This is defined in <ipxe/efi/IndustryStandard/Pci22.h>, but we + * can't #include that since it collides with <ipxe/pci.h>. + */ +#define PCI_MAX_BAR 6 + +/** An NII NIC */ +struct nii_nic { + /** EFI device */ + struct efi_device *efidev; + /** Network interface identifier protocol */ + EFI_NETWORK_INTERFACE_IDENTIFIER_PROTOCOL *nii; + /** !PXE structure */ + PXE_SW_UNDI *undi; + /** Entry point */ + EFIAPI VOID ( * issue ) ( UINT64 cdb ); + /** Generic device */ + struct device dev; + + /** PCI device */ + EFI_HANDLE pci_device; + /** PCI I/O protocol */ + EFI_PCI_IO_PROTOCOL *pci_io; + /** Memory BAR */ + unsigned int mem_bar; + /** I/O BAR */ + unsigned int io_bar; + + /** Broadcast address */ + PXE_MAC_ADDR broadcast; + /** Maximum packet length */ + size_t mtu; + + /** Hardware transmit/receive buffer */ + userptr_t buffer; + /** Hardware transmit/receive buffer length */ + size_t buffer_len; + + /** Saved task priority level */ + EFI_TPL saved_tpl; + + /** Current transmit buffer */ + struct io_buffer *txbuf; + /** Current receive buffer */ + struct io_buffer *rxbuf; +}; + +/** Maximum number of received packets per poll */ +#define NII_RX_QUOTA 4 + +/** + * Open PCI I/O protocol and identify BARs + * + * @v nii NII NIC + * @ret rc Return status code + */ +static int nii_pci_open ( struct nii_nic *nii ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + EFI_HANDLE device = nii->efidev->device; + EFI_HANDLE pci_device; + union { + EFI_PCI_IO_PROTOCOL *pci_io; + void *interface; + } pci_io; + union { + EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR *acpi; + void *resource; + } desc; + unsigned int bar; + EFI_STATUS efirc; + int rc; + + /* Locate PCI I/O protocol */ + if ( ( rc = efi_locate_device ( device, &efi_pci_io_protocol_guid, + &pci_device ) ) != 0 ) { + DBGC ( nii, "NII %s could not locate PCI I/O protocol: %s\n", + nii->dev.name, strerror ( rc ) ); + goto err_locate; + } + nii->pci_device = pci_device; + + /* Open PCI I/O protocol */ + if ( ( efirc = bs->OpenProtocol ( pci_device, &efi_pci_io_protocol_guid, + &pci_io.interface, efi_image_handle, + device, + EFI_OPEN_PROTOCOL_GET_PROTOCOL ))!=0){ + rc = -EEFI ( efirc ); + DBGC ( nii, "NII %s could not open PCI I/O protocol: %s\n", + nii->dev.name, strerror ( rc ) ); + goto err_open; + } + nii->pci_io = pci_io.pci_io; + + /* Identify memory and I/O BARs */ + nii->mem_bar = PCI_MAX_BAR; + nii->io_bar = PCI_MAX_BAR; + for ( bar = 0 ; bar < PCI_MAX_BAR ; bar++ ) { + efirc = nii->pci_io->GetBarAttributes ( nii->pci_io, bar, NULL, + &desc.resource ); + if ( efirc == EFI_UNSUPPORTED ) { + /* BAR not present; ignore */ + continue; + } + if ( efirc != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( nii, "NII %s could not get BAR %d attributes: " + "%s\n", nii->dev.name, bar, strerror ( rc ) ); + goto err_get_bar_attributes; + } + if ( desc.acpi->ResType == ACPI_ADDRESS_SPACE_TYPE_MEM ) { + nii->mem_bar = bar; + } else if ( desc.acpi->ResType == ACPI_ADDRESS_SPACE_TYPE_IO ) { + nii->io_bar = bar; + } + bs->FreePool ( desc.resource ); + } + DBGC ( nii, "NII %s has ", nii->dev.name ); + if ( nii->mem_bar < PCI_MAX_BAR ) { + DBGC ( nii, "memory BAR %d and ", nii->mem_bar ); + } else { + DBGC ( nii, "no memory BAR and " ); + } + if ( nii->io_bar < PCI_MAX_BAR ) { + DBGC ( nii, "I/O BAR %d\n", nii->io_bar ); + } else { + DBGC ( nii, "no I/O BAR\n" ); + } + + return 0; + + err_get_bar_attributes: + bs->CloseProtocol ( pci_device, &efi_pci_io_protocol_guid, + efi_image_handle, device ); + err_open: + err_locate: + return rc; +} + +/** + * Close PCI I/O protocol + * + * @v nii NII NIC + * @ret rc Return status code + */ +static void nii_pci_close ( struct nii_nic *nii ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + + bs->CloseProtocol ( nii->pci_device, &efi_pci_io_protocol_guid, + efi_image_handle, nii->efidev->device ); +} + +/** + * I/O callback + * + * @v unique_id NII NIC + * @v op Operations + * @v len Length of data + * @v addr Address + * @v data Data buffer + */ +static EFIAPI VOID nii_io ( UINT64 unique_id, UINT8 op, UINT8 len, UINT64 addr, + UINT64 data ) { + struct nii_nic *nii = ( ( void * ) ( intptr_t ) unique_id ); + EFI_PCI_IO_PROTOCOL_ACCESS *access; + EFI_PCI_IO_PROTOCOL_IO_MEM io; + EFI_PCI_IO_PROTOCOL_WIDTH width; + unsigned int bar; + EFI_STATUS efirc; + int rc; + + /* Determine accessor and BAR */ + if ( op & ( PXE_MEM_READ | PXE_MEM_WRITE ) ) { + access = &nii->pci_io->Mem; + bar = nii->mem_bar; + } else { + access = &nii->pci_io->Io; + bar = nii->io_bar; + } + + /* Determine operaton */ + io = ( ( op & ( PXE_IO_WRITE | PXE_MEM_WRITE ) ) ? + access->Write : access->Read ); + + /* Determine width */ + width = ( fls ( len ) - 1 ); + + /* Issue operation */ + if ( ( efirc = io ( nii->pci_io, width, bar, addr, 1, + ( ( void * ) ( intptr_t ) data ) ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( nii, "NII %s I/O operation %#x failed: %s\n", + nii->dev.name, op, strerror ( rc ) ); + /* No way to report failure */ + return; + } +} + +/** + * Delay callback + * + * @v unique_id NII NIC + * @v microseconds Delay in microseconds + */ +static EFIAPI VOID nii_delay ( UINT64 unique_id __unused, UINTN microseconds ) { + + udelay ( microseconds ); +} + +/** + * Block callback + * + * @v unique_id NII NIC + * @v acquire Acquire lock + */ +static EFIAPI VOID nii_block ( UINT64 unique_id, UINT32 acquire ) { + struct nii_nic *nii = ( ( void * ) ( intptr_t ) unique_id ); + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + + /* This functionality (which is copied verbatim from the + * SnpDxe implementation of this function) appears to be + * totally brain-dead, since it produces no actual blocking + * behaviour. + */ + if ( acquire ) { + nii->saved_tpl = bs->RaiseTPL ( TPL_NOTIFY ); + } else { + bs->RestoreTPL ( nii->saved_tpl ); + } +} + +/** + * Construct operation from opcode and flags + * + * @v opcode Opcode + * @v opflags Flags + * @ret op Operation + */ +#define NII_OP( opcode, opflags ) ( (opcode) | ( (opflags) << 16 ) ) + +/** + * Extract opcode from operation + * + * @v op Operation + * @ret opcode Opcode + */ +#define NII_OPCODE( op ) ( (op) & 0xffff ) + +/** + * Extract flags from operation + * + * @v op Operation + * @ret opflags Flags + */ +#define NII_OPFLAGS( op ) ( (op) >> 16 ) + +/** + * Issue command with parameter block and data block + * + * @v nii NII NIC + * @v op Operation + * @v cpb Command parameter block, or NULL + * @v cpb_len Command parameter block length + * @v db Data block, or NULL + * @v db_len Data block length + * @ret stat Status flags, or negative status code + */ +static int nii_issue_cpb_db ( struct nii_nic *nii, unsigned int op, void *cpb, + size_t cpb_len, void *db, size_t db_len ) { + PXE_CDB cdb; + + /* Prepare command descriptor block */ + memset ( &cdb, 0, sizeof ( cdb ) ); + cdb.OpCode = NII_OPCODE ( op ); + cdb.OpFlags = NII_OPFLAGS ( op ); + cdb.CPBaddr = ( ( intptr_t ) cpb ); + cdb.CPBsize = cpb_len; + cdb.DBaddr = ( ( intptr_t ) db ); + cdb.DBsize = db_len; + cdb.IFnum = nii->nii->IfNum; + + /* Issue command */ + nii->issue ( ( intptr_t ) &cdb ); + + /* Check completion status */ + if ( cdb.StatCode != PXE_STATCODE_SUCCESS ) + return -cdb.StatCode; + + /* Return command-specific status flags */ + return ( cdb.StatFlags & ~PXE_STATFLAGS_STATUS_MASK ); +} + +/** + * Issue command with parameter block + * + * @v nii NII NIC + * @v op Operation + * @v cpb Command parameter block, or NULL + * @v cpb_len Command parameter block length + * @ret stat Status flags, or negative status code + */ +static int nii_issue_cpb ( struct nii_nic *nii, unsigned int op, void *cpb, + size_t cpb_len ) { + + return nii_issue_cpb_db ( nii, op, cpb, cpb_len, NULL, 0 ); +} + +/** + * Issue command with data block + * + * @v nii NII NIC + * @v op Operation + * @v db Data block, or NULL + * @v db_len Data block length + * @ret stat Status flags, or negative status code + */ +static int nii_issue_db ( struct nii_nic *nii, unsigned int op, void *db, + size_t db_len ) { + + return nii_issue_cpb_db ( nii, op, NULL, 0, db, db_len ); +} + +/** + * Issue command + * + * + * @v nii NII NIC + * @v op Operation + * @ret stat Status flags, or negative status code + */ +static int nii_issue ( struct nii_nic *nii, unsigned int op ) { + + return nii_issue_cpb_db ( nii, op, NULL, 0, NULL, 0 ); +} + +/** + * Start UNDI + * + * @v nii NII NIC + * @ret rc Return status code + */ +static int nii_start_undi ( struct nii_nic *nii ) { + PXE_CPB_START_31 cpb; + int stat; + int rc; + + /* Construct parameter block */ + memset ( &cpb, 0, sizeof ( cpb ) ); + cpb.Delay = ( ( intptr_t ) nii_delay ); + cpb.Block = ( ( intptr_t ) nii_block ); + cpb.Mem_IO = ( ( intptr_t ) nii_io ); + cpb.Unique_ID = ( ( intptr_t ) nii ); + + /* Issue command */ + if ( ( stat = nii_issue_cpb ( nii, PXE_OPCODE_START, &cpb, + sizeof ( cpb ) ) ) < 0 ) { + rc = -EIO_STAT ( stat ); + DBGC ( nii, "NII %s could not start: %s\n", + nii->dev.name, strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Stop UNDI + * + * @v nii NII NIC + */ +static void nii_stop_undi ( struct nii_nic *nii ) { + int stat; + int rc; + + /* Issue command */ + if ( ( stat = nii_issue ( nii, PXE_OPCODE_STOP ) ) < 0 ) { + rc = -EIO_STAT ( stat ); + DBGC ( nii, "NII %s could not stop: %s\n", + nii->dev.name, strerror ( rc ) ); + /* Nothing we can do about it */ + return; + } +} + +/** + * Get initialisation information + * + * @v nii NII NIC + * @v netdev Network device to fill in + * @ret rc Return status code + */ +static int nii_get_init_info ( struct nii_nic *nii, + struct net_device *netdev ) { + PXE_DB_GET_INIT_INFO db; + int stat; + int rc; + + /* Issue command */ + if ( ( stat = nii_issue_db ( nii, PXE_OPCODE_GET_INIT_INFO, &db, + sizeof ( db ) ) ) < 0 ) { + rc = -EIO_STAT ( stat ); + DBGC ( nii, "NII %s could not get initialisation info: %s\n", + nii->dev.name, strerror ( rc ) ); + return rc; + } + + /* Determine link layer protocol */ + switch ( db.IFtype ) { + case PXE_IFTYPE_ETHERNET : + netdev->ll_protocol = ðernet_protocol; + break; + default: + DBGC ( nii, "NII %s unknown interface type %#02x\n", + nii->dev.name, db.IFtype ); + return -ENOTSUP; + } + + /* Sanity checks */ + assert ( db.MediaHeaderLen == netdev->ll_protocol->ll_header_len ); + assert ( db.HWaddrLen == netdev->ll_protocol->hw_addr_len ); + assert ( db.HWaddrLen == netdev->ll_protocol->ll_addr_len ); + + /* Extract parameters */ + nii->buffer_len = db.MemoryRequired; + nii->mtu = ( db.FrameDataLen + db.MediaHeaderLen ); + netdev->max_pkt_len = nii->mtu; + + return 0; +} + +/** + * Initialise UNDI + * + * @v nii NII NIC + * @ret rc Return status code + */ +static int nii_initialise ( struct nii_nic *nii ) { + PXE_CPB_INITIALIZE cpb; + unsigned int op; + int stat; + int rc; + + /* Allocate memory buffer */ + nii->buffer = umalloc ( nii->buffer_len ); + if ( ! nii->buffer ) { + rc = -ENOMEM; + goto err_alloc; + } + + /* Construct parameter block */ + memset ( &cpb, 0, sizeof ( cpb ) ); + cpb.MemoryAddr = ( ( intptr_t ) nii->buffer ); + cpb.MemoryLength = nii->buffer_len; + + /* Issue command */ + op = NII_OP ( PXE_OPCODE_INITIALIZE, + PXE_OPFLAGS_INITIALIZE_DO_NOT_DETECT_CABLE ); + if ( ( stat = nii_issue_cpb ( nii, op, &cpb, sizeof ( cpb ) ) ) < 0 ) { + rc = -EIO_STAT ( stat ); + DBGC ( nii, "NII %s could not initialise: %s\n", + nii->dev.name, strerror ( rc ) ); + goto err_initialize; + } + + return 0; + + err_initialize: + ufree ( nii->buffer ); + err_alloc: + return rc; +} + +/** + * Shut down UNDI + * + * @v nii NII NIC + */ +static void nii_shutdown ( struct nii_nic *nii ) { + int stat; + int rc; + + /* Issue command */ + if ( ( stat = nii_issue ( nii, PXE_OPCODE_SHUTDOWN ) ) < 0 ) { + rc = -EIO_STAT ( stat ); + DBGC ( nii, "NII %s could not shut down: %s\n", + nii->dev.name, strerror ( rc ) ); + /* Leak memory to avoid corruption */ + return; + } + + /* Free buffer */ + ufree ( nii->buffer ); +} + +/** + * Get station addresses + * + * @v nii NII NIC + * @v netdev Network device to fill in + * @ret rc Return status code + */ +static int nii_get_station_address ( struct nii_nic *nii, + struct net_device *netdev ) { + PXE_DB_STATION_ADDRESS db; + int stat; + int rc; + + /* Initialise UNDI */ + if ( ( rc = nii_initialise ( nii ) ) != 0 ) + goto err_initialise; + + /* Issue command */ + if ( ( stat = nii_issue_db ( nii, PXE_OPCODE_STATION_ADDRESS, &db, + sizeof ( db ) ) ) < 0 ) { + rc = -EIO_STAT ( stat ); + DBGC ( nii, "NII %s could not get station address: %s\n", + nii->dev.name, strerror ( rc ) ); + goto err_station_address; + } + + /* Copy MAC addresses */ + memcpy ( netdev->ll_addr, db.StationAddr, + netdev->ll_protocol->ll_addr_len ); + memcpy ( netdev->hw_addr, db.PermanentAddr, + netdev->ll_protocol->hw_addr_len ); + memcpy ( nii->broadcast, db.BroadcastAddr, + sizeof ( nii->broadcast ) ); + + err_station_address: + nii_shutdown ( nii ); + err_initialise: + return rc; +} + +/** + * Set station address + * + * @v nii NII NIC + * @v netdev Network device + * @ret rc Return status code + */ +static int nii_set_station_address ( struct nii_nic *nii, + struct net_device *netdev ) { + PXE_CPB_STATION_ADDRESS cpb; + int stat; + int rc; + + /* Construct parameter block */ + memset ( &cpb, 0, sizeof ( cpb ) ); + memcpy ( cpb.StationAddr, netdev->ll_addr, + netdev->ll_protocol->ll_addr_len ); + + /* Issue command */ + if ( ( stat = nii_issue_cpb ( nii, PXE_OPCODE_STATION_ADDRESS, + &cpb, sizeof ( cpb ) ) ) < 0 ) { + rc = -EIO_STAT ( stat ); + DBGC ( nii, "NII %s could not set station address: %s\n", + nii->dev.name, strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Set receive filters + * + * @v nii NII NIC + * @ret rc Return status code + */ +static int nii_set_rx_filters ( struct nii_nic *nii ) { + unsigned int op; + int stat; + int rc; + + /* Issue command */ + op = NII_OP ( PXE_OPCODE_RECEIVE_FILTERS, + ( PXE_OPFLAGS_RECEIVE_FILTER_ENABLE | + PXE_OPFLAGS_RECEIVE_FILTER_UNICAST | + PXE_OPFLAGS_RECEIVE_FILTER_BROADCAST | + PXE_OPFLAGS_RECEIVE_FILTER_PROMISCUOUS | + PXE_OPFLAGS_RECEIVE_FILTER_ALL_MULTICAST ) ); + if ( ( stat = nii_issue ( nii, op ) ) < 0 ) { + rc = -EIO_STAT ( stat ); + DBGC ( nii, "NII %s could not set receive filters: %s\n", + nii->dev.name, strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Transmit packet + * + * @v netdev Network device + * @v iobuf I/O buffer + * @ret rc Return status code + */ +static int nii_transmit ( struct net_device *netdev, + struct io_buffer *iobuf ) { + struct nii_nic *nii = netdev->priv; + PXE_CPB_TRANSMIT cpb; + int stat; + int rc; + + /* Defer the packet if there is already a transmission in progress */ + if ( nii->txbuf ) { + netdev_tx_defer ( netdev, iobuf ); + return 0; + } + + /* Construct parameter block */ + memset ( &cpb, 0, sizeof ( cpb ) ); + cpb.FrameAddr = virt_to_bus ( iobuf->data ); + cpb.DataLen = iob_len ( iobuf ); + cpb.MediaheaderLen = netdev->ll_protocol->ll_header_len; + + /* Transmit packet */ + if ( ( stat = nii_issue_cpb ( nii, PXE_OPCODE_TRANSMIT, &cpb, + sizeof ( cpb ) ) ) < 0 ) { + rc = -EIO_STAT ( stat ); + DBGC ( nii, "NII %s could not transmit: %s\n", + nii->dev.name, strerror ( rc ) ); + return rc; + } + nii->txbuf = iobuf; + + return 0; +} + +/** + * Poll for completed packets + * + * @v netdev Network device + * @v stat Status flags + */ +static void nii_poll_tx ( struct net_device *netdev, unsigned int stat ) { + struct nii_nic *nii = netdev->priv; + struct io_buffer *iobuf; + + /* Do nothing unless we have a completion */ + if ( stat & PXE_STATFLAGS_GET_STATUS_NO_TXBUFS_WRITTEN ) + return; + + /* Sanity check */ + if ( ! nii->txbuf ) { + DBGC ( nii, "NII %s reported spurious TX completion\n", + nii->dev.name ); + netdev_tx_err ( netdev, NULL, -EPIPE ); + return; + } + + /* Complete transmission */ + iobuf = nii->txbuf; + nii->txbuf = NULL; + netdev_tx_complete ( netdev, iobuf ); +} + +/** + * Poll for received packets + * + * @v netdev Network device + */ +static void nii_poll_rx ( struct net_device *netdev ) { + struct nii_nic *nii = netdev->priv; + PXE_CPB_RECEIVE cpb; + PXE_DB_RECEIVE db; + unsigned int quota; + int stat; + int rc; + + /* Retrieve up to NII_RX_QUOTA packets */ + for ( quota = NII_RX_QUOTA ; quota ; quota-- ) { + + /* Allocate buffer, if required */ + if ( ! nii->rxbuf ) { + nii->rxbuf = alloc_iob ( nii->mtu ); + if ( ! nii->rxbuf ) { + /* Leave for next poll */ + break; + } + } + + /* Construct parameter block */ + memset ( &cpb, 0, sizeof ( cpb ) ); + cpb.BufferAddr = virt_to_bus ( nii->rxbuf->data ); + cpb.BufferLen = iob_tailroom ( nii->rxbuf ); + + /* Issue command */ + if ( ( stat = nii_issue_cpb_db ( nii, PXE_OPCODE_RECEIVE, + &cpb, sizeof ( cpb ), + &db, sizeof ( db ) ) ) < 0 ) { + + /* PXE_STATCODE_NO_DATA is just the usual "no packet" + * status indicator; ignore it. + */ + if ( stat == -PXE_STATCODE_NO_DATA ) + break; + + /* Anything else is an error */ + rc = -EIO_STAT ( stat ); + DBGC ( nii, "NII %s could not receive: %s\n", + nii->dev.name, strerror ( rc ) ); + netdev_rx_err ( netdev, NULL, rc ); + break; + } + + /* Hand off to network stack */ + iob_put ( nii->rxbuf, db.FrameLen ); + netdev_rx ( netdev, nii->rxbuf ); + nii->rxbuf = NULL; + } +} + +/** + * Check for link state changes + * + * @v netdev Network device + * @v stat Status flags + */ +static void nii_poll_link ( struct net_device *netdev, unsigned int stat ) { + int no_media = ( stat & PXE_STATFLAGS_GET_STATUS_NO_MEDIA ); + + if ( no_media && netdev_link_ok ( netdev ) ) { + netdev_link_down ( netdev ); + } else if ( ( ! no_media ) && ( ! netdev_link_ok ( netdev ) ) ) { + netdev_link_up ( netdev ); + } +} + +/** + * Poll for completed packets + * + * @v netdev Network device + */ +static void nii_poll ( struct net_device *netdev ) { + struct nii_nic *nii = netdev->priv; + PXE_DB_GET_STATUS db; + unsigned int op; + int stat; + int rc; + + /* Get status */ + op = NII_OP ( PXE_OPCODE_GET_STATUS, + ( PXE_OPFLAGS_GET_INTERRUPT_STATUS | + PXE_OPFLAGS_GET_TRANSMITTED_BUFFERS | + PXE_OPFLAGS_GET_MEDIA_STATUS ) ); + if ( ( stat = nii_issue_db ( nii, op, &db, sizeof ( db ) ) ) < 0 ) { + rc = -EIO_STAT ( stat ); + DBGC ( nii, "NII %s could not get status: %s\n", + nii->dev.name, strerror ( rc ) ); + return; + } + + /* Process any TX completions */ + nii_poll_tx ( netdev, stat ); + + /* Process any RX completions */ + nii_poll_rx ( netdev ); + + /* Check for link state changes */ + nii_poll_link ( netdev, stat ); +} + +/** + * Open network device + * + * @v netdev Network device + * @ret rc Return status code + */ +static int nii_open ( struct net_device *netdev ) { + struct nii_nic *nii = netdev->priv; + int rc; + + /* Initialise NIC */ + if ( ( rc = nii_initialise ( nii ) ) != 0 ) + goto err_initialise; + + /* Attempt to set station address */ + if ( ( rc = nii_set_station_address ( nii, netdev ) ) != 0 ) { + DBGC ( nii, "NII %s could not set station address: %s\n", + nii->dev.name, strerror ( rc ) ); + /* Treat as non-fatal */ + } + + /* Set receive filters */ + if ( ( rc = nii_set_rx_filters ( nii ) ) != 0 ) + goto err_set_rx_filters; + + return 0; + + err_set_rx_filters: + nii_shutdown ( nii ); + err_initialise: + return rc; +} + +/** + * Close network device + * + * @v netdev Network device + */ +static void nii_close ( struct net_device *netdev ) { + struct nii_nic *nii = netdev->priv; + + /* Shut down NIC */ + nii_shutdown ( nii ); + + /* Discard transmit buffer, if applicable */ + if ( nii->txbuf ) { + netdev_tx_complete_err ( netdev, nii->txbuf, -ECANCELED ); + nii->txbuf = NULL; + } + + /* Discard receive buffer, if applicable */ + if ( nii->rxbuf ) { + free_iob ( nii->rxbuf ); + nii->rxbuf = NULL; + } +} + +/** NII network device operations */ +static struct net_device_operations nii_operations = { + .open = nii_open, + .close = nii_close, + .transmit = nii_transmit, + .poll = nii_poll, +}; + +/** + * Attach driver to device + * + * @v efidev EFI device + * @ret rc Return status code + */ +int nii_start ( struct efi_device *efidev ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + EFI_HANDLE device = efidev->device; + struct net_device *netdev; + struct nii_nic *nii; + void *interface; + EFI_STATUS efirc; + int rc; + + /* Allocate and initialise structure */ + netdev = alloc_netdev ( sizeof ( *nii ) ); + if ( ! netdev ) { + rc = -ENOMEM; + goto err_alloc; + } + netdev_init ( netdev, &nii_operations ); + nii = netdev->priv; + nii->efidev = efidev; + netdev->ll_broadcast = nii->broadcast; + efidev_set_drvdata ( efidev, netdev ); + + /* Populate underlying device information */ + efi_device_info ( device, "NII", &nii->dev ); + nii->dev.driver_name = "NII"; + nii->dev.parent = &efidev->dev; + list_add ( &nii->dev.siblings, &efidev->dev.children ); + INIT_LIST_HEAD ( &nii->dev.children ); + netdev->dev = &nii->dev; + + /* Open NII protocol */ + if ( ( efirc = bs->OpenProtocol ( device, &efi_nii31_protocol_guid, + &interface, efi_image_handle, device, + ( EFI_OPEN_PROTOCOL_BY_DRIVER | + EFI_OPEN_PROTOCOL_EXCLUSIVE )))!=0){ + rc = -EEFI ( efirc ); + DBGC ( nii, "NII %s cannot open NII protocol: %s\n", + nii->dev.name, strerror ( rc ) ); + DBGC_EFI_OPENERS ( device, device, &efi_nii31_protocol_guid ); + goto err_open_protocol; + } + nii->nii = interface; + + /* Locate UNDI and entry point */ + nii->undi = ( ( void * ) ( intptr_t ) nii->nii->Id ); + if ( ! nii->undi ) { + DBGC ( nii, "NII %s has no UNDI\n", nii->dev.name ); + rc = -ENODEV; + goto err_no_undi; + } + if ( nii->undi->Implementation & PXE_ROMID_IMP_HW_UNDI ) { + DBGC ( nii, "NII %s is a mythical hardware UNDI\n", + nii->dev.name ); + rc = -ENOTSUP; + goto err_hw_undi; + } + if ( nii->undi->Implementation & PXE_ROMID_IMP_SW_VIRT_ADDR ) { + nii->issue = ( ( void * ) ( intptr_t ) nii->undi->EntryPoint ); + } else { + nii->issue = ( ( ( void * ) nii->undi ) + + nii->undi->EntryPoint ); + } + DBGC ( nii, "NII %s using UNDI v%x.%x at %p entry %p\n", nii->dev.name, + nii->nii->MajorVer, nii->nii->MinorVer, nii->undi, nii->issue ); + + /* Open PCI I/O protocols and locate BARs */ + if ( ( rc = nii_pci_open ( nii ) ) != 0 ) + goto err_pci_open; + + /* Start UNDI */ + if ( ( rc = nii_start_undi ( nii ) ) != 0 ) + goto err_start_undi; + + /* Get initialisation information */ + if ( ( rc = nii_get_init_info ( nii, netdev ) ) != 0 ) + goto err_get_init_info; + + /* Get MAC addresses */ + if ( ( rc = nii_get_station_address ( nii, netdev ) ) != 0 ) + goto err_get_station_address; + + /* Register network device */ + if ( ( rc = register_netdev ( netdev ) ) != 0 ) + goto err_register_netdev; + DBGC ( nii, "NII %s registered as %s for %p %s\n", nii->dev.name, + netdev->name, device, efi_handle_name ( device ) ); + + return 0; + + unregister_netdev ( netdev ); + err_register_netdev: + err_get_station_address: + err_get_init_info: + nii_stop_undi ( nii ); + err_start_undi: + nii_pci_close ( nii ); + err_pci_open: + err_hw_undi: + err_no_undi: + bs->CloseProtocol ( device, &efi_nii31_protocol_guid, + efi_image_handle, device ); + err_open_protocol: + list_del ( &nii->dev.siblings ); + netdev_nullify ( netdev ); + netdev_put ( netdev ); + err_alloc: + return rc; +} + +/** + * Detach driver from device + * + * @v efidev EFI device + */ +void nii_stop ( struct efi_device *efidev ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + struct net_device *netdev = efidev_get_drvdata ( efidev ); + struct nii_nic *nii = netdev->priv; + EFI_HANDLE device = efidev->device; + + /* Unregister network device */ + unregister_netdev ( netdev ); + + /* Stop UNDI */ + nii_stop_undi ( nii ); + + /* Close PCI I/O protocols */ + nii_pci_close ( nii ); + + /* Close NII protocol */ + bs->CloseProtocol ( device, &efi_nii31_protocol_guid, + efi_image_handle, device ); + + /* Free network device */ + list_del ( &nii->dev.siblings ); + netdev_nullify ( netdev ); + netdev_put ( netdev ); +} diff --git a/qemu/roms/ipxe/src/drivers/net/efi/nii.h b/qemu/roms/ipxe/src/drivers/net/efi/nii.h new file mode 100644 index 000000000..de0ac687b --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/net/efi/nii.h @@ -0,0 +1,17 @@ +#ifndef _NII_H +#define _NII_H + +/** @file + * + * NII driver + * + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +struct efi_device; + +extern int nii_start ( struct efi_device *efidev ); +extern void nii_stop ( struct efi_device *efidev ); + +#endif /* _NII_H */ diff --git a/qemu/roms/ipxe/src/drivers/net/efi/snp.c b/qemu/roms/ipxe/src/drivers/net/efi/snp.c new file mode 100644 index 000000000..2b5fc8618 --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/net/efi/snp.c @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2014 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 <errno.h> +#include <ipxe/efi/efi.h> +#include <ipxe/efi/efi_driver.h> +#include <ipxe/efi/efi_snp.h> +#include "snpnet.h" +#include "nii.h" + +/** @file + * + * SNP driver + * + */ + +/** + * Check to see if driver supports a device + * + * @v device EFI device handle + * @ret rc Return status code + */ +static int snp_supported ( EFI_HANDLE device ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + EFI_STATUS efirc; + + /* Check that this is not a device we are providing ourselves */ + if ( find_snpdev ( device ) != NULL ) { + DBGCP ( device, "SNP %p %s is provided by this binary\n", + device, efi_handle_name ( device ) ); + return -ENOTTY; + } + + /* Test for presence of simple network protocol */ + if ( ( efirc = bs->OpenProtocol ( device, + &efi_simple_network_protocol_guid, + NULL, efi_image_handle, device, + EFI_OPEN_PROTOCOL_TEST_PROTOCOL))!=0){ + DBGCP ( device, "SNP %p %s is not an SNP device\n", + device, efi_handle_name ( device ) ); + return -EEFI ( efirc ); + } + DBGC ( device, "SNP %p %s is an SNP device\n", + device, efi_handle_name ( device ) ); + + return 0; +} + +/** + * Check to see if driver supports a device + * + * @v device EFI device handle + * @ret rc Return status code + */ +static int nii_supported ( EFI_HANDLE device ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + EFI_STATUS efirc; + + /* Check that this is not a device we are providing ourselves */ + if ( find_snpdev ( device ) != NULL ) { + DBGCP ( device, "NII %p %s is provided by this binary\n", + device, efi_handle_name ( device ) ); + return -ENOTTY; + } + + /* Test for presence of NII protocol */ + if ( ( efirc = bs->OpenProtocol ( device, + &efi_nii31_protocol_guid, + NULL, efi_image_handle, device, + EFI_OPEN_PROTOCOL_TEST_PROTOCOL))!=0){ + DBGCP ( device, "NII %p %s is not an NII device\n", + device, efi_handle_name ( device ) ); + return -EEFI ( efirc ); + } + DBGC ( device, "NII %p %s is an NII device\n", + device, efi_handle_name ( device ) ); + + return 0; +} + +/** EFI SNP driver */ +struct efi_driver snp_driver __efi_driver ( EFI_DRIVER_NORMAL ) = { + .name = "SNP", + .supported = snp_supported, + .start = snpnet_start, + .stop = snpnet_stop, +}; + +/** EFI NII driver */ +struct efi_driver nii_driver __efi_driver ( EFI_DRIVER_NORMAL ) = { + .name = "NII", + .supported = nii_supported, + .start = nii_start, + .stop = nii_stop, +}; diff --git a/qemu/roms/ipxe/src/drivers/net/efi/snpnet.c b/qemu/roms/ipxe/src/drivers/net/efi/snpnet.c new file mode 100644 index 000000000..96642c4ca --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/net/efi/snpnet.c @@ -0,0 +1,563 @@ +/* + * Copyright (C) 2014 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 <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <ipxe/iobuf.h> +#include <ipxe/netdevice.h> +#include <ipxe/ethernet.h> +#include <ipxe/vsprintf.h> +#include <ipxe/efi/efi.h> +#include <ipxe/efi/Protocol/SimpleNetwork.h> +#include <ipxe/efi/efi_driver.h> +#include <ipxe/efi/efi_utils.h> +#include "snpnet.h" + +/** @file + * + * SNP NIC driver + * + */ + +/** An SNP NIC */ +struct snp_nic { + /** EFI device */ + struct efi_device *efidev; + /** Simple network protocol */ + EFI_SIMPLE_NETWORK_PROTOCOL *snp; + /** Generic device */ + struct device dev; + + /** Maximum packet size + * + * This is calculated as the sum of MediaHeaderSize and + * MaxPacketSize, and may therefore be an overestimate. + */ + size_t mtu; + + /** Current transmit buffer */ + struct io_buffer *txbuf; + /** Current receive buffer */ + struct io_buffer *rxbuf; +}; + +/** Maximum number of received packets per poll */ +#define SNP_RX_QUOTA 4 + +/** + * Format SNP MAC address (for debugging) + * + * @v mac MAC address + * @v len Length of MAC address + * @ret text MAC address as text + */ +static const char * snpnet_mac_text ( EFI_MAC_ADDRESS *mac, size_t len ) { + static char buf[ sizeof ( *mac ) * 3 /* "xx:" or "xx\0" */ ]; + size_t used = 0; + unsigned int i; + + for ( i = 0 ; i < len ; i++ ) { + used += ssnprintf ( &buf[used], ( sizeof ( buf ) - used ), + "%s%02x", ( used ? ":" : "" ), + mac->Addr[i] ); + } + return buf; +} + +/** + * Dump SNP mode information (for debugging) + * + * @v netdev Network device + */ +static void snpnet_dump_mode ( struct net_device *netdev ) { + struct snp_nic *snp = netdev_priv ( netdev ); + EFI_SIMPLE_NETWORK_MODE *mode = snp->snp->Mode; + size_t mac_len = mode->HwAddressSize; + unsigned int i; + + /* Do nothing unless debugging is enabled */ + if ( ! DBG_EXTRA ) + return; + + DBGC2 ( snp, "SNP %s st %d type %d hdr %d pkt %d rxflt %#x/%#x%s " + "nvram %d acc %d mcast %d/%d\n", netdev->name, mode->State, + mode->IfType, mode->MediaHeaderSize, mode->MaxPacketSize, + mode->ReceiveFilterSetting, mode->ReceiveFilterMask, + ( mode->MultipleTxSupported ? " multitx" : "" ), + mode->NvRamSize, mode->NvRamAccessSize, + mode->MCastFilterCount, mode->MaxMCastFilterCount ); + DBGC2 ( snp, "SNP %s hw %s", netdev->name, + snpnet_mac_text ( &mode->PermanentAddress, mac_len ) ); + DBGC2 ( snp, " addr %s%s", + snpnet_mac_text ( &mode->CurrentAddress, mac_len ), + ( mode->MacAddressChangeable ? "" : "(f)" ) ); + DBGC2 ( snp, " bcast %s\n", + snpnet_mac_text ( &mode->BroadcastAddress, mac_len ) ); + for ( i = 0 ; i < mode->MCastFilterCount ; i++ ) { + DBGC2 ( snp, "SNP %s mcast %s\n", netdev->name, + snpnet_mac_text ( &mode->MCastFilter[i], mac_len ) ); + } + DBGC2 ( snp, "SNP %s media %s\n", netdev->name, + ( mode->MediaPresentSupported ? + ( mode->MediaPresent ? "present" : "not present" ) : + "presence not supported" ) ); +} + +/** + * Check link state + * + * @v netdev Network device + */ +static void snpnet_check_link ( struct net_device *netdev ) { + struct snp_nic *snp = netdev_priv ( netdev ); + EFI_SIMPLE_NETWORK_MODE *mode = snp->snp->Mode; + + /* Do nothing unless media presence detection is supported */ + if ( ! mode->MediaPresentSupported ) + return; + + /* Report any link status change */ + if ( mode->MediaPresent && ( ! netdev_link_ok ( netdev ) ) ) { + netdev_link_up ( netdev ); + } else if ( ( ! mode->MediaPresent ) && netdev_link_ok ( netdev ) ) { + netdev_link_down ( netdev ); + } +} + +/** + * 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 snp_nic *snp = netdev_priv ( netdev ); + EFI_STATUS efirc; + int rc; + + /* Defer the packet if there is already a transmission in progress */ + if ( snp->txbuf ) { + netdev_tx_defer ( netdev, iobuf ); + return 0; + } + + /* Transmit packet */ + if ( ( efirc = snp->snp->Transmit ( snp->snp, 0, iob_len ( iobuf ), + iobuf->data, NULL, NULL, + NULL ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( snp, "SNP %s could not transmit: %s\n", + netdev->name, strerror ( rc ) ); + return rc; + } + snp->txbuf = iobuf; + + return 0; +} + +/** + * Poll for completed packets + * + * @v netdev Network device + */ +static void snpnet_poll_tx ( struct net_device *netdev ) { + struct snp_nic *snp = netdev->priv; + struct io_buffer *iobuf; + UINT32 irq; + VOID *txbuf; + EFI_STATUS efirc; + int rc; + + /* Get status */ + if ( ( efirc = snp->snp->GetStatus ( snp->snp, &irq, &txbuf ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( snp, "SNP %s could not get status: %s\n", + netdev->name, strerror ( rc ) ); + netdev_rx_err ( netdev, NULL, rc ); + return; + } + + /* Do nothing unless we have a completion */ + if ( ! txbuf ) + return; + + /* Sanity check */ + if ( ! snp->txbuf ) { + DBGC ( snp, "SNP %s reported spurious TX completion\n", + netdev->name ); + netdev_tx_err ( netdev, NULL, -EPIPE ); + return; + } + + /* Complete transmission */ + iobuf = snp->txbuf; + snp->txbuf = NULL; + netdev_tx_complete ( netdev, iobuf ); +} + +/** + * Poll for received packets + * + * @v netdev Network device + */ +static void snpnet_poll_rx ( struct net_device *netdev ) { + struct snp_nic *snp = netdev->priv; + UINTN len; + unsigned int quota; + EFI_STATUS efirc; + int rc; + + /* Retrieve up to SNP_RX_QUOTA packets */ + for ( quota = SNP_RX_QUOTA ; quota ; quota-- ) { + + /* Allocate buffer, if required */ + if ( ! snp->rxbuf ) { + snp->rxbuf = alloc_iob ( snp->mtu ); + if ( ! snp->rxbuf ) { + /* Leave for next poll */ + break; + } + } + + /* Receive packet */ + len = iob_tailroom ( snp->rxbuf ); + if ( ( efirc = snp->snp->Receive ( snp->snp, NULL, &len, + snp->rxbuf->data, NULL, + NULL, NULL ) ) != 0 ) { + + /* EFI_NOT_READY is just the usual "no packet" + * status indication; ignore it. + */ + if ( efirc == EFI_NOT_READY ) + break; + + /* Anything else is an error */ + rc = -EEFI ( efirc ); + DBGC ( snp, "SNP %s could not receive: %s\n", + netdev->name, strerror ( rc ) ); + netdev_rx_err ( netdev, NULL, rc ); + break; + } + + /* Hand off to network stack */ + iob_put ( snp->rxbuf, len ); + netdev_rx ( netdev, snp->rxbuf ); + snp->rxbuf = NULL; + } +} + +/** + * Poll for completed packets + * + * @v netdev Network device + */ +static void snpnet_poll ( struct net_device *netdev ) { + + /* Process any TX completions */ + snpnet_poll_tx ( netdev ); + + /* Process any RX completions */ + snpnet_poll_rx ( netdev ); + + /* Check for link state changes */ + snpnet_check_link ( netdev ); +} + +/** + * Set receive filters + * + * @v netdev Network device + * @ret rc Return status code + */ +static int snpnet_rx_filters ( struct net_device *netdev ) { + struct snp_nic *snp = netdev->priv; + UINT32 filters[] = { + snp->snp->Mode->ReceiveFilterMask, + ( EFI_SIMPLE_NETWORK_RECEIVE_UNICAST | + EFI_SIMPLE_NETWORK_RECEIVE_MULTICAST | + EFI_SIMPLE_NETWORK_RECEIVE_BROADCAST ), + ( EFI_SIMPLE_NETWORK_RECEIVE_UNICAST | + EFI_SIMPLE_NETWORK_RECEIVE_BROADCAST ), + ( EFI_SIMPLE_NETWORK_RECEIVE_UNICAST ), + }; + unsigned int i; + EFI_STATUS efirc; + int rc; + + /* Try possible receive filters in turn */ + for ( i = 0; i < ( sizeof ( filters ) / sizeof ( filters[0] ) ); i++ ) { + efirc = snp->snp->ReceiveFilters ( snp->snp, filters[i], + 0, TRUE, 0, NULL ); + if ( efirc == 0 ) + return 0; + rc = -EEFI ( efirc ); + DBGC ( snp, "SNP %s could not set receive filters %#02x (have " + "%#02x): %s\n", netdev->name, filters[i], + snp->snp->Mode->ReceiveFilterSetting, strerror ( rc ) ); + } + + return rc; +} + +/** + * Open network device + * + * @v netdev Network device + * @ret rc Return status code + */ +static int snpnet_open ( struct net_device *netdev ) { + struct snp_nic *snp = netdev->priv; + EFI_MAC_ADDRESS *mac = ( ( void * ) netdev->ll_addr ); + EFI_STATUS efirc; + int rc; + + /* Try setting MAC address (before initialising) */ + if ( ( efirc = snp->snp->StationAddress ( snp->snp, FALSE, mac ) ) !=0){ + rc = -EEFI ( efirc ); + DBGC ( snp, "SNP %s could not set station address before " + "initialising: %s\n", netdev->name, strerror ( rc ) ); + /* Ignore error */ + } + + /* Initialise NIC */ + if ( ( efirc = snp->snp->Initialize ( snp->snp, 0, 0 ) ) != 0 ) { + rc = -EEFI ( efirc ); + snpnet_dump_mode ( netdev ); + DBGC ( snp, "SNP %s could not initialise: %s\n", + netdev->name, strerror ( rc ) ); + return rc; + } + + /* Try setting MAC address (after initialising) */ + if ( ( efirc = snp->snp->StationAddress ( snp->snp, FALSE, mac ) ) !=0){ + rc = -EEFI ( efirc ); + DBGC ( snp, "SNP %s could not set station address after " + "initialising: %s\n", netdev->name, strerror ( rc ) ); + /* Ignore error */ + } + + /* Set receive filters */ + if ( ( rc = snpnet_rx_filters ( netdev ) ) != 0 ) { + /* Ignore error */ + } + + /* Dump mode information (for debugging) */ + snpnet_dump_mode ( netdev ); + + return 0; +} + +/** + * Close network device + * + * @v netdev Network device + */ +static void snpnet_close ( struct net_device *netdev ) { + struct snp_nic *snp = netdev->priv; + EFI_STATUS efirc; + int rc; + + /* Shut down NIC */ + if ( ( efirc = snp->snp->Shutdown ( snp->snp ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( snp, "SNP %s could not shut down: %s\n", + netdev->name, strerror ( rc ) ); + /* Nothing we can do about this */ + } + + /* Discard transmit buffer, if applicable */ + if ( snp->txbuf ) { + netdev_tx_complete_err ( netdev, snp->txbuf, -ECANCELED ); + snp->txbuf = NULL; + } + + /* Discard receive buffer, if applicable */ + if ( snp->rxbuf ) { + free_iob ( snp->rxbuf ); + snp->rxbuf = NULL; + } +} + +/** SNP network device operations */ +static struct net_device_operations snpnet_operations = { + .open = snpnet_open, + .close = snpnet_close, + .transmit = snpnet_transmit, + .poll = snpnet_poll, +}; + +/** + * Attach driver to device + * + * @v efidev EFI device + * @ret rc Return status code + */ +int snpnet_start ( struct efi_device *efidev ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + EFI_HANDLE device = efidev->device; + EFI_SIMPLE_NETWORK_MODE *mode; + struct net_device *netdev; + struct snp_nic *snp; + void *interface; + EFI_STATUS efirc; + int rc; + + /* Open SNP protocol */ + if ( ( efirc = bs->OpenProtocol ( device, + &efi_simple_network_protocol_guid, + &interface, efi_image_handle, device, + ( EFI_OPEN_PROTOCOL_BY_DRIVER | + EFI_OPEN_PROTOCOL_EXCLUSIVE )))!=0){ + rc = -EEFI ( efirc ); + DBGC ( device, "SNP %p %s cannot open SNP protocol: %s\n", + device, efi_handle_name ( device ), strerror ( rc ) ); + DBGC_EFI_OPENERS ( device, device, + &efi_simple_network_protocol_guid ); + goto err_open_protocol; + } + + /* Allocate and initialise structure */ + netdev = alloc_etherdev ( sizeof ( *snp ) ); + if ( ! netdev ) { + rc = -ENOMEM; + goto err_alloc; + } + netdev_init ( netdev, &snpnet_operations ); + snp = netdev->priv; + snp->efidev = efidev; + snp->snp = interface; + mode = snp->snp->Mode; + efidev_set_drvdata ( efidev, netdev ); + + /* Populate underlying device information */ + efi_device_info ( device, "SNP", &snp->dev ); + snp->dev.driver_name = "SNP"; + snp->dev.parent = &efidev->dev; + list_add ( &snp->dev.siblings, &efidev->dev.children ); + INIT_LIST_HEAD ( &snp->dev.children ); + netdev->dev = &snp->dev; + + /* Bring to the Started state */ + if ( ( mode->State == EfiSimpleNetworkStopped ) && + ( ( efirc = snp->snp->Start ( snp->snp ) ) != 0 ) ) { + rc = -EEFI ( efirc ); + DBGC ( device, "SNP %p %s could not start: %s\n", device, + efi_handle_name ( device ), strerror ( rc ) ); + goto err_start; + } + if ( ( mode->State == EfiSimpleNetworkInitialized ) && + ( ( efirc = snp->snp->Shutdown ( snp->snp ) ) != 0 ) ) { + rc = -EEFI ( efirc ); + DBGC ( device, "SNP %p %s could not shut down: %s\n", device, + efi_handle_name ( device ), strerror ( rc ) ); + goto err_shutdown; + } + + /* Populate network device parameters */ + if ( mode->HwAddressSize != netdev->ll_protocol->hw_addr_len ) { + DBGC ( device, "SNP %p %s has invalid hardware address " + "length %d\n", device, efi_handle_name ( device ), + mode->HwAddressSize ); + rc = -ENOTSUP; + goto err_hw_addr_len; + } + memcpy ( netdev->hw_addr, &mode->PermanentAddress, + netdev->ll_protocol->hw_addr_len ); + if ( mode->HwAddressSize != netdev->ll_protocol->ll_addr_len ) { + DBGC ( device, "SNP %p %s has invalid link-layer address " + "length %d\n", device, efi_handle_name ( device ), + mode->HwAddressSize ); + rc = -ENOTSUP; + goto err_ll_addr_len; + } + memcpy ( netdev->ll_addr, &mode->CurrentAddress, + netdev->ll_protocol->ll_addr_len ); + snp->mtu = ( snp->snp->Mode->MaxPacketSize + + snp->snp->Mode->MediaHeaderSize ); + + /* Register network device */ + if ( ( rc = register_netdev ( netdev ) ) != 0 ) + goto err_register_netdev; + DBGC ( device, "SNP %p %s registered as %s\n", + device, efi_handle_name ( device ), netdev->name ); + + /* Set initial link state */ + if ( snp->snp->Mode->MediaPresentSupported ) { + snpnet_check_link ( netdev ); + } else { + netdev_link_up ( netdev ); + } + + return 0; + + unregister_netdev ( netdev ); + err_register_netdev: + err_ll_addr_len: + err_hw_addr_len: + err_shutdown: + err_start: + list_del ( &snp->dev.siblings ); + netdev_nullify ( netdev ); + netdev_put ( netdev ); + err_alloc: + bs->CloseProtocol ( device, &efi_simple_network_protocol_guid, + efi_image_handle, device ); + err_open_protocol: + return rc; +} + +/** + * Detach driver from device + * + * @v efidev EFI device + */ +void snpnet_stop ( struct efi_device *efidev ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + struct net_device *netdev = efidev_get_drvdata ( efidev ); + struct snp_nic *snp = netdev->priv; + EFI_HANDLE device = efidev->device; + EFI_STATUS efirc; + int rc; + + /* Unregister network device */ + unregister_netdev ( netdev ); + + /* Stop SNP protocol */ + if ( ( efirc = snp->snp->Stop ( snp->snp ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( device, "SNP %p %s could not stop: %s\n", device, + efi_handle_name ( device ), strerror ( rc ) ); + /* Nothing we can do about this */ + } + + /* Free network device */ + list_del ( &snp->dev.siblings ); + netdev_nullify ( netdev ); + netdev_put ( netdev ); + + /* Close SNP protocol */ + bs->CloseProtocol ( device, &efi_simple_network_protocol_guid, + efi_image_handle, device ); +} diff --git a/qemu/roms/ipxe/src/drivers/net/efi/snpnet.h b/qemu/roms/ipxe/src/drivers/net/efi/snpnet.h new file mode 100644 index 000000000..e6d31d5e4 --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/net/efi/snpnet.h @@ -0,0 +1,17 @@ +#ifndef _SNPNET_H +#define _SNPNET_H + +/** @file + * + * SNP NIC driver + * + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +struct efi_device; + +extern int snpnet_start ( struct efi_device *efidev ); +extern void snpnet_stop ( struct efi_device *efidev ); + +#endif /* _SNPNET_H */ diff --git a/qemu/roms/ipxe/src/drivers/net/efi/snponly.c b/qemu/roms/ipxe/src/drivers/net/efi/snponly.c new file mode 100644 index 000000000..99f264bca --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/net/efi/snponly.c @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2014 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 <errno.h> +#include <ipxe/init.h> +#include <ipxe/efi/efi.h> +#include <ipxe/efi/efi_driver.h> +#include <ipxe/efi/efi_utils.h> +#include <ipxe/efi/Protocol/SimpleNetwork.h> +#include <ipxe/efi/Protocol/NetworkInterfaceIdentifier.h> +#include "snpnet.h" +#include "nii.h" + +/** @file + * + * EFI chainloaded-device-only driver + * + */ + +/** A chainloaded protocol */ +struct chained_protocol { + /** Protocol GUID */ + EFI_GUID *protocol; + /** + * Protocol instance installed on the loaded image's device handle + * + * We match against the protocol instance (rather than simply + * matching against the device handle itself) because some + * systems load us via a child of the underlying device, with + * a duplicate protocol installed on the child handle. + */ + void *interface; +}; + +/** Chainloaded SNP protocol */ +static struct chained_protocol chained_snp = { + .protocol = &efi_simple_network_protocol_guid, +}; + +/** Chainloaded NII protocol */ +static struct chained_protocol chained_nii = { + .protocol = &efi_nii31_protocol_guid, +}; + +/** + * Locate chainloaded protocol instance + * + * @v chained Chainloaded protocol + * @ret rc Return status code + */ +static int chained_locate ( struct chained_protocol *chained ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + EFI_HANDLE device = efi_loaded_image->DeviceHandle; + EFI_HANDLE parent; + EFI_STATUS efirc; + int rc; + + /* Locate handle supporting this protocol */ + if ( ( rc = efi_locate_device ( device, chained->protocol, + &parent ) ) != 0 ) { + DBGC ( device, "CHAINED %p %s does not support %s: %s\n", + device, efi_handle_name ( device ), + efi_guid_ntoa ( chained->protocol ), strerror ( rc ) ); + goto err_locate_device; + } + DBGC ( device, "CHAINED %p %s found %s on ", device, + efi_handle_name ( device ), efi_guid_ntoa ( chained->protocol )); + DBGC ( device, "%p %s\n", parent, efi_handle_name ( parent ) ); + + /* Get protocol instance */ + if ( ( efirc = bs->OpenProtocol ( parent, chained->protocol, + &chained->interface, efi_image_handle, + device, + EFI_OPEN_PROTOCOL_GET_PROTOCOL ))!=0){ + rc = -EEFI ( efirc ); + DBGC ( device, "CHAINED %p %s could not open %s on ", + device, efi_handle_name ( device ), + efi_guid_ntoa ( chained->protocol ) ); + DBGC ( device, "%p %s: %s\n", + parent, efi_handle_name ( parent ), strerror ( rc ) ); + goto err_open_protocol; + } + + err_locate_device: + bs->CloseProtocol ( parent, chained->protocol, efi_image_handle, + device ); + err_open_protocol: + return rc; +} + +/** + * Check to see if driver supports a device + * + * @v device EFI device handle + * @v chained Chainloaded protocol + * @ret rc Return status code + */ +static int chained_supported ( EFI_HANDLE device, + struct chained_protocol *chained ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + EFI_STATUS efirc; + void *interface; + int rc; + + /* Get protocol */ + if ( ( efirc = bs->OpenProtocol ( device, chained->protocol, &interface, + efi_image_handle, device, + EFI_OPEN_PROTOCOL_GET_PROTOCOL ))!=0){ + rc = -EEFI ( efirc ); + DBGCP ( device, "CHAINED %p %s is not a %s device\n", + device, efi_handle_name ( device ), + efi_guid_ntoa ( chained->protocol ) ); + goto err_open_protocol; + } + + /* Test for a match against the chainloading device */ + if ( interface != chained->interface ) { + DBGC ( device, "CHAINED %p %s %p is not the chainloaded " + "%s\n", device, efi_handle_name ( device ), + interface, efi_guid_ntoa ( chained->protocol ) ); + rc = -ENOTTY; + goto err_no_match; + } + + /* Success */ + rc = 0; + DBGC ( device, "CHAINED %p %s %p is the chainloaded %s\n", + device, efi_handle_name ( device ), interface, + efi_guid_ntoa ( chained->protocol ) ); + + err_no_match: + bs->CloseProtocol ( device, chained->protocol, efi_image_handle, + device ); + err_open_protocol: + return rc; +} + +/** + * Check to see if driver supports a device + * + * @v device EFI device handle + * @ret rc Return status code + */ +static int snponly_supported ( EFI_HANDLE device ) { + + return chained_supported ( device, &chained_snp ); +} + +/** + * Check to see if driver supports a device + * + * @v device EFI device handle + * @ret rc Return status code + */ +static int niionly_supported ( EFI_HANDLE device ) { + + return chained_supported ( device, &chained_nii ); +} + +/** EFI SNP chainloading-device-only driver */ +struct efi_driver snponly_driver __efi_driver ( EFI_DRIVER_NORMAL ) = { + .name = "SNPONLY", + .supported = snponly_supported, + .start = snpnet_start, + .stop = snpnet_stop, +}; + +/** EFI NII chainloading-device-only driver */ +struct efi_driver niionly_driver __efi_driver ( EFI_DRIVER_NORMAL ) = { + .name = "NIIONLY", + .supported = niionly_supported, + .start = nii_start, + .stop = nii_stop, +}; + +/** + * Initialise EFI chainloaded-device-only driver + * + */ +static void chained_init ( void ) { + + chained_locate ( &chained_snp ); + chained_locate ( &chained_nii ); +} + +/** EFI chainloaded-device-only initialisation function */ +struct init_fn chained_init_fn __init_fn ( INIT_LATE ) = { + .initialise = chained_init, +}; |