static enum enq_res do_tx()

in eni.c [1027:1177]


static enum enq_res do_tx(struct sk_buff *skb)
{
	struct atm_vcc *vcc;
	struct eni_dev *eni_dev;
	struct eni_vcc *eni_vcc;
	struct eni_tx *tx;
	dma_addr_t paddr;
	u32 dma_rd,dma_wr;
	u32 size; /* in words */
	int aal5,dma_size,i,j;
	unsigned char skb_data3;

	DPRINTK(">do_tx\n");
	NULLCHECK(skb);
	EVENT("do_tx: skb=0x%lx, %ld bytes\n",(unsigned long) skb,skb->len);
	vcc = ATM_SKB(skb)->vcc;
	NULLCHECK(vcc);
	eni_dev = ENI_DEV(vcc->dev);
	NULLCHECK(eni_dev);
	eni_vcc = ENI_VCC(vcc);
	tx = eni_vcc->tx;
	NULLCHECK(tx);
#if 0 /* Enable this for testing with the "align" program */
	{
		unsigned int hack = *((char *) skb->data)-'0';

		if (hack < 8) {
			skb->data += hack;
			skb->len -= hack;
		}
	}
#endif
#if 0 /* should work now */
	if ((unsigned long) skb->data & 3)
		printk(KERN_ERR DEV_LABEL "(itf %d): VCI %d has mis-aligned "
		    "TX data\n",vcc->dev->number,vcc->vci);
#endif
	/*
	 * Potential future IP speedup: make hard_header big enough to put
	 * segmentation descriptor directly into PDU. Saves: 4 slave writes,
	 * 1 DMA xfer & 2 DMA'ed bytes (protocol layering is for wimps :-)
	 */

