From e44e3482bdb4d0ebde2d8b41830ac2cdb07948fb Mon Sep 17 00:00:00 2001 From: Yang Zhang Date: Fri, 28 Aug 2015 09:58:54 +0800 Subject: Add qemu 2.4.0 Change-Id: Ic99cbad4b61f8b127b7dc74d04576c0bcbaaf4f5 Signed-off-by: Yang Zhang --- qemu/roms/ipxe/src/interface/efi/efi_snp.c | 1209 ++++++++++++++++++++++++++++ 1 file changed, 1209 insertions(+) create mode 100644 qemu/roms/ipxe/src/interface/efi/efi_snp.c (limited to 'qemu/roms/ipxe/src/interface/efi/efi_snp.c') diff --git a/qemu/roms/ipxe/src/interface/efi/efi_snp.c b/qemu/roms/ipxe/src/interface/efi/efi_snp.c new file mode 100644 index 000000000..67fba342e --- /dev/null +++ b/qemu/roms/ipxe/src/interface/efi/efi_snp.c @@ -0,0 +1,1209 @@ +/* + * Copyright (C) 2008 Michael Brown . + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** List of SNP devices */ +static LIST_HEAD ( efi_snp_devices ); + +/** Network devices are currently claimed for use by iPXE */ +static int efi_snp_claimed; + +/** + * Set EFI SNP mode state + * + * @v snp SNP interface + */ +static void efi_snp_set_state ( struct efi_snp_device *snpdev ) { + struct net_device *netdev = snpdev->netdev; + EFI_SIMPLE_NETWORK_MODE *mode = &snpdev->mode; + + /* Calculate state */ + if ( ! snpdev->started ) { + /* Start() method not called; report as Stopped */ + mode->State = EfiSimpleNetworkStopped; + } else if ( ! netdev_is_open ( netdev ) ) { + /* Network device not opened; report as Started */ + mode->State = EfiSimpleNetworkStarted; + } else if ( efi_snp_claimed ) { + /* Network device opened but claimed for use by iPXE; report + * as Started to inhibit receive polling. + */ + mode->State = EfiSimpleNetworkStarted; + } else { + /* Network device opened and available for use via SNP; report + * as Initialized. + */ + mode->State = EfiSimpleNetworkInitialized; + } +} + +/** + * Set EFI SNP mode based on iPXE net device parameters + * + * @v snp SNP interface + */ +static void efi_snp_set_mode ( struct efi_snp_device *snpdev ) { + struct net_device *netdev = snpdev->netdev; + EFI_SIMPLE_NETWORK_MODE *mode = &snpdev->mode; + struct ll_protocol *ll_protocol = netdev->ll_protocol; + unsigned int ll_addr_len = ll_protocol->ll_addr_len; + + mode->HwAddressSize = ll_addr_len; + mode->MediaHeaderSize = ll_protocol->ll_header_len; + mode->MaxPacketSize = netdev->max_pkt_len; + mode->ReceiveFilterMask = ( EFI_SIMPLE_NETWORK_RECEIVE_UNICAST | + EFI_SIMPLE_NETWORK_RECEIVE_MULTICAST | + EFI_SIMPLE_NETWORK_RECEIVE_BROADCAST ); + assert ( ll_addr_len <= sizeof ( mode->CurrentAddress ) ); + memcpy ( &mode->CurrentAddress, netdev->ll_addr, ll_addr_len ); + memcpy ( &mode->BroadcastAddress, netdev->ll_broadcast, ll_addr_len ); + ll_protocol->init_addr ( netdev->hw_addr, &mode->PermanentAddress ); + mode->IfType = ntohs ( ll_protocol->ll_proto ); + mode->MacAddressChangeable = TRUE; + mode->MediaPresentSupported = TRUE; + mode->MediaPresent = ( netdev_link_ok ( netdev ) ? TRUE : FALSE ); +} + +/** + * Poll net device and count received packets + * + * @v snpdev SNP device + */ +static void efi_snp_poll ( struct efi_snp_device *snpdev ) { + struct io_buffer *iobuf; + unsigned int before = 0; + unsigned int after = 0; + unsigned int arrived; + + /* We have to report packet arrivals, and this is the easiest + * way to fake it. + */ + list_for_each_entry ( iobuf, &snpdev->netdev->rx_queue, list ) + before++; + netdev_poll ( snpdev->netdev ); + list_for_each_entry ( iobuf, &snpdev->netdev->rx_queue, list ) + after++; + arrived = ( after - before ); + + snpdev->rx_count_interrupts += arrived; + snpdev->rx_count_events += arrived; +} + +/** + * Change SNP state from "stopped" to "started" + * + * @v snp SNP interface + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_snp_start ( EFI_SIMPLE_NETWORK_PROTOCOL *snp ) { + struct efi_snp_device *snpdev = + container_of ( snp, struct efi_snp_device, snp ); + + DBGC2 ( snpdev, "SNPDEV %p START\n", snpdev ); + + /* Fail if net device is currently claimed for use by iPXE */ + if ( efi_snp_claimed ) + return EFI_NOT_READY; + + snpdev->started = 1; + efi_snp_set_state ( snpdev ); + return 0; +} + +/** + * Change SNP state from "started" to "stopped" + * + * @v snp SNP interface + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_snp_stop ( EFI_SIMPLE_NETWORK_PROTOCOL *snp ) { + struct efi_snp_device *snpdev = + container_of ( snp, struct efi_snp_device, snp ); + + DBGC2 ( snpdev, "SNPDEV %p STOP\n", snpdev ); + + /* Fail if net device is currently claimed for use by iPXE */ + if ( efi_snp_claimed ) + return EFI_NOT_READY; + + snpdev->started = 0; + efi_snp_set_state ( snpdev ); + return 0; +} + +/** + * Open the network device + * + * @v snp SNP interface + * @v extra_rx_bufsize Extra RX buffer size, in bytes + * @v extra_tx_bufsize Extra TX buffer size, in bytes + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_snp_initialize ( EFI_SIMPLE_NETWORK_PROTOCOL *snp, + UINTN extra_rx_bufsize, UINTN extra_tx_bufsize ) { + struct efi_snp_device *snpdev = + container_of ( snp, struct efi_snp_device, snp ); + int rc; + + DBGC2 ( snpdev, "SNPDEV %p INITIALIZE (%ld extra RX, %ld extra TX)\n", + snpdev, ( ( unsigned long ) extra_rx_bufsize ), + ( ( unsigned long ) extra_tx_bufsize ) ); + + /* Fail if net device is currently claimed for use by iPXE */ + if ( efi_snp_claimed ) + return EFI_NOT_READY; + + if ( ( rc = netdev_open ( snpdev->netdev ) ) != 0 ) { + DBGC ( snpdev, "SNPDEV %p could not open %s: %s\n", + snpdev, snpdev->netdev->name, strerror ( rc ) ); + return EFIRC ( rc ); + } + efi_snp_set_state ( snpdev ); + + return 0; +} + +/** + * Reset the network device + * + * @v snp SNP interface + * @v ext_verify Extended verification required + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_snp_reset ( EFI_SIMPLE_NETWORK_PROTOCOL *snp, BOOLEAN ext_verify ) { + struct efi_snp_device *snpdev = + container_of ( snp, struct efi_snp_device, snp ); + int rc; + + DBGC2 ( snpdev, "SNPDEV %p RESET (%s extended verification)\n", + snpdev, ( ext_verify ? "with" : "without" ) ); + + /* Fail if net device is currently claimed for use by iPXE */ + if ( efi_snp_claimed ) + return EFI_NOT_READY; + + netdev_close ( snpdev->netdev ); + efi_snp_set_state ( snpdev ); + + if ( ( rc = netdev_open ( snpdev->netdev ) ) != 0 ) { + DBGC ( snpdev, "SNPDEV %p could not reopen %s: %s\n", + snpdev, snpdev->netdev->name, strerror ( rc ) ); + return EFIRC ( rc ); + } + efi_snp_set_state ( snpdev ); + + return 0; +} + +/** + * Shut down the network device + * + * @v snp SNP interface + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_snp_shutdown ( EFI_SIMPLE_NETWORK_PROTOCOL *snp ) { + struct efi_snp_device *snpdev = + container_of ( snp, struct efi_snp_device, snp ); + + DBGC2 ( snpdev, "SNPDEV %p SHUTDOWN\n", snpdev ); + + /* Fail if net device is currently claimed for use by iPXE */ + if ( efi_snp_claimed ) + return EFI_NOT_READY; + + netdev_close ( snpdev->netdev ); + efi_snp_set_state ( snpdev ); + + return 0; +} + +/** + * Manage receive filters + * + * @v snp SNP interface + * @v enable Receive filters to enable + * @v disable Receive filters to disable + * @v mcast_reset Reset multicast filters + * @v mcast_count Number of multicast filters + * @v mcast Multicast filters + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_snp_receive_filters ( EFI_SIMPLE_NETWORK_PROTOCOL *snp, UINT32 enable, + UINT32 disable, BOOLEAN mcast_reset, + UINTN mcast_count, EFI_MAC_ADDRESS *mcast ) { + struct efi_snp_device *snpdev = + container_of ( snp, struct efi_snp_device, snp ); + unsigned int i; + + DBGC2 ( snpdev, "SNPDEV %p RECEIVE_FILTERS %08x&~%08x%s %ld mcast\n", + snpdev, enable, disable, ( mcast_reset ? " reset" : "" ), + ( ( unsigned long ) mcast_count ) ); + for ( i = 0 ; i < mcast_count ; i++ ) { + DBGC2_HDA ( snpdev, i, &mcast[i], + snpdev->netdev->ll_protocol->ll_addr_len ); + } + + /* Fail if net device is currently claimed for use by iPXE */ + if ( efi_snp_claimed ) + return EFI_NOT_READY; + + /* Lie through our teeth, otherwise MNP refuses to accept us */ + return 0; +} + +/** + * Set station address + * + * @v snp SNP interface + * @v reset Reset to permanent address + * @v new New station address + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_snp_station_address ( EFI_SIMPLE_NETWORK_PROTOCOL *snp, BOOLEAN reset, + EFI_MAC_ADDRESS *new ) { + struct efi_snp_device *snpdev = + container_of ( snp, struct efi_snp_device, snp ); + struct ll_protocol *ll_protocol = snpdev->netdev->ll_protocol; + + DBGC2 ( snpdev, "SNPDEV %p STATION_ADDRESS %s\n", snpdev, + ( reset ? "reset" : ll_protocol->ntoa ( new ) ) ); + + /* Fail if net device is currently claimed for use by iPXE */ + if ( efi_snp_claimed ) + return EFI_NOT_READY; + + /* Set the MAC address */ + if ( reset ) + new = &snpdev->mode.PermanentAddress; + memcpy ( snpdev->netdev->ll_addr, new, ll_protocol->ll_addr_len ); + + /* MAC address changes take effect only on netdev_open() */ + if ( netdev_is_open ( snpdev->netdev ) ) { + DBGC ( snpdev, "SNPDEV %p MAC address changed while net " + "device open\n", snpdev ); + } + + return 0; +} + +/** + * Get (or reset) statistics + * + * @v snp SNP interface + * @v reset Reset statistics + * @v stats_len Size of statistics table + * @v stats Statistics table + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_snp_statistics ( EFI_SIMPLE_NETWORK_PROTOCOL *snp, BOOLEAN reset, + UINTN *stats_len, EFI_NETWORK_STATISTICS *stats ) { + struct efi_snp_device *snpdev = + container_of ( snp, struct efi_snp_device, snp ); + EFI_NETWORK_STATISTICS stats_buf; + + DBGC2 ( snpdev, "SNPDEV %p STATISTICS%s", snpdev, + ( reset ? " reset" : "" ) ); + + /* Fail if net device is currently claimed for use by iPXE */ + if ( efi_snp_claimed ) + return EFI_NOT_READY; + + /* Gather statistics */ + memset ( &stats_buf, 0, sizeof ( stats_buf ) ); + stats_buf.TxGoodFrames = snpdev->netdev->tx_stats.good; + stats_buf.TxDroppedFrames = snpdev->netdev->tx_stats.bad; + stats_buf.TxTotalFrames = ( snpdev->netdev->tx_stats.good + + snpdev->netdev->tx_stats.bad ); + stats_buf.RxGoodFrames = snpdev->netdev->rx_stats.good; + stats_buf.RxDroppedFrames = snpdev->netdev->rx_stats.bad; + stats_buf.RxTotalFrames = ( snpdev->netdev->rx_stats.good + + snpdev->netdev->rx_stats.bad ); + if ( *stats_len > sizeof ( stats_buf ) ) + *stats_len = sizeof ( stats_buf ); + if ( stats ) + memcpy ( stats, &stats_buf, *stats_len ); + + /* Reset statistics if requested to do so */ + if ( reset ) { + memset ( &snpdev->netdev->tx_stats, 0, + sizeof ( snpdev->netdev->tx_stats ) ); + memset ( &snpdev->netdev->rx_stats, 0, + sizeof ( snpdev->netdev->rx_stats ) ); + } + + return 0; +} + +/** + * Convert multicast IP address to MAC address + * + * @v snp SNP interface + * @v ipv6 Address is IPv6 + * @v ip IP address + * @v mac MAC address + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_snp_mcast_ip_to_mac ( EFI_SIMPLE_NETWORK_PROTOCOL *snp, BOOLEAN ipv6, + EFI_IP_ADDRESS *ip, EFI_MAC_ADDRESS *mac ) { + struct efi_snp_device *snpdev = + container_of ( snp, struct efi_snp_device, snp ); + struct ll_protocol *ll_protocol = snpdev->netdev->ll_protocol; + const char *ip_str; + int rc; + + ip_str = ( ipv6 ? "(IPv6)" /* FIXME when we have inet6_ntoa() */ : + inet_ntoa ( *( ( struct in_addr * ) ip ) ) ); + DBGC2 ( snpdev, "SNPDEV %p MCAST_IP_TO_MAC %s\n", snpdev, ip_str ); + + /* Fail if net device is currently claimed for use by iPXE */ + if ( efi_snp_claimed ) + return EFI_NOT_READY; + + /* Try to hash the address */ + if ( ( rc = ll_protocol->mc_hash ( ( ipv6 ? AF_INET6 : AF_INET ), + ip, mac ) ) != 0 ) { + DBGC ( snpdev, "SNPDEV %p could not hash %s: %s\n", + snpdev, ip_str, strerror ( rc ) ); + return EFIRC ( rc ); + } + + return 0; +} + +/** + * Read or write non-volatile storage + * + * @v snp SNP interface + * @v read Operation is a read + * @v offset Starting offset within NVRAM + * @v len Length of data buffer + * @v data Data buffer + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_snp_nvdata ( EFI_SIMPLE_NETWORK_PROTOCOL *snp, BOOLEAN read, + UINTN offset, UINTN len, VOID *data ) { + struct efi_snp_device *snpdev = + container_of ( snp, struct efi_snp_device, snp ); + + DBGC2 ( snpdev, "SNPDEV %p NVDATA %s %lx+%lx\n", snpdev, + ( read ? "read" : "write" ), ( ( unsigned long ) offset ), + ( ( unsigned long ) len ) ); + if ( ! read ) + DBGC2_HDA ( snpdev, offset, data, len ); + + /* Fail if net device is currently claimed for use by iPXE */ + if ( efi_snp_claimed ) + return EFI_NOT_READY; + + return EFI_UNSUPPORTED; +} + +/** + * Read interrupt status and TX recycled buffer status + * + * @v snp SNP interface + * @v interrupts Interrupt status, or NULL + * @v txbufs Recycled transmit buffer address, or NULL + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_snp_get_status ( EFI_SIMPLE_NETWORK_PROTOCOL *snp, + UINT32 *interrupts, VOID **txbufs ) { + struct efi_snp_device *snpdev = + container_of ( snp, struct efi_snp_device, snp ); + + DBGC2 ( snpdev, "SNPDEV %p GET_STATUS", snpdev ); + + /* Fail if net device is currently claimed for use by iPXE */ + if ( efi_snp_claimed ) + return EFI_NOT_READY; + + /* Poll the network device */ + efi_snp_poll ( snpdev ); + + /* Interrupt status. In practice, this seems to be used only + * to detect TX completions. + */ + if ( interrupts ) { + *interrupts = 0; + /* Report TX completions once queue is empty; this + * avoids having to add hooks in the net device layer. + */ + if ( snpdev->tx_count_interrupts && + list_empty ( &snpdev->netdev->tx_queue ) ) { + *interrupts |= EFI_SIMPLE_NETWORK_TRANSMIT_INTERRUPT; + snpdev->tx_count_interrupts--; + } + /* Report RX */ + if ( snpdev->rx_count_interrupts ) { + *interrupts |= EFI_SIMPLE_NETWORK_RECEIVE_INTERRUPT; + snpdev->rx_count_interrupts--; + } + DBGC2 ( snpdev, " INTS:%02x", *interrupts ); + } + + /* TX completions. It would be possible to design a more + * idiotic scheme for this, but it would be a challenge. + * According to the UEFI header file, txbufs will be filled in + * with a list of "recycled transmit buffers" (i.e. completed + * TX buffers). Observant readers may care to note that + * *txbufs is a void pointer. Precisely how a list of + * completed transmit buffers is meant to be represented as an + * array of voids is left as an exercise for the reader. + * + * The only users of this interface (MnpDxe/MnpIo.c and + * PxeBcDxe/Bc.c within the EFI dev kit) both just poll until + * seeing a non-NULL result return in txbufs. This is valid + * provided that they do not ever attempt to transmit more + * than one packet concurrently (and that TX never times out). + */ + if ( txbufs ) { + if ( snpdev->tx_count_txbufs && + list_empty ( &snpdev->netdev->tx_queue ) ) { + *txbufs = "Which idiot designed this API?"; + snpdev->tx_count_txbufs--; + } else { + *txbufs = NULL; + } + DBGC2 ( snpdev, " TX:%s", ( *txbufs ? "some" : "none" ) ); + } + + DBGC2 ( snpdev, "\n" ); + return 0; +} + +/** + * Start packet transmission + * + * @v snp SNP interface + * @v ll_header_len Link-layer header length, if to be filled in + * @v len Length of data buffer + * @v data Data buffer + * @v ll_src Link-layer source address, if specified + * @v ll_dest Link-layer destination address, if specified + * @v net_proto Network-layer protocol (in host order) + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_snp_transmit ( EFI_SIMPLE_NETWORK_PROTOCOL *snp, + UINTN ll_header_len, UINTN len, VOID *data, + EFI_MAC_ADDRESS *ll_src, EFI_MAC_ADDRESS *ll_dest, + UINT16 *net_proto ) { + struct efi_snp_device *snpdev = + container_of ( snp, struct efi_snp_device, snp ); + struct ll_protocol *ll_protocol = snpdev->netdev->ll_protocol; + struct io_buffer *iobuf; + size_t payload_len; + int rc; + + DBGC2 ( snpdev, "SNPDEV %p TRANSMIT %p+%lx", snpdev, data, + ( ( unsigned long ) len ) ); + if ( ll_header_len ) { + if ( ll_src ) { + DBGC2 ( snpdev, " src %s", + ll_protocol->ntoa ( ll_src ) ); + } + if ( ll_dest ) { + DBGC2 ( snpdev, " dest %s", + ll_protocol->ntoa ( ll_dest ) ); + } + if ( net_proto ) { + DBGC2 ( snpdev, " proto %04x", *net_proto ); + } + } + DBGC2 ( snpdev, "\n" ); + + /* Fail if net device is currently claimed for use by iPXE */ + if ( efi_snp_claimed ) + return EFI_NOT_READY; + + /* Sanity checks */ + if ( ll_header_len ) { + if ( ll_header_len != ll_protocol->ll_header_len ) { + DBGC ( snpdev, "SNPDEV %p TX invalid header length " + "%ld\n", snpdev, + ( ( unsigned long ) ll_header_len ) ); + rc = -EINVAL; + goto err_sanity; + } + if ( len < ll_header_len ) { + DBGC ( snpdev, "SNPDEV %p invalid packet length %ld\n", + snpdev, ( ( unsigned long ) len ) ); + rc = -EINVAL; + goto err_sanity; + } + if ( ! ll_dest ) { + DBGC ( snpdev, "SNPDEV %p TX missing destination " + "address\n", snpdev ); + rc = -EINVAL; + goto err_sanity; + } + if ( ! net_proto ) { + DBGC ( snpdev, "SNPDEV %p TX missing network " + "protocol\n", snpdev ); + rc = -EINVAL; + goto err_sanity; + } + if ( ! ll_src ) + ll_src = &snpdev->mode.CurrentAddress; + } + + /* Allocate buffer */ + payload_len = ( len - ll_protocol->ll_header_len ); + iobuf = alloc_iob ( MAX_LL_HEADER_LEN + ( ( payload_len > IOB_ZLEN ) ? + payload_len : IOB_ZLEN ) ); + if ( ! iobuf ) { + DBGC ( snpdev, "SNPDEV %p TX could not allocate %ld-byte " + "buffer\n", snpdev, ( ( unsigned long ) len ) ); + rc = -ENOMEM; + goto err_alloc_iob; + } + iob_reserve ( iobuf, ( MAX_LL_HEADER_LEN - + ll_protocol->ll_header_len ) ); + memcpy ( iob_put ( iobuf, len ), data, len ); + + /* Create link-layer header, if specified */ + if ( ll_header_len ) { + iob_pull ( iobuf, ll_protocol->ll_header_len ); + if ( ( rc = ll_protocol->push ( snpdev->netdev, + iobuf, ll_dest, ll_src, + htons ( *net_proto ) )) != 0 ){ + DBGC ( snpdev, "SNPDEV %p TX could not construct " + "header: %s\n", snpdev, strerror ( rc ) ); + goto err_ll_push; + } + } + + /* Transmit packet */ + if ( ( rc = netdev_tx ( snpdev->netdev, iob_disown ( iobuf ) ) ) != 0){ + DBGC ( snpdev, "SNPDEV %p TX could not transmit: %s\n", + snpdev, strerror ( rc ) ); + goto err_tx; + } + + /* Record transmission as outstanding */ + snpdev->tx_count_interrupts++; + snpdev->tx_count_txbufs++; + + return 0; + + err_tx: + err_ll_push: + free_iob ( iobuf ); + err_alloc_iob: + err_sanity: + return EFIRC ( rc ); +} + +/** + * Receive packet + * + * @v snp SNP interface + * @v ll_header_len Link-layer header length, if to be filled in + * @v len Length of data buffer + * @v data Data buffer + * @v ll_src Link-layer source address, if specified + * @v ll_dest Link-layer destination address, if specified + * @v net_proto Network-layer protocol (in host order) + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_snp_receive ( EFI_SIMPLE_NETWORK_PROTOCOL *snp, + UINTN *ll_header_len, UINTN *len, VOID *data, + EFI_MAC_ADDRESS *ll_src, EFI_MAC_ADDRESS *ll_dest, + UINT16 *net_proto ) { + struct efi_snp_device *snpdev = + container_of ( snp, struct efi_snp_device, snp ); + struct ll_protocol *ll_protocol = snpdev->netdev->ll_protocol; + struct io_buffer *iobuf; + const void *iob_ll_dest; + const void *iob_ll_src; + uint16_t iob_net_proto; + unsigned int iob_flags; + int rc; + + DBGC2 ( snpdev, "SNPDEV %p RECEIVE %p(+%lx)", snpdev, data, + ( ( unsigned long ) *len ) ); + + /* Fail if net device is currently claimed for use by iPXE */ + if ( efi_snp_claimed ) + return EFI_NOT_READY; + + /* Poll the network device */ + efi_snp_poll ( snpdev ); + + /* Dequeue a packet, if one is available */ + iobuf = netdev_rx_dequeue ( snpdev->netdev ); + if ( ! iobuf ) { + DBGC2 ( snpdev, "\n" ); + rc = -EAGAIN; + goto out_no_packet; + } + DBGC2 ( snpdev, "+%zx\n", iob_len ( iobuf ) ); + + /* Return packet to caller */ + memcpy ( data, iobuf->data, iob_len ( iobuf ) ); + *len = iob_len ( iobuf ); + + /* Attempt to decode link-layer header */ + if ( ( rc = ll_protocol->pull ( snpdev->netdev, iobuf, &iob_ll_dest, + &iob_ll_src, &iob_net_proto, + &iob_flags ) ) != 0 ) { + DBGC ( snpdev, "SNPDEV %p could not parse header: %s\n", + snpdev, strerror ( rc ) ); + goto out_bad_ll_header; + } + + /* Return link-layer header parameters to caller, if required */ + if ( ll_header_len ) + *ll_header_len = ll_protocol->ll_header_len; + if ( ll_src ) + memcpy ( ll_src, iob_ll_src, ll_protocol->ll_addr_len ); + if ( ll_dest ) + memcpy ( ll_dest, iob_ll_dest, ll_protocol->ll_addr_len ); + if ( net_proto ) + *net_proto = ntohs ( iob_net_proto ); + + rc = 0; + + out_bad_ll_header: + free_iob ( iobuf ); + out_no_packet: + return EFIRC ( rc ); +} + +/** + * Poll event + * + * @v event Event + * @v context Event context + */ +static VOID EFIAPI efi_snp_wait_for_packet ( EFI_EVENT event, + VOID *context ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + struct efi_snp_device *snpdev = context; + + DBGCP ( snpdev, "SNPDEV %p WAIT_FOR_PACKET\n", snpdev ); + + /* Do nothing unless the net device is open */ + if ( ! netdev_is_open ( snpdev->netdev ) ) + return; + + /* Do nothing if net device is currently claimed for use by iPXE */ + if ( efi_snp_claimed ) + return; + + /* Poll the network device */ + efi_snp_poll ( snpdev ); + + /* Fire event if packets have been received */ + if ( snpdev->rx_count_events != 0 ) { + DBGC2 ( snpdev, "SNPDEV %p firing WaitForPacket event\n", + snpdev ); + bs->SignalEvent ( event ); + snpdev->rx_count_events--; + } +} + +/** SNP interface */ +static EFI_SIMPLE_NETWORK_PROTOCOL efi_snp_device_snp = { + .Revision = EFI_SIMPLE_NETWORK_PROTOCOL_REVISION, + .Start = efi_snp_start, + .Stop = efi_snp_stop, + .Initialize = efi_snp_initialize, + .Reset = efi_snp_reset, + .Shutdown = efi_snp_shutdown, + .ReceiveFilters = efi_snp_receive_filters, + .StationAddress = efi_snp_station_address, + .Statistics = efi_snp_statistics, + .MCastIpToMac = efi_snp_mcast_ip_to_mac, + .NvData = efi_snp_nvdata, + .GetStatus = efi_snp_get_status, + .Transmit = efi_snp_transmit, + .Receive = efi_snp_receive, +}; + +/****************************************************************************** + * + * Component name protocol + * + ****************************************************************************** + */ + +/** + * Look up driver name + * + * @v name2 Component name protocol + * @v language Language to use + * @v driver_name Driver name to fill in + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_snp_get_driver_name ( EFI_COMPONENT_NAME2_PROTOCOL *name2, + CHAR8 *language __unused, CHAR16 **driver_name ) { + struct efi_snp_device *snpdev = + container_of ( name2, struct efi_snp_device, name2 ); + + *driver_name = snpdev->driver_name; + return 0; +} + +/** + * Look up controller name + * + * @v name2 Component name protocol + * @v device Device + * @v child Child device, or NULL + * @v language Language to use + * @v driver_name Device name to fill in + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_snp_get_controller_name ( EFI_COMPONENT_NAME2_PROTOCOL *name2, + EFI_HANDLE device __unused, + EFI_HANDLE child __unused, + CHAR8 *language __unused, + CHAR16 **controller_name ) { + struct efi_snp_device *snpdev = + container_of ( name2, struct efi_snp_device, name2 ); + + *controller_name = snpdev->controller_name; + return 0; +} + +/****************************************************************************** + * + * Load file protocol + * + ****************************************************************************** + */ + +/** + * Load file + * + * @v loadfile Load file protocol + * @v path File path + * @v booting Loading as part of a boot attempt + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_snp_load_file ( EFI_LOAD_FILE_PROTOCOL *load_file, + EFI_DEVICE_PATH_PROTOCOL *path __unused, + BOOLEAN booting, UINTN *len __unused, + VOID *data __unused ) { + struct efi_snp_device *snpdev = + container_of ( load_file, struct efi_snp_device, load_file ); + struct net_device *netdev = snpdev->netdev; + + /* Fail unless this is a boot attempt */ + if ( ! booting ) { + DBGC ( snpdev, "SNPDEV %p cannot load non-boot file\n", + snpdev ); + return EFI_UNSUPPORTED; + } + + /* Claim network devices for use by iPXE */ + efi_snp_claim(); + + /* Boot from network device */ + ipxe ( netdev ); + + /* Release network devices for use via SNP */ + efi_snp_release(); + + /* Assume boot process was aborted */ + return EFI_ABORTED; +} + +/** Load file protocol */ +static EFI_LOAD_FILE_PROTOCOL efi_snp_load_file_protocol = { + .LoadFile = efi_snp_load_file, +}; + +/****************************************************************************** + * + * iPXE network driver + * + ****************************************************************************** + */ + +/** + * Locate SNP device corresponding to network device + * + * @v netdev Network device + * @ret snp SNP device, or NULL if not found + */ +static struct efi_snp_device * efi_snp_demux ( struct net_device *netdev ) { + struct efi_snp_device *snpdev; + + list_for_each_entry ( snpdev, &efi_snp_devices, list ) { + if ( snpdev->netdev == netdev ) + return snpdev; + } + return NULL; +} + +/** + * Create SNP device + * + * @v netdev Network device + * @ret rc Return status code + */ +static int efi_snp_probe ( struct net_device *netdev ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + struct efi_device *efidev; + struct efi_snp_device *snpdev; + union { + EFI_DEVICE_PATH_PROTOCOL *path; + void *interface; + } path; + EFI_DEVICE_PATH_PROTOCOL *path_end; + MAC_ADDR_DEVICE_PATH *macpath; + size_t path_prefix_len = 0; + EFI_STATUS efirc; + int rc; + + /* Find parent EFI device */ + efidev = efidev_parent ( netdev->dev ); + if ( ! efidev ) { + DBG ( "SNP skipping non-EFI device %s\n", netdev->name ); + rc = 0; + goto err_no_efidev; + } + + /* Allocate the SNP device */ + snpdev = zalloc ( sizeof ( *snpdev ) ); + if ( ! snpdev ) { + rc = -ENOMEM; + goto err_alloc_snp; + } + snpdev->netdev = netdev_get ( netdev ); + snpdev->efidev = efidev; + + /* Sanity check */ + if ( netdev->ll_protocol->ll_addr_len > sizeof ( EFI_MAC_ADDRESS ) ) { + DBGC ( snpdev, "SNPDEV %p cannot support link-layer address " + "length %d for %s\n", snpdev, + netdev->ll_protocol->ll_addr_len, netdev->name ); + rc = -ENOTSUP; + goto err_ll_addr_len; + } + + /* Populate the SNP structure */ + memcpy ( &snpdev->snp, &efi_snp_device_snp, sizeof ( snpdev->snp ) ); + snpdev->snp.Mode = &snpdev->mode; + if ( ( efirc = bs->CreateEvent ( EVT_NOTIFY_WAIT, TPL_NOTIFY, + efi_snp_wait_for_packet, snpdev, + &snpdev->snp.WaitForPacket ) ) != 0 ){ + rc = -EEFI ( efirc ); + DBGC ( snpdev, "SNPDEV %p could not create event: %s\n", + snpdev, strerror ( rc ) ); + goto err_create_event; + } + + /* Populate the SNP mode structure */ + snpdev->mode.State = EfiSimpleNetworkStopped; + efi_snp_set_mode ( snpdev ); + + /* Populate the NII structure */ + snpdev->nii.Revision = + EFI_NETWORK_INTERFACE_IDENTIFIER_PROTOCOL_REVISION; + strncpy ( snpdev->nii.StringId, "iPXE", + sizeof ( snpdev->nii.StringId ) ); + + /* Populate the component name structure */ + efi_snprintf ( snpdev->driver_name, + ( sizeof ( snpdev->driver_name ) / + sizeof ( snpdev->driver_name[0] ) ), + "%s %s", product_short_name, netdev->dev->driver_name ); + efi_snprintf ( snpdev->controller_name, + ( sizeof ( snpdev->controller_name ) / + sizeof ( snpdev->controller_name[0] ) ), + "%s %s (%s, %s)", product_short_name, + netdev->dev->driver_name, netdev->dev->name, + netdev_addr ( netdev ) ); + snpdev->name2.GetDriverName = efi_snp_get_driver_name; + snpdev->name2.GetControllerName = efi_snp_get_controller_name; + snpdev->name2.SupportedLanguages = "en"; + + /* Populate the load file protocol structure */ + memcpy ( &snpdev->load_file, &efi_snp_load_file_protocol, + sizeof ( snpdev->load_file ) ); + + /* Populate the device name */ + efi_snprintf ( snpdev->name, ( sizeof ( snpdev->name ) / + sizeof ( snpdev->name[0] ) ), + "%s", netdev->name ); + + /* Get the parent device path */ + if ( ( efirc = bs->OpenProtocol ( efidev->device, + &efi_device_path_protocol_guid, + &path.interface, efi_image_handle, + efidev->device, + EFI_OPEN_PROTOCOL_GET_PROTOCOL ))!=0){ + rc = -EEFI ( efirc ); + DBGC ( snpdev, "SNPDEV %p cannot get %p %s device path: %s\n", + snpdev, efidev->device, + efi_handle_name ( efidev->device ), strerror ( rc ) ); + goto err_open_device_path; + } + + /* Allocate the new device path */ + path_end = efi_devpath_end ( path.path ); + path_prefix_len = ( ( ( void * ) path_end ) - ( ( void * ) path.path )); + snpdev->path = zalloc ( path_prefix_len + sizeof ( *macpath ) + + sizeof ( *path_end ) ); + if ( ! snpdev->path ) { + rc = -ENOMEM; + goto err_alloc_device_path; + } + + /* Populate the device path */ + memcpy ( snpdev->path, path.path, path_prefix_len ); + macpath = ( ( ( void * ) snpdev->path ) + path_prefix_len ); + path_end = ( ( void * ) ( macpath + 1 ) ); + memset ( macpath, 0, sizeof ( *macpath ) ); + macpath->Header.Type = MESSAGING_DEVICE_PATH; + macpath->Header.SubType = MSG_MAC_ADDR_DP; + macpath->Header.Length[0] = sizeof ( *macpath ); + memcpy ( &macpath->MacAddress, netdev->ll_addr, + sizeof ( macpath->MacAddress ) ); + macpath->IfType = ntohs ( netdev->ll_protocol->ll_proto ); + memset ( path_end, 0, sizeof ( *path_end ) ); + path_end->Type = END_DEVICE_PATH_TYPE; + path_end->SubType = END_ENTIRE_DEVICE_PATH_SUBTYPE; + path_end->Length[0] = sizeof ( *path_end ); + + /* Install the SNP */ + if ( ( efirc = bs->InstallMultipleProtocolInterfaces ( + &snpdev->handle, + &efi_simple_network_protocol_guid, &snpdev->snp, + &efi_device_path_protocol_guid, snpdev->path, + &efi_nii_protocol_guid, &snpdev->nii, + &efi_nii31_protocol_guid, &snpdev->nii, + &efi_component_name2_protocol_guid, &snpdev->name2, + &efi_load_file_protocol_guid, &snpdev->load_file, + NULL ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( snpdev, "SNPDEV %p could not install protocols: " + "%s\n", snpdev, strerror ( rc ) ); + goto err_install_protocol_interface; + } + + /* Add as child of EFI parent device */ + if ( ( rc = efi_child_add ( efidev->device, snpdev->handle ) ) != 0 ) { + DBGC ( snpdev, "SNPDEV %p could not become child of %p %s: " + "%s\n", snpdev, efidev->device, + efi_handle_name ( efidev->device ), strerror ( rc ) ); + goto err_efi_child_add; + } + + /* Install HII */ + if ( ( rc = efi_snp_hii_install ( snpdev ) ) != 0 ) { + DBGC ( snpdev, "SNPDEV %p could not install HII: %s\n", + snpdev, strerror ( rc ) ); + /* HII fails on several platforms. It's + * non-essential, so treat this as a non-fatal + * error. + */ + } + + /* Add to list of SNP devices */ + list_add ( &snpdev->list, &efi_snp_devices ); + + /* Close device path */ + bs->CloseProtocol ( efidev->device, &efi_device_path_protocol_guid, + efi_image_handle, efidev->device ); + + DBGC ( snpdev, "SNPDEV %p installed for %s as device %p %s\n", + snpdev, netdev->name, snpdev->handle, + efi_handle_name ( snpdev->handle ) ); + return 0; + + if ( snpdev->package_list ) + efi_snp_hii_uninstall ( snpdev ); + efi_child_del ( efidev->device, snpdev->handle ); + err_efi_child_add: + bs->UninstallMultipleProtocolInterfaces ( + snpdev->handle, + &efi_simple_network_protocol_guid, &snpdev->snp, + &efi_device_path_protocol_guid, snpdev->path, + &efi_nii_protocol_guid, &snpdev->nii, + &efi_nii31_protocol_guid, &snpdev->nii, + &efi_component_name2_protocol_guid, &snpdev->name2, + &efi_load_file_protocol_guid, &snpdev->load_file, + NULL ); + err_install_protocol_interface: + free ( snpdev->path ); + err_alloc_device_path: + bs->CloseProtocol ( efidev->device, &efi_device_path_protocol_guid, + efi_image_handle, efidev->device ); + err_open_device_path: + bs->CloseEvent ( snpdev->snp.WaitForPacket ); + err_create_event: + err_ll_addr_len: + netdev_put ( netdev ); + free ( snpdev ); + err_alloc_snp: + err_no_efidev: + return rc; +} + +/** + * Handle SNP device or link state change + * + * @v netdev Network device + */ +static void efi_snp_notify ( struct net_device *netdev ) { + struct efi_snp_device *snpdev; + + /* Locate SNP device */ + snpdev = efi_snp_demux ( netdev ); + if ( ! snpdev ) { + DBG ( "SNP skipping non-SNP device %s\n", netdev->name ); + return; + } + + /* Update link state */ + snpdev->mode.MediaPresent = + ( netdev_link_ok ( netdev ) ? TRUE : FALSE ); + DBGC ( snpdev, "SNPDEV %p link is %s\n", snpdev, + ( snpdev->mode.MediaPresent ? "up" : "down" ) ); + + /* Update mode state */ + efi_snp_set_state ( snpdev ); +} + +/** + * Destroy SNP device + * + * @v netdev Network device + */ +static void efi_snp_remove ( struct net_device *netdev ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + struct efi_snp_device *snpdev; + + /* Locate SNP device */ + snpdev = efi_snp_demux ( netdev ); + if ( ! snpdev ) { + DBG ( "SNP skipping non-SNP device %s\n", netdev->name ); + return; + } + + /* Uninstall the SNP */ + if ( snpdev->package_list ) + efi_snp_hii_uninstall ( snpdev ); + efi_child_del ( snpdev->efidev->device, snpdev->handle ); + list_del ( &snpdev->list ); + bs->UninstallMultipleProtocolInterfaces ( + snpdev->handle, + &efi_simple_network_protocol_guid, &snpdev->snp, + &efi_device_path_protocol_guid, snpdev->path, + &efi_nii_protocol_guid, &snpdev->nii, + &efi_nii31_protocol_guid, &snpdev->nii, + &efi_component_name2_protocol_guid, &snpdev->name2, + &efi_load_file_protocol_guid, &snpdev->load_file, + NULL ); + free ( snpdev->path ); + bs->CloseEvent ( snpdev->snp.WaitForPacket ); + netdev_put ( snpdev->netdev ); + free ( snpdev ); +} + +/** SNP driver */ +struct net_driver efi_snp_driver __net_driver = { + .name = "SNP", + .probe = efi_snp_probe, + .notify = efi_snp_notify, + .remove = efi_snp_remove, +}; + +/** + * Find SNP device by EFI device handle + * + * @v handle EFI device handle + * @ret snpdev SNP device, or NULL + */ +struct efi_snp_device * find_snpdev ( EFI_HANDLE handle ) { + struct efi_snp_device *snpdev; + + list_for_each_entry ( snpdev, &efi_snp_devices, list ) { + if ( snpdev->handle == handle ) + return snpdev; + } + return NULL; +} + +/** + * Get most recently opened SNP device + * + * @ret snpdev Most recently opened SNP device, or NULL + */ +struct efi_snp_device * last_opened_snpdev ( void ) { + struct net_device *netdev; + + netdev = last_opened_netdev(); + if ( ! netdev ) + return NULL; + + return efi_snp_demux ( netdev ); +} + +/** + * Set SNP claimed/released state + * + * @v claimed Network devices are claimed for use by iPXE + */ +void efi_snp_set_claimed ( int claimed ) { + struct efi_snp_device *snpdev; + + /* Claim SNP devices */ + efi_snp_claimed = claimed; + + /* Update SNP mode state for each interface */ + list_for_each_entry ( snpdev, &efi_snp_devices, list ) + efi_snp_set_state ( snpdev ); +} -- cgit 1.2.3-korg