+/*
+ * Remove a device from the PCI device list.
+ *
+ * @v pci PCI device to remove.
+ *
+ * This is a PCI Device Driver API function.
+ */
+static void ifec_pci_remove ( struct pci_device *pci )
+{
+ struct net_device *netdev = pci_get_drvdata ( pci );
+
+ DBGP ( "ifec_pci_remove\n" );
+
+ unregister_netdev ( netdev );
+ ifec_reset ( netdev );
+ netdev_nullify ( netdev );
+ netdev_put ( netdev );
+}
+
+/****************** gPXE Network Device Driver API functions *****************/
+
+/*
+ * Close a network device.
+ *
+ * @v netdev Device to close.
+ *
+ * This is a gPXE Network Device Driver API function.
+ */
+static void ifec_net_close ( struct net_device *netdev )
+{
+ struct ifec_private *priv = netdev->priv;
+ unsigned long ioaddr = priv->ioaddr;
+ unsigned short intr_status;
+
+ DBGP ( "ifec_net_close\n" );
+
+ /* disable interrupts */
+ ifec_net_irq ( netdev, 0 );
+
+ /* Ack & clear ints */
+ intr_status = inw ( ioaddr + SCBStatus );
+ outw ( intr_status, ioaddr + SCBStatus );
+ inw ( ioaddr + SCBStatus );
+
+ ifec_reset ( netdev );
+
+ /* Free any resources */
+ ifec_free ( netdev );
+}
+
+/* Interrupts to be masked */
+#define INTERRUPT_MASK ( SCBMaskEarlyRx | SCBMaskFlowCtl )
+
+/*
+ * Enable or disable IRQ masking.
+ *
+ * @v netdev Device to control.
+ * @v enable Zero to mask off IRQ, non-zero to enable IRQ.
+ *
+ * This is a gPXE Network Driver API function.
+ */
+static void ifec_net_irq ( struct net_device *netdev, int enable )
+{
+ struct ifec_private *priv = netdev->priv;
+ unsigned long ioaddr = priv->ioaddr;
+
+ DBGP ( "ifec_net_irq\n" );
+
+ outw ( enable ? INTERRUPT_MASK : SCBMaskAll, ioaddr + SCBCmd );
+}
+
+/*
+ * Opens a network device.
+ *
+ * @v netdev Device to be opened.
+ * @ret rc Non-zero if failed to open.
+ *
+ * This enables tx and rx on the device.
+ * This is a gPXE Network Device Driver API function.
+ */
+static int ifec_net_open ( struct net_device *netdev )
+{
+ struct ifec_private *priv = netdev->priv;
+ struct ifec_ias *ias = NULL;
+ struct ifec_cfg *cfg = NULL;
+ int i, options;
+ int rc = -ENOMEM;
+
+ DBGP ( "ifec_net_open: " );
+
+ /* Ensure interrupts are disabled. */
+ ifec_net_irq ( netdev, 0 );
+
+ /* Initialize Command Unit and Receive Unit base addresses. */
+ ifec_scb_cmd ( netdev, 0, RUAddrLoad );
+ ifec_scb_cmd ( netdev, virt_to_bus ( &priv->stats ), CUStatsAddr );
+ ifec_scb_cmd ( netdev, 0, CUCmdBase );
+
+ /* Initialize both rings */
+ if ( ( rc = ifec_rx_setup ( netdev ) ) != 0 )
+ goto error;
+ if ( ( rc = ifec_tx_setup ( netdev ) ) != 0 )
+ goto error;
+
+ /* Initialize MDIO */
+ options = 0x00; /* 0x40 = 10mbps half duplex, 0x00 = Autosense */
+ ifec_mdio_setup ( netdev, options );
+
+ /* Prepare MAC address w/ Individual Address Setup (ias) command.*/
+ ias = malloc_dma ( sizeof ( *ias ), CB_ALIGN );
+ if ( !ias ) {
+ rc = -ENOMEM;
+ goto error;
+ }
+ ias->command = CmdIASetup;
+ ias->status = 0;
+ memcpy ( ias->ia, netdev->ll_addr, ETH_ALEN );
+
+ /* Prepare operating parameters w/ a configure command. */
+ cfg = malloc_dma ( sizeof ( *cfg ), CB_ALIGN );
+ if ( !cfg ) {
+ rc = -ENOMEM;
+ goto error;
+ }
+ memcpy ( cfg, &ifec_cfg, sizeof ( *cfg ) );
+ cfg->link = virt_to_bus ( priv->tcbs );
+ cfg->byte[19] = ( options & 0x10 ) ? 0xC0 : 0x80;
+ ias->link = virt_to_bus ( cfg );
+
+ /* Issue the ias and configure commands. */
+ ifec_scb_cmd ( netdev, virt_to_bus ( ias ), CUStart );
+ ifec_scb_cmd_wait ( netdev );
+ priv->configured = 1;
+
+ /* Wait up to 10 ms for configuration to initiate */
+ for ( i = 10; i && !cfg->status; i-- )
+ mdelay ( 1 );
+ if ( ! cfg->status ) {
+ DBG ( "Failed to initiate!\n" );
+ goto error;
+ }
+ free_dma ( ias, sizeof ( *ias ) );
+ free_dma ( cfg, sizeof ( *cfg ) );
+ DBG2 ( "cfg " );
+
+ /* Enable rx by sending ring address to card */
+ if ( priv->rfds[0] != NULL ) {
+ ifec_scb_cmd ( netdev, virt_to_bus( priv->rfds[0] ), RUStart );
+ ifec_scb_cmd_wait ( netdev );
+ }
+ DBG2 ( "rx_start\n" );
+
+ return 0;
+
+error:
+ free_dma ( cfg, sizeof ( *cfg ) );
+ free_dma ( ias, sizeof ( *ias ) );
+ ifec_free ( netdev );
+ ifec_reset ( netdev );
+ return rc;
+}
+
+/*
+ * This function allows a driver to process events during operation.
+ *
+ * @v netdev Device being polled.
+ *
+ * This is called periodically by gPXE to let the driver check the status of
+ * transmitted packets and to allow the driver to check for received packets.
+ * This is a gPXE Network Device Driver API function.
+ */
+static void ifec_net_poll ( struct net_device *netdev )
+{
+ struct ifec_private *priv = netdev->priv;
+ static int linkpoll = 0;
+ unsigned short intr_status;
+
+ DBGP ( "ifec_net_poll\n" );
+
+ /* acknowledge interrupts ASAP */
+ intr_status = inw ( priv->ioaddr + SCBStatus );
+ outw ( intr_status, priv->ioaddr + SCBStatus );
+ inw ( priv->ioaddr + SCBStatus );
+
+ DBG2 ( "poll - status: 0x%04X\n", intr_status );
+
+ if ( ++linkpoll > LINK_CHECK_PERIOD ) {
+ linkpoll = 0;
+ ifec_link_update ( netdev ); /* Update link state */
+ }
+
+ /* anything to do here? */
+ if ( ( intr_status & ( ~INTERRUPT_MASK ) ) == 0 )
+ return;
+
+ /* process received and transmitted packets */
+ ifec_tx_process ( netdev );
+ ifec_rx_process ( netdev );
+
+ ifec_check_ru_status ( netdev, intr_status );
+
+ return;
+}
+
+/*
+ * This transmits a packet.
+ *
+ * @v netdev Device to transmit from.
+ * @v iobuf Data to transmit.
+ * @ret rc Non-zero if failed to transmit.
+ *
+ * This is a gPXE Network Driver API function.
+ */
+static int ifec_net_transmit ( struct net_device *netdev,
+ struct io_buffer *iobuf )
+{
+ struct ifec_private *priv = netdev->priv;
+ struct ifec_tcb *tcb = priv->tcb_head->next;
+ unsigned long ioaddr = priv->ioaddr;
+
+ DBGP ( "ifec_net_transmit\n" );
+
+ /* Wait for TCB to become available. */
+ if ( tcb->status || tcb->iob ) {
+ DBG ( "TX overflow\n" );
+ return -ENOBUFS;
+ }
+
+ DBG2 ( "transmitting packet (%d bytes). status = %hX, cmd=%hX\n",
+ iob_len ( iobuf ), tcb->status, inw ( ioaddr + SCBCmd ) );
+
+ tcb->command = CmdSuspend | CmdTx | CmdTxFlex;
+ tcb->count = 0x01208000;
+ tcb->tbd_addr0 = virt_to_bus ( iobuf->data );
+ tcb->tbd_size0 = 0x3FFF & iob_len ( iobuf );
+ tcb->iob = iobuf;
+
+ ifec_tx_wake ( netdev );
+
+ /* Append to end of ring. */
+ priv->tcb_head = tcb;