irqreturn_t islpci_interrupt(int irq, void *config) { u32 reg; islpci_private *priv = config; struct net_device *ndev = priv->ndev; void __iomem *device = priv->device_base; int powerstate = ISL38XX_PSM_POWERSAVE_STATE; /* lock the interrupt handler */ spin_lock(&priv->slock); /* received an interrupt request on a shared IRQ line * first check whether the device is in sleep mode */ reg = readl(device + ISL38XX_CTRL_STAT_REG); if (reg & ISL38XX_CTRL_STAT_SLEEPMODE) /* device is in sleep mode, IRQ was generated by someone else */ { #if VERBOSE > SHOW_ERROR_MESSAGES DEBUG(SHOW_TRACING, "Assuming someone else called the IRQ\n"); #endif spin_unlock(&priv->slock); return IRQ_NONE; } /* check whether there is any source of interrupt on the device */ reg = readl(device + ISL38XX_INT_IDENT_REG); /* also check the contents of the Interrupt Enable Register, because this * will filter out interrupt sources from other devices on the same irq ! */ reg &= readl(device + ISL38XX_INT_EN_REG); reg &= ISL38XX_INT_SOURCES; if (reg != 0) { if (islpci_get_state(priv) != PRV_STATE_SLEEP) powerstate = ISL38XX_PSM_ACTIVE_STATE; /* reset the request bits in the Identification register */ isl38xx_w32_flush(device, reg, ISL38XX_INT_ACK_REG); #if VERBOSE > SHOW_ERROR_MESSAGES DEBUG(SHOW_FUNCTION_CALLS, "IRQ: Identification register 0x%p 0x%x \n", device, reg); #endif /* check for each bit in the register separately */ if (reg & ISL38XX_INT_IDENT_UPDATE) { #if VERBOSE > SHOW_ERROR_MESSAGES /* Queue has been updated */ DEBUG(SHOW_TRACING, "IRQ: Update flag \n"); DEBUG(SHOW_QUEUE_INDEXES, "CB drv Qs: [%i][%i][%i][%i][%i][%i]\n", le32_to_cpu(priv->control_block-> driver_curr_frag[0]), le32_to_cpu(priv->control_block-> driver_curr_frag[1]), le32_to_cpu(priv->control_block-> driver_curr_frag[2]), le32_to_cpu(priv->control_block-> driver_curr_frag[3]), le32_to_cpu(priv->control_block-> driver_curr_frag[4]), le32_to_cpu(priv->control_block-> driver_curr_frag[5]) ); DEBUG(SHOW_QUEUE_INDEXES, "CB dev Qs: [%i][%i][%i][%i][%i][%i]\n", le32_to_cpu(priv->control_block-> device_curr_frag[0]), le32_to_cpu(priv->control_block-> device_curr_frag[1]), le32_to_cpu(priv->control_block-> device_curr_frag[2]), le32_to_cpu(priv->control_block-> device_curr_frag[3]), le32_to_cpu(priv->control_block-> device_curr_frag[4]), le32_to_cpu(priv->control_block-> device_curr_frag[5]) ); #endif /* cleanup the data low transmit queue */ islpci_eth_cleanup_transmit(priv, priv->control_block); /* device is in active state, update the * powerstate flag if necessary */ powerstate = ISL38XX_PSM_ACTIVE_STATE; /* check all three queues in priority order * call the PIMFOR receive function until the * queue is empty */ if (isl38xx_in_queue(priv->control_block, ISL38XX_CB_RX_MGMTQ) != 0) { #if VERBOSE > SHOW_ERROR_MESSAGES DEBUG(SHOW_TRACING, "Received frame in Management Queue\n"); #endif islpci_mgt_receive(ndev); islpci_mgt_cleanup_transmit(ndev); /* Refill slots in receive queue */ islpci_mgmt_rx_fill(ndev); /* no need to trigger the device, next islpci_mgt_transaction does it */ } while (isl38xx_in_queue(priv->control_block, ISL38XX_CB_RX_DATA_LQ) != 0) { #if VERBOSE > SHOW_ERROR_MESSAGES DEBUG(SHOW_TRACING, "Received frame in Data Low Queue \n"); #endif islpci_eth_receive(priv); } /* check whether the data transmit queues were full */ if (priv->data_low_tx_full) { /* check whether the transmit is not full anymore */ if (ISL38XX_CB_TX_QSIZE - isl38xx_in_queue(priv->control_block, ISL38XX_CB_TX_DATA_LQ) >= ISL38XX_MIN_QTHRESHOLD) { /* nope, the driver is ready for more network frames */ netif_wake_queue(priv->ndev); /* reset the full flag */ priv->data_low_tx_full = 0; } } } if (reg & ISL38XX_INT_IDENT_INIT) { /* Device has been initialized */ #if VERBOSE > SHOW_ERROR_MESSAGES DEBUG(SHOW_TRACING, "IRQ: Init flag, device initialized \n"); #endif wake_up(&priv->reset_done); } if (reg & ISL38XX_INT_IDENT_SLEEP) { /* Device intends to move to powersave state */ #if VERBOSE > SHOW_ERROR_MESSAGES DEBUG(SHOW_TRACING, "IRQ: Sleep flag \n"); #endif isl38xx_handle_sleep_request(priv->control_block, &powerstate, priv->device_base); } if (reg & ISL38XX_INT_IDENT_WAKEUP) { /* Device has been woken up to active state */ #if VERBOSE > SHOW_ERROR_MESSAGES DEBUG(SHOW_TRACING, "IRQ: Wakeup flag \n"); #endif isl38xx_handle_wakeup(priv->control_block, &powerstate, priv->device_base); } } else { #if VERBOSE > SHOW_ERROR_MESSAGES DEBUG(SHOW_TRACING, "Assuming someone else called the IRQ\n"); #endif spin_unlock(&priv->slock); return IRQ_NONE; } /* sleep -> ready */ if (islpci_get_state(priv) == PRV_STATE_SLEEP && powerstate == ISL38XX_PSM_ACTIVE_STATE) islpci_set_state(priv, PRV_STATE_READY); /* !sleep -> sleep */ if (islpci_get_state(priv) != PRV_STATE_SLEEP && powerstate == ISL38XX_PSM_POWERSAVE_STATE) islpci_set_state(priv, PRV_STATE_SLEEP); /* unlock the interrupt handler */ spin_unlock(&priv->slock); return IRQ_HANDLED; }
int islpci_eth_transmit(struct sk_buff *skb, struct net_device *nwdev) { islpci_private *private_config = nwdev->priv; isl38xx_control_block *control_block = private_config->control_block; isl38xx_fragment *fragment; u32 index, counter, pci_map_address; int fragments; int frame_size; int offset; struct sk_buff *newskb; int newskb_offset; unsigned long flags; unsigned char wds_mac[6]; #if VERBOSE > SHOW_ERROR_MESSAGES DEBUG(SHOW_FUNCTION_CALLS, "islpci_eth_transmit \n"); #endif // lock the driver code driver_lock( &private_config->slock, &flags ); // check whether the deviec is running // if( !netif_running( nwdev ) ) // { // DEBUG( SHOW_ERROR_MESSAGES, "%s: Tx on stopped device!\n", // nwdev->name); // return 1; // } // tx_timeout_check( nwdev, islpci_eth_tx_timeout ); // skb_tx_check( nwdev, skb ); // determine the amount of fragments needed to store the frame memset (wds_mac, 0, 6); #ifdef WDS_LINKS check_skb_for_wds(skb, wds_mac); #ifdef ISLPCI_ETH_DEBUG if ( wds_mac[0] || wds_mac[1] || wds_mac[2] || wds_mac[3] || wds_mac[4] || wds_mac[5] ) { printk("islpci_eth_transmit:check_skb_for_wds ! %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X\n", wds_mac[0], wds_mac[1], wds_mac[2], wds_mac[3], wds_mac[4], wds_mac[5] ); } else { printk("islpci_eth_transmit:check_skb_for_wds %2.2X %2.2X %2.2X %2.2X %2.2X %2.2X\n", wds_mac[0], wds_mac[1], wds_mac[2], wds_mac[3], wds_mac[4], wds_mac[5] ); } #endif #endif frame_size = skb->len < ETH_ZLEN ? ETH_ZLEN : skb->len; if( init_wds ) frame_size += 6; fragments = frame_size / MAX_FRAGMENT_SIZE; fragments += frame_size % MAX_FRAGMENT_SIZE == 0 ? 0 : 1; #if VERBOSE > SHOW_ERROR_MESSAGES DEBUG(SHOW_TRACING, "Fragments needed for frame %i\n", fragments); #endif // check whether the destination queue has enough fragments for the frame if (ISL38XX_CB_TX_QSIZE - isl38xx_in_queue( control_block, ISL38XX_CB_TX_DATA_LQ ) < fragments ) { // Error, cannot add the frame because the queue is full DEBUG(SHOW_ERROR_MESSAGES, "Error: Queue [%i] not enough room\n", ISL38XX_CB_TX_DATA_LQ); // trigger the device by setting the Update bit in the Device Int reg writel(ISL38XX_DEV_INT_UPDATE, private_config->remapped_device_base + ISL38XX_DEV_INT_REG); udelay(ISL38XX_WRITEIO_DELAY); // unlock the driver code driver_unlock( &private_config->slock, &flags ); return -EBUSY; } // Check alignment and WDS frame formatting. The start of the packet should // be aligned on a 4-byte boundary. If WDS is enabled add another 6 bytes // and add WDS address information if( ((int) skb->data & 0x03) | init_wds ) { // get the number of bytes to add and re-allign offset = (int) skb->data & 0x03; offset += init_wds ? 6 : 0; // check whether the current skb can be used if (!skb_cloned(skb) && (skb_tailroom(skb) >= offset)) { unsigned char *src = skb->data; #if VERBOSE > SHOW_ERROR_MESSAGES DEBUG(SHOW_TRACING, "skb offset %i wds %i\n", offset, init_wds ); #endif // allign the buffer on 4-byte boundary skb_reserve(skb, (int) skb->data & 0x03 ); if( init_wds ) { // wds requires an additional address field of 6 bytes skb_put( skb, 6 ); #ifdef ISLPCI_ETH_DEBUG printk("islpci_eth_transmit:wds_mac\n" ); #endif memmove( skb->data+6, src, skb->len ); memcpy( skb->data, wds_mac, 6 ); } else { memmove( skb->data, src, skb->len ); } #if VERBOSE > SHOW_ERROR_MESSAGES DEBUG(SHOW_TRACING, "memmove %p %p %i \n", skb->data, src, skb->len ); #endif } else { newskb = dev_alloc_skb( init_wds ? skb->len+6 : skb->len ); newskb_offset = (int) newskb->data & 0x03; // Check if newskb->data is aligned if( newskb_offset ) skb_reserve(newskb, newskb_offset); skb_put(newskb, init_wds ? skb->len+6 : skb->len ); if( init_wds ) { memcpy( newskb->data+6, skb->data, skb->len ); memcpy( newskb->data, wds_mac, 6 ); #ifdef ISLPCI_ETH_DEBUG printk("islpci_eth_transmit:wds_mac\n" ); #endif } else memcpy( newskb->data, skb->data, skb->len ); #if VERBOSE > SHOW_ERROR_MESSAGES DEBUG(SHOW_TRACING, "memcpy %p %p %i wds %i\n", newskb->data, skb->data, skb->len, init_wds ); #endif newskb->dev = skb->dev; dev_kfree_skb(skb); skb = newskb; } } // display the buffer contents for debugging #if VERBOSE > SHOW_ERROR_MESSAGES DEBUG( SHOW_BUFFER_CONTENTS, "\ntx %p ", skb->data ); display_buffer((char *) skb->data, skb->len ); #endif // map the skb buffer to pci memory for DMA operation pci_map_address = pci_map_single( private_config->pci_device, (void *) skb->data, skb->len, PCI_DMA_TODEVICE ); if( pci_map_address == (dma_addr_t) NULL ) { // error mapping the buffer to device accessable memory address DEBUG(SHOW_ERROR_MESSAGES, "Error map DMA addr, data lost\n" ); // free the skbuf structure before aborting dev_kfree_skb(skb); // unlock the driver code driver_unlock( &private_config->slock, &flags ); return -EIO; } // place each fragment in the control block structure and store in the last // needed fragment entry of the pci_map_tx_address and data_low_tx arrays // the skb frame information for (counter = 0; counter < fragments; counter++ ) { // get a pointer to the target control block fragment index = (counter + le32_to_cpu( control_block->driver_curr_frag[ISL38XX_CB_TX_DATA_LQ])) % ISL38XX_CB_TX_QSIZE; fragment = &control_block->tx_data_low[index]; // check whether this frame fragment is the last one if( counter == fragments-1 ) { // the fragment is the last one, add the streaming DMA mapping for // proper PCI bus operation private_config->pci_map_tx_address[index] = (dma_addr_t) pci_map_address; // store the skb address for future freeing private_config->data_low_tx[index] = skb; } else { // the fragment will be followed by more fragments // clear the pci_map_tx_address and data_low_tx entries private_config->pci_map_tx_address[index] = (dma_addr_t) NULL; private_config->data_low_tx[index] = NULL; } // set the proper fragment start address and size information fragment->address = cpu_to_le32( pci_map_address + counter*MAX_FRAGMENT_SIZE ); fragment->size = cpu_to_le16( frame_size > MAX_FRAGMENT_SIZE ? MAX_FRAGMENT_SIZE : frame_size ); fragment->flags = cpu_to_le16( frame_size > MAX_FRAGMENT_SIZE ? 1 : 0 ); frame_size -= MAX_FRAGMENT_SIZE; } // ready loading all fragements to the control block and setup pci mapping // update the control block interface information add_le32p(&control_block->driver_curr_frag[ISL38XX_CB_TX_DATA_LQ], fragments); // check whether the data tx queue is full now if( ISL38XX_CB_TX_QSIZE - isl38xx_in_queue( control_block, ISL38XX_CB_TX_DATA_LQ ) < ISL38XX_MIN_QTHRESHOLD ) { // stop the transmission of network frames to the driver netif_stop_queue(nwdev); // set the full flag for the data low transmission queue private_config->data_low_tx_full = 1; } // trigger the device isl38xx_trigger_device( &private_config->powerstate, private_config->remapped_device_base ); // set the transmission time nwdev->trans_start = jiffies; private_config->statistics.tx_packets++; private_config->statistics.tx_bytes += skb->len; // cleanup the data low transmit queue islpci_eth_cleanup_transmit( private_config, private_config->control_block ); // unlock the driver code driver_unlock( &private_config->slock, &flags ); return 0; }