123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561 |
- /*
- * 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 %s cannot open SNP protocol: %s\n",
- 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 %s could not start: %s\n",
- 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 %s could not shut down: %s\n",
- 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 %s has invalid hardware address length "
- "%d\n", 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 %s has invalid link-layer address length "
- "%d\n", 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 %s registered as %s\n",
- 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 %s could not stop: %s\n",
- 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 );
- }
|