/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id: $ * * Copyright (C) 2021 by Tomasz Moń * Copied with minor modifications from Sansa Connect Linux driver * Copyright (c) 2005,2006 Zermatt Systems, Inc. * Written by: Ben Bostwick * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ****************************************************************************/ #include #include "config.h" #include "system.h" #include "kernel.h" #include "panic.h" #include "tnetv105_usb_drv.h" #include "tnetv105_cppi.h" #if USB_CPPI_LOGGING #define LOGF_ENABLE #endif #include "logf.h" /* This file is pretty much directly copied from the Sansa Connect * Linux kernel source code. This is because the functionality is * nicely separated from actual kernel specific code and CPPI seems * complex (atleast without access to the datasheet). * * The only non cosmetic change was changing the dynamic allocations * to static allocations. * * It seems that the only way to get interrupt on non-control endpoint * acticity is to use the CPPI. This sounds like a plausible explanation * for the fake DMA buffers mentioned in CPPI code. */ /* Translate Linux consistent_sync() to Rockbox functions */ #define DMA_TO_DEVICE commit_discard_dcache_range #define DMA_FROM_DEVICE discard_dcache_range #define consistent_sync(ptr, size, func) func(ptr, size) /* Rockbox TMS320DM320 crt0.S maps everything to itself */ #define __virt_to_phys(ptr) ptr #define __phys_to_virt(ptr) ptr // CPPI functions #define CB_SOF_BIT (1<<31) #define CB_EOF_BIT (1<<30) #define CB_OWNERSHIP_BIT (1<<29) #define CB_EOQ_BIT (1<<28) #define CB_ZLP_GARBAGE (1<<23) #define CB_SIZE_MASK 0x0000ffff #define CB_OFFSET_MASK 0xffff0000 #define TEARDOWN_VAL 0xfffffffc #define MAX_BUF_SIZE 512 #define CPPI_DMA_RX_BUF_SIZE (MAX_BUF_SIZE * CPPI_RX_NUM_BUFS) static uint8_t *dma_recv_buf[CPPI_NUM_CHANNELS]; static uint8_t ch0_rx_buf[CPPI_DMA_RX_BUF_SIZE]; static uint8_t ch1_rx_buf[CPPI_DMA_RX_BUF_SIZE]; #if USB_CPPI_LOGGING #define cppi_log_event0(msg) logf(msg) #define cppi_log_event1(msg, data0) logf(msg " %lx", (uint32_t)data0) #define cppi_log_event2(msg, data0, data1) logf(msg " %lx %lx", (uint32_t)data0, (uint32_t)data1) #define cppi_log_event3(msg, data0, data1, data2) logf(msg " %lx %lx %lx", (uint32_t)data0, (uint32_t)data1, (uint32_t)data2) #define cppi_log_event4(msg, data0, data1, data2, data3) logf(msg " %lx %lx %lx %lx", (uint32_t)data0, (uint32_t)data1, (uint32_t)data2, (uint32_t)data3) #else #define cppi_log_event0(x) #define cppi_log_event1(x, y) #define cppi_log_event2(x, y, z) #define cppi_log_event3(x, y, z, w) #define cppi_log_event4(x, y, z, w, u) #endif /* * This function processes transmit interrupts. It traverses the * transmit buffer queue, detecting sent data buffers * * @return 0 if OK, non-zero otherwise. */ int tnetv_cppi_tx_int(struct cppi_info *cppi, int ch) { cppi_tcb *CurrentTcb,*LastTcbProcessed; uint32_t TxFrameStatus; cppi_txcntl *pTxCtl = &cppi->tx_ctl[ch]; int bytes_sent = 0; cppi_log_event1("[cppi]TxInt ch", ch); CurrentTcb = pTxCtl->TxActQueueHead; if (CurrentTcb == 0) { cppi_log_event0("[cppi] tx int: no current tcb"); return -1; } // sync up the tcb from memory consistent_sync(CurrentTcb, sizeof(*CurrentTcb), DMA_FROM_DEVICE); TxFrameStatus = CurrentTcb->mode; LastTcbProcessed = NULL; cppi_log_event3("[cppi] int tcb status", (uint32_t) CurrentTcb, TxFrameStatus, CurrentTcb->Off_BLen); while(CurrentTcb && (TxFrameStatus & CB_OWNERSHIP_BIT) == 0) { cppi_log_event3("[cppi] tx int: tcb (mode) (len)", (uint32_t) CurrentTcb, CurrentTcb->mode, CurrentTcb->Off_BLen); // calculate the amount of bytes sent. // don't count the fake ZLP byte if (CurrentTcb->Off_BLen > 0x1) { bytes_sent += CurrentTcb->Off_BLen & 0xFFFF; } if (CurrentTcb->mode & CB_EOQ_BIT) { if (CurrentTcb->Next) { cppi_log_event0(" [cppi] misqueue!"); // Misqueued packet tnetv_usb_reg_write(TNETV_DMA_TX_STATE(ch, TNETV_CPPI_TX_WORD_HDP), CurrentTcb->HNext); } else { cppi_log_event0("[cppi] eoq"); /* Tx End of Queue */ pTxCtl->TxActive = 0; } } cppi_log_event1("[cppi]SendComplete: ", CurrentTcb->Off_BLen & 0xFFFF); // Write the completion pointer tnetv_usb_reg_write(TNETV_DMA_TX_CMPL(ch), __dma_to_vlynq_phys(CurrentTcb->dma_handle)); LastTcbProcessed = CurrentTcb; CurrentTcb = CurrentTcb->Next; // clean up TCB fields LastTcbProcessed->HNext = 0; LastTcbProcessed->Next = 0; LastTcbProcessed->BufPtr = 0; LastTcbProcessed->Off_BLen = 0; LastTcbProcessed->mode = 0; LastTcbProcessed->Eop = 0; /* Push Tcb(s) back onto the list */ if (pTxCtl->TcbPool) { LastTcbProcessed->Next = pTxCtl->TcbPool->Next; pTxCtl->TcbPool->Next = LastTcbProcessed; } else { pTxCtl->TcbPool = LastTcbProcessed; } consistent_sync(LastTcbProcessed, sizeof(*LastTcbProcessed), DMA_TO_DEVICE); // get the status of the next packet if (CurrentTcb) { // sync up the tcb from memory consistent_sync(CurrentTcb, sizeof(*CurrentTcb), DMA_FROM_DEVICE); TxFrameStatus = CurrentTcb->mode; } } pTxCtl->TxActQueueHead = CurrentTcb; if (!LastTcbProcessed) { cppi_log_event1(" [cppi]No Tx packets serviced on int! ch", ch); return -1; } return bytes_sent; } int tnetv_cppi_flush_tx_queue(struct cppi_info *cppi, int ch) { cppi_txcntl *pTxCtl = &cppi->tx_ctl[ch]; cppi_tcb *tcb, *next_tcb; tcb = pTxCtl->TxActQueueHead; cppi_log_event1("[cppi] flush TX ", (uint32_t) pTxCtl->TxActQueueHead); while (tcb) { tcb->mode = 0; tcb->BufPtr = 0; tcb->Off_BLen = 0; tcb->Eop = 0; tcb->HNext = 0; next_tcb = tcb->Next; tcb->Next = pTxCtl->TcbPool; pTxCtl->TcbPool = tcb; tcb = next_tcb; } pTxCtl->TxActQueueHead = 0; pTxCtl->TxActQueueTail = 0; pTxCtl->TxActive = 0; return 0; } /** * @ingroup CPHAL_Functions * This function transmits the data in FragList using available transmit * buffer descriptors. More information on the use of the Mode parameter * is available in the module-specific appendices. Note: The OS should * not call Send() for a channel that has been requested to be torndown. * */ int tnetv_cppi_send(struct cppi_info *cppi, int ch, dma_addr_t buf, unsigned length, int send_zlp) { cppi_txcntl *pTxCtl; cppi_tcb *first_tcb; cppi_tcb *tcb; int queued_len; dma_addr_t buf_to_send; dma_addr_t buf_ptr; int total_len = length; int pktlen; pTxCtl = &cppi->tx_ctl[ch]; if (length == 0) { cppi_log_event0("[cppi] len = 0, nothing to send"); return -1; } // no send buffers.. try again later if (!pTxCtl->TcbPool) { cppi_log_event0("[cppi] out of cppi buffers"); return -1; } // only send 1 packet at a time if (pTxCtl->TxActQueueHead || pTxCtl->TxActive) { cppi_log_event0("[cppi] already sending!"); return -1; } buf_to_send = buf; // usb_requests can have a 32 bit length, but CPPI DMA fragments // have a (64k - 1) limit. Split the usb_request up into fragments here. first_tcb = pTxCtl->TcbPool; tcb = first_tcb; cppi_log_event4("[cppi]cppi_send (buf) (len) (pool) (dma)", (uint32_t) buf_to_send, total_len, (uint32_t) first_tcb, first_tcb->dma_handle); queued_len = 0; do { buf_ptr = buf_to_send + queued_len; tcb->BufPtr = __dma_to_vlynq_phys(buf_ptr); tcb->HNext = 0; // can't transfer more that 64k-1 bytes in 1 CPPI transfer // need to queue up transfers if it's greater than that pktlen = ((total_len - queued_len) > CPPI_MAX_FRAG) ? CPPI_MAX_FRAG : (total_len - queued_len); tcb->Off_BLen = pktlen; tcb->mode = (CB_OWNERSHIP_BIT | CB_SOF_BIT | CB_EOF_BIT | pktlen); queued_len += pktlen; if (queued_len < total_len) { tcb->HNext = __dma_to_vlynq_phys(((cppi_tcb *) tcb->Next)->dma_handle); // write out the buffer to memory consistent_sync(tcb, sizeof(*tcb), DMA_TO_DEVICE); cppi_log_event4("[cppi] q tcb", (uint32_t) tcb, ((uint32_t *) tcb)[0], ((uint32_t *) tcb)[1], ((uint32_t *) tcb)[2]); cppi_log_event4("[cppi] ", ((uint32_t *) tcb)[3], ((uint32_t *) tcb)[4], ((uint32_t *) tcb)[5], ((uint32_t *) tcb)[6]); tcb = tcb->Next; } } while (queued_len < total_len); /* In the Tx Interrupt handler, we will need to know which TCB is EOP, so we can save that information in the SOP */ first_tcb->Eop = tcb; // set the secret ZLP bit if necessary, this will be a completely separate packet if (send_zlp) { #if defined(AUTO_ZLP) && AUTO_ZLP // add an extra buffer at the end to hold the ZLP tcb->HNext = __dma_to_vlynq_phys(((cppi_tcb *) tcb->Next)->dma_handle); // write out the buffer to memory consistent_sync(tcb, sizeof(*tcb), DMA_TO_DEVICE); tcb = tcb->Next; /* In the Tx Interrupt handler, we will need to know which TCB is EOP, so we can save that information in the SOP */ first_tcb->Eop = tcb; #endif buf_ptr = buf_to_send + queued_len; tcb->BufPtr = __dma_to_vlynq_phys(buf_ptr); // not used, but can't be zero tcb->HNext = 0; tcb->Off_BLen = 0x1; // device will send (((len - 1) / maxpacket) + 1) ZLPs tcb->mode = (CB_SOF_BIT | CB_EOF_BIT | CB_OWNERSHIP_BIT | CB_ZLP_GARBAGE | 0x1); // send 1 ZLP tcb->Eop = tcb; cppi_log_event0("[cppi] Send ZLP!"); } pTxCtl->TcbPool = tcb->Next; tcb->Next = 0; tcb->HNext = 0; // write out the buffer to memory consistent_sync(tcb, sizeof(*tcb), DMA_TO_DEVICE); cppi_log_event4("[cppi] q tcb", (uint32_t) tcb, ((uint32_t *) tcb)[0], ((uint32_t *) tcb)[1], ((uint32_t *) tcb)[2]); cppi_log_event4("[cppi] ", ((uint32_t *) tcb)[3], ((uint32_t *) tcb)[4], ((uint32_t *) tcb)[5], ((uint32_t *) tcb)[6]); cppi_log_event4("[cppi] send queued (ptr) (len) (ftcb, ltcb)", (uint32_t) tcb->BufPtr, tcb->Off_BLen, (uint32_t) first_tcb, (uint32_t) tcb); /* put it on the queue */ pTxCtl->TxActQueueHead = first_tcb; pTxCtl->TxActQueueTail = tcb; cppi_log_event3("[cppi] setting state (head) (virt) (next)", (uint32_t) first_tcb, __dma_to_vlynq_phys(first_tcb->dma_handle), (uint32_t) first_tcb->HNext); /* write CPPI TX HDP - cache is cleaned above */ tnetv_usb_reg_write(TNETV_DMA_TX_STATE(ch, TNETV_CPPI_TX_WORD_HDP), __dma_to_vlynq_phys(first_tcb->dma_handle)); pTxCtl->TxActive = 1; return 0; } /* * This function allocates transmit buffer descriptors (internal CPHAL function). * It creates a high priority transmit queue by default for a single Tx * channel. If QoS is enabled for the given CPHAL device, this function * will also allocate a low priority transmit queue. * * @return 0 OK, Non-Zero Not OK */ int tnetv_cppi_init_tcb(struct cppi_info *cppi, int ch) { int i, num; cppi_tcb *pTcb = 0; char *AllTcb; int tcbSize; cppi_txcntl *pTxCtl = &cppi->tx_ctl[ch]; num = pTxCtl->TxNumBuffers; tcbSize = (sizeof(cppi_tcb) + 0xf) & ~0xf; cppi_log_event4("[cppi] init_tcb (ch) (num) (dma) (tcbsz)", ch, num, pTxCtl->tcb_start_dma_addr, tcbSize); if (pTxCtl->TxNumBuffers == 0) { return -1; } /* if the memory has already been allocated, simply reuse it! */ AllTcb = pTxCtl->TcbStart; // now reinitialize the TCB pool pTxCtl->TcbPool = 0; for (i = 0; i < num; i++) { pTcb = (cppi_tcb *)(AllTcb + (i * tcbSize)); pTcb->dma_handle = pTxCtl->tcb_start_dma_addr + (i * tcbSize); pTcb->BufPtr = 0; pTcb->mode = 0; pTcb->HNext = 0; pTcb->Off_BLen = 0; pTcb->Eop = 0; pTcb->Next = (void *) pTxCtl->TcbPool; pTxCtl->TcbPool = pTcb; } cppi_log_event2(" [cppi]TcbPool", (uint32_t) pTxCtl->TcbPool, pTxCtl->TcbPool->dma_handle); #if USB_CPPI_LOGGING { // BEN DEBUG cppi_tcb *first_tcb = pTxCtl->TcbPool; cppi_log_event4("[cppi] init tcb", (uint32_t) first_tcb, ((uint32_t *) first_tcb)[0], ((uint32_t *) first_tcb)[1], ((uint32_t *) first_tcb)[2]); cppi_log_event4("[cppi] ", ((uint32_t *) first_tcb)[3], ((uint32_t *) first_tcb)[4], ((uint32_t *) first_tcb)[5], ((uint32_t *) first_tcb)[6]); } #endif return 0; } // BEN DEBUG void tnetv_cppi_dump_info(struct cppi_info *cppi) { int ch; cppi_rxcntl *pRxCtl; cppi_txcntl *pTxCtl; cppi_tcb *tcb; cppi_rcb *rcb; logf("CPPI struct:\n"); logf("Buf mem: %lx Buf size: %d int: %lx %lx\n\n", (uint32_t) cppi->dma_mem, cppi->dma_size, tnetv_usb_reg_read(TNETV_USB_RX_INT_STATUS), tnetv_usb_reg_read(VL_INTST)); for (ch = 0; ch < CPPI_NUM_CHANNELS; ch++) { pRxCtl = &cppi->rx_ctl[ch]; pTxCtl = &cppi->tx_ctl[ch]; logf("ch: %d\n", ch); logf(" rx_numbufs: %d active %ld free_buf_cnt %ld\n", pRxCtl->RxNumBuffers, pRxCtl->RxActive, tnetv_usb_reg_read(TNETV_USB_RX_FREE_BUF_CNT(ch))); logf(" q_cnt %ld head %lx tail %lx\n", pRxCtl->RxActQueueCount, (uint32_t) pRxCtl->RxActQueueHead, (uint32_t) pRxCtl->RxActQueueTail); logf(" fake_head: %lx fake_tail: %lx\n", (uint32_t) pRxCtl->RxFakeRcvHead, (uint32_t) pRxCtl->RxFakeRcvTail); rcb = (cppi_rcb *) pRxCtl->RcbStart; do { if (!rcb) break; logf(" Rcb: %lx\n", (uint32_t) rcb); logf(" HNext %lx BufPtr %lx Off_BLen %lx mode %lx\n", rcb->HNext, rcb->BufPtr, rcb->Off_BLen, rcb->mode); logf(" Next %lx Eop %lx dma_handle %lx fake_bytes %lx\n", (uint32_t) rcb->Next, (uint32_t) rcb->Eop, rcb->dma_handle, rcb->fake_bytes); rcb = rcb->Next; } while (rcb && rcb != (cppi_rcb *) pRxCtl->RcbStart); logf("\n"); logf(" tx_numbufs: %d active %ld\n", pTxCtl->TxNumBuffers, pTxCtl->TxActive); logf(" q_cnt %ld head %lx tail %lx\n", pTxCtl->TxActQueueCount, (uint32_t) pTxCtl->TxActQueueHead, (uint32_t) pTxCtl->TxActQueueTail); tcb = (cppi_tcb *) pTxCtl->TcbPool; do { if (!tcb) break; logf(" Tcb (pool): %lx\n", (uint32_t) tcb); logf(" HNext %lx BufPtr %lx Off_BLen %lx mode %lx\n", tcb->HNext, tcb->BufPtr, tcb->Off_BLen, tcb->mode); logf(" Next %lx Eop %lx dma_handle %lx\n", (uint32_t) tcb->Next, (uint32_t) tcb->Eop, tcb->dma_handle); tcb = tcb->Next; } while (tcb && tcb != (cppi_tcb *) pTxCtl->TcbPool); tcb = (cppi_tcb *) pTxCtl->TxActQueueHead; do { if (!tcb) break; logf(" Tcb (act): %lx\n", (uint32_t) tcb); logf(" HNext %lx BufPtr %lx Off_BLen %lx mode %lx\n", tcb->HNext, tcb->BufPtr, tcb->Off_BLen, tcb->mode); logf(" Next %lx Eop %lx dma_handle %lx\n", (uint32_t) tcb->Next, (uint32_t) tcb->Eop, tcb->dma_handle); tcb = tcb->Next; } while (tcb && tcb != (cppi_tcb *) pTxCtl->TxActQueueTail); } } /** * * This function is called to indicate to the CPHAL that the upper layer * software has finished processing the receive data (given to it by * osReceive()). The CPHAL will then return the appropriate receive buffers * and buffer descriptors to the available pool. * */ int tnetv_cppi_rx_return(struct cppi_info *cppi, int ch, cppi_rcb *done_rcb) { cppi_rxcntl *pRxCtl = &cppi->rx_ctl[ch]; cppi_rcb *curRcb, *lastRcb, *endRcb; int num_bufs = 0; if (!done_rcb) return -1; //cppi_log_event3("[cppi] rx_return (last) (first) bufinq", (uint32_t) done_rcb, (uint32_t) done_rcb->Eop, tnetv_usb_reg_read(TNETV_USB_RX_FREE_BUF_CNT(ch))); curRcb = done_rcb; endRcb = done_rcb->Eop; do { curRcb->mode = CB_OWNERSHIP_BIT; curRcb->Off_BLen = MAX_BUF_SIZE; curRcb->Eop = 0; pRxCtl->RxActQueueCount++; num_bufs++; lastRcb = curRcb; curRcb = lastRcb->Next; consistent_sync(lastRcb, sizeof(*lastRcb), DMA_TO_DEVICE); } while (lastRcb != endRcb); cppi_log_event1("[cppi] rx_return done", num_bufs); // let the hardware know about the buffer(s) tnetv_usb_reg_write(TNETV_USB_RX_FREE_BUF_CNT(ch), num_bufs); return 0; } int tnetv_cppi_rx_int_recv(struct cppi_info *cppi, int ch, int *buf_size, void *buf, int maxpacket) { cppi_rxcntl *pRxCtl = &cppi->rx_ctl[ch]; cppi_rcb *CurrentRcb, *LastRcb = 0, *SopRcb; uint8_t *cur_buf_data_addr; int cur_buf_bytes; int copy_buf_size = *buf_size; int ret = -EAGAIN; *buf_size = 0; CurrentRcb = pRxCtl->RxFakeRcvHead; if (!CurrentRcb) { cppi_log_event2("[cppi] rx_int recv: nothing in q", tnetv_usb_reg_read(TNETV_USB_RX_INT_STATUS), tnetv_usb_reg_read(VL_INTST)); return -1; } cppi_log_event1("[cppi] rx_int recv (ch)", ch); cppi_log_event4(" [cppi] recv - Processing SOP descriptor fb hd tl", (uint32_t) CurrentRcb, CurrentRcb->fake_bytes, (uint32_t) pRxCtl->RxFakeRcvHead, (uint32_t) pRxCtl->RxFakeRcvTail); SopRcb = CurrentRcb; LastRcb = 0; do { // convert from vlynq phys to virt cur_buf_data_addr = (uint8_t *) __vlynq_phys_to_dma(CurrentRcb->BufPtr); cur_buf_data_addr = (uint8_t *) __phys_to_virt(cur_buf_data_addr); cur_buf_bytes = (CurrentRcb->mode) & CB_SIZE_MASK; // make sure we don't overflow the buffer. if (cur_buf_bytes > copy_buf_size) { ret = 0; break; } // BEN - packet can be ZLP if (cur_buf_bytes) { consistent_sync(cur_buf_data_addr, MAX_BUF_SIZE, DMA_FROM_DEVICE); memcpy((buf + *buf_size), cur_buf_data_addr, cur_buf_bytes); copy_buf_size -= cur_buf_bytes; *buf_size += cur_buf_bytes; CurrentRcb->fake_bytes -= cur_buf_bytes; } else { CurrentRcb->fake_bytes = 0; } cppi_log_event4(" [cppi] bytes totrcvd amtleft fake", cur_buf_bytes, *buf_size, copy_buf_size, CurrentRcb->fake_bytes); LastRcb = CurrentRcb; CurrentRcb = LastRcb->Next; // sync out fake bytes info consistent_sync(LastRcb, sizeof(*LastRcb), DMA_TO_DEVICE); // make sure each packet processed individually if (cur_buf_bytes < maxpacket) { ret = 0; break; } } while (LastRcb != pRxCtl->RxFakeRcvTail && CurrentRcb->fake_bytes && copy_buf_size > 0); // make sure that the CurrentRcb isn't in the cache consistent_sync(CurrentRcb, sizeof(*CurrentRcb), DMA_FROM_DEVICE); if (copy_buf_size == 0) { ret = 0; } if (LastRcb) { SopRcb->Eop = LastRcb; cppi_log_event3(" [cppi] rcv end", *buf_size, (uint32_t) CurrentRcb, (uint32_t) SopRcb->Eop); if (LastRcb == pRxCtl->RxFakeRcvTail) { pRxCtl->RxFakeRcvHead = 0; pRxCtl->RxFakeRcvTail = 0; } else { pRxCtl->RxFakeRcvHead = CurrentRcb; } cppi_log_event1(" [cppi] st rx return", ch); cppi_log_event2(" rcv fake hd tl", (uint32_t) pRxCtl->RxFakeRcvHead, (uint32_t) pRxCtl->RxFakeRcvTail); // all done, clean up the RCBs tnetv_cppi_rx_return(cppi, ch, SopRcb); } return ret; } /* * This function processes receive interrupts. It traverses the receive * buffer queue, extracting the data and passing it to the upper layer software via * osReceive(). It handles all error conditions and fragments without valid data by * immediately returning the RCB's to the RCB pool. */ int tnetv_cppi_rx_int(struct cppi_info *cppi, int ch) { cppi_rxcntl *pRxCtl = &cppi->rx_ctl[ch]; cppi_rcb *CurrentRcb, *LastRcb = 0, *SopRcb; uint32_t RxBufStatus,PacketsServiced; int TotalFrags; cppi_log_event1("[cppi] rx_int (ch)", ch); CurrentRcb = pRxCtl->RxActQueueHead; if (!CurrentRcb) { cppi_log_event1("[cppi] rx_int no bufs!", (uint32_t) CurrentRcb); return -1; } // make sure that all of the buffers get an invalidated cache consistent_sync(pRxCtl->RcbStart, sizeof(cppi_rcb) * CPPI_RX_NUM_BUFS, DMA_FROM_DEVICE); RxBufStatus = CurrentRcb->mode; PacketsServiced = 0; cppi_log_event4("[cppi] currentrcb, mode numleft fake", (uint32_t) CurrentRcb, CurrentRcb->mode, pRxCtl->RxActQueueCount, CurrentRcb->fake_bytes); cppi_log_event4("[cppi]", ((uint32_t *) CurrentRcb)[0], ((uint32_t *) CurrentRcb)[1], ((uint32_t *) CurrentRcb)[2], ((uint32_t *) CurrentRcb)[3]); while(((RxBufStatus & CB_OWNERSHIP_BIT) == 0) && (pRxCtl->RxActQueueCount > 0)) { cppi_log_event2(" [cppi]Processing SOP descriptor st", (uint32_t) CurrentRcb, RxBufStatus); SopRcb = CurrentRcb; TotalFrags = 0; do { TotalFrags++; PacketsServiced++; // Write the completion pointer tnetv_usb_reg_write(TNETV_DMA_RX_CMPL(ch), __dma_to_vlynq_phys(CurrentRcb->dma_handle)); CurrentRcb->fake_bytes = (CurrentRcb->mode) & 0xFFFF; // BEN - make sure this gets marked! if (!CurrentRcb->fake_bytes || (CurrentRcb->mode & CB_ZLP_GARBAGE)) { CurrentRcb->mode &= 0xFFFF0000; CurrentRcb->fake_bytes = 0x10000; } cppi_log_event1(" fake_bytes:", CurrentRcb->fake_bytes); RxBufStatus = CurrentRcb->mode; LastRcb = CurrentRcb; CurrentRcb = LastRcb->Next; // sync the fake_bytes value back to mem consistent_sync(LastRcb, sizeof(*LastRcb), DMA_TO_DEVICE); } while (((CurrentRcb->mode & CB_OWNERSHIP_BIT) == 0) && ((RxBufStatus & CB_EOF_BIT) == 0)); SopRcb->Eop = LastRcb; pRxCtl->RxActQueueHead = CurrentRcb; pRxCtl->RxActQueueCount -= TotalFrags; if (LastRcb->mode & CB_EOQ_BIT) { if (CurrentRcb) { cppi_log_event1(" [cppi] rcv done q next", LastRcb->HNext); tnetv_usb_reg_write(TNETV_DMA_RX_STATE(ch, TNETV_CPPI_RX_WORD_HDP), LastRcb->HNext); } else { cppi_log_event0(" [cppi] rcv done"); pRxCtl->RxActive = 0; } } // BEN - add to the list of buffers we need to deal with if (!pRxCtl->RxFakeRcvHead) { pRxCtl->RxFakeRcvHead = SopRcb; pRxCtl->RxFakeRcvTail = SopRcb->Eop; } else { pRxCtl->RxFakeRcvTail = SopRcb->Eop; } // make sure we have enough buffers cppi_log_event1(" nextrcb", CurrentRcb->mode); if (CurrentRcb) { // continue the loop RxBufStatus = CurrentRcb->mode; } } /* while */ cppi_log_event2("[cppi] fake hd tl", (uint32_t) pRxCtl->RxFakeRcvHead, (uint32_t) pRxCtl->RxFakeRcvTail); // sync out all buffers before leaving consistent_sync(pRxCtl->RcbStart, (CPPI_RX_NUM_BUFS * sizeof(cppi_rcb)), DMA_FROM_DEVICE); return PacketsServiced; } static void tnetv_cppi_rx_queue_init(struct cppi_info *cppi, int ch, dma_addr_t buf, unsigned length) { cppi_rxcntl *pRxCtl = &cppi->rx_ctl[ch]; cppi_rcb *rcb, *first_rcb; unsigned int queued_len = 0; int rcblen; int num_frags = 0; dma_addr_t buf_ptr; if (length == 0) { cppi_log_event0("[cppi] len = 0, nothing to recv"); return; } // usb_requests can have a 32 bit length, but CPPI DMA fragments // have a 64k limit. Split the usb_request up into fragments here. first_rcb = pRxCtl->RcbPool; rcb = first_rcb; cppi_log_event2("[cppi] Rx queue add: head len", (uint32_t) first_rcb, length); while (queued_len < length) { buf_ptr = buf + queued_len; rcb->BufPtr = __dma_to_vlynq_phys(buf_ptr); rcb->HNext = 0; rcb->mode = CB_OWNERSHIP_BIT; rcblen = ((length - queued_len) > MAX_BUF_SIZE) ? MAX_BUF_SIZE : (length - queued_len); rcb->Off_BLen = rcblen; queued_len += rcblen; if (queued_len < length) { rcb->HNext = __dma_to_vlynq_phys(((cppi_rcb *) (rcb->Next))->dma_handle); rcb = rcb->Next; } num_frags++; } pRxCtl->RcbPool = rcb->Next; rcb->Next = 0; cppi_log_event4("[cppi] Adding Rcb (dma) (paddr) (buf)", (uint32_t) rcb, rcb->dma_handle, __dma_to_vlynq_phys(rcb->dma_handle), (uint32_t) rcb->BufPtr); cppi_log_event4("[cppi] Next HNext (len) of (total)", (uint32_t) rcb->Next, rcb->HNext, queued_len, length); pRxCtl->RxActQueueCount += num_frags; cppi_log_event4("[cppi] rx queued (ptr) (len) (ftcb, ltcb)", (uint32_t) rcb->BufPtr, rcb->Off_BLen, (uint32_t) first_rcb, (uint32_t) rcb); cppi_log_event2(" [cppi] mode num_frags", rcb->mode, num_frags); pRxCtl->RxActQueueHead = first_rcb; pRxCtl->RxActQueueTail = rcb; cppi_log_event2("[cppi] setting rx (head) (virt)", (uint32_t) first_rcb, __dma_to_vlynq_phys(first_rcb->dma_handle)); cppi_log_event4("[cppi] ", ((uint32_t *) first_rcb)[0], ((uint32_t *) first_rcb)[1], ((uint32_t *) first_rcb)[2], ((uint32_t *) first_rcb)[3]); // make this into a circular buffer so we never get caught with // no free buffers left rcb->Next = pRxCtl->RxActQueueHead; rcb->HNext = (uint32_t) (__dma_to_vlynq_phys(pRxCtl->RxActQueueHead->dma_handle)); } int tnetv_cppi_rx_queue_add(struct cppi_info *cppi, int ch, dma_addr_t buf, unsigned length) { (void)buf; (void)length; cppi_rxcntl *pRxCtl = &cppi->rx_ctl[ch]; unsigned int cur_bufs; cur_bufs = tnetv_usb_reg_read(TNETV_USB_RX_FREE_BUF_CNT(ch)); if (!pRxCtl->RxActive) { cppi_log_event0("[cppi] queue add - not active"); pRxCtl->RcbPool = (cppi_rcb *) pRxCtl->RcbStart; // add all the buffers to the active (circular) queue tnetv_cppi_rx_queue_init(cppi, ch, (dma_addr_t) __virt_to_phys(dma_recv_buf[ch]), (MAX_BUF_SIZE * pRxCtl->RxNumBuffers)); /* write Rx Queue Head Descriptor Pointer */ tnetv_usb_reg_write(TNETV_DMA_RX_STATE(ch, TNETV_CPPI_RX_WORD_HDP), __dma_to_vlynq_phys(pRxCtl->RxActQueueHead->dma_handle)); pRxCtl->RxActive = 1; // sync out all buffers before starting consistent_sync(pRxCtl->RcbStart, (CPPI_RX_NUM_BUFS * sizeof(cppi_rcb)), DMA_TO_DEVICE); // sync out temp rx buffer consistent_sync(dma_recv_buf[ch], CPPI_DMA_RX_BUF_SIZE, DMA_FROM_DEVICE); if (cur_bufs < pRxCtl->RxActQueueCount) { // let the hardware know about the buffer(s) tnetv_usb_reg_write(TNETV_USB_RX_FREE_BUF_CNT(ch), pRxCtl->RxActQueueCount - cur_bufs); } } cppi_log_event3("[cppi] rx add: (cur_bufs) (avail_bufs) (now)", cur_bufs, pRxCtl->RxActQueueCount, tnetv_usb_reg_read(TNETV_USB_RX_FREE_BUF_CNT(ch))); return 0; } int tnetv_cppi_flush_rx_queue(struct cppi_info *cppi, int ch) { cppi_rxcntl *pRxCtl = &cppi->rx_ctl[ch]; cppi_rcb *rcb; int num_bufs; cppi_log_event1("[cppi] flush RX ", (uint32_t) pRxCtl->RxActQueueHead); // flush out any pending receives tnetv_cppi_rx_int(cppi, ch); // now discard all received data rcb = pRxCtl->RxFakeRcvHead; if (rcb) { rcb->Eop = pRxCtl->RxFakeRcvTail; // clean up any unreceived RCBs tnetv_cppi_rx_return(cppi, ch, rcb); } pRxCtl->RxFakeRcvHead = 0; pRxCtl->RxFakeRcvTail = 0; pRxCtl->RxActive = 0; // drain the HW free buffer count num_bufs = tnetv_usb_reg_read(TNETV_USB_RX_FREE_BUF_CNT(ch)); tnetv_usb_reg_write(TNETV_USB_RX_FREE_BUF_CNT(ch), -num_bufs); cppi_log_event2("[cppi] flush RX queue done (freed) act: ", num_bufs, (uint32_t) pRxCtl->RxActQueueCount); return 0; } /* * This function allocates receive buffer descriptors (internal CPHAL function). * After allocation, the function 'queues' (gives to the hardware) the newly * created receive buffers to enable packet reception. * * @param ch Channel number. * * @return 0 OK, Non-Zero Not OK */ int tnetv_cppi_init_rcb(struct cppi_info *cppi, int ch) { int i, num; cppi_rcb *pRcb; char *AllRcb; int rcbSize; cppi_rxcntl *pRxCtl = &cppi->rx_ctl[ch]; num = pRxCtl->RxNumBuffers; rcbSize = (sizeof(cppi_rcb) + 0xf) & ~0xf; cppi_log_event2("[cppi] init_rcb ch num", ch, num); if (pRxCtl->RxNumBuffers == 0) { return -1; } /* if the memory has already been allocated, simply reuse it! */ AllRcb = pRxCtl->RcbStart; // now reinitialize the RCB pool pRxCtl->RcbPool = 0; for (i = (num - 1); i >= 0; i--) { pRcb = (cppi_rcb *)(AllRcb + (i * rcbSize)); pRcb->dma_handle = pRxCtl->rcb_start_dma_addr + (i * rcbSize); pRcb->BufPtr = 0; pRcb->mode = 0; pRcb->HNext = 0; pRcb->Next = (void *) pRxCtl->RcbPool; pRcb->Off_BLen = 0; pRcb->Eop = 0; pRcb->fake_bytes = 0; pRxCtl->RcbPool = pRcb; } cppi_log_event2(" [cppi]RcbPool (dma)", (uint32_t) pRxCtl->RcbPool, pRxCtl->RcbPool->dma_handle); pRxCtl->RxActQueueCount = 0; pRxCtl->RxActQueueHead = 0; pRxCtl->RxActive = 0; pRxCtl->RxFakeRcvHead = 0; pRxCtl->RxFakeRcvTail = 0; return 0; } static uint8_t ch_buf_cnt[][2] = { {CPPI_RX_NUM_BUFS, 2}, // ch0: bulk out/in {CPPI_RX_NUM_BUFS, 2}, // ch1: bulk out/in {0, 2}, // ch2: interrupt {0, 2} // ch3: interrupt }; void tnetv_cppi_init(struct cppi_info *cppi) { int ch; uint8_t *alloc_ptr; int ch_mem_size[CPPI_NUM_CHANNELS]; // wipe cppi memory memset(cppi, 0, sizeof(*cppi)); // find out how much memory we need to allocate cppi->dma_size = 0; for (ch = 0; ch < CPPI_NUM_CHANNELS; ch++) { ch_mem_size[ch] = (ch_buf_cnt[ch][0] * sizeof(cppi_rcb)) + (ch_buf_cnt[ch][1] * sizeof(cppi_tcb)); cppi->dma_size += ch_mem_size[ch]; } // allocate DMA-able memory if (cppi->dma_size != CPPI_INFO_MEM_SIZE) { panicf("Invalid dma size expected %d got %d", cppi->dma_size, CPPI_INFO_MEM_SIZE); } cppi->dma_handle = (dma_addr_t) __virt_to_phys(cppi->dma_mem); memset(cppi->dma_mem, 0, cppi->dma_size); cppi_log_event2("[cppi] all CBs sz mem", cppi->dma_size, (uint32_t) cppi->dma_mem); // now set up the pointers alloc_ptr = cppi->dma_mem; for (ch = 0; ch < CPPI_NUM_CHANNELS; ch++) { cppi->rx_ctl[ch].RxNumBuffers = ch_buf_cnt[ch][0]; cppi->rx_ctl[ch].RcbStart = alloc_ptr; cppi->rx_ctl[ch].rcb_start_dma_addr = (dma_addr_t) __virt_to_phys(alloc_ptr); alloc_ptr += (ch_buf_cnt[ch][0] * sizeof(cppi_rcb)); cppi->tx_ctl[ch].TxNumBuffers = ch_buf_cnt[ch][1]; cppi->tx_ctl[ch].TcbStart = alloc_ptr; cppi->tx_ctl[ch].tcb_start_dma_addr = (dma_addr_t) __virt_to_phys(alloc_ptr); alloc_ptr += (ch_buf_cnt[ch][1] * sizeof(cppi_tcb)); cppi_log_event3("[cppi] alloc bufs: ch dmarcb dmatcb", ch, cppi->rx_ctl[ch].rcb_start_dma_addr, cppi->tx_ctl[ch].tcb_start_dma_addr); // set up receive buffer if (ch_buf_cnt[ch][0]) { dma_recv_buf[ch] = (ch == 0) ? ch0_rx_buf : ((ch == 1) ? ch1_rx_buf : 0); cppi_log_event3("[cppi] Alloc fake DMA buf ch", ch, (uint32_t) dma_recv_buf[ch], (uint32_t) __virt_to_phys(dma_recv_buf[ch])); } else { dma_recv_buf[ch] = 0; } } } void tnetv_cppi_cleanup(struct cppi_info *cppi) { cppi_log_event0("wipe cppi mem"); // wipe cppi memory memset(cppi, 0, sizeof(*cppi)); }