📄 cosa.c
字号:
* The following loop is indexed through i (instead of id) * to avoid looping forever when for any reason * the port returns '\r', '\n' or '\x2e' permanently. */ for (i=0; i<COSA_MAX_ID_STRING-1; i++, prev=curr) { if ((curr = get_wait_data(cosa)) == -1) { return -1; } curr &= 0xff; if (curr != '\r' && curr != '\n' && curr != 0x2e) idstring[id++] = curr; if (curr == 0x2e && prev == '\n') break; } /* Perhaps we should fail when i==COSA_MAX_ID_STRING-1 ? */ idstring[id] = '\0'; return id;}/* ---------- Auxiliary routines for COSA/SRP monitor ---------- *//* * This routine gets the data byte from the card waiting for the SR_RX_RDY * bit to be set in a loop. It should be used in the exceptional cases * only (for example when resetting the card or downloading the firmware. */static int get_wait_data(struct cosa_data *cosa){ int retries = 1000; while (--retries) { /* read data and return them */ if (cosa_getstatus(cosa) & SR_RX_RDY) { short r; r = cosa_getdata8(cosa);#if 0 printk(KERN_INFO "cosa: get_wait_data returning after %d retries\n", 999-retries);#endif return r; } /* sleep if not ready to read */ current->state = TASK_INTERRUPTIBLE; schedule_timeout(1); } printk(KERN_INFO "cosa: timeout in get_wait_data (status 0x%x)\n", cosa_getstatus(cosa)); return -1;}/* * This routine puts the data byte to the card waiting for the SR_TX_RDY * bit to be set in a loop. It should be used in the exceptional cases * only (for example when resetting the card or downloading the firmware). */static int put_wait_data(struct cosa_data *cosa, int data){ int retries = 1000; while (--retries) { /* read data and return them */ if (cosa_getstatus(cosa) & SR_TX_RDY) { cosa_putdata8(cosa, data);#if 0 printk(KERN_INFO "Putdata: %d retries\n", 999-retries);#endif return 0; }#if 0 /* sleep if not ready to read */ current->state = TASK_INTERRUPTIBLE; schedule_timeout(1);#endif } printk(KERN_INFO "cosa%d: timeout in put_wait_data (status 0x%x)\n", cosa->num, cosa_getstatus(cosa)); return -1;} /* * The following routine puts the hexadecimal number into the SRP monitor * and verifies the proper echo of the sent bytes. Returns 0 on success, * negative number on failure (-1,-3,-5,-7) means that put_wait_data() failed, * (-2,-4,-6,-8) means that reading echo failed. */static int puthexnumber(struct cosa_data *cosa, int number){ char temp[5]; int i; /* Well, I should probably replace this by something faster. */ sprintf(temp, "%04X", number); for (i=0; i<4; i++) { if (put_wait_data(cosa, temp[i]) == -1) { printk(KERN_NOTICE "cosa%d: puthexnumber failed to write byte %d\n", cosa->num, i); return -1-2*i; } if (get_wait_data(cosa) != temp[i]) { printk(KERN_NOTICE "cosa%d: puthexhumber failed to read echo of byte %d\n", cosa->num, i); return -2-2*i; } } return 0;}/* ---------- Interrupt routines ---------- *//* * There are three types of interrupt: * At the beginning of transmit - this handled is in tx_interrupt(), * at the beginning of receive - it is in rx_interrupt() and * at the end of transmit/receive - it is the eot_interrupt() function. * These functions are multiplexed by cosa_interrupt() according to the * COSA status byte. I have moved the rx/tx/eot interrupt handling into * separate functions to make it more readable. These functions are inline, * so there should be no overhead of function call. * * In the COSA bus-master mode, we need to tell the card the address of a * buffer. Unfortunately, COSA may be too slow for us, so we must busy-wait. * It's time to use the bottom half :-( *//* * Transmit interrupt routine - called when COSA is willing to obtain * data from the OS. The most tricky part of the routine is selection * of channel we (OS) want to send packet for. For SRP we should probably * use the round-robin approach. The newer COSA firmwares have a simple * flow-control - in the status word has bits 2 and 3 set to 1 means that the * channel 0 or 1 doesn't want to receive data. * * It seems there is a bug in COSA firmware (need to trace it further): * When the driver status says that the kernel has no more data for transmit * (e.g. at the end of TX DMA) and then the kernel changes its mind * (e.g. new packet is queued to hard_start_xmit()), the card issues * the TX interrupt but does not mark the channel as ready-to-transmit. * The fix seems to be to push the packet to COSA despite its request. * We first try to obey the card's opinion, and then fall back to forced TX. */static inline void tx_interrupt(struct cosa_data *cosa, int status){ unsigned long flags, flags1;#ifdef DEBUG_IRQS printk(KERN_INFO "cosa%d: SR_DOWN_REQUEST status=0x%04x\n", cosa->num, status);#endif spin_lock_irqsave(&cosa->lock, flags); set_bit(TXBIT, &cosa->rxtx); if (!test_bit(IRQBIT, &cosa->rxtx)) { /* flow control, see the comment above */ int i=0; if (!cosa->txbitmap) { printk(KERN_WARNING "%s: No channel wants data " "in TX IRQ. Expect DMA timeout.", cosa->name); put_driver_status_nolock(cosa); clear_bit(TXBIT, &cosa->rxtx); spin_unlock_irqrestore(&cosa->lock, flags); return; } while(1) { cosa->txchan++; i++; if (cosa->txchan >= cosa->nchannels) cosa->txchan = 0; if (!(cosa->txbitmap & (1<<cosa->txchan))) continue; if (~status & (1 << (cosa->txchan+DRIVER_TXMAP_SHIFT))) break; /* in second pass, accept first ready-to-TX channel */ if (i > cosa->nchannels) { /* Can be safely ignored */#ifdef DEBUG_IRQS printk(KERN_DEBUG "%s: Forcing TX " "to not-ready channel %d\n", cosa->name, cosa->txchan);#endif break; } } cosa->txsize = cosa->chan[cosa->txchan].txsize; if (cosa_dma_able(cosa->chan+cosa->txchan, cosa->chan[cosa->txchan].txbuf, cosa->txsize)) { cosa->txbuf = cosa->chan[cosa->txchan].txbuf; } else { memcpy(cosa->bouncebuf, cosa->chan[cosa->txchan].txbuf, cosa->txsize); cosa->txbuf = cosa->bouncebuf; } } if (is_8bit(cosa)) { if (!test_bit(IRQBIT, &cosa->rxtx)) { cosa_putstatus(cosa, SR_TX_INT_ENA); cosa_putdata8(cosa, ((cosa->txchan << 5) & 0xe0)| ((cosa->txsize >> 8) & 0x1f));#ifdef DEBUG_IO debug_status_out(cosa, SR_TX_INT_ENA); debug_data_out(cosa, ((cosa->txchan << 5) & 0xe0)| ((cosa->txsize >> 8) & 0x1f)); debug_data_in(cosa, cosa_getdata8(cosa));#else cosa_getdata8(cosa);#endif set_bit(IRQBIT, &cosa->rxtx); spin_unlock_irqrestore(&cosa->lock, flags); return; } else { clear_bit(IRQBIT, &cosa->rxtx); cosa_putstatus(cosa, 0); cosa_putdata8(cosa, cosa->txsize&0xff);#ifdef DEBUG_IO debug_status_out(cosa, 0); debug_data_out(cosa, cosa->txsize&0xff);#endif } } else { cosa_putstatus(cosa, SR_TX_INT_ENA); cosa_putdata16(cosa, ((cosa->txchan<<13) & 0xe000) | (cosa->txsize & 0x1fff));#ifdef DEBUG_IO debug_status_out(cosa, SR_TX_INT_ENA); debug_data_out(cosa, ((cosa->txchan<<13) & 0xe000) | (cosa->txsize & 0x1fff)); debug_data_in(cosa, cosa_getdata8(cosa)); debug_status_out(cosa, 0);#else cosa_getdata8(cosa);#endif cosa_putstatus(cosa, 0); } if (cosa->busmaster) { unsigned long addr = virt_to_bus(cosa->txbuf); int count=0; printk(KERN_INFO "busmaster IRQ\n"); while (!(cosa_getstatus(cosa)&SR_TX_RDY)) { count++; udelay(10); if (count > 1000) break; } printk(KERN_INFO "status %x\n", cosa_getstatus(cosa)); printk(KERN_INFO "ready after %d loops\n", count); cosa_putdata16(cosa, (addr >> 16)&0xffff); count = 0; while (!(cosa_getstatus(cosa)&SR_TX_RDY)) { count++; if (count > 1000) break; udelay(10); } printk(KERN_INFO "ready after %d loops\n", count); cosa_putdata16(cosa, addr &0xffff); flags1 = claim_dma_lock(); set_dma_mode(cosa->dma, DMA_MODE_CASCADE); enable_dma(cosa->dma); release_dma_lock(flags1); } else { /* start the DMA */ flags1 = claim_dma_lock(); disable_dma(cosa->dma); clear_dma_ff(cosa->dma); set_dma_mode(cosa->dma, DMA_MODE_WRITE); set_dma_addr(cosa->dma, virt_to_bus(cosa->txbuf)); set_dma_count(cosa->dma, cosa->txsize); enable_dma(cosa->dma); release_dma_lock(flags1); } cosa_putstatus(cosa, SR_TX_DMA_ENA|SR_USR_INT_ENA);#ifdef DEBUG_IO debug_status_out(cosa, SR_TX_DMA_ENA|SR_USR_INT_ENA);#endif spin_unlock_irqrestore(&cosa->lock, flags);}static inline void rx_interrupt(struct cosa_data *cosa, int status){ unsigned long flags;#ifdef DEBUG_IRQS printk(KERN_INFO "cosa%d: SR_UP_REQUEST\n", cosa->num);#endif spin_lock_irqsave(&cosa->lock, flags); set_bit(RXBIT, &cosa->rxtx); if (is_8bit(cosa)) { if (!test_bit(IRQBIT, &cosa->rxtx)) { set_bit(IRQBIT, &cosa->rxtx); put_driver_status_nolock(cosa); cosa->rxsize = cosa_getdata8(cosa) <<8;#ifdef DEBUG_IO debug_data_in(cosa, cosa->rxsize >> 8);#endif spin_unlock_irqrestore(&cosa->lock, flags); return; } else { clear_bit(IRQBIT, &cosa->rxtx); cosa->rxsize |= cosa_getdata8(cosa) & 0xff;#ifdef DEBUG_IO debug_data_in(cosa, cosa->rxsize & 0xff);#endif#if 0 printk(KERN_INFO "cosa%d: receive rxsize = (0x%04x).\n", cosa->num, cosa->rxsize);#endif } } else { cosa->rxsize = cosa_getdata16(cosa);#ifdef DEBUG_IO debug_data_in(cosa, cosa->rxsize);#endif#if 0 printk(KERN_INFO "cosa%d: receive rxsize = (0x%04x).\n", cosa->num, cosa->rxsize);#endif } if (((cosa->rxsize & 0xe000) >> 13) >= cosa->nchannels) { printk(KERN_WARNING "%s: rx for unknown channel (0x%04x)\n", cosa->name, cosa->rxsize); spin_unlock_irqrestore(&cosa->lock, flags); goto reject; } cosa->rxchan = cosa->chan + ((cosa->rxsize & 0xe000) >> 13); cosa->rxsize &= 0x1fff; spin_unlock_irqrestore(&cosa->lock, flags); cosa->rxbuf = NULL; if (cosa->rxchan->setup_rx) cosa->rxbuf = cosa->rxchan->setup_rx(cosa->rxchan, cosa->rxsize); if (!cosa->rxbuf) {reject: /* Reject the packet */ printk(KERN_INFO "cosa%d: rejecting packet on channel %d\n", cosa->num, cosa->rxchan->num); cosa->rxbuf = cosa->bouncebuf; } /* start the DMA */ flags = claim_dma_lock(); disable_dma(cosa->dma); clear_dma_ff(cosa->dma); set_dma_mode(cosa->dma, DMA_MODE_READ); if (cosa_dma_able(cosa->rxchan, cosa->rxbuf, cosa->rxsize & 0x1fff)) { set_dma_addr(cosa->dma, virt_to_bus(cosa->rxbuf)); } else { set_dma_addr(cosa->dma, virt_to_bus(cosa->bouncebuf)); } set_dma_count(cosa->dma, (cosa->rxsize&0x1fff)); enable_dma(cosa->dma); release_dma_lock(flags); spin_lock_irqsave(&cosa->lock, flags); cosa_putstatus(cosa, SR_RX_DMA_ENA|SR_USR_INT_ENA); if (!is_8bit(cosa) && (status & SR_TX_RDY)) cosa_putdata8(cosa, DRIVER_RX_READY);#ifdef DEBUG_IO debug_status_out(cosa, SR_RX_DMA_ENA|SR_USR_INT_ENA); if (!is_8bit(cosa) && (status & SR_TX_RDY)) debug_data_cmd(cosa, DRIVER_RX_READY);#endif spin_unlock_irqrestore(&cosa->lock, flags);}static inline void eot_interrupt(struct cosa_data *cosa, int status){ unsigned long flags, flags1; spin_lock_irqsave(&cosa->lock, flags); flags1 = claim_dma_lock(); disable_dma(cosa->dma); clear_dma_ff(cosa->dma); release_dma_lock(flags1); if (test_bit(TXBIT, &cosa->rxtx)) { struct channel_data *chan = cosa->chan+cosa->txchan; if (chan->tx_done) if (chan->tx_done(chan, cosa->txsize)) clear_bit(chan->num, &cosa->txbitmap); } else if (test_bit(RXBIT, &cosa->rxtx)) {#ifdef DEBUG_DATA { int i; printk(KERN_INFO "cosa%dc%d: done rx(0x%x)", cosa->num, cosa->rxchan->num, cosa->rxsize); for (i=0; i<cosa->rxsize; i++) printk (" %02x", cosa->rxbuf[i]&0xff); printk("\n"); }#endif /* Packet for unknown channel? */ if (cosa->rxbuf == cosa->bouncebuf) goto out; if (!cosa_dma_able(cosa->rxchan, cosa->rxbuf, cosa->rxsize)) memcpy(cosa->rxbuf, cosa->bouncebuf, cosa->rxsize); if (cosa->rxchan->rx_done) if (cosa->rxchan->rx_done(cosa->rxchan)) clear_bit(cosa->rxchan->num, &cosa->rxbitmap); } else { printk(KERN_NOTICE "cosa%d: unexpected EOT interrupt\n", cosa->num); } /* * Clear the RXBIT, TXBIT and IRQBIT (the latest should be * cleared anyway). We should do it as soon as possible * so that we can tell the COSA we are done and to give it a time * for recovery. */out: cosa->rxtx = 0; put_driver_status_nolock(cosa); spin_unlock_irqrestore(&cosa->lock, flags);}static irqreturn_t cosa_interrupt(int irq, void *cosa_, struct pt_regs *regs){ unsigned status; int count = 0; struct cosa_data *cosa = cosa_;again: status = cosa_getstatus(cosa);#ifdef DEBUG_IRQS printk(KERN_INFO "cosa%d: got IRQ, status 0x%02x\n", cosa->num, status & 0xff);#endif#ifdef DEBUG_IO debug_status_in(cosa, status);#endif switch (status & SR_CMD_FROM_SRP_MASK) { case SR_DOWN_REQUEST: tx_interrupt(cosa, status); break; case SR_UP_REQUEST: rx_interrupt(cosa, status); break; case SR_END_OF_TRANSFER: eot_interrupt(cosa, status); break; default: /* We may be too fast for SRP. Try to wait a bit more. */ if (count++ < 100) { udelay(100); goto again; } printk(KERN_INFO "cosa%d: unknown status 0x%02x in IRQ after %d retries\n", cosa->num, status & 0xff, count); }#ifdef DEBUG_IRQS if (count) printk(KERN_INFO "%s: %d-times got unknown status in IRQ\n", cosa->name, count); else printk(KERN_INFO "%s: returning from IRQ\n", cosa->name);#endif return IRQ_HANDLED;}/* ---------- I/O debugging routines ---------- *//* * These routines can be used to monitor COSA/SRP I/O and to printk() * the data being transferred on the data and status I/O port in a * readable way. */#ifdef DEBUG_IOstatic void debug_status_in(struct cosa_data *cosa, int status){ char *s; switch(status & SR_CMD_FROM_SRP_MASK) { case SR_UP_REQUEST: s = "RX_REQ"; break; case SR_DOWN_REQUEST: s = "TX_REQ"; break; case SR_END_OF_TRANSFER: s = "ET_REQ"; break; default: s = "NO_REQ"; break; } printk(KERN_INFO "%s: IO: status -> 0x%02x (%s%s%s%s)\n", cosa->name, status, status & SR_USR_RQ ? "USR_RQ|":"", status & SR_TX_RDY ? "TX_RDY|":"", status & SR_RX_RDY ? "RX_RDY|":"", s);}static void debug_status_out(struct cosa_data *cosa, int status){ printk(KERN_INFO "%s: IO: status <- 0x%02x (%s%s%s%s%s%s)\n", cosa->name, status, status & SR_RX_DMA_ENA ? "RXDMA|":"!rxdma|", status & SR_TX_DMA_ENA ? "TXDMA|":"!txdma|", status & SR_RST ? "RESET|":"", status & SR_USR_INT_ENA ? "USRINT|":"!usrint|", status & SR_TX_INT_ENA ? "TXINT|":"!txint|", status & SR_RX_INT_ENA ? "RXINT":"!rxint");}static void debug_data_in(struct cosa_data *cosa, int data){ printk(KERN_INFO "%s: IO: data -> 0x%04x\n", cosa->name, data);}static void debug_data_out(struct cosa_data *cosa, int data){ printk(KERN_INFO "%s: IO: data <- 0x%04x\n", cosa->name, data);}static void debug_data_cmd(struct cosa_data *cosa, int data){ printk(KERN_INFO "%s: IO: data <- 0x%04x (%s|%s)\n", cosa->name, data, data & SR_RDY_RCV ? "RX_RDY" : "!rx_rdy", data & SR_RDY_SND ? "TX_RDY" : "!tx_rdy");}#endif/* EOF -- this file has not been truncated */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -