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;
}