| /* |
| * Copyright (c) 2014 Christopher Anderson |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining |
| * a copy of this software and associated documentation files |
| * (the "Software"), to deal in the Software without restriction, |
| * including without limitation the rights to use, copy, modify, merge, |
| * publish, distribute, sublicense, and/or sell copies of the Software, |
| * and to permit persons to whom the Software is furnished to do so, |
| * subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be |
| * included in all copies or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
| * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
| * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY |
| * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, |
| * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE |
| * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
| */ |
| #include <assert.h> |
| #include <lib/console.h> |
| #include <debug.h> |
| #include <list.h> |
| #include <err.h> |
| #include <errno.h> |
| #include <reg.h> |
| #include <endian.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <malloc.h> |
| #include <trace.h> |
| #include <bits.h> |
| #include <pow2.h> |
| #include <sys/types.h> |
| #include <lib/cbuf.h> |
| #include <kernel/timer.h> |
| #include <kernel/thread.h> |
| #include <kernel/vm.h> |
| #include <kernel/spinlock.h> |
| #include <kernel/debug.h> |
| #include <platform/interrupts.h> |
| #include <platform/debug.h> |
| #include <platform/gem.h> |
| #include <platform.h> |
| #include <kernel/event.h> |
| #include <kernel/semaphore.h> |
| |
| #include <lib/pktbuf.h> |
| #include <lib/pool.h> |
| |
| #define LOCAL_TRACE 0 |
| |
| /* Allow targets to override these values */ |
| #ifndef GEM_RX_DESC_CNT |
| #define GEM_RX_DESC_CNT 32 |
| #endif |
| |
| #ifndef GEM_TX_DESC_CNT |
| #define GEM_TX_DESC_CNT 32 |
| #endif |
| |
| #ifndef GEM_RX_BUF_SIZE |
| #define GEM_RX_BUF_SIZE 1536 |
| #endif |
| |
| #ifndef GEM_TX_BUF_SIZE |
| #define GEM_TX_BUF_SIZE 1536 |
| #endif |
| |
| pool_t rx_buf_pool; |
| static spin_lock_t lock = SPIN_LOCK_INITIAL_VALUE; |
| |
| struct gem_desc { |
| uint32_t addr; |
| uint32_t ctrl; |
| }; |
| |
| /* Quick overview: |
| * RX: |
| * rx_tbl contains rx descriptors. A pktbuf is allocated for each of these and a descriptor |
| * entry in the table points to a buffer in the pktbuf. rx_tbl[X]'s pktbuf is stored in rx_pbufs[X] |
| * |
| * TX: |
| * The current position to write new tx descriptors to is maintained by gem.tx_head. As frames are |
| * queued in tx_tbl their pktbufs are stored in the list queued_pbufs. As frame transmission is |
| * completed these pktbufs are released back to the pool by the interrupt handler for TX_COMPLETE |
| */ |
| struct gem_descs { |
| struct gem_desc rx_tbl[GEM_RX_DESC_CNT]; |
| struct gem_desc tx_tbl[GEM_TX_DESC_CNT]; |
| }; |
| |
| struct gem_state { |
| volatile struct gem_regs *regs; |
| |
| struct gem_descs *descs; |
| paddr_t descs_phys; |
| |
| unsigned int tx_head; |
| unsigned int tx_tail; |
| unsigned int tx_count; |
| struct list_node tx_queue; |
| struct list_node queued_pbufs; |
| |
| gem_cb_t rx_callback; |
| event_t rx_pending; |
| event_t tx_complete; |
| bool debug_rx; |
| pktbuf_t *rx_pbufs[GEM_RX_DESC_CNT]; |
| }; |
| |
| struct gem_state gem; |
| |
| static void debug_rx_handler(pktbuf_t *p) |
| { |
| static uint32_t pkt = 0; |
| |
| printf("[%10u] packet %u, %zu bytes:\n", (uint32_t)current_time(), ++pkt, p->dlen); |
| hexdump8(p->data, p->dlen); |
| putchar('\n'); |
| } |
| |
| static int free_completed_pbuf_frames(void) { |
| int ret = 0; |
| |
| gem.regs->tx_status = gem.regs->tx_status; |
| |
| while (gem.tx_count > 0 && |
| (gem.descs->tx_tbl[gem.tx_tail].ctrl & TX_DESC_USED)) { |
| |
| bool eof; |
| do { |
| pktbuf_t *p = list_remove_head_type(&gem.queued_pbufs, pktbuf_t, list); |
| DEBUG_ASSERT(p); |
| eof = p->flags & PKTBUF_FLAG_EOF; |
| ret += pktbuf_free(p, false); |
| } while (!eof); |
| |
| gem.tx_tail = (gem.tx_tail + 1) % GEM_TX_DESC_CNT; |
| gem.tx_count--; |
| } |
| |
| return ret; |
| } |
| |
| void queue_pkts_in_tx_tbl(void) { |
| pktbuf_t *p; |
| unsigned int cur_pos; |
| |
| if (list_is_empty(&gem.tx_queue)) { |
| return; |
| } |
| |
| // XXX handle multi part buffers |
| |
| /* Queue packets in the descriptor table until we're either out of space in the table |
| * or out of packets in our tx queue. Any packets left will remain in the list and be |
| * processed the next time available */ |
| while (gem.tx_count < GEM_TX_DESC_CNT && |
| ((p = list_remove_head_type(&gem.tx_queue, pktbuf_t, list)) != NULL)) { |
| cur_pos = gem.tx_head; |
| |
| uint32_t addr = pktbuf_data_phys(p); |
| uint32_t ctrl = gem.descs->tx_tbl[cur_pos].ctrl & TX_DESC_WRAP; /* protect the wrap bit */ |
| ctrl |= TX_BUF_LEN(p->dlen); |
| |
| DEBUG_ASSERT(p->flags & PKTBUF_FLAG_EOF); // a multi part buffer would have caused a race condition w/hardware |
| if (p->flags & PKTBUF_FLAG_EOF) { |
| ctrl |= TX_LAST_BUF; |
| } |
| |
| /* fill in the descriptor, control word last (in case hardware is racing us) */ |
| gem.descs->tx_tbl[cur_pos].addr = addr; |
| gem.descs->tx_tbl[cur_pos].ctrl = ctrl; |
| |
| gem.tx_head = (gem.tx_head + 1) % GEM_TX_DESC_CNT; |
| gem.tx_count++; |
| list_add_tail(&gem.queued_pbufs, &p->list); |
| } |
| |
| DMB; |
| gem.regs->net_ctrl |= NET_CTRL_START_TX; |
| } |
| |
| int gem_send_raw_pkt(struct pktbuf *p) |
| { |
| status_t ret = NO_ERROR; |
| |
| if (!p || !p->dlen) { |
| ret = -1; |
| goto err; |
| } |
| |
| /* make sure the output buffer is fully written to memory before |
| * placing on the outgoing list. */ |
| |
| // XXX handle multi part buffers |
| arch_clean_cache_range((vaddr_t)p->data, p->dlen); |
| |
| spin_lock_saved_state_t irqstate; |
| spin_lock_irqsave(&lock, irqstate); |
| list_add_tail(&gem.tx_queue, &p->list); |
| queue_pkts_in_tx_tbl(); |
| spin_unlock_irqrestore(&lock, irqstate); |
| |
| err: |
| return ret; |
| } |
| |
| |
| enum handler_return gem_int_handler(void *arg) { |
| uint32_t intr_status; |
| bool resched = false; |
| |
| intr_status = gem.regs->intr_status; |
| |
| spin_lock(&lock); |
| |
| while (intr_status) { |
| // clear any pending status |
| gem.regs->intr_status = intr_status; |
| |
| // Received an RX complete |
| if (intr_status & INTR_RX_COMPLETE) { |
| event_signal(&gem.rx_pending, false); |
| |
| gem.regs->rx_status |= INTR_RX_COMPLETE; |
| |
| resched = true; |
| } |
| |
| if (intr_status & INTR_RX_USED_READ) { |
| |
| for (int i = 0; i < GEM_RX_DESC_CNT; i++) { |
| gem.descs->rx_tbl[i].addr &= ~RX_DESC_USED; |
| } |
| |
| gem.regs->rx_status &= ~RX_STATUS_BUFFER_NOT_AVAIL; |
| gem.regs->net_ctrl &= ~NET_CTRL_RX_EN; |
| gem.regs->net_ctrl |= NET_CTRL_RX_EN; |
| printf("GEM overflow, dumping pending packets\n"); |
| } |
| |
| if (intr_status & INTR_TX_CORRUPT) { |
| printf("tx ahb error!\n"); |
| if (free_completed_pbuf_frames() > 0) { |
| resched = true; |
| } |
| } |
| |
| /* A frame has been completed so we can clean up ownership of its buffers */ |
| if (intr_status & INTR_TX_COMPLETE) { |
| if (free_completed_pbuf_frames() > 0) { |
| resched = true; |
| } |
| } |
| |
| /* The controller has processed packets until it hit a buffer owned by the driver */ |
| if (intr_status & INTR_TX_USED_READ) { |
| queue_pkts_in_tx_tbl(); |
| gem.regs->tx_status |= TX_STATUS_USED_READ; |
| } |
| |
| /* see if we have any more */ |
| intr_status = gem.regs->intr_status; |
| } |
| |
| spin_unlock(&lock); |
| |
| return (resched) ? INT_RESCHEDULE : INT_NO_RESCHEDULE; |
| } |
| |
| static bool wait_for_phy_idle(void) |
| { |
| int iters = 1000; |
| while (iters && !(gem.regs->net_status & NET_STATUS_PHY_MGMT_IDLE)) { |
| iters--; |
| } |
| |
| if (iters == 0) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static bool gem_phy_init(void) { |
| return wait_for_phy_idle(); |
| } |
| |
| static status_t gem_cfg_buffer_descs(void) |
| { |
| void *rx_buf_vaddr; |
| status_t ret; |
| |
| |
| if ((ret = vmm_alloc_contiguous(vmm_get_kernel_aspace(), "gem_rx_bufs", |
| GEM_RX_DESC_CNT * GEM_RX_BUF_SIZE, (void **) &rx_buf_vaddr, 0, 0, |
| ARCH_MMU_FLAG_CACHED)) < 0) { |
| return ret; |
| } |
| |
| /* Take pktbufs from the allocated target pool and assign them to the gem RX |
| * descriptor table */ |
| pool_init(&rx_buf_pool, GEM_RX_BUF_SIZE, CACHE_LINE, GEM_RX_DESC_CNT, rx_buf_vaddr); |
| for (unsigned int n = 0; n < GEM_RX_DESC_CNT; n++) { |
| void *b = pool_alloc(&rx_buf_pool); |
| pktbuf_t *p = pktbuf_alloc_empty(); |
| if (!p || !b) { |
| return -1; |
| } |
| |
| pktbuf_add_buffer(p, b, GEM_RX_BUF_SIZE, 0, PKTBUF_FLAG_CACHED, NULL, NULL); |
| gem.rx_pbufs[n] = p; |
| gem.descs->rx_tbl[n].addr = (uintptr_t) p->phys_base; |
| gem.descs->rx_tbl[n].ctrl = 0; |
| } |
| |
| /* Claim ownership of TX descriptors for the driver */ |
| for (unsigned i = 0; i < GEM_TX_DESC_CNT; i++) { |
| gem.descs->tx_tbl[i].addr = 0; |
| gem.descs->tx_tbl[i].ctrl = TX_DESC_USED; |
| } |
| |
| /* Both set of descriptors need wrap bits set at the end of their tables*/ |
| gem.descs->rx_tbl[GEM_RX_DESC_CNT-1].addr |= RX_DESC_WRAP; |
| gem.descs->tx_tbl[GEM_TX_DESC_CNT-1].ctrl |= TX_DESC_WRAP; |
| |
| /* Point the controller at the offset into state's physical location for RX descs */ |
| gem.regs->rx_qbar = ((uintptr_t)&gem.descs->rx_tbl[0] - (uintptr_t)gem.descs) + gem.descs_phys; |
| gem.regs->tx_qbar = ((uintptr_t)&gem.descs->tx_tbl[0] - (uintptr_t)gem.descs) + gem.descs_phys; |
| |
| return NO_ERROR; |
| } |
| |
| static void gem_cfg_ints(void) |
| { |
| uint32_t gem_base = (uintptr_t)gem.regs; |
| |
| if (gem_base == GEM0_BASE) { |
| register_int_handler(ETH0_INT, gem_int_handler, NULL); |
| unmask_interrupt(ETH0_INT); |
| } else if (gem_base == GEM1_BASE) { |
| register_int_handler(ETH1_INT, gem_int_handler, NULL); |
| unmask_interrupt(ETH1_INT); |
| } else { |
| printf("Illegal gem periph base address 0x%08X!\n", gem_base); |
| return; |
| } |
| |
| /* Enable all interrupts */ |
| gem.regs->intr_en = INTR_RX_COMPLETE | INTR_TX_COMPLETE | INTR_HRESP_NOT_OK | INTR_MGMT_SENT | |
| INTR_RX_USED_READ | INTR_TX_CORRUPT | INTR_TX_USED_READ | INTR_RX_OVERRUN; |
| } |
| |
| int gem_rx_thread(void *arg) |
| { |
| pktbuf_t *p; |
| int bp = 0; |
| |
| while (1) { |
| event_wait(&gem.rx_pending); |
| |
| for (;;) { |
| if (gem.descs->rx_tbl[bp].addr & RX_DESC_USED) { |
| uint32_t ctrl = gem.descs->rx_tbl[bp].ctrl; |
| |
| p = gem.rx_pbufs[bp]; |
| p->dlen = RX_BUF_LEN(ctrl); |
| p->data = p->buffer + 2; |
| |
| /* copy the checksum offloading bits */ |
| p->flags = 0; |
| p->flags |= (BITS_SHIFT(ctrl, 23, 22) != 0) ? PKTBUF_FLAG_CKSUM_IP_GOOD : 0; |
| p->flags |= (BITS_SHIFT(ctrl, 23, 22) == 1) ? PKTBUF_FLAG_CKSUM_UDP_GOOD : 0; |
| p->flags |= (BITS_SHIFT(ctrl, 23, 22) == 2) ? PKTBUF_FLAG_CKSUM_TCP_GOOD : 0; |
| |
| /* invalidate any stale cache lines on the receive buffer to ensure |
| * the cpu has a fresh copy of incomding data. */ |
| arch_invalidate_cache_range((vaddr_t)p->data, p->dlen); |
| |
| if (unlikely(gem.debug_rx)) { |
| debug_rx_handler(p); |
| } |
| |
| if (likely(gem.rx_callback)) { |
| gem.rx_callback(p); |
| } |
| |
| /* make sure all dirty data is flushed out of the buffer before |
| * putting into the receive queue */ |
| arch_clean_invalidate_cache_range((vaddr_t)p->buffer, PKTBUF_SIZE); |
| |
| gem.descs->rx_tbl[bp].addr &= ~RX_DESC_USED; |
| gem.descs->rx_tbl[bp].ctrl = 0; |
| bp = (bp + 1) % GEM_RX_DESC_CNT; |
| } else { |
| break; |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| |
| int gem_stat_thread(void *arg) { |
| volatile bool *run = ((bool *)arg); |
| static uint32_t frames_rx = 0, frames_tx = 0; |
| |
| while (*run) { |
| frames_tx += gem.regs->frames_tx; |
| frames_rx += gem.regs->frames_rx; |
| printf("GEM tx_head %u, tx_tail %u, tx_count %u, tx_frames %u, rx_frames %u\n", |
| gem.tx_head, gem.tx_tail, gem.tx_count, frames_tx, frames_rx); |
| thread_sleep(1000); |
| } |
| |
| return 0; |
| } |
| |
| void gem_deinit(uintptr_t base) |
| { |
| /* reset the gem peripheral */ |
| uint32_t rst_mask; |
| if (base == GEM0_BASE) { |
| rst_mask = (1<<6) | (1<<4) | (1<<0); |
| } else { |
| rst_mask = (1<<7) | (1<<5) | (1<<1); |
| } |
| SLCR->GEM_RST_CTRL |= rst_mask; |
| spin(1); |
| SLCR->GEM_RST_CTRL &= ~rst_mask; |
| |
| |
| /* Clear Network control / status registers */ |
| gem.regs->net_ctrl |= NET_CTRL_STATCLR; |
| gem.regs->rx_status = 0x0F; |
| gem.regs->tx_status = 0xFF; |
| /* Disable interrupts */ |
| gem.regs->intr_dis = 0x7FFFEFF; |
| |
| /* Empty out the buffer queues */ |
| gem.regs->rx_qbar = 0; |
| gem.regs->tx_qbar = 0; |
| } |
| |
| status_t gem_init(uintptr_t gem_base) |
| { |
| status_t ret; |
| uint32_t reg_val; |
| thread_t *rx_thread; |
| void *descs_vaddr; |
| paddr_t descs_paddr; |
| |
| DEBUG_ASSERT(gem_base == GEM0_BASE || gem_base == GEM1_BASE); |
| |
| /* Data structure init */ |
| event_init(&gem.tx_complete, false, EVENT_FLAG_AUTOUNSIGNAL); |
| event_init(&gem.rx_pending, false, EVENT_FLAG_AUTOUNSIGNAL); |
| list_initialize(&gem.queued_pbufs); |
| list_initialize(&gem.tx_queue); |
| |
| /* allocate a block of uncached contiguous memory for the peripheral descriptors */ |
| if ((ret = vmm_alloc_contiguous(vmm_get_kernel_aspace(), "gem_desc", |
| sizeof(*gem.descs), &descs_vaddr, 0, 0, ARCH_MMU_FLAG_UNCACHED_DEVICE)) < 0) { |
| return ret; |
| } |
| descs_paddr = kvaddr_to_paddr((void *)descs_vaddr); |
| |
| /* tx/rx descriptor tables and memory mapped registers */ |
| gem.descs = (void *)descs_vaddr; |
| gem.descs_phys = descs_paddr; |
| gem.regs = (struct gem_regs *)gem_base; |
| |
| /* rx background thread */ |
| rx_thread = thread_create("gem_rx", gem_rx_thread, NULL, HIGH_PRIORITY, DEFAULT_STACK_SIZE); |
| thread_resume(rx_thread); |
| |
| /* Bring whatever existing configuration is up down so we can do it cleanly */ |
| gem_deinit(gem_base); |
| gem_cfg_buffer_descs(); |
| |
| /* Self explanatory configuration for the gige */ |
| reg_val = NET_CFG_FULL_DUPLEX; |
| reg_val |= NET_CFG_GIGE_EN; |
| reg_val |= NET_CFG_SPEED_100; |
| reg_val |= NET_CFG_RX_CHKSUM_OFFLD_EN; |
| reg_val |= NET_CFG_FCS_REMOVE; |
| reg_val |= NET_CFG_MDC_CLK_DIV(0x7); |
| reg_val |= NET_CFG_RX_BUF_OFFSET(2); |
| gem.regs->net_cfg = reg_val; |
| |
| /* Set DMA to 1600 byte rx buffer, 8KB addr space for rx, 4KB addr space for tx, |
| * hw checksumming, little endian, and use INCR16 ahb bursts |
| */ |
| reg_val = DMA_CFG_AHB_MEM_RX_BUF_SIZE(0x19); |
| reg_val |= DMA_CFG_RX_PKTBUF_MEMSZ_SEL(0x3); |
| reg_val |= DMA_CFG_TX_PKTBUF_MEMSZ_SEL; |
| reg_val |= DMA_CFG_CSUM_GEN_OFFLOAD_EN; |
| reg_val |= DMA_CFG_AHB_FIXED_BURST_LEN(0x10); |
| gem.regs->dma_cfg = reg_val; |
| |
| /* Enable VREF from GPIOB */ |
| SLCR_REG(GPIOB_CTRL) = 0x1; |
| |
| ret = gem_phy_init(); |
| if (!ret) { |
| printf("Phy not idle, aborting!\n"); |
| return ret; |
| } |
| |
| gem_cfg_ints(); |
| |
| reg_val = NET_CTRL_MD_EN; |
| reg_val |= NET_CTRL_RX_EN; |
| reg_val |= NET_CTRL_TX_EN; |
| gem.regs->net_ctrl = reg_val; |
| |
| return NO_ERROR; |
| } |
| |
| void gem_disable(void) |
| { |
| /* disable all the interrupts */ |
| gem.regs->intr_en = 0; |
| mask_interrupt(ETH0_INT); |
| |
| /* stop tx and rx */ |
| gem.regs->net_ctrl = 0; |
| } |
| |
| void gem_set_callback(gem_cb_t rx) |
| { |
| gem.rx_callback = rx; |
| } |
| |
| void gem_set_macaddr(uint8_t mac[6]) { |
| uint32_t en = gem.regs->net_ctrl &= NET_CTRL_RX_EN | NET_CTRL_TX_EN; |
| |
| if (en) { |
| gem.regs->net_ctrl &= ~(en); |
| } |
| |
| /* _top register must be written after _bot register */ |
| gem.regs->spec_addr1_bot = (mac[3] << 24) | (mac[2] << 16) | (mac[1] << 8) | mac[0]; |
| gem.regs->spec_addr1_top = (mac[5] << 8) | mac[4]; |
| |
| if (en) { |
| gem.regs->net_ctrl |= en; |
| } |
| } |
| |
| |
| /* Debug console commands */ |
| static int cmd_gem(int argc, const cmd_args *argv) |
| { |
| static uint32_t frames_rx = 0; |
| static uint32_t frames_tx = 0; |
| static bool run_stats = false; |
| thread_t *stat_thread; |
| |
| if (argc == 1) { |
| printf("gem raw <iter> <length>: Send <iter> raw mac packet for testing\n"); |
| printf("gem rx_debug: toggle RX debug output\n"); |
| printf("gem stats toggle periodic output of driver stats\n"); |
| printf("gem status: print driver status\n"); |
| } else if (strncmp(argv[1].str, "rx_debug", sizeof("rx_debug")) == 0) { |
| pktbuf_t *p; |
| int iter; |
| if (argc < 4) { |
| return 0; |
| } |
| |
| if ((p = pktbuf_alloc()) == NULL) { |
| printf("out of buffers\n"); |
| } |
| |
| iter = argv[2].u; |
| p->dlen = argv[3].u; |
| while (iter--) { |
| memset(p->data, iter, 12); |
| gem_send_raw_pkt(p); |
| } |
| } else if (strncmp(argv[1].str, "status", sizeof("status")) == 0) { |
| uint32_t mac_top = gem.regs->spec_addr1_top; |
| uint32_t mac_bot = gem.regs->spec_addr1_bot; |
| printf("mac addr: %02x:%02x:%02x:%02x:%02x:%02x\n", |
| mac_top >> 8, mac_top & 0xFF, mac_bot >> 24, (mac_bot >> 16) & 0xFF, |
| (mac_bot >> 8) & 0xFF, mac_bot & 0xFF); |
| uint32_t rx_used = 0, tx_used = 0; |
| for (int i = 0; i < GEM_RX_DESC_CNT; i++) { |
| rx_used += !!(gem.descs->rx_tbl[i].addr & RX_DESC_USED); |
| } |
| |
| for (int i = 0; i < GEM_TX_DESC_CNT; i++) { |
| tx_used += !!(gem.descs->tx_tbl[i].ctrl & TX_DESC_USED); |
| } |
| |
| frames_tx += gem.regs->frames_tx; |
| frames_rx += gem.regs->frames_rx; |
| printf("rx usage: %u/%u, tx usage %u/%u\n", |
| rx_used, GEM_RX_DESC_CNT, tx_used, GEM_TX_DESC_CNT); |
| printf("frames rx: %u, frames tx: %u\n", |
| frames_rx, frames_tx); |
| printf("tx:\n"); |
| for (size_t i = 0; i < GEM_TX_DESC_CNT; i++) { |
| uint32_t ctrl = gem.descs->tx_tbl[i].ctrl; |
| uint32_t addr = gem.descs->tx_tbl[i].addr; |
| |
| printf("%3zu 0x%08X 0x%08X: len %u, %s%s%s %s%s\n", |
| i, addr, ctrl, TX_BUF_LEN(ctrl), |
| (ctrl & TX_DESC_USED) ? "driver " : "controller ", |
| (ctrl & TX_DESC_WRAP) ? "wrap " : "", |
| (ctrl & TX_LAST_BUF) ? "eof " : "", |
| (i == gem.tx_head) ? "<-- HEAD " : "", |
| (i == gem.tx_tail) ? "<-- TAIL " : ""); |
| } |
| |
| } else if (strncmp(argv[1].str, "stats", sizeof("stats")) == 0) { |
| run_stats = !run_stats; |
| if (run_stats) { |
| stat_thread = thread_create("gem_stat", |
| gem_stat_thread, &run_stats, LOW_PRIORITY, DEFAULT_STACK_SIZE); |
| thread_resume(stat_thread); |
| } |
| } else if (argv[1].str[0] == 'd') { |
| gem.debug_rx = !gem.debug_rx; |
| } |
| |
| return 0; |
| } |
| |
| STATIC_COMMAND_START |
| STATIC_COMMAND("gem", "ZYNQ GEM commands", &cmd_gem) |
| STATIC_COMMAND_END(gem); |