diff options
Diffstat (limited to 'roms/ipxe/src/arch/i386/interface/pxe/pxe_undi.c')
-rw-r--r-- | roms/ipxe/src/arch/i386/interface/pxe/pxe_undi.c | 1080 |
1 files changed, 1080 insertions, 0 deletions
diff --git a/roms/ipxe/src/arch/i386/interface/pxe/pxe_undi.c b/roms/ipxe/src/arch/i386/interface/pxe/pxe_undi.c new file mode 100644 index 00000000..29e586ed --- /dev/null +++ b/roms/ipxe/src/arch/i386/interface/pxe/pxe_undi.c @@ -0,0 +1,1080 @@ +/** @file + * + * PXE UNDI API + * + */ + +/* + * Copyright (C) 2004 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 <stdint.h> +#include <stdio.h> +#include <string.h> +#include <byteswap.h> +#include <basemem_packet.h> +#include <ipxe/netdevice.h> +#include <ipxe/iobuf.h> +#include <ipxe/device.h> +#include <ipxe/pci.h> +#include <ipxe/if_ether.h> +#include <ipxe/ip.h> +#include <ipxe/arp.h> +#include <ipxe/rarp.h> +#include <ipxe/profile.h> +#include "pxe.h" + +/** + * Count of outstanding transmitted packets + * + * This is incremented each time PXENV_UNDI_TRANSMIT is called, and + * decremented each time that PXENV_UNDI_ISR is called with the TX + * queue empty, stopping when the count reaches zero. This allows us + * to provide a pessimistic approximation of TX completion events to + * the PXE NBP simply by monitoring the netdev's TX queue. + */ +static int undi_tx_count = 0; + +struct net_device *pxe_netdev = NULL; + +/** Transmit profiler */ +static struct profiler undi_tx_profiler __profiler = { .name = "undi.tx" }; + +/** + * Set network device as current PXE network device + * + * @v netdev Network device, or NULL + */ +void pxe_set_netdev ( struct net_device *netdev ) { + + if ( pxe_netdev ) { + netdev_rx_unfreeze ( pxe_netdev ); + netdev_put ( pxe_netdev ); + } + + pxe_netdev = NULL; + + if ( netdev ) + pxe_netdev = netdev_get ( netdev ); +} + +/** + * Open PXE network device + * + * @ret rc Return status code + */ +static int pxe_netdev_open ( void ) { + int rc; + + assert ( pxe_netdev != NULL ); + + if ( ( rc = netdev_open ( pxe_netdev ) ) != 0 ) + return rc; + + netdev_rx_freeze ( pxe_netdev ); + netdev_irq ( pxe_netdev, 1 ); + + return 0; +} + +/** + * Close PXE network device + * + */ +static void pxe_netdev_close ( void ) { + + assert ( pxe_netdev != NULL ); + netdev_rx_unfreeze ( pxe_netdev ); + netdev_irq ( pxe_netdev, 0 ); + netdev_close ( pxe_netdev ); + undi_tx_count = 0; +} + +/** + * Dump multicast address list + * + * @v mcast PXE multicast address list + */ +static void pxe_dump_mcast_list ( struct s_PXENV_UNDI_MCAST_ADDRESS *mcast ) { + struct ll_protocol *ll_protocol = pxe_netdev->ll_protocol; + unsigned int i; + + for ( i = 0 ; i < mcast->MCastAddrCount ; i++ ) { + DBGC ( &pxe_netdev, " %s", + ll_protocol->ntoa ( mcast->McastAddr[i] ) ); + } +} + +/* PXENV_UNDI_STARTUP + * + * Status: working + */ +static PXENV_EXIT_t +pxenv_undi_startup ( struct s_PXENV_UNDI_STARTUP *undi_startup ) { + DBGC ( &pxe_netdev, "PXENV_UNDI_STARTUP\n" ); + + /* Sanity check */ + if ( ! pxe_netdev ) { + DBGC ( &pxe_netdev, "PXENV_UNDI_STARTUP called with no " + "network device\n" ); + undi_startup->Status = PXENV_STATUS_UNDI_INVALID_STATE; + return PXENV_EXIT_FAILURE; + } + + undi_startup->Status = PXENV_STATUS_SUCCESS; + return PXENV_EXIT_SUCCESS; +} + +/* PXENV_UNDI_CLEANUP + * + * Status: working + */ +static PXENV_EXIT_t +pxenv_undi_cleanup ( struct s_PXENV_UNDI_CLEANUP *undi_cleanup ) { + DBGC ( &pxe_netdev, "PXENV_UNDI_CLEANUP\n" ); + + /* Sanity check */ + if ( ! pxe_netdev ) { + DBGC ( &pxe_netdev, "PXENV_UNDI_CLEANUP called with no " + "network device\n" ); + undi_cleanup->Status = PXENV_STATUS_UNDI_INVALID_STATE; + return PXENV_EXIT_FAILURE; + } + + /* Close network device */ + pxe_netdev_close(); + + undi_cleanup->Status = PXENV_STATUS_SUCCESS; + return PXENV_EXIT_SUCCESS; +} + +/* PXENV_UNDI_INITIALIZE + * + * Status: working + */ +static PXENV_EXIT_t +pxenv_undi_initialize ( struct s_PXENV_UNDI_INITIALIZE *undi_initialize ) { + DBGC ( &pxe_netdev, "PXENV_UNDI_INITIALIZE protocolini %08x\n", + undi_initialize->ProtocolIni ); + + /* Sanity check */ + if ( ! pxe_netdev ) { + DBGC ( &pxe_netdev, "PXENV_UNDI_INITIALIZE called with no " + "network device\n" ); + undi_initialize->Status = PXENV_STATUS_UNDI_INVALID_STATE; + return PXENV_EXIT_FAILURE; + } + + undi_initialize->Status = PXENV_STATUS_SUCCESS; + return PXENV_EXIT_SUCCESS; +} + +/* PXENV_UNDI_RESET_ADAPTER + * + * Status: working + */ +static PXENV_EXIT_t +pxenv_undi_reset_adapter ( struct s_PXENV_UNDI_RESET *undi_reset_adapter ) { + int rc; + + DBGC ( &pxe_netdev, "PXENV_UNDI_RESET_ADAPTER" ); + pxe_dump_mcast_list ( &undi_reset_adapter->R_Mcast_Buf ); + DBGC ( &pxe_netdev, "\n" ); + + /* Sanity check */ + if ( ! pxe_netdev ) { + DBGC ( &pxe_netdev, "PXENV_UNDI_RESET_ADAPTER called with no " + "network device\n" ); + undi_reset_adapter->Status = PXENV_STATUS_UNDI_INVALID_STATE; + return PXENV_EXIT_FAILURE; + } + + /* Close and reopen network device */ + pxe_netdev_close(); + if ( ( rc = pxe_netdev_open() ) != 0 ) { + DBGC ( &pxe_netdev, "PXENV_UNDI_RESET_ADAPTER could not " + "reopen %s: %s\n", pxe_netdev->name, strerror ( rc ) ); + undi_reset_adapter->Status = PXENV_STATUS ( rc ); + return PXENV_EXIT_FAILURE; + } + + undi_reset_adapter->Status = PXENV_STATUS_SUCCESS; + return PXENV_EXIT_SUCCESS; +} + +/* PXENV_UNDI_SHUTDOWN + * + * Status: working + */ +static PXENV_EXIT_t +pxenv_undi_shutdown ( struct s_PXENV_UNDI_SHUTDOWN *undi_shutdown ) { + DBGC ( &pxe_netdev, "PXENV_UNDI_SHUTDOWN\n" ); + + /* Sanity check */ + if ( ! pxe_netdev ) { + DBGC ( &pxe_netdev, "PXENV_UNDI_SHUTDOWN called with no " + "network device\n" ); + undi_shutdown->Status = PXENV_STATUS_UNDI_INVALID_STATE; + return PXENV_EXIT_FAILURE; + } + + /* Close network device */ + pxe_netdev_close(); + + undi_shutdown->Status = PXENV_STATUS_SUCCESS; + return PXENV_EXIT_SUCCESS; +} + +/* PXENV_UNDI_OPEN + * + * Status: working + */ +static PXENV_EXIT_t pxenv_undi_open ( struct s_PXENV_UNDI_OPEN *undi_open ) { + int rc; + + DBGC ( &pxe_netdev, "PXENV_UNDI_OPEN flag %04x filter %04x", + undi_open->OpenFlag, undi_open->PktFilter ); + pxe_dump_mcast_list ( &undi_open->R_Mcast_Buf ); + DBGC ( &pxe_netdev, "\n" ); + + /* Sanity check */ + if ( ! pxe_netdev ) { + DBGC ( &pxe_netdev, "PXENV_UNDI_OPEN called with no " + "network device\n" ); + undi_open->Status = PXENV_STATUS_UNDI_INVALID_STATE; + return PXENV_EXIT_FAILURE; + } + + /* Open network device */ + if ( ( rc = pxe_netdev_open() ) != 0 ) { + DBGC ( &pxe_netdev, "PXENV_UNDI_OPEN could not open %s: %s\n", + pxe_netdev->name, strerror ( rc ) ); + undi_open->Status = PXENV_STATUS ( rc ); + return PXENV_EXIT_FAILURE; + } + + undi_open->Status = PXENV_STATUS_SUCCESS; + return PXENV_EXIT_SUCCESS; +} + +/* PXENV_UNDI_CLOSE + * + * Status: working + */ +static PXENV_EXIT_t pxenv_undi_close ( struct s_PXENV_UNDI_CLOSE *undi_close ) { + DBGC ( &pxe_netdev, "PXENV_UNDI_CLOSE\n" ); + + /* Sanity check */ + if ( ! pxe_netdev ) { + DBGC ( &pxe_netdev, "PXENV_UNDI_CLOSE called with no " + "network device\n" ); + undi_close->Status = PXENV_STATUS_UNDI_INVALID_STATE; + return PXENV_EXIT_FAILURE; + } + + /* Close network device */ + pxe_netdev_close(); + + undi_close->Status = PXENV_STATUS_SUCCESS; + return PXENV_EXIT_SUCCESS; +} + +/* PXENV_UNDI_TRANSMIT + * + * Status: working + */ +static PXENV_EXIT_t +pxenv_undi_transmit ( struct s_PXENV_UNDI_TRANSMIT *undi_transmit ) { + struct s_PXENV_UNDI_TBD tbd; + struct DataBlk *datablk; + struct io_buffer *iobuf; + struct net_protocol *net_protocol; + struct ll_protocol *ll_protocol; + char destaddr[MAX_LL_ADDR_LEN]; + const void *ll_dest; + size_t len; + unsigned int i; + int rc; + + /* Start profiling */ + profile_start ( &undi_tx_profiler ); + + /* Sanity check */ + if ( ! pxe_netdev ) { + DBGC ( &pxe_netdev, "PXENV_UNDI_TRANSMIT called with no " + "network device\n" ); + undi_transmit->Status = PXENV_STATUS_UNDI_INVALID_STATE; + return PXENV_EXIT_FAILURE; + } + + DBGC2 ( &pxe_netdev, "PXENV_UNDI_TRANSMIT" ); + + /* Forcibly enable interrupts and freeze receive queue + * processing at this point, to work around callers that never + * call PXENV_UNDI_OPEN before attempting to use the UNDI API. + */ + if ( ! netdev_rx_frozen ( pxe_netdev ) ) { + netdev_rx_freeze ( pxe_netdev ); + netdev_irq ( pxe_netdev, 1 ); + } + + /* Identify network-layer protocol */ + switch ( undi_transmit->Protocol ) { + case P_IP: net_protocol = &ipv4_protocol; break; + case P_ARP: net_protocol = &arp_protocol; break; + case P_RARP: net_protocol = &rarp_protocol; break; + case P_UNKNOWN: + net_protocol = NULL; + break; + default: + DBGC2 ( &pxe_netdev, " %02x invalid protocol\n", + undi_transmit->Protocol ); + undi_transmit->Status = PXENV_STATUS_UNDI_INVALID_PARAMETER; + return PXENV_EXIT_FAILURE; + } + DBGC2 ( &pxe_netdev, " %s", + ( net_protocol ? net_protocol->name : "RAW" ) ); + + /* Calculate total packet length */ + copy_from_real ( &tbd, undi_transmit->TBD.segment, + undi_transmit->TBD.offset, sizeof ( tbd ) ); + len = tbd.ImmedLength; + DBGC2 ( &pxe_netdev, " %04x:%04x+%x", tbd.Xmit.segment, tbd.Xmit.offset, + tbd.ImmedLength ); + for ( i = 0 ; i < tbd.DataBlkCount ; i++ ) { + datablk = &tbd.DataBlock[i]; + len += datablk->TDDataLen; + DBGC2 ( &pxe_netdev, " %04x:%04x+%x", + datablk->TDDataPtr.segment, datablk->TDDataPtr.offset, + datablk->TDDataLen ); + } + + /* Allocate and fill I/O buffer */ + iobuf = alloc_iob ( MAX_LL_HEADER_LEN + + ( ( len > IOB_ZLEN ) ? len : IOB_ZLEN ) ); + if ( ! iobuf ) { + DBGC2 ( &pxe_netdev, " could not allocate iobuf\n" ); + undi_transmit->Status = PXENV_STATUS_OUT_OF_RESOURCES; + return PXENV_EXIT_FAILURE; + } + iob_reserve ( iobuf, MAX_LL_HEADER_LEN ); + copy_from_real ( iob_put ( iobuf, tbd.ImmedLength ), tbd.Xmit.segment, + tbd.Xmit.offset, tbd.ImmedLength ); + for ( i = 0 ; i < tbd.DataBlkCount ; i++ ) { + datablk = &tbd.DataBlock[i]; + copy_from_real ( iob_put ( iobuf, datablk->TDDataLen ), + datablk->TDDataPtr.segment, + datablk->TDDataPtr.offset, + datablk->TDDataLen ); + } + + /* Add link-layer header, if required to do so */ + if ( net_protocol != NULL ) { + + /* Calculate destination address */ + ll_protocol = pxe_netdev->ll_protocol; + if ( undi_transmit->XmitFlag == XMT_DESTADDR ) { + copy_from_real ( destaddr, + undi_transmit->DestAddr.segment, + undi_transmit->DestAddr.offset, + ll_protocol->ll_addr_len ); + ll_dest = destaddr; + DBGC2 ( &pxe_netdev, " DEST %s", + ll_protocol->ntoa ( ll_dest ) ); + } else { + ll_dest = pxe_netdev->ll_broadcast; + DBGC2 ( &pxe_netdev, " BCAST" ); + } + + /* Add link-layer header */ + if ( ( rc = ll_protocol->push ( pxe_netdev, iobuf, ll_dest, + pxe_netdev->ll_addr, + net_protocol->net_proto ))!=0){ + DBGC2 ( &pxe_netdev, " could not add link-layer " + "header: %s\n", strerror ( rc ) ); + free_iob ( iobuf ); + undi_transmit->Status = PXENV_STATUS ( rc ); + return PXENV_EXIT_FAILURE; + } + } + + /* Flag transmission as in-progress. Do this before starting + * to transmit the packet, because the ISR may trigger before + * we return from netdev_tx(). + */ + undi_tx_count++; + + /* Transmit packet */ + DBGC2 ( &pxe_netdev, "\n" ); + if ( ( rc = netdev_tx ( pxe_netdev, iobuf ) ) != 0 ) { + DBGC2 ( &pxe_netdev, "PXENV_UNDI_TRANSMIT could not transmit: " + "%s\n", strerror ( rc ) ); + undi_tx_count--; + undi_transmit->Status = PXENV_STATUS ( rc ); + return PXENV_EXIT_FAILURE; + } + + profile_stop ( &undi_tx_profiler ); + undi_transmit->Status = PXENV_STATUS_SUCCESS; + return PXENV_EXIT_SUCCESS; +} + +/* PXENV_UNDI_SET_MCAST_ADDRESS + * + * Status: working (for NICs that support receive-all-multicast) + */ +static PXENV_EXIT_t +pxenv_undi_set_mcast_address ( struct s_PXENV_UNDI_SET_MCAST_ADDRESS + *undi_set_mcast_address ) { + DBGC ( &pxe_netdev, "PXENV_UNDI_SET_MCAST_ADDRESS" ); + pxe_dump_mcast_list ( &undi_set_mcast_address->R_Mcast_Buf ); + DBGC ( &pxe_netdev, "\n" ); + + /* Sanity check */ + if ( ! pxe_netdev ) { + DBGC ( &pxe_netdev, "PXENV_UNDI_SET_MCAST_ADDRESS called with " + "no network device\n" ); + undi_set_mcast_address->Status = + PXENV_STATUS_UNDI_INVALID_STATE; + return PXENV_EXIT_FAILURE; + } + + undi_set_mcast_address->Status = PXENV_STATUS_SUCCESS; + return PXENV_EXIT_SUCCESS; +} + +/* PXENV_UNDI_SET_STATION_ADDRESS + * + * Status: working + */ +static PXENV_EXIT_t +pxenv_undi_set_station_address ( struct s_PXENV_UNDI_SET_STATION_ADDRESS + *undi_set_station_address ) { + struct ll_protocol *ll_protocol; + + /* Sanity check */ + if ( ! pxe_netdev ) { + DBGC ( &pxe_netdev, "PXENV_UNDI_SET_STATION_ADDRESS called " + "with no network device\n" ); + undi_set_station_address->Status = + PXENV_STATUS_UNDI_INVALID_STATE; + return PXENV_EXIT_FAILURE; + } + + ll_protocol = pxe_netdev->ll_protocol; + DBGC ( &pxe_netdev, "PXENV_UNDI_SET_STATION_ADDRESS %s", + ll_protocol->ntoa ( undi_set_station_address->StationAddress ) ); + + /* If adapter is open, the change will have no effect; return + * an error + */ + if ( netdev_is_open ( pxe_netdev ) ) { + DBGC ( &pxe_netdev, " failed: netdev is open\n" ); + undi_set_station_address->Status = + PXENV_STATUS_UNDI_INVALID_STATE; + return PXENV_EXIT_FAILURE; + } + + /* Update MAC address */ + memcpy ( pxe_netdev->ll_addr, + &undi_set_station_address->StationAddress, + ll_protocol->ll_addr_len ); + + DBGC ( &pxe_netdev, "\n" ); + undi_set_station_address->Status = PXENV_STATUS_SUCCESS; + return PXENV_EXIT_SUCCESS; +} + +/* PXENV_UNDI_SET_PACKET_FILTER + * + * Status: won't implement (would require driver API changes for no + * real benefit) + */ +static PXENV_EXIT_t +pxenv_undi_set_packet_filter ( struct s_PXENV_UNDI_SET_PACKET_FILTER + *undi_set_packet_filter ) { + + DBGC ( &pxe_netdev, "PXENV_UNDI_SET_PACKET_FILTER %02x\n", + undi_set_packet_filter->filter ); + + /* Sanity check */ + if ( ! pxe_netdev ) { + DBGC ( &pxe_netdev, "PXENV_UNDI_SET_PACKET_FILTER called with " + "no network device\n" ); + undi_set_packet_filter->Status = + PXENV_STATUS_UNDI_INVALID_STATE; + return PXENV_EXIT_FAILURE; + } + + /* Pretend that we succeeded, otherwise the 3Com DOS UNDI + * driver refuses to load. (We ignore the filter value in the + * PXENV_UNDI_OPEN call anyway.) + */ + undi_set_packet_filter->Status = PXENV_STATUS_SUCCESS; + + return PXENV_EXIT_SUCCESS; +} + +/* PXENV_UNDI_GET_INFORMATION + * + * Status: working + */ +static PXENV_EXIT_t +pxenv_undi_get_information ( struct s_PXENV_UNDI_GET_INFORMATION + *undi_get_information ) { + struct device *dev; + struct ll_protocol *ll_protocol; + + /* Sanity check */ + if ( ! pxe_netdev ) { + DBGC ( &pxe_netdev, "PXENV_UNDI_GET_INFORMATION called with no " + "network device\n" ); + undi_get_information->Status = PXENV_STATUS_UNDI_INVALID_STATE; + return PXENV_EXIT_FAILURE; + } + + DBGC ( &pxe_netdev, "PXENV_UNDI_GET_INFORMATION" ); + + /* Fill in information */ + dev = pxe_netdev->dev; + ll_protocol = pxe_netdev->ll_protocol; + undi_get_information->BaseIo = dev->desc.ioaddr; + undi_get_information->IntNumber = + ( netdev_irq_supported ( pxe_netdev ) ? dev->desc.irq : 0 ); + /* Cheat: assume all cards can cope with this */ + undi_get_information->MaxTranUnit = ETH_MAX_MTU; + undi_get_information->HwType = ntohs ( ll_protocol->ll_proto ); + undi_get_information->HwAddrLen = ll_protocol->ll_addr_len; + assert ( ll_protocol->ll_addr_len <= + sizeof ( undi_get_information->CurrentNodeAddress ) ); + memcpy ( &undi_get_information->CurrentNodeAddress, + pxe_netdev->ll_addr, + sizeof ( undi_get_information->CurrentNodeAddress ) ); + ll_protocol->init_addr ( pxe_netdev->hw_addr, + &undi_get_information->PermNodeAddress ); + undi_get_information->ROMAddress = 0; + /* nic.rom_info->rom_segment; */ + /* We only provide the ability to receive or transmit a single + * packet at a time. This is a bootloader, not an OS. + */ + undi_get_information->RxBufCt = 1; + undi_get_information->TxBufCt = 1; + + DBGC ( &pxe_netdev, " io %04x irq %d mtu %d %s %s\n", + undi_get_information->BaseIo, undi_get_information->IntNumber, + undi_get_information->MaxTranUnit, ll_protocol->name, + ll_protocol->ntoa ( &undi_get_information->CurrentNodeAddress )); + undi_get_information->Status = PXENV_STATUS_SUCCESS; + return PXENV_EXIT_SUCCESS; +} + +/* PXENV_UNDI_GET_STATISTICS + * + * Status: working + */ +static PXENV_EXIT_t +pxenv_undi_get_statistics ( struct s_PXENV_UNDI_GET_STATISTICS + *undi_get_statistics ) { + + /* Sanity check */ + if ( ! pxe_netdev ) { + DBGC ( &pxe_netdev, "PXENV_UNDI_GET_STATISTICS called with no " + "network device\n" ); + undi_get_statistics->Status = PXENV_STATUS_UNDI_INVALID_STATE; + return PXENV_EXIT_FAILURE; + } + + DBGC ( &pxe_netdev, "PXENV_UNDI_GET_STATISTICS" ); + + /* Report statistics */ + undi_get_statistics->XmtGoodFrames = pxe_netdev->tx_stats.good; + undi_get_statistics->RcvGoodFrames = pxe_netdev->rx_stats.good; + undi_get_statistics->RcvCRCErrors = pxe_netdev->rx_stats.bad; + undi_get_statistics->RcvResourceErrors = pxe_netdev->rx_stats.bad; + DBGC ( &pxe_netdev, " txok %d rxok %d rxcrc %d rxrsrc %d\n", + undi_get_statistics->XmtGoodFrames, + undi_get_statistics->RcvGoodFrames, + undi_get_statistics->RcvCRCErrors, + undi_get_statistics->RcvResourceErrors ); + + undi_get_statistics->Status = PXENV_STATUS_SUCCESS; + return PXENV_EXIT_SUCCESS; +} + +/* PXENV_UNDI_CLEAR_STATISTICS + * + * Status: working + */ +static PXENV_EXIT_t +pxenv_undi_clear_statistics ( struct s_PXENV_UNDI_CLEAR_STATISTICS + *undi_clear_statistics ) { + DBGC ( &pxe_netdev, "PXENV_UNDI_CLEAR_STATISTICS\n" ); + + /* Sanity check */ + if ( ! pxe_netdev ) { + DBGC ( &pxe_netdev, "PXENV_UNDI_CLEAR_STATISTICS called with " + "no network device\n" ); + undi_clear_statistics->Status = PXENV_STATUS_UNDI_INVALID_STATE; + return PXENV_EXIT_FAILURE; + } + + /* Clear statistics */ + memset ( &pxe_netdev->tx_stats, 0, sizeof ( pxe_netdev->tx_stats ) ); + memset ( &pxe_netdev->rx_stats, 0, sizeof ( pxe_netdev->rx_stats ) ); + + undi_clear_statistics->Status = PXENV_STATUS_SUCCESS; + return PXENV_EXIT_SUCCESS; +} + +/* PXENV_UNDI_INITIATE_DIAGS + * + * Status: won't implement (would require driver API changes for no + * real benefit) + */ +static PXENV_EXIT_t +pxenv_undi_initiate_diags ( struct s_PXENV_UNDI_INITIATE_DIAGS + *undi_initiate_diags ) { + DBGC ( &pxe_netdev, "PXENV_UNDI_INITIATE_DIAGS failed: unsupported\n" ); + + /* Sanity check */ + if ( ! pxe_netdev ) { + DBGC ( &pxe_netdev, "PXENV_UNDI_INITIATE_DIAGS called with no " + "network device\n" ); + undi_initiate_diags->Status = PXENV_STATUS_UNDI_INVALID_STATE; + return PXENV_EXIT_FAILURE; + } + + undi_initiate_diags->Status = PXENV_STATUS_UNSUPPORTED; + return PXENV_EXIT_FAILURE; +} + +/* PXENV_UNDI_FORCE_INTERRUPT + * + * Status: won't implement (would require driver API changes for no + * perceptible benefit) + */ +static PXENV_EXIT_t +pxenv_undi_force_interrupt ( struct s_PXENV_UNDI_FORCE_INTERRUPT + *undi_force_interrupt ) { + DBGC ( &pxe_netdev, + "PXENV_UNDI_FORCE_INTERRUPT failed: unsupported\n" ); + + /* Sanity check */ + if ( ! pxe_netdev ) { + DBGC ( &pxe_netdev, "PXENV_UNDI_FORCE_INTERRUPT called with no " + "network device\n" ); + undi_force_interrupt->Status = PXENV_STATUS_UNDI_INVALID_STATE; + return PXENV_EXIT_FAILURE; + } + + undi_force_interrupt->Status = PXENV_STATUS_UNSUPPORTED; + return PXENV_EXIT_FAILURE; +} + +/* PXENV_UNDI_GET_MCAST_ADDRESS + * + * Status: working + */ +static PXENV_EXIT_t +pxenv_undi_get_mcast_address ( struct s_PXENV_UNDI_GET_MCAST_ADDRESS + *undi_get_mcast_address ) { + struct ll_protocol *ll_protocol; + struct in_addr ip = { .s_addr = undi_get_mcast_address->InetAddr }; + int rc; + + /* Sanity check */ + if ( ! pxe_netdev ) { + DBGC ( &pxe_netdev, "PXENV_UNDI_GET_MCAST_ADDRESS called with " + "no network device\n" ); + undi_get_mcast_address->Status = + PXENV_STATUS_UNDI_INVALID_STATE; + return PXENV_EXIT_FAILURE; + } + + DBGC ( &pxe_netdev, "PXENV_UNDI_GET_MCAST_ADDRESS %s", + inet_ntoa ( ip ) ); + + /* Hash address using the network device's link-layer protocol */ + ll_protocol = pxe_netdev->ll_protocol; + if ( ( rc = ll_protocol->mc_hash ( AF_INET, &ip, + undi_get_mcast_address->MediaAddr ))!=0){ + DBGC ( &pxe_netdev, " failed: %s\n", strerror ( rc ) ); + undi_get_mcast_address->Status = PXENV_STATUS ( rc ); + return PXENV_EXIT_FAILURE; + } + DBGC ( &pxe_netdev, "=>%s\n", + ll_protocol->ntoa ( undi_get_mcast_address->MediaAddr ) ); + + undi_get_mcast_address->Status = PXENV_STATUS_SUCCESS; + return PXENV_EXIT_SUCCESS; +} + +/* PXENV_UNDI_GET_NIC_TYPE + * + * Status: working + */ +static PXENV_EXIT_t pxenv_undi_get_nic_type ( struct s_PXENV_UNDI_GET_NIC_TYPE + *undi_get_nic_type ) { + struct device *dev; + + /* Sanity check */ + if ( ! pxe_netdev ) { + DBGC ( &pxe_netdev, "PXENV_UNDI_GET_NIC_TYPE called with " + "no network device\n" ); + undi_get_nic_type->Status = PXENV_STATUS_UNDI_INVALID_STATE; + return PXENV_EXIT_FAILURE; + } + + DBGC ( &pxe_netdev, "PXENV_UNDI_GET_NIC_TYPE" ); + + /* Fill in information */ + memset ( &undi_get_nic_type->info, 0, + sizeof ( undi_get_nic_type->info ) ); + dev = pxe_netdev->dev; + switch ( dev->desc.bus_type ) { + case BUS_TYPE_PCI: { + struct pci_nic_info *info = &undi_get_nic_type->info.pci; + + undi_get_nic_type->NicType = PCI_NIC; + info->Vendor_ID = dev->desc.vendor; + info->Dev_ID = dev->desc.device; + info->Base_Class = PCI_BASE_CLASS ( dev->desc.class ); + info->Sub_Class = PCI_SUB_CLASS ( dev->desc.class ); + info->Prog_Intf = PCI_PROG_INTF ( dev->desc.class ); + info->BusDevFunc = dev->desc.location; + /* Earlier versions of the PXE specification do not + * have the SubVendor_ID and SubDevice_ID fields. It + * is possible that some NBPs will not provide space + * for them, and so we must not fill them in. + */ + DBGC ( &pxe_netdev, " PCI %02x:%02x.%x %04x:%04x " + "('%04x:%04x') %02x%02x%02x rev %02x\n", + PCI_BUS ( info->BusDevFunc ), + PCI_SLOT ( info->BusDevFunc ), + PCI_FUNC ( info->BusDevFunc ), info->Vendor_ID, + info->Dev_ID, info->SubVendor_ID, info->SubDevice_ID, + info->Base_Class, info->Sub_Class, info->Prog_Intf, + info->Rev ); + break; } + case BUS_TYPE_ISAPNP: { + struct pnp_nic_info *info = &undi_get_nic_type->info.pnp; + + undi_get_nic_type->NicType = PnP_NIC; + info->EISA_Dev_ID = ( ( dev->desc.vendor << 16 ) | + dev->desc.device ); + info->CardSelNum = dev->desc.location; + /* Cheat: remaining fields are probably unnecessary, + * and would require adding extra code to isapnp.c. + */ + DBGC ( &pxe_netdev, " ISAPnP CSN %04x %08x %02x%02x%02x\n", + info->CardSelNum, info->EISA_Dev_ID, + info->Base_Class, info->Sub_Class, info->Prog_Intf ); + break; } + default: + DBGC ( &pxe_netdev, " failed: unknown bus type\n" ); + undi_get_nic_type->Status = PXENV_STATUS_FAILURE; + return PXENV_EXIT_FAILURE; + } + + undi_get_nic_type->Status = PXENV_STATUS_SUCCESS; + return PXENV_EXIT_SUCCESS; +} + +/* PXENV_UNDI_GET_IFACE_INFO + * + * Status: working + */ +static PXENV_EXIT_t +pxenv_undi_get_iface_info ( struct s_PXENV_UNDI_GET_IFACE_INFO + *undi_get_iface_info ) { + + /* Sanity check */ + if ( ! pxe_netdev ) { + DBGC ( &pxe_netdev, "PXENV_UNDI_GET_IFACE_INFO called with " + "no network device\n" ); + undi_get_iface_info->Status = PXENV_STATUS_UNDI_INVALID_STATE; + return PXENV_EXIT_FAILURE; + } + + DBGC ( &pxe_netdev, "PXENV_UNDI_GET_IFACE_INFO" ); + + /* Just hand back some info, doesn't really matter what it is. + * Most PXE stacks seem to take this approach. + */ + snprintf ( ( char * ) undi_get_iface_info->IfaceType, + sizeof ( undi_get_iface_info->IfaceType ), "DIX+802.3" ); + undi_get_iface_info->LinkSpeed = 10000000; /* 10 Mbps */ + undi_get_iface_info->ServiceFlags = + ( SUPPORTED_BROADCAST | SUPPORTED_MULTICAST | + SUPPORTED_SET_STATION_ADDRESS | SUPPORTED_RESET | + SUPPORTED_OPEN_CLOSE ); + if ( netdev_irq_supported ( pxe_netdev ) ) + undi_get_iface_info->ServiceFlags |= SUPPORTED_IRQ; + memset ( undi_get_iface_info->Reserved, 0, + sizeof(undi_get_iface_info->Reserved) ); + + DBGC ( &pxe_netdev, " %s %dbps flags %08x\n", + undi_get_iface_info->IfaceType, undi_get_iface_info->LinkSpeed, + undi_get_iface_info->ServiceFlags ); + undi_get_iface_info->Status = PXENV_STATUS_SUCCESS; + return PXENV_EXIT_SUCCESS; +} + +/* PXENV_UNDI_GET_STATE + * + * Status: impossible due to opcode collision + */ + +/* PXENV_UNDI_ISR + * + * Status: working + */ +static PXENV_EXIT_t pxenv_undi_isr ( struct s_PXENV_UNDI_ISR *undi_isr ) { + struct io_buffer *iobuf; + size_t len; + struct ll_protocol *ll_protocol; + const void *ll_dest; + const void *ll_source; + uint16_t net_proto; + unsigned int flags; + size_t ll_hlen; + struct net_protocol *net_protocol; + unsigned int prottype; + int rc; + + /* Use a different debug colour, since UNDI ISR messages are + * likely to be interspersed amongst other UNDI messages. + */ + + /* Sanity check */ + if ( ! pxe_netdev ) { + DBGC ( &pxenv_undi_isr, "PXENV_UNDI_ISR called with " + "no network device\n" ); + undi_isr->Status = PXENV_STATUS_UNDI_INVALID_STATE; + return PXENV_EXIT_FAILURE; + } + + DBGC2 ( &pxenv_undi_isr, "PXENV_UNDI_ISR" ); + + /* Just in case some idiot actually looks at these fields when + * we weren't meant to fill them in... + */ + undi_isr->BufferLength = 0; + undi_isr->FrameLength = 0; + undi_isr->FrameHeaderLength = 0; + undi_isr->ProtType = 0; + undi_isr->PktType = 0; + + switch ( undi_isr->FuncFlag ) { + case PXENV_UNDI_ISR_IN_START : + DBGC2 ( &pxenv_undi_isr, " START" ); + + /* Call poll(). This should acknowledge the device + * interrupt and queue up any received packet. + */ + net_poll(); + + /* A 100% accurate determination of "OURS" vs "NOT + * OURS" is difficult to achieve without invasive and + * unpleasant changes to the driver model. We settle + * for always returning "OURS" if interrupts are + * currently enabled. + * + * Returning "NOT OURS" when interrupts are disabled + * allows us to avoid a potential interrupt storm when + * we are on a shared interrupt line; if we were to + * always return "OURS" then the other device's ISR + * may never be called. + */ + if ( netdev_irq_enabled ( pxe_netdev ) ) { + DBGC2 ( &pxenv_undi_isr, " OURS" ); + undi_isr->FuncFlag = PXENV_UNDI_ISR_OUT_OURS; + } else { + DBGC2 ( &pxenv_undi_isr, " NOT OURS" ); + undi_isr->FuncFlag = PXENV_UNDI_ISR_OUT_NOT_OURS; + } + + /* Disable interrupts */ + netdev_irq ( pxe_netdev, 0 ); + + break; + case PXENV_UNDI_ISR_IN_PROCESS : + case PXENV_UNDI_ISR_IN_GET_NEXT : + DBGC2 ( &pxenv_undi_isr, " %s", + ( ( undi_isr->FuncFlag == PXENV_UNDI_ISR_IN_PROCESS ) ? + "PROCESS" : "GET_NEXT" ) ); + + /* Some dumb NBPs (e.g. emBoot's winBoot/i) never call + * PXENV_UNDI_ISR with FuncFlag=PXENV_UNDI_ISR_START; + * they just sit in a tight polling loop merrily + * violating the PXE spec with repeated calls to + * PXENV_UNDI_ISR_IN_PROCESS. Force extra polls to + * cope with these out-of-spec clients. + */ + net_poll(); + + /* If we have not yet marked a TX as complete, and the + * netdev TX queue is empty, report the TX completion. + */ + if ( undi_tx_count && list_empty ( &pxe_netdev->tx_queue ) ) { + DBGC2 ( &pxenv_undi_isr, " TXC" ); + undi_tx_count--; + undi_isr->FuncFlag = PXENV_UNDI_ISR_OUT_TRANSMIT; + break; + } + + /* Remove first packet from netdev RX queue */ + iobuf = netdev_rx_dequeue ( pxe_netdev ); + if ( ! iobuf ) { + DBGC2 ( &pxenv_undi_isr, " DONE" ); + /* No more packets remaining */ + undi_isr->FuncFlag = PXENV_UNDI_ISR_OUT_DONE; + /* Re-enable interrupts */ + netdev_irq ( pxe_netdev, 1 ); + break; + } + + /* Copy packet to base memory buffer */ + len = iob_len ( iobuf ); + DBGC2 ( &pxenv_undi_isr, " RX" ); + if ( len > sizeof ( basemem_packet ) ) { + /* Should never happen */ + DBGC2 ( &pxenv_undi_isr, " overlength (%zx)", len ); + len = sizeof ( basemem_packet ); + } + memcpy ( basemem_packet, iobuf->data, len ); + + /* Strip link-layer header */ + ll_protocol = pxe_netdev->ll_protocol; + if ( ( rc = ll_protocol->pull ( pxe_netdev, iobuf, &ll_dest, + &ll_source, &net_proto, + &flags ) ) != 0 ) { + /* Assume unknown net_proto and no ll_source */ + net_proto = 0; + ll_source = NULL; + } + ll_hlen = ( len - iob_len ( iobuf ) ); + + /* Determine network-layer protocol */ + switch ( net_proto ) { + case htons ( ETH_P_IP ): + net_protocol = &ipv4_protocol; + prottype = P_IP; + break; + case htons ( ETH_P_ARP ): + net_protocol = &arp_protocol; + prottype = P_ARP; + break; + case htons ( ETH_P_RARP ): + net_protocol = &rarp_protocol; + prottype = P_RARP; + break; + default: + net_protocol = NULL; + prottype = P_UNKNOWN; + break; + } + + /* Fill in UNDI_ISR structure */ + undi_isr->FuncFlag = PXENV_UNDI_ISR_OUT_RECEIVE; + undi_isr->BufferLength = len; + undi_isr->FrameLength = len; + undi_isr->FrameHeaderLength = ll_hlen; + undi_isr->Frame.segment = rm_ds; + undi_isr->Frame.offset = __from_data16 ( basemem_packet ); + undi_isr->ProtType = prottype; + if ( flags & LL_BROADCAST ) { + undi_isr->PktType = P_BROADCAST; + } else if ( flags & LL_MULTICAST ) { + undi_isr->PktType = P_MULTICAST; + } else { + undi_isr->PktType = P_DIRECTED; + } + DBGC2 ( &pxenv_undi_isr, " %04x:%04x+%x(%x) %s hlen %d", + undi_isr->Frame.segment, undi_isr->Frame.offset, + undi_isr->BufferLength, undi_isr->FrameLength, + ( net_protocol ? net_protocol->name : "RAW" ), + undi_isr->FrameHeaderLength ); + + /* Free packet */ + free_iob ( iobuf ); + break; + default : + DBGC2 ( &pxenv_undi_isr, " INVALID(%04x)\n", + undi_isr->FuncFlag ); + + /* Should never happen */ + undi_isr->FuncFlag = PXENV_UNDI_ISR_OUT_DONE; + undi_isr->Status = PXENV_STATUS_UNDI_INVALID_PARAMETER; + return PXENV_EXIT_FAILURE; + } + + DBGC2 ( &pxenv_undi_isr, "\n" ); + undi_isr->Status = PXENV_STATUS_SUCCESS; + return PXENV_EXIT_SUCCESS; +} + +/** PXE UNDI API */ +struct pxe_api_call pxe_undi_api[] __pxe_api_call = { + PXE_API_CALL ( PXENV_UNDI_STARTUP, pxenv_undi_startup, + struct s_PXENV_UNDI_STARTUP ), + PXE_API_CALL ( PXENV_UNDI_CLEANUP, pxenv_undi_cleanup, + struct s_PXENV_UNDI_CLEANUP ), + PXE_API_CALL ( PXENV_UNDI_INITIALIZE, pxenv_undi_initialize, + struct s_PXENV_UNDI_INITIALIZE ), + PXE_API_CALL ( PXENV_UNDI_RESET_ADAPTER, pxenv_undi_reset_adapter, + struct s_PXENV_UNDI_RESET ), + PXE_API_CALL ( PXENV_UNDI_SHUTDOWN, pxenv_undi_shutdown, + struct s_PXENV_UNDI_SHUTDOWN ), + PXE_API_CALL ( PXENV_UNDI_OPEN, pxenv_undi_open, + struct s_PXENV_UNDI_OPEN ), + PXE_API_CALL ( PXENV_UNDI_CLOSE, pxenv_undi_close, + struct s_PXENV_UNDI_CLOSE ), + PXE_API_CALL ( PXENV_UNDI_TRANSMIT, pxenv_undi_transmit, + struct s_PXENV_UNDI_TRANSMIT ), + PXE_API_CALL ( PXENV_UNDI_SET_MCAST_ADDRESS, + pxenv_undi_set_mcast_address, + struct s_PXENV_UNDI_SET_MCAST_ADDRESS ), + PXE_API_CALL ( PXENV_UNDI_SET_STATION_ADDRESS, + pxenv_undi_set_station_address, + struct s_PXENV_UNDI_SET_STATION_ADDRESS ), + PXE_API_CALL ( PXENV_UNDI_SET_PACKET_FILTER, + pxenv_undi_set_packet_filter, + struct s_PXENV_UNDI_SET_PACKET_FILTER ), + PXE_API_CALL ( PXENV_UNDI_GET_INFORMATION, pxenv_undi_get_information, + struct s_PXENV_UNDI_GET_INFORMATION ), + PXE_API_CALL ( PXENV_UNDI_GET_STATISTICS, pxenv_undi_get_statistics, + struct s_PXENV_UNDI_GET_STATISTICS ), + PXE_API_CALL ( PXENV_UNDI_CLEAR_STATISTICS, pxenv_undi_clear_statistics, + struct s_PXENV_UNDI_CLEAR_STATISTICS ), + PXE_API_CALL ( PXENV_UNDI_INITIATE_DIAGS, pxenv_undi_initiate_diags, + struct s_PXENV_UNDI_INITIATE_DIAGS ), + PXE_API_CALL ( PXENV_UNDI_FORCE_INTERRUPT, pxenv_undi_force_interrupt, + struct s_PXENV_UNDI_FORCE_INTERRUPT ), + PXE_API_CALL ( PXENV_UNDI_GET_MCAST_ADDRESS, + pxenv_undi_get_mcast_address, + struct s_PXENV_UNDI_GET_MCAST_ADDRESS ), + PXE_API_CALL ( PXENV_UNDI_GET_NIC_TYPE, pxenv_undi_get_nic_type, + struct s_PXENV_UNDI_GET_NIC_TYPE ), + PXE_API_CALL ( PXENV_UNDI_GET_IFACE_INFO, pxenv_undi_get_iface_info, + struct s_PXENV_UNDI_GET_IFACE_INFO ), + PXE_API_CALL ( PXENV_UNDI_ISR, pxenv_undi_isr, + struct s_PXENV_UNDI_ISR ), +}; |