/* * * =================================== * HARP | Host ATM Research Platform * =================================== * * * This Host ATM Research Platform ("HARP") file (the "Software") is * made available by Network Computing Services, Inc. ("NetworkCS") * "AS IS". NetworkCS does not provide maintenance, improvements or * support of any kind. * * NETWORKCS MAKES NO WARRANTIES OR REPRESENTATIONS, EXPRESS OR IMPLIED, * INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE, AS TO ANY ELEMENT OF THE * SOFTWARE OR ANY SUPPORT PROVIDED IN CONNECTION WITH THIS SOFTWARE. * In no event shall NetworkCS be responsible for any damages, including * but not limited to consequential damages, arising from or relating to * any use of the Software or related support. * * Copyright 1994-1998 Network Computing Services, Inc. * * Copies of this Software may be made, however, the above copyright * notice must be reproduced on all copies. * * @(#) $FreeBSD: src/sys/dev/hea/eni_receive.c,v 1.5 1999/08/28 00:41:45 peter Exp $ * @(#) $DragonFly: src/sys/dev/atm/hea/eni_receive.c,v 1.10 2008/03/01 22:03:13 swildner Exp $ */ /* * Efficient ENI Adapter Support * ----------------------------- * * Receive management * */ #include #include "eni_stats.h" #include "eni.h" #include "eni_var.h" static void eni_recv_stack (void *, KBuffer *); #ifdef DIAGNOSTIC extern int eni_pdu_print; #endif /* * Procedure to remove VCs from the Service List and generate DMA * requests to move the associated PDUs into host memory. As PDUs * are completed in adapter memory, the adapter examines the IN_SERVICE * bit for the VC in the VC table. If this bit is not set, the adapter * will place the VC number at the end of the service list queue, set * the IN_SERVICE bit in the VC table, and interrupt the host. The host * will remove VCs from the service list, clear the IN_SERVICE bit in * the VC table, and create a DMA list to move the PDU into host buffers. * * Arguments: * eup pointer to per unit structure * * Returns: * none * */ void eni_do_service(Eni_unit *eup) { int vcc; Eni_vcc *evp; u_long servwrite; VCI_Table *vct; u_long rdptr; u_long *rxp; KBuffer *m; u_long dma[TEMP_DMA_SIZE]; u_long i, j; u_long dma_rd, dma_wr; u_long dma_avail; int pdulen; int mask; u_long *upp; /* * Where is the adapter currently inserting entries? */ servwrite = eup->eu_midway[MIDWAY_SVCWR] & SVC_SIZE_MASK; /* * As long as we're not caught up with the adapter, keep * removing VCs from the service list. */ while ( servwrite != eup->eu_servread ) { int vci_hdr; u_long descr; /* * Get VC number and find VC table entry. */ vcc = eup->eu_svclist[eup->eu_servread]; vct = &eup->eu_vcitbl[vcc]; vci_hdr = vct->vci_control; /* Current status */ /* * Check that this VCC still needs servicing. We * might have closed this VCC down in between * the adapter setting the flag and our checking * the flag. Also check that we haven't placed the * VCC into TRASH mode. */ if ( ( vci_hdr & VCI_IN_SERVICE ) == 0 || ( (vci_hdr & ~VCI_MODE_MASK) == (VCI_MODE_TRASH << VCI_MODE_SHIFT) ) ) goto next_vcc; /* * Find the size of this VCs buffer */ mask = (vci_hdr >> VCI_SIZE_SHIFT) & VCI_SIZE_MASK; mask = 1 << (ENI_LOC_PREDIV + mask); /* Turn byte count into word count */ mask >>= 2; /* * Find the start of the adapter buffer for this VC. */ rxp = (u_long *) ((int)(((vci_hdr >> VCI_LOC_SHIFT ) & VCI_LOC_MASK) << ENI_LOC_PREDIV) + (int)eup->eu_ram); /* * Locate incoming VCC for this PDU and find where we * should next read from. */ evp = (Eni_vcc *) atm_dev_vcc_find ( (Cmn_unit *)eup, 0, vcc, VCC_IN ); if ( evp == NULL ) goto next_vcc; /* VCI no longer active */ rdptr = evp->ev_rxpos; /* * Find out where the adapter is currently reassembling. * The PDU which starts at descr is not yet complete so we * must stop there. */ descr = ( vct->vci_descr >> 16 ) & 0x7FFF; /* * As long as we haven't processed all the completed PDUs on * this VC, keep going... */ while ( rdptr != descr ) { int n_cells; int pdu_descr; int aal5; /* * Ensure that the following are reset for every new * PDU. */ upp = NULL; m = NULL; /* * Fisrt build a DMA with JK to skip the descriptor word. * We must always skip the descriptor even if it turns out * that there isn't any PDU here. */ j = 0; dma[j++] = (((rdptr + 1) & (mask-1)) << DMA_COUNT_SHIFT ) | ( vcc << DMA_VCC_SHIFT ) | DMA_JK; dma[j++] = 0; /* * We'll use some of the values below for skipping * bad PDUs or counting statistics so compute them * now. */ /* * Grab a copy of the descriptor word */ pdu_descr = rxp[rdptr]; /* * Strip out cell count from descriptor word. * At this point, we still don't know if there * is any real data until after we check for * TRASH mode. */ n_cells = pdu_descr & DESCR_CELL_COUNT; /* * Is this an AAL5 PDU? Check MODE in vci_hdr. */ aal5 = ( ( vci_hdr & ~VCI_MODE_MASK ) == VCI_MODE_AAL5 << VCI_MODE_SHIFT ); /* * Now check to see if we're trashing on this vcc. * If so, there is no data with this VC and the * next word after the current descriptor is the * descriptor for the next PDU. */ if ( ( pdu_descr & DESCR_TRASH_BIT ) != 0 ) { if ( aal5 ) /* * Count as number of AAL5 cells dropped */ eup->eu_stats.eni_st_aal5.aal5_drops += n_cells; else /* * Count as number of AAL0 cells dropped */ eup->eu_stats.eni_st_aal0.aal0_drops += n_cells; eup->eu_pif.pif_ierrors++; /* * When cells have been trashed, all we have in the * buffer is a descriptor word. There are no data * words. Set the number of cells to zero so that * we correctly skip to the next word which will * be the descriptor for the next PDU. */ n_cells = 0; /* * Go issue the DMA to skip this descriptor word. */ goto send_dma; } /* * Data length: number of cells * cell size */ pdulen = n_cells * BYTES_PER_CELL; /* * If this is an AAL5 PDU, then we need to check * for the presence of any CRC errors. If there * is one or more CRC errors, then we are going to * drop this PDU. */ if ( aal5 && ( pdu_descr & DESCR_CRC_ERR ) ) { /* * Count the stat */ eup->eu_pif.pif_ierrors++; eup->eu_stats.eni_st_aal5.aal5_pdu_crc++; if ( evp->ev_connvc->cvc_vcc ) evp->ev_connvc->cvc_vcc->vc_ierrors++; /* * Build a DMA entry to skip the rest of this * PDU. */ dma[j++] = (((rdptr + n_cells*WORDS_PER_CELL + 1) & (mask-1)) << DMA_COUNT_SHIFT ) | (vcc << DMA_VCC_SHIFT ) | DMA_JK; dma[j++] = 0; /* * All done with this PDU. Get a buffer to save some * data for reclamation services. */ KB_ALLOCPKT ( m, ENI_SMALL_BSIZE, KB_F_NOWAIT, KB_T_DATA ); if ( m ) { u_long *up; KB_DATASTART ( m, up, u_long * ); /* * Indicate no PDU */ KB_PLENSET ( m, 0 ); /* * Set buffer length - only driver overhead */ KB_LEN ( m ) = 3 * sizeof ( u_long ); /* * Insert vcc, space for DMA pointers, * and pdulen */ *up++ = vcc; upp = up; /* Remember location */ up++; /* And skip it */ /* - to be filled later */ *up = pdulen; /* Actual PDU length if it */ /* were valid */ } else { /* * We've a real problem here as now we can't * reclaim/advance resources/safety pointers. */ eup->eu_stats.eni_st_drv.drv_rv_norsc++; #ifdef DO_LOG log ( LOG_ERR, "eni_do_service: No drain buffers available. Receiver about to lock.\n" ); #endif } goto send_dma; } /* * Do we need to strip the AAL layer? Yes if this * is an AAL5 PDU. */ if ( aal5 ) { /* * Grab the CS-PDU length. Find the address of the * last word, back up one word to skip CRC, and * then mask the whole thing to handle circular wraps. */ pdulen = rxp[(rdptr + n_cells*WORDS_PER_CELL - 1) & (mask-1)] & 0xFFFF; } /* * We now have a valid PDU of some length. Build * the necessary DMA list to move it into host * memory. */ /* * Get an initial buffer. */ KB_ALLOCPKT ( m, ENI_SMALL_BSIZE, KB_F_NOWAIT, KB_T_DATA ); /* * Do we have a valid buffer? */ if ( m != NULL ) { int len; u_long *up; KBuffer *m0; KB_DATASTART ( m, up, u_long * ); /* * Fill in pdulen in PKTHDR structure (for IP). */ KB_PLENSET ( m, pdulen ); /* * We're going to save the VCI nuber, the start * and stop DMA pointers, and the PDU length at * the head of the buffer. We'll pull this out * later after the DMA has completed. * * Insert VCI number as first word in first buffer, * remeber where we want to store the start/stop * pointers, and store the PDU length. */ *up++ = vcc; /* PDU's VCC */ upp = up; /* Remember where we are */ up++; /* To stuff start/stop pointers in */ *up++ = pdulen; /* PDU's length */ /* * Leave some extra room in case a higher protocol * (IP) wants to do a pullup. Maybe we can keep * someone from having to allocate another buffer * a do a larger memory copy. */ len = MIN ( ENI_SMALL_BSIZE, pdulen ); (void) eni_set_dma ( eup, 1, dma, TEMP_DMA_SIZE, &j, vcc, (u_long)up, len ); /* * Adjust length of remaining data in PDU */ pdulen -= len; /* * Set buffer length, including our overhead */ KB_LEN ( m ) = len + 3 * sizeof ( u_long ); /* * Finish by moving anything which won't fit in * first buffer */ m0 = m; while ( pdulen ) { KBuffer *m1; u_long data_addr; /* * Get another buffer */ KB_ALLOCEXT ( m1, ENI_LARGE_BSIZE, KB_F_NOWAIT, KB_T_DATA ); /* * If we succeeded... */ if ( m1 ) { /* * Figure out how much we can move into * this buffer. */ len = MIN ( ENI_LARGE_BSIZE, pdulen ); /* * Setup DMA list for this buffer */ KB_DATASTART ( m1, data_addr, u_long ); (void) eni_set_dma ( eup, 1, dma, TEMP_DMA_SIZE, &j, vcc, data_addr, len ); /* * Adjust remaining length */ pdulen -= len; /* * Set buffer length */ KB_LEN ( m1 ) = len; /* * Link new buffer onto end and advance * pointer */ KB_NEXT ( m0 ) = m1; m0 = m1; } else { /* * Either we were unable to grab another * buffer or there are no large buffers * available. We know that the first * buffer is valid, so drop everything * else, build a JK DMA to skip/drop this * PDU, set the pointers to reclaim * resources/advance pointers, and * finish this PDU now. */ if ( KB_NEXT ( m ) ) KB_FREEALL ( KB_NEXT ( m ) ); eup->eu_pif.pif_ierrors++; j = 2; dma[j++] = (((rdptr + n_cells*WORDS_PER_CELL + 1) & (mask-1)) << DMA_COUNT_SHIFT ) | (vcc << DMA_VCC_SHIFT ) | DMA_JK; dma[j++] = 0; /* * Reset PDU length to zero */ KB_PLENSET ( m, 0 ); /* * Count some statistics */ /* * Count this as dropped cells */ if ( aal5 ) { eup->eu_stats.eni_st_aal5.aal5_drops += n_cells; eup->eu_stats.eni_st_aal5.aal5_pdu_drops++; } else eup->eu_stats.eni_st_aal0.aal0_drops += n_cells; /* * Drop it */ goto send_dma; } } /* * If necessary, skip AAL layer */ if ( aal5 ) { dma[j++] = (((rdptr + n_cells*WORDS_PER_CELL + 1) & (mask-1)) << DMA_COUNT_SHIFT) | (vcc << DMA_VCC_SHIFT) | DMA_JK; dma[j++] = 0; } } else { /* * We failed to get an initial buffer. Since we * haven't changed anything for this PDU yet and the * PDU is still valid, exit now and try to service it * next time around. We're not very likely to get * another buffer right now anyways. */ eup->eu_stats.eni_st_drv.drv_rv_nobufs++; #ifdef DO_LOG log ( LOG_ERR, "eni_do_service: No buffers available. Exiting without servicing service list.\n" ); #endif /* * Clear the IN_SERVICE indicator for this VCC */ vct->vci_control &= ~VCI_IN_SERVICE; return; } send_dma: /* * Set the end bit on the last DMA for this PDU */ dma[j-2] |= DMA_END_BIT; /* * Where are the current DMA pointers */ dma_rd = eup->eu_midway[MIDWAY_RX_RD]; dma_wr = eup->eu_midway[MIDWAY_RX_WR]; /* * Check how much space is available */ if ( dma_rd == dma_wr ) dma_avail = DMA_LIST_SIZE; else dma_avail = ( dma_rd + DMA_LIST_SIZE - dma_wr ) & (DMA_LIST_SIZE-1); /* * Check for queue full or wrap past write okay pointer */ if ( dma_avail < j || ( dma_wr + j > eup->eu_rxdmawr + DMA_LIST_SIZE ) ) { /* * There's no room in the DMA list to insert * this request. Since we haven't changed anything * yet and the PDU is good, exit now and service * it next time around. What we really need to do * is wait for the RX list to drain and that won't * happen if we keep trying to process PDUs here. */ eup->eu_stats.eni_st_drv.drv_rv_nodma++; #ifdef DO_LOG log ( LOG_ERR, "eni_do_service: No room in receive DMA list. Postponing service request.\n" ); #endif /* * Free the local buffer chain */ KB_FREEALL ( m ); /* * Clear the IN_SERVICE indicator for this VCC. */ vct->vci_control &= ~VCI_IN_SERVICE; return; } /* * If we have a buffer chain, save the starting * dma_list location. */ if ( upp ) { *upp = dma_wr << 16; } /* * Stuff the DMA list */ j >>= 1; for ( i = 0; i < j; i++ ) { eup->eu_rxdma[dma_wr*2] = dma[i*2]; eup->eu_rxdma[dma_wr*2+1] = dma[i*2+1]; dma_wr = (dma_wr+1) & (DMA_LIST_SIZE-1); } /* * If we have a buffer chain, save the location of * the ending dma_list location and queue the chain * so that we can recover the resources later. */ if ( upp ) { *upp |= dma_wr; /* * Place buffer on receive queue waiting for RX_DMA */ if ( IF_QFULL ( &eup->eu_rxqueue ) ) { /* * We haven't done anything we can't back out * of. Drop request and service it next time. * We've inserted the DMA list but it's not * valid until we advance the RX_WR pointer, * thus it's okay to bail here... */ eup->eu_stats.eni_st_drv.drv_rv_rxq++; #ifdef DO_LOG log ( LOG_ERR, "eni_do_service: RX drain queue full. Postponing servicing.\n" ); #endif KB_FREEALL ( m ); /* * Clear the IN_SERVICE indicator for this VCC. */ vct->vci_control &= ~VCI_IN_SERVICE; return; } else { IF_ENQUEUE ( &eup->eu_rxqueue, m ); /* * Advance the RX_WR pointer to cause * the adapter to work on this DMA list. */ eup->eu_midway[MIDWAY_RX_WR] = dma_wr; } } /* * Advance our notion of where the next PDU * should start. */ rdptr = (rdptr + n_cells*WORDS_PER_CELL + 1) & (mask-1); evp->ev_rxpos = rdptr; /* * Increment cells/pdu received stats. */ eup->eu_stats.eni_st_atm.atm_rcvd += n_cells; if ( aal5 ) { eup->eu_stats.eni_st_aal5.aal5_rcvd += n_cells; eup->eu_stats.eni_st_aal5.aal5_pdu_rcvd++; } else { eup->eu_stats.eni_st_aal0.aal0_rcvd += n_cells; } /* * Continue processing PDUs on this same VCI */ } next_vcc: /* * Advance to next entry in the service_list. */ eup->eu_servread = (eup->eu_servread + 1) & SVC_SIZE_MASK; /* * And clear the IN_SERVICE indicator for this VCC. */ vct->vci_control &= ~VCI_IN_SERVICE; } return; } /* * Drain Receive queue * * As we build DMA lists to move PDUs from adapter buffers into host * buffers, we place the request on a private ifqueue so that we can * free any resources AFTER we know they've been successfully DMAed. * As part of the service processing, we record the PDUs start and stop * entries in the DMA list, and prevent wrapping. When we pull the top * entry off, we simply check that the current DMA location is outside * this PDU and if so, it's okay to free things. * * Arguments: * eup pointer to device unit structure * * Returns: * none * */ void eni_recv_drain(Eni_unit *eup) { KBuffer *m; Eni_vcc *evp; struct vccb *vcp; u_long vcc; u_long DMA_Rdptr; u_long dma_wrp; u_long start, stop; crit_enter(); /* Pop first buffer */ IF_DEQUEUE ( &eup->eu_rxqueue, m ); while ( m ) { u_long *up; u_long pdulen; KB_DATASTART ( m, up, u_long * ); /* * Grab the VCI number */ vcc = *up++; /* * Check to see if we can process this buffer yet. */ /* Get current DMA_Rdptr */ DMA_Rdptr = eup->eu_midway[MIDWAY_RX_RD]; /* Boundaries for first buffer */ dma_wrp = *up++; start = dma_wrp >> 16; stop = dma_wrp & 0xffff; /* * Start should not equal stop because that would * mean we tried inserting a NULL DMA list. */ if ( start > stop ) { /* We wrapped */ if ( !(DMA_Rdptr >= stop && DMA_Rdptr < start) ) { IF_PREPEND ( &eup->eu_rxqueue, m ); goto finish; } } else { if ( DMA_Rdptr < stop && DMA_Rdptr >= start ) { IF_PREPEND ( &eup->eu_rxqueue, m ); goto finish; } } /* * Adapter is finished with this buffer, we can * continue processing it now. */ /* * Locate incoming VCC for this PDU */ evp = (Eni_vcc *) atm_dev_vcc_find ( (Cmn_unit *)eup, 0, vcc, VCC_IN ); if ( evp == NULL ) { eup->eu_stats.eni_st_drv.drv_rv_novcc++; KB_FREEALL ( m ); goto next_buffer; } #ifdef DIAGNOSTIC if ( eni_pdu_print ) atm_dev_pdu_print ( (Cmn_unit *)eup, (Cmn_vcc *)evp, m, "eni_stack_drain" ); #endif /* * Grab theoretical PDU length */ pdulen = *up++; /* * Quick, count the PDU */ eup->eu_pif.pif_ipdus++; eup->eu_pif.pif_ibytes += pdulen; if ( evp ) { vcp = evp->ev_connvc->cvc_vcc; if ( vcp ) { vcp->vc_ipdus++; vcp->vc_ibytes += pdulen; if ( vcp->vc_nif ) { vcp->vc_nif->nif_ibytes += pdulen; vcp->vc_nif->nif_if.if_ipackets++; vcp->vc_nif->nif_if.if_ibytes += pdulen; } } } /* * Advance DMA write allowable pointer */ eup->eu_rxdmawr = stop; /* * Get packet PDU length */ KB_PLENGET ( m, pdulen ); /* * Only try queueing this if there is data * to be handed up to the next layer. Errors * such as CRC and VC trashing will get us this * far to advance pointers, etc., but the PDU * length will be zero. */ if ( pdulen ) { /* * We saved three words back in eni_do_service() * to use for callback. Since the core only * expects two words, skip over the first one. * Then, reset up pointer to start of buffer data * area and write the callback info. */ KB_HEADADJ ( m, -sizeof(u_long) ); KB_DATASTART ( m, up, u_long * ); *((int *)up) = (int)eni_recv_stack; up++; *((int *)up) = (int)evp; /* * Schedule callback */ if (netisr_queue(NETISR_ATM, m)) { eup->eu_stats.eni_st_drv.drv_rv_intrq++; eup->eu_pif.pif_ierrors++; #ifdef DO_LOG log ( LOG_ERR, "eni_receive_drain: ATM_INTRQ is full. Unable to pass up stack.\n" ); #endif KB_FREEALL ( m ); } } else { /* * Free zero-length buffer */ KB_FREEALL(m); } next_buffer: /* * Look for next buffer */ IF_DEQUEUE ( &eup->eu_rxqueue, m ); } finish: crit_exit(); } /* * Pass incoming PDU up Stack * * This function is called via the core ATM interrupt queue callback * set in eni_recv_drain(). It will pass the supplied incoming * PDU up the incoming VCC's stack. * * Arguments: * tok token to identify stack instantiation * m pointer to incoming PDU buffer chain * * Returns: * none */ static void eni_recv_stack(void *tok, KBuffer *m) { Eni_vcc *evp = (Eni_vcc *)tok; int err; /* * This should never happen now but if it does and we don't stop it, * we end up panic'ing in netatm when trying to pull a function * pointer and token value out of a buffer with address zero. */ if ( !m ) { #ifdef DO_LOG log ( LOG_ERR, "eni_recv_stack: NULL buffer, tok = %p\n", tok ); #endif return; } /* * Send the data up the stack */ STACK_CALL ( CPCS_UNITDATA_SIG, evp->ev_upper, (void *)evp->ev_toku, evp->ev_connvc, (int)m, 0, err ); if ( err ) { KB_FREEALL ( m ); } return; }