	aal5 = vcc->qos.aal == ATM_AAL5;
	/* check space in buffer */
	if (!aal5)
		size = (ATM_CELL_PAYLOAD >> 2)+TX_DESCR_SIZE;
			/* cell without HEC plus segmentation header (includes
			   four-byte cell header) */
	else {
		size = skb->len+4*AAL5_TRAILER+ATM_CELL_PAYLOAD-1;
			/* add AAL5 trailer */
		size = ((size-(size % ATM_CELL_PAYLOAD)) >> 2)+TX_DESCR_SIZE;
						/* add segmentation header */
	}
	/*
	 * Can I move tx_pos by size bytes without getting closer than TX_GAP
	 * to the read pointer ? TX_GAP means to leave some space for what
	 * the manual calls "too close".
	 */
	if (!NEPMOK(tx->tx_pos,size+TX_GAP,
	    eni_in(MID_TX_RDPTR(tx->index)),tx->words)) {
		DPRINTK(DEV_LABEL "(itf %d): TX full (size %d)\n",
		    vcc->dev->number,size);
		return enq_next;
	}
	/* check DMA */
	dma_wr = eni_in(MID_DMA_WR_TX);
	dma_rd = eni_in(MID_DMA_RD_TX);
	dma_size = 3; /* JK for descriptor and final fill, plus final size
			 mis-alignment fix */
DPRINTK("iovcnt = %d\n",skb_shinfo(skb)->nr_frags);
	if (!skb_shinfo(skb)->nr_frags) dma_size += 5;
	else dma_size += 5*(skb_shinfo(skb)->nr_frags+1);
	if (dma_size > TX_DMA_BUF) {
		printk(KERN_CRIT DEV_LABEL "(itf %d): needs %d DMA entries "
		    "(got only %d)\n",vcc->dev->number,dma_size,TX_DMA_BUF);
	}
	DPRINTK("dma_wr is %d, tx_pos is %ld\n",dma_wr,tx->tx_pos);
	if (dma_wr != dma_rd && ((dma_rd+NR_DMA_TX-dma_wr) & (NR_DMA_TX-1)) <
	     dma_size) {
		printk(KERN_WARNING DEV_LABEL "(itf %d): TX DMA full\n",
		    vcc->dev->number);
		return enq_jam;
	}
	skb_data3 = skb->data[3];
	paddr = dma_map_single(&eni_dev->pci_dev->dev,skb->data,skb->len,
			       DMA_TO_DEVICE);
	ENI_PRV_PADDR(skb) = paddr;
	/* prepare DMA queue entries */
	j = 0;
	eni_dev->dma[j++] = (((tx->tx_pos+TX_DESCR_SIZE) & (tx->words-1)) <<
	     MID_DMA_COUNT_SHIFT) | (tx->index << MID_DMA_CHAN_SHIFT) |
	     MID_DT_JK;
	j++;
	if (!skb_shinfo(skb)->nr_frags)
		if (aal5) put_dma(tx->index,eni_dev->dma,&j,paddr,skb->len);
		else put_dma(tx->index,eni_dev->dma,&j,paddr+4,skb->len-4);
	else {
DPRINTK("doing direct send\n"); /* @@@ well, this doesn't work anyway */
		for (i = -1; i < skb_shinfo(skb)->nr_frags; i++)
			if (i == -1)
				put_dma(tx->index,eni_dev->dma,&j,(unsigned long)
				    skb->data,
				    skb_headlen(skb));
			else
				put_dma(tx->index,eni_dev->dma,&j,(unsigned long)
				    skb_frag_page(&skb_shinfo(skb)->frags[i]) +
					skb_frag_off(&skb_shinfo(skb)->frags[i]),
				    skb_frag_size(&skb_shinfo(skb)->frags[i]));
	}
	if (skb->len & 3) {
		put_dma(tx->index, eni_dev->dma, &j, eni_dev->zero.dma,
			4 - (skb->len & 3));
	}
	/* JK for AAL5 trailer - AAL0 doesn't need it, but who cares ... */
	eni_dev->dma[j++] = (((tx->tx_pos+size) & (tx->words-1)) <<
	     MID_DMA_COUNT_SHIFT) | (tx->index << MID_DMA_CHAN_SHIFT) |
	     MID_DMA_END | MID_DT_JK;
	j++;
	DPRINTK("DMA at end: %d\n",j);
	/* store frame */
	writel((MID_SEG_TX_ID << MID_SEG_ID_SHIFT) |
	    (aal5 ? MID_SEG_AAL5 : 0) | (tx->prescaler << MID_SEG_PR_SHIFT) |
	    (tx->resolution << MID_SEG_RATE_SHIFT) |
	    (size/(ATM_CELL_PAYLOAD/4)),tx->send+tx->tx_pos*4);
/*printk("dsc = 0x%08lx\n",(unsigned long) readl(tx->send+tx->tx_pos*4));*/
	writel((vcc->vci << MID_SEG_VCI_SHIFT) |
            (aal5 ? 0 : (skb_data3 & 0xf)) |
	    (ATM_SKB(skb)->atm_options & ATM_ATMOPT_CLP ? MID_SEG_CLP : 0),
	    tx->send+((tx->tx_pos+1) & (tx->words-1))*4);
	DPRINTK("size: %d, len:%d\n",size,skb->len);
	if (aal5)
		writel(skb->len,tx->send+
                    ((tx->tx_pos+size-AAL5_TRAILER) & (tx->words-1))*4);
	j = j >> 1;
	for (i = 0; i < j; i++) {
		writel(eni_dev->dma[i*2],eni_dev->tx_dma+dma_wr*8);
		writel(eni_dev->dma[i*2+1],eni_dev->tx_dma+dma_wr*8+4);
		dma_wr = (dma_wr+1) & (NR_DMA_TX-1);
	}
	ENI_PRV_POS(skb) = tx->tx_pos;
	ENI_PRV_SIZE(skb) = size;
	ENI_VCC(vcc)->txing += size;
	tx->tx_pos = (tx->tx_pos+size) & (tx->words-1);
	DPRINTK("dma_wr set to %d, tx_pos is now %ld\n",dma_wr,tx->tx_pos);
	eni_out(dma_wr,MID_DMA_WR_TX);
	skb_queue_tail(&eni_dev->tx_queue,skb);
	queued++;
	return enq_ok;
}