Thread: [Linux1394-cvslog] rev 752 - trunk
Brought to you by:
aeb,
bencollins
From: SVN U. <dm...@li...> - 2003-02-01 14:23:35
|
Author: dmaas Date: 2003-01-15 10:36:43 -0500 (Wed, 15 Jan 2003) New Revision: 752 Modified: trunk/ohci1394.c Log: rawiso - implement packet-per-buffer mode as an option, and implement sync matching for receive Modified: trunk/ohci1394.c ============================================================================== --- trunk/ohci1394.c (original) +++ trunk/ohci1394.c 2003-02-01 08:59:33.000000000 -0500 @@ -1084,15 +1084,16 @@ ***********************************/ /* - We use buffer-fill DMA mode. The DMA buffer is split into "blocks" - of one page each. It would be fairly easy to make blocks smaller - than a page, but I think the latency is good enough already. + We use either buffer-fill or packet-per-buffer DMA mode. The DMA + buffer is split into "blocks" (regions described by one DMA + descriptor). Each block must be one page or less in size, and + must not cross a page boundary. There is one little wrinkle with buffer-fill mode: a packet that starts in the final block may wrap around into the first block. But the user API expects all packets to be contiguous. Our solution is to keep the very last page of the DMA buffer in reserve - if a - packet span the gap, we copy its tail into this page. + packet spans the gap, we copy its tail into this page. */ struct ohci_iso_recv { @@ -1101,93 +1102,122 @@ struct ohci1394_iso_tasklet task; int task_active; + enum { BUFFER_FILL_MODE, + PACKET_PER_BUFFER_MODE } dma_mode; + /* memory and PCI mapping for the DMA descriptors */ struct dma_prog_region prog; struct dma_cmd *block; /* = (struct dma_cmd*) prog.virt */ - /* how many INPUT_MORE blocks fit in the buffer */ + /* how many DMA blocks fit in the buffer */ unsigned int nblocks; + /* stride of DMA blocks */ + unsigned int buf_stride; + /* number of blocks to batch between interrupts */ int block_irq_interval; - /* block that the reader will release next */ + /* block that DMA will finish next */ + int block_dma; + + /* (buffer-fill only) block that the reader will release next */ int block_reader; - /* bytes of buffer the reader has released, less than one block */ + /* (buffer-fill only) bytes of buffer the reader has released, + less than one block */ int released_bytes; - /* the following four fields are used exclusively by the interrupt - (no locking is required) */ - - /* block that DMA will finish next */ - int block_dma; - - /* buffer offset at which the next packet will appear */ + /* (buffer-fill only) buffer offset at which the next packet will appear */ int dma_offset; - /* throttle the rate of buffer-space warnings to avoid - spamming the syslog */ - int warn_backoff; - /* OHCI DMA context control registers */ u32 ContextControlSet; u32 ContextControlClear; u32 CommandPtr; u32 ContextMatch; - }; -static void ohci_iso_recv_task(unsigned long data); +static void ohci_iso_recv_bufferfill_task(unsigned long data); +static void ohci_iso_recv_packetperbuf_task(unsigned long data); static void ohci_iso_recv_stop(struct hpsb_iso *iso); static void ohci_iso_recv_shutdown(struct hpsb_iso *iso); +static int ohci_iso_recv_start(struct hpsb_iso *iso, int cycle, int tag_mask, int sync); static void ohci_iso_recv_program(struct hpsb_iso *iso); -static int ohci_iso_recv_start(struct hpsb_iso *iso, int cycle, int tag_mask); - static int ohci_iso_recv_init(struct hpsb_iso *iso) { struct ti_ohci *ohci = iso->host->hostdata; struct ohci_iso_recv *recv; - unsigned int nblocks; int ctx; int ret = -ENOMEM; - /* one block per page of data in the DMA buffer, minus the final guard page */ - nblocks = iso->buf_size/PAGE_SIZE - 1; - if(nblocks < 3) { - DBGMSG(ohci->id, "ohci_iso_recv_init: DMA buffer too small"); - return -EINVAL; - } - recv = kmalloc(sizeof(*recv), SLAB_KERNEL); - if(!recv) { + if(!recv) return -ENOMEM; - } iso->hostdata = recv; recv->ohci = ohci; recv->task_active = 0; dma_prog_region_init(&recv->prog); recv->block = NULL; - recv->nblocks = nblocks; - /* iso->irq_interval is in packets - translate that to blocks */ - /* (err, sort of... 1 is always the safest value) */ - recv->block_irq_interval = iso->irq_interval / nblocks; - if(recv->block_irq_interval < 1) + /* use buffer-fill mode, unless irq_interval is 1 + (note: multichannel requires buffer-fill) */ + + if(iso->irq_interval == 1 && iso->channel != -1) { + recv->dma_mode = PACKET_PER_BUFFER_MODE; + } else { + recv->dma_mode = BUFFER_FILL_MODE; + } + + /* set nblocks, buf_stride, block_irq_interval */ + + if(recv->dma_mode == BUFFER_FILL_MODE) { + recv->buf_stride = PAGE_SIZE; + + /* one block per page of data in the DMA buffer, minus the final guard page */ + recv->nblocks = iso->buf_size/PAGE_SIZE - 1; + if(recv->nblocks < 3) { + DBGMSG(ohci->id, "ohci_iso_recv_init: DMA buffer too small"); + goto err; + } + + /* iso->irq_interval is in packets - translate that to blocks */ + /* (err, sort of... 1 is always the safest value) */ + recv->block_irq_interval = iso->irq_interval / recv->nblocks; + if(recv->block_irq_interval < 1) + recv->block_irq_interval = 1; + else if(recv->block_irq_interval*4 > recv->nblocks) + recv->block_irq_interval = recv->nblocks/4; + } else { + int max_packet_size; + + recv->nblocks = iso->buf_packets; recv->block_irq_interval = 1; - else if(recv->block_irq_interval*4 > recv->nblocks) - recv->block_irq_interval = nblocks/4; + + /* choose a buffer stride */ + /* must be a power of 2, and <= PAGE_SIZE */ + + max_packet_size = iso->buf_size / iso->buf_packets; + + for(recv->buf_stride = 8; recv->buf_stride < max_packet_size; + recv->buf_stride *= 2); + + if(recv->buf_stride*iso->buf_packets > iso->buf_size || + recv->buf_stride > PAGE_SIZE) { + /* this shouldn't happen, but anyway... */ + DBGMSG(ohci->id, "ohci_iso_recv_init: problem choosing a buffer stride"); + goto err; + } + } recv->block_reader = 0; recv->released_bytes = 0; - recv->block_dma = 0; - recv->dma_offset = recv->block_dma * PAGE_SIZE; - recv->warn_backoff = 0; + recv->dma_offset = 0; - /* size of DMA program = one INPUT_MORE per block */ + /* size of DMA program = one descriptor per block */ if(dma_prog_region_alloc(&recv->prog, sizeof(struct dma_cmd) * recv->nblocks, recv->ohci->dev)) @@ -1198,7 +1228,10 @@ ohci1394_init_iso_tasklet(&recv->task, iso->channel == -1 ? OHCI_ISO_MULTICHANNEL_RECEIVE : OHCI_ISO_RECEIVE, - ohci_iso_recv_task, (unsigned long) iso); + recv->dma_mode == BUFFER_FILL_MODE ? + ohci_iso_recv_bufferfill_task : + ohci_iso_recv_packetperbuf_task, + (unsigned long) iso); if(ohci1394_register_iso_tasklet(recv->ohci, &recv->task) < 0) goto err; @@ -1221,10 +1254,12 @@ /* write the DMA program */ ohci_iso_recv_program(iso); - DBGMSG(ohci->id, "ohci_iso_recv_init: DMA buffer is %lu pages" - " (%u bytes), using %u blocks, block_irq_interval %d", + DBGMSG(ohci->id, "ohci_iso_recv_init: %s mode, DMA buffer is %lu pages" + " (%u bytes), using %u blocks, buf_stride %u, block_irq_interval %d", + recv->dma_mode == BUFFER_FILL_MODE ? + "buffer-fill" : "packet-per-buffer", iso->buf_size/PAGE_SIZE, iso->buf_size, - recv->nblocks, recv->block_irq_interval); + recv->nblocks, recv->buf_stride, recv->block_irq_interval); return 0; @@ -1263,51 +1298,48 @@ static void ohci_iso_recv_program(struct hpsb_iso *iso) { struct ohci_iso_recv *recv = iso->hostdata; + int blk; /* address of 'branch' field in previous DMA descriptor */ u32 *prev_branch = NULL; - /* start at block_dma and go around the whole buffer */ - int blk = recv->block_dma; - int i; - - for(i = 0; i < recv->nblocks; i++) { + for(blk = 0; blk < recv->nblocks; blk++) { /* the DMA descriptor */ - struct dma_cmd *im = &recv->block[blk]; + struct dma_cmd *cmd = &recv->block[blk]; /* offset of the DMA descriptor relative to the DMA prog buffer */ unsigned long prog_offset = blk * sizeof(struct dma_cmd); /* offset of this packet's data within the DMA buffer */ - unsigned long buf_offset = blk*PAGE_SIZE; + unsigned long buf_offset = blk * recv->buf_stride; - unsigned short data_size = PAGE_SIZE; - - im->control = 2 << 28; /* INPUT_MORE */ - im->control |= 8 << 24; /* s = 1, update xferStatus and resCount */ + if(recv->dma_mode == BUFFER_FILL_MODE) { + cmd->control = 2 << 28; /* INPUT_MORE */ + } else { + cmd->control = 3 << 28; /* INPUT_LAST */ + } + + cmd->control |= 8 << 24; /* s = 1, update xferStatus and resCount */ /* interrupt on last block, and at intervals */ - if(i == recv->nblocks-1 || (i % recv->block_irq_interval) == 0) { - im->control |= 3 << 20; /* want interrupt */ + if(blk == recv->nblocks-1 || (blk % recv->block_irq_interval) == 0) { + cmd->control |= 3 << 20; /* want interrupt */ } - im->control |= 3 << 18; /* enable branch to address */ - im->control |= data_size; + cmd->control |= 3 << 18; /* enable branch to address */ + cmd->control |= recv->buf_stride; - im->address = dma_region_offset_to_bus(&iso->data_buf, buf_offset); - im->branchAddress = 0; /* filled in on next loop */ - im->status = data_size; + cmd->address = dma_region_offset_to_bus(&iso->data_buf, buf_offset); + cmd->branchAddress = 0; /* filled in on next loop */ + cmd->status = recv->buf_stride; /* link the previous descriptor to this one */ if(prev_branch) { - *prev_branch = dma_prog_region_offset_to_bus(&recv->prog, - prog_offset); + *prev_branch = dma_prog_region_offset_to_bus(&recv->prog, prog_offset); *prev_branch |= 1; /* set Z=1 */ } - prev_branch = &im->branchAddress; - - blk = (blk + 1) % recv->nblocks; + prev_branch = &cmd->branchAddress; } /* the final descriptor's branch address and Z should be left at 0 */ @@ -1358,7 +1390,7 @@ reg_read(recv->ohci, OHCI1394_IsochronousCycleTimer); } -static int ohci_iso_recv_start(struct hpsb_iso *iso, int cycle, int tag_mask) +static int ohci_iso_recv_start(struct hpsb_iso *iso, int cycle, int tag_mask, int sync) { struct ohci_iso_recv *recv = iso->hostdata; u32 command, contextMatch; @@ -1366,8 +1398,13 @@ reg_write(recv->ohci, recv->ContextControlClear, 0xFFFFFFFF); wmb(); - /* use buffer-fill mode, and keep ISO headers (required for multichannel) */ - reg_write(recv->ohci, recv->ContextControlSet, (1 << 31) | (1 << 30)); + /* always keep ISO headers */ + command = (1 << 30); + + if(recv->dma_mode == BUFFER_FILL_MODE) + command |= (1 << 31); + + reg_write(recv->ohci, recv->ContextControlSet, command); /* match on specified tags */ contextMatch = tag_mask << 28; @@ -1401,6 +1438,15 @@ contextMatch |= cycle << 12; } + if(sync != -1) { + /* set sync flag on first DMA descriptor */ + struct dma_cmd *cmd = &recv->block[recv->block_dma]; + cmd->control |= DMA_CTL_WAIT; + + /* match sync field */ + contextMatch |= (sync&0xf)<<8; + } + reg_write(recv->ohci, recv->ContextMatch, contextMatch); /* address of first descriptor block */ @@ -1434,42 +1480,22 @@ return 0; } -static void ohci_iso_recv_check_block_overflow(struct ohci_iso_recv *recv) -{ - /* see how close we are to running out of blocks */ - int gap = 0; - int blk = recv->block_reader; - while(blk != recv->block_dma) { - blk = (blk+1) % recv->nblocks; - gap++; - } - - if(gap*2 > recv->nblocks) { - DBGMSG(recv->ohci->id, - "Running out of DMA blocks for ISO reception - " - "overrun likely (gap = %d)", gap); - } -} - -static void ohci_iso_recv_release_block(struct hpsb_iso *iso, struct ohci_iso_recv *recv) +static void ohci_iso_recv_release_block(struct ohci_iso_recv *recv, int block) { - /* re-use the DMA descriptor for block_reader */ + /* re-use the DMA descriptor for the block */ /* by linking the previous descriptor to it */ - int next_i = recv->block_reader; + int next_i = block; int prev_i = (next_i == 0) ? (recv->nblocks - 1) : (next_i - 1); struct dma_cmd *next = &recv->block[next_i]; struct dma_cmd *prev = &recv->block[prev_i]; - ohci_iso_recv_check_block_overflow(recv); - /* 'next' becomes the new end of the DMA chain, so disable branch and enable interrupt */ - next->branchAddress = 0; next->control |= 3 << 20; - + /* link prev to next */ prev->branchAddress = dma_prog_region_offset_to_bus(&recv->prog, sizeof(struct dma_cmd) * next_i) @@ -1485,14 +1511,11 @@ /* wake up DMA in case it fell asleep */ reg_write(recv->ohci, recv->ContextControlSet, (1 << 12)); - - /* advance block cursor */ - recv->block_reader = (recv->block_reader + 1) % recv->nblocks; } -static void ohci_iso_recv_release(struct hpsb_iso *iso, struct hpsb_iso_packet_info *info) +static void ohci_iso_recv_bufferfill_release(struct ohci_iso_recv *recv, + struct hpsb_iso_packet_info *info) { - struct ohci_iso_recv *recv = iso->hostdata; int len; /* release the memory where the packet was */ @@ -1508,14 +1531,25 @@ recv->released_bytes += len; /* have we released enough memory for one block? */ - while(recv->released_bytes > PAGE_SIZE) { - ohci_iso_recv_release_block(iso, recv); - recv->released_bytes -= PAGE_SIZE; + while(recv->released_bytes > recv->buf_stride) { + ohci_iso_recv_release_block(recv, recv->block_reader); + recv->block_reader = (recv->block_reader + 1) % recv->nblocks; + recv->released_bytes -= recv->buf_stride; + } +} + +static inline void ohci_iso_recv_release(struct hpsb_iso *iso, struct hpsb_iso_packet_info *info) +{ + struct ohci_iso_recv *recv = iso->hostdata; + if(recv->dma_mode == BUFFER_FILL_MODE) { + ohci_iso_recv_bufferfill_release(recv, info); + } else { + ohci_iso_recv_release_block(recv, info - iso->infos); } } /* parse all packets from blocks that have been fully received */ -static void ohci_iso_recv_bytes(struct hpsb_iso *iso, struct ohci_iso_recv *recv) +static void ohci_iso_recv_bufferfill_parse(struct hpsb_iso *iso, struct ohci_iso_recv *recv) { int wake = 0; int runaway = 0; @@ -1530,7 +1564,7 @@ unsigned char *p = iso->data_buf.kvirt; - unsigned int this_block = recv->dma_offset/PAGE_SIZE; + unsigned int this_block = recv->dma_offset/recv->buf_stride; /* don't loop indefinitely */ if(runaway++ > 100000) { @@ -1553,7 +1587,7 @@ len = p[recv->dma_offset+2] | (p[recv->dma_offset+3] << 8); - if(len > PAGE_SIZE) { + if(len > 4096) { PRINT(KERN_ERR, recv->ohci->id, "IR DMA error - bogus 'len' value %u\n", len); } @@ -1566,8 +1600,8 @@ recv->dma_offset += 4; /* check for wrap-around */ - if(recv->dma_offset >= PAGE_SIZE*recv->nblocks) { - recv->dma_offset -= PAGE_SIZE*recv->nblocks; + if(recv->dma_offset >= recv->buf_stride*recv->nblocks) { + recv->dma_offset -= recv->buf_stride*recv->nblocks; } /* dma_offset now points to the first byte of the data payload */ @@ -1582,22 +1616,22 @@ } /* check for wrap-around */ - if(recv->dma_offset >= PAGE_SIZE*recv->nblocks) { + if(recv->dma_offset >= recv->buf_stride*recv->nblocks) { /* uh oh, the packet data wraps from the last to the first DMA block - make the packet contiguous by copying its "tail" into the guard page */ - int guard_off = recv->nblocks*PAGE_SIZE; + int guard_off = recv->buf_stride*recv->nblocks; int tail_len = len - (guard_off - offset); - if(tail_len > 0 && tail_len < PAGE_SIZE) { + if(tail_len > 0 && tail_len < recv->buf_stride) { memcpy(iso->data_buf.kvirt + guard_off, iso->data_buf.kvirt, tail_len); } - recv->dma_offset -= PAGE_SIZE*recv->nblocks; + recv->dma_offset -= recv->buf_stride*recv->nblocks; } /* parse timestamp */ @@ -1608,8 +1642,8 @@ recv->dma_offset += 4; /* check for wrap-around */ - if(recv->dma_offset >= PAGE_SIZE*recv->nblocks) { - recv->dma_offset -= PAGE_SIZE*recv->nblocks; + if(recv->dma_offset >= recv->buf_stride*recv->nblocks) { + recv->dma_offset -= recv->buf_stride*recv->nblocks; } hpsb_iso_packet_received(iso, offset, len, cycle, channel, tag, sy); @@ -1619,7 +1653,7 @@ hpsb_iso_wake(iso); } -static void ohci_iso_recv_task(unsigned long data) +static void ohci_iso_recv_bufferfill_task(unsigned long data) { struct hpsb_iso *iso = (struct hpsb_iso*) data; struct ohci_iso_recv *recv = iso->hostdata; @@ -1661,11 +1695,11 @@ /* OK, the block is finished... */ /* sync our view of the block */ - dma_region_sync(&iso->data_buf, recv->block_dma*PAGE_SIZE, PAGE_SIZE); + dma_region_sync(&iso->data_buf, recv->block_dma*recv->buf_stride, recv->buf_stride); /* reset the DMA descriptor */ - im->status = PAGE_SIZE; - + im->status = recv->buf_stride; + /* advance block_dma */ recv->block_dma = (recv->block_dma + 1) % recv->nblocks; @@ -1677,7 +1711,84 @@ } /* parse any packets that have arrived */ - ohci_iso_recv_bytes(iso, recv); + ohci_iso_recv_bufferfill_parse(iso, recv); +} + +static void ohci_iso_recv_packetperbuf_task(unsigned long data) +{ + struct hpsb_iso *iso = (struct hpsb_iso*) data; + struct ohci_iso_recv *recv = iso->hostdata; + int count; + int wake = 0; + + /* loop over the entire buffer */ + for(count = 0; count < recv->nblocks; count++) { + u32 packet_len = 0; + + /* pointer to the DMA descriptor */ + struct dma_cmd *il = ((struct dma_cmd*) recv->prog.kvirt) + iso->pkt_dma; + + /* check the DMA descriptor for new writes to xferStatus */ + u16 xferstatus = il->status >> 16; + u16 rescount = il->status & 0xFFFF; + + unsigned char event = xferstatus & 0x1F; + + if(!event) { + /* this packet hasn't come in yet; we are done for now */ + goto out; + } + + if(event == 0x11) { + /* packet received successfully! */ + + /* rescount is the number of bytes *remaining* in the packet buffer, + after the packet was written */ + packet_len = recv->buf_stride - rescount; + + } else if(event == 0x02) { + PRINT(KERN_ERR, recv->ohci->id, "IR DMA error - packet too long for buffer\n"); + } else if(event) { + PRINT(KERN_ERR, recv->ohci->id, "IR DMA error - OHCI error code 0x%02x\n", event); + } + + /* sync our view of the buffer */ + dma_region_sync(&iso->data_buf, iso->pkt_dma * recv->buf_stride, recv->buf_stride); + + /* record the per-packet info */ + { + /* iso header is 8 bytes ahead of the data payload */ + unsigned char *hdr; + + unsigned int offset; + unsigned short cycle; + unsigned char channel, tag, sy; + + offset = iso->pkt_dma * recv->buf_stride; + hdr = iso->data_buf.kvirt + offset; + + /* skip iso header */ + offset += 8; + packet_len -= 8; + + cycle = (hdr[0] | (hdr[1] << 8)) & 0x1FFF; + channel = hdr[5] & 0x3F; + tag = hdr[5] >> 6; + sy = hdr[4] & 0xF; + + hpsb_iso_packet_received(iso, offset, packet_len, cycle, channel, tag, sy); + } + + /* reset the DMA descriptor */ + il->status = recv->buf_stride; + + wake = 1; + recv->block_dma = iso->pkt_dma; + } + +out: + if(wake) + hpsb_iso_wake(iso); } @@ -1994,7 +2105,7 @@ return ohci_iso_recv_init(iso); case RECV_START: { int *args = (int*) arg; - return ohci_iso_recv_start(iso, args[0], args[1]); + return ohci_iso_recv_start(iso, args[0], args[1], args[2]); } case RECV_STOP: ohci_iso_recv_stop(iso); |