summaryrefslogtreecommitdiffstats
path: root/src/drivers/net/sfc/efx_hunt.c
diff options
context:
space:
mode:
authorMartin Habets2017-04-07 11:46:15 +0200
committerMichael Brown2017-04-10 17:17:08 +0200
commitf3788fa8376b449965c464ee0200f13e68045035 (patch)
tree857beff224b0f5ca1be762ca3b1ae6732be09c33 /src/drivers/net/sfc/efx_hunt.c
parent[libc] Add stdbool.h standard header (diff)
downloadipxe-f3788fa8376b449965c464ee0200f13e68045035.tar.gz
ipxe-f3788fa8376b449965c464ee0200f13e68045035.tar.xz
ipxe-f3788fa8376b449965c464ee0200f13e68045035.zip
[sfc] Add driver for Solarflare SFC8XXX adapters
Signed-off-by: Martin Habets <mhabets@solarflare.com> Modified-by: Michael Brown <mcb30@ipxe.org> Signed-off-by: Michael Brown <mcb30@ipxe.org>
Diffstat (limited to 'src/drivers/net/sfc/efx_hunt.c')
-rw-r--r--src/drivers/net/sfc/efx_hunt.c510
1 files changed, 510 insertions, 0 deletions
diff --git a/src/drivers/net/sfc/efx_hunt.c b/src/drivers/net/sfc/efx_hunt.c
new file mode 100644
index 00000000..07dd7dfe
--- /dev/null
+++ b/src/drivers/net/sfc/efx_hunt.c
@@ -0,0 +1,510 @@
+/**************************************************************************
+ *
+ * Driver datapath for Solarflare network cards
+ *
+ * Written by Shradha Shah <sshah@solarflare.com>
+ *
+ * Copyright 2012-2017 Solarflare Communications Inc.
+ *
+ * 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 any later version.
+ *
+ * You can also choose to distribute this program under the terms of
+ * the Unmodified Binary Distribution Licence (as given in the file
+ * COPYING.UBDL), provided that you have satisfied its requirements.
+ *
+ ***************************************************************************/
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <assert.h>
+#include <byteswap.h>
+#include <ipxe/io.h>
+#include <ipxe/pci.h>
+#include <ipxe/malloc.h>
+#include <ipxe/iobuf.h>
+#include <ipxe/netdevice.h>
+#include "efx_hunt.h"
+#include "efx_bitfield.h"
+#include "ef10_regs.h"
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+void efx_hunt_free_special_buffer(void *buf, int bytes)
+{
+ free_dma(buf, bytes);
+}
+
+static void *efx_hunt_alloc_special_buffer(int bytes,
+ struct efx_special_buffer *entry)
+{
+ void *buffer;
+ dma_addr_t dma_addr;
+
+ /* Allocate the buffer, aligned on a buffer address boundary. This
+ * buffer will be passed into an MC_CMD_INIT_*Q command to setup the
+ * appropriate type of queue via MCDI.
+ */
+ buffer = malloc_dma(bytes, EFX_BUF_ALIGN);
+ if (!buffer)
+ return NULL;
+
+ entry->dma_addr = dma_addr = virt_to_bus(buffer);
+ assert((dma_addr & (EFX_BUF_ALIGN - 1)) == 0);
+
+ /* Buffer table entries aren't allocated, so set id to zero */
+ entry->id = 0;
+ DBGP("Allocated 0x%x bytes at %p\n", bytes, buffer);
+
+ return buffer;
+}
+
+/*******************************************************************************
+ *
+ *
+ * TX
+ *
+ *
+ ******************************************************************************/
+static void
+efx_hunt_build_tx_desc(efx_tx_desc_t *txd, struct io_buffer *iob)
+{
+ dma_addr_t dma_addr;
+
+ dma_addr = virt_to_bus(iob->data);
+
+ EFX_POPULATE_QWORD_4(*txd,
+ ESF_DZ_TX_KER_TYPE, 0,
+ ESF_DZ_TX_KER_CONT, 0,
+ ESF_DZ_TX_KER_BYTE_CNT, iob_len(iob),
+ ESF_DZ_TX_KER_BUF_ADDR, dma_addr);
+}
+
+static void
+efx_hunt_notify_tx_desc(struct efx_nic *efx)
+{
+ struct efx_tx_queue *txq = &efx->txq;
+ int ptr = txq->write_ptr & EFX_TXD_MASK;
+ efx_dword_t reg;
+
+ EFX_POPULATE_DWORD_1(reg, ERF_DZ_TX_DESC_WPTR_DWORD, ptr);
+ efx_writel_page(efx, &reg, 0, ER_DZ_TX_DESC_UPD_DWORD);
+}
+
+int
+efx_hunt_transmit(struct net_device *netdev, struct io_buffer *iob)
+{
+ struct efx_nic *efx = netdev_priv(netdev);
+ struct efx_tx_queue *txq = &efx->txq;
+ int fill_level, space;
+ efx_tx_desc_t *txd;
+ int buf_id;
+
+ fill_level = txq->write_ptr - txq->read_ptr;
+ space = EFX_TXD_SIZE - fill_level - 1;
+ if (space < 1)
+ return -ENOBUFS;
+
+ /* Save the iobuffer for later completion */
+ buf_id = txq->write_ptr & EFX_TXD_MASK;
+ assert(txq->buf[buf_id] == NULL);
+ txq->buf[buf_id] = iob;
+
+ DBGCIO(efx, "tx_buf[%d] for iob %p data %p len %zd\n",
+ buf_id, iob, iob->data, iob_len(iob));
+
+ /* Form the descriptor, and push it to hardware */
+ txd = txq->ring + buf_id;
+ efx_hunt_build_tx_desc(txd, iob);
+ ++txq->write_ptr;
+ efx_hunt_notify_tx_desc(efx);
+
+ return 0;
+}
+
+static void
+efx_hunt_transmit_done(struct efx_nic *efx, int id)
+{
+ struct efx_tx_queue *txq = &efx->txq;
+ unsigned int read_ptr, stop;
+
+ /* Complete all buffers from read_ptr up to and including id */
+ read_ptr = txq->read_ptr & EFX_TXD_MASK;
+ stop = (id + 1) & EFX_TXD_MASK;
+
+ while (read_ptr != stop) {
+ struct io_buffer *iob = txq->buf[read_ptr];
+
+ assert(iob);
+ /* Complete the tx buffer */
+ if (iob)
+ netdev_tx_complete(efx->netdev, iob);
+ DBGCIO(efx, "tx_buf[%d] for iob %p done\n", read_ptr, iob);
+ txq->buf[read_ptr] = NULL;
+
+ ++txq->read_ptr;
+ read_ptr = txq->read_ptr & EFX_TXD_MASK;
+ }
+}
+
+int efx_hunt_tx_init(struct net_device *netdev, dma_addr_t *dma_addr)
+{
+ struct efx_nic *efx = netdev_priv(netdev);
+ struct efx_tx_queue *txq = &efx->txq;
+ size_t bytes;
+
+ /* Allocate hardware transmit queue */
+ bytes = sizeof(efx_tx_desc_t) * EFX_TXD_SIZE;
+ txq->ring = efx_hunt_alloc_special_buffer(bytes, &txq->entry);
+ if (!txq->ring)
+ return -ENOMEM;
+
+ txq->read_ptr = txq->write_ptr = 0;
+ *dma_addr = txq->entry.dma_addr;
+ return 0;
+}
+
+/*******************************************************************************
+ *
+ *
+ * RX
+ *
+ *
+ ******************************************************************************/
+static void
+efx_hunt_build_rx_desc(efx_rx_desc_t *rxd, struct io_buffer *iob)
+{
+ dma_addr_t dma_addr = virt_to_bus(iob->data);
+
+ EFX_POPULATE_QWORD_2(*rxd,
+ ESF_DZ_RX_KER_BYTE_CNT, EFX_RX_BUF_SIZE,
+ ESF_DZ_RX_KER_BUF_ADDR, dma_addr);
+}
+
+static void
+efx_hunt_notify_rx_desc(struct efx_nic *efx)
+{
+ struct efx_rx_queue *rxq = &efx->rxq;
+ int ptr = rxq->write_ptr & EFX_RXD_MASK;
+ efx_dword_t reg;
+
+ EFX_POPULATE_DWORD_1(reg, ERF_DZ_RX_DESC_WPTR, ptr);
+ efx_writel_page(efx, &reg, 0, ER_DZ_RX_DESC_UPD);
+}
+
+static void
+efx_hunt_rxq_fill(struct efx_nic *efx)
+{
+ struct efx_rx_queue *rxq = &efx->rxq;
+ int fill_level = rxq->write_ptr - rxq->read_ptr;
+ int space = EFX_NUM_RX_DESC - fill_level - 1;
+ int pushed = 0;
+
+ while (space) {
+ int buf_id = rxq->write_ptr & (EFX_NUM_RX_DESC - 1);
+ int desc_id = rxq->write_ptr & EFX_RXD_MASK;
+ struct io_buffer *iob;
+ efx_rx_desc_t *rxd;
+
+ assert(rxq->buf[buf_id] == NULL);
+ iob = alloc_iob(EFX_RX_BUF_SIZE);
+ if (!iob)
+ break;
+
+ DBGCP(efx, "pushing rx_buf[%d] iob %p data %p\n",
+ buf_id, iob, iob->data);
+
+ rxq->buf[buf_id] = iob;
+ rxd = rxq->ring + desc_id;
+ efx_hunt_build_rx_desc(rxd, iob);
+ ++rxq->write_ptr;
+ ++pushed;
+ --space;
+ }
+
+ /* Push the ptr to hardware */
+ if (pushed > 0) {
+ efx_hunt_notify_rx_desc(efx);
+
+ DBGCP(efx, "pushed %d rx buffers to fill level %d\n",
+ pushed, rxq->write_ptr - rxq->read_ptr);
+ }
+}
+
+static void
+efx_hunt_receive(struct efx_nic *efx, unsigned int id, int len, int drop)
+{
+ struct efx_rx_queue *rxq = &efx->rxq;
+ unsigned int read_ptr = rxq->read_ptr & EFX_RXD_MASK;
+ unsigned int buf_ptr = rxq->read_ptr & EFX_NUM_RX_DESC_MASK;
+ struct io_buffer *iob;
+
+ /* id is the lower 4 bits of the desc index + 1 in huntington*/
+ /* hence anding with 15 */
+ assert((id & 15) == ((read_ptr + (len != 0)) & 15));
+
+ /* Pop this rx buffer out of the software ring */
+ iob = rxq->buf[buf_ptr];
+ rxq->buf[buf_ptr] = NULL;
+
+ DBGCIO(efx, "popping rx_buf[%d] iob %p data %p with %d bytes %s %x\n",
+ read_ptr, iob, iob->data, len, drop ? "bad" : "ok", drop);
+
+ /* Pass the packet up if required */
+ if (drop)
+ netdev_rx_err(efx->netdev, iob, EBADMSG);
+ else {
+ iob_put(iob, len);
+ iob_pull(iob, efx->rx_prefix_size);
+ netdev_rx(efx->netdev, iob);
+ }
+
+ ++rxq->read_ptr;
+}
+
+int efx_hunt_rx_init(struct net_device *netdev, dma_addr_t *dma_addr)
+{
+ struct efx_nic *efx = netdev_priv(netdev);
+ struct efx_rx_queue *rxq = &efx->rxq;
+ size_t bytes;
+
+ /* Allocate hardware receive queue */
+ bytes = sizeof(efx_rx_desc_t) * EFX_RXD_SIZE;
+ rxq->ring = efx_hunt_alloc_special_buffer(bytes, &rxq->entry);
+ if (rxq->ring == NULL)
+ return -ENOMEM;
+
+ rxq->read_ptr = rxq->write_ptr = 0;
+ *dma_addr = rxq->entry.dma_addr;
+ return 0;
+}
+
+/*******************************************************************************
+ *
+ *
+ * Event queues and interrupts
+ *
+ *
+ ******************************************************************************/
+int efx_hunt_ev_init(struct net_device *netdev, dma_addr_t *dma_addr)
+{
+ struct efx_nic *efx = netdev_priv(netdev);
+ struct efx_ev_queue *evq = &efx->evq;
+ size_t bytes;
+
+ /* Allocate the hardware event queue */
+ bytes = sizeof(efx_event_t) * EFX_EVQ_SIZE;
+ evq->ring = efx_hunt_alloc_special_buffer(bytes, &evq->entry);
+ if (evq->ring == NULL)
+ return -ENOMEM;
+
+ memset(evq->ring, 0xff, bytes);
+ evq->read_ptr = 0;
+ *dma_addr = evq->entry.dma_addr;
+ return 0;
+}
+
+static void
+efx_hunt_clear_interrupts(struct efx_nic *efx)
+{
+ efx_dword_t reg;
+ /* read the ISR */
+ efx_readl(efx, &reg, ER_DZ_BIU_INT_ISR);
+}
+
+/**
+ * See if an event is present
+ *
+ * @v event EFX event structure
+ * @ret True An event is pending
+ * @ret False No event is pending
+ *
+ * We check both the high and low dword of the event for all ones. We
+ * wrote all ones when we cleared the event, and no valid event can
+ * have all ones in either its high or low dwords. This approach is
+ * robust against reordering.
+ *
+ * Note that using a single 64-bit comparison is incorrect; even
+ * though the CPU read will be atomic, the DMA write may not be.
+ */
+static inline int
+efx_hunt_event_present(efx_event_t *event)
+{
+ return (!(EFX_DWORD_IS_ALL_ONES(event->dword[0]) |
+ EFX_DWORD_IS_ALL_ONES(event->dword[1])));
+}
+
+static void
+efx_hunt_evq_read_ack(struct efx_nic *efx)
+{
+ struct efx_ev_queue *evq = &efx->evq;
+ efx_dword_t reg;
+
+ if (efx->workaround_35388) {
+ EFX_POPULATE_DWORD_2(reg, ERF_DD_EVQ_IND_RPTR_FLAGS,
+ EFE_DD_EVQ_IND_RPTR_FLAGS_HIGH,
+ ERF_DD_EVQ_IND_RPTR,
+ evq->read_ptr >> ERF_DD_EVQ_IND_RPTR_WIDTH);
+ efx_writel_page(efx, &reg, 0, ER_DD_EVQ_INDIRECT);
+ EFX_POPULATE_DWORD_2(reg, ERF_DD_EVQ_IND_RPTR_FLAGS,
+ EFE_DD_EVQ_IND_RPTR_FLAGS_LOW,
+ ERF_DD_EVQ_IND_RPTR, evq->read_ptr &
+ ((1 << ERF_DD_EVQ_IND_RPTR_WIDTH) - 1));
+ efx_writel_page(efx, &reg, 0, ER_DD_EVQ_INDIRECT);
+ } else {
+ EFX_POPULATE_DWORD_1(reg, ERF_DZ_EVQ_RPTR, evq->read_ptr);
+ efx_writel_table(efx, &reg, 0, ER_DZ_EVQ_RPTR);
+ }
+}
+
+static unsigned int
+efx_hunt_handle_event(struct efx_nic *efx, efx_event_t *evt)
+{
+ struct efx_rx_queue *rxq = &efx->rxq;
+ int ev_code, desc_ptr, len;
+ int next_ptr_lbits, packet_drop;
+ int rx_cont;
+
+ /* Decode event */
+ ev_code = EFX_QWORD_FIELD(*evt, ESF_DZ_EV_CODE);
+
+ switch (ev_code) {
+ case ESE_DZ_EV_CODE_TX_EV:
+ desc_ptr = EFX_QWORD_FIELD(*evt, ESF_DZ_TX_DESCR_INDX);
+ efx_hunt_transmit_done(efx, desc_ptr);
+ break;
+
+ case ESE_DZ_EV_CODE_RX_EV:
+ len = EFX_QWORD_FIELD(*evt, ESF_DZ_RX_BYTES);
+ next_ptr_lbits = EFX_QWORD_FIELD(*evt, ESF_DZ_RX_DSC_PTR_LBITS);
+ rx_cont = EFX_QWORD_FIELD(*evt, ESF_DZ_RX_CONT);
+
+ /* We don't expect to receive scattered packets, so drop the
+ * packet if RX_CONT is set on the current or previous event, or
+ * if len is zero.
+ */
+ packet_drop = (len == 0) | (rx_cont << 1) |
+ (rxq->rx_cont_prev << 2);
+ efx_hunt_receive(efx, next_ptr_lbits, len, packet_drop);
+ rxq->rx_cont_prev = rx_cont;
+ return 1;
+
+ default:
+ DBGCP(efx, "Unknown event type %d\n", ev_code);
+ break;
+ }
+ return 0;
+}
+
+void efx_hunt_poll(struct net_device *netdev)
+{
+ struct efx_nic *efx = netdev_priv(netdev);
+ struct efx_ev_queue *evq = &efx->evq;
+ efx_event_t *evt;
+ int budget = 10;
+
+ /* Read the event queue by directly looking for events
+ * (we don't even bother to read the eventq write ptr)
+ */
+ evt = evq->ring + evq->read_ptr;
+ while (efx_hunt_event_present(evt) && (budget > 0)) {
+ DBGCP(efx, "Event at index 0x%x address %p is "
+ EFX_QWORD_FMT "\n", evq->read_ptr,
+ evt, EFX_QWORD_VAL(*evt));
+
+ budget -= efx_hunt_handle_event(efx, evt);
+
+ /* Clear the event */
+ EFX_SET_QWORD(*evt);
+
+ /* Move to the next event. We don't ack the event
+ * queue until the end
+ */
+ evq->read_ptr = ((evq->read_ptr + 1) & EFX_EVQ_MASK);
+ evt = evq->ring + evq->read_ptr;
+ }
+
+ /* Push more rx buffers if needed */
+ efx_hunt_rxq_fill(efx);
+
+ /* Clear any pending interrupts */
+ efx_hunt_clear_interrupts(efx);
+
+ /* Ack the event queue if interrupts are enabled */
+ if (efx->int_en)
+ efx_hunt_evq_read_ack(efx);
+}
+
+void efx_hunt_irq(struct net_device *netdev, int enable)
+{
+ struct efx_nic *efx = netdev_priv(netdev);
+
+ efx->int_en = enable;
+
+ /* If interrupts are enabled, prime the event queue. Otherwise ack any
+ * pending interrupts
+ */
+ if (enable)
+ efx_hunt_evq_read_ack(efx);
+ else if (efx->netdev->state & NETDEV_OPEN)
+ efx_hunt_clear_interrupts(efx);
+}
+
+/*******************************************************************************
+ *
+ *
+ * Initialization and Close
+ *
+ *
+ ******************************************************************************/
+int efx_hunt_open(struct net_device *netdev)
+{
+ struct efx_nic *efx = netdev_priv(netdev);
+ efx_dword_t cmd;
+
+ /* Set interrupt moderation to 0*/
+ EFX_POPULATE_DWORD_2(cmd,
+ ERF_DZ_TC_TIMER_MODE, 0,
+ ERF_DZ_TC_TIMER_VAL, 0);
+ efx_writel_page(efx, &cmd, 0, ER_DZ_EVQ_TMR);
+
+ /* Ack the eventq */
+ if (efx->int_en)
+ efx_hunt_evq_read_ack(efx);
+
+ /* Push receive buffers */
+ efx_hunt_rxq_fill(efx);
+
+ return 0;
+}
+
+void efx_hunt_close(struct net_device *netdev)
+{
+ struct efx_nic *efx = netdev_priv(netdev);
+ struct efx_rx_queue *rxq = &efx->rxq;
+ struct efx_tx_queue *txq = &efx->txq;
+ int i;
+
+ /* Complete outstanding descriptors */
+ for (i = 0; i < EFX_NUM_RX_DESC; i++) {
+ if (rxq->buf[i]) {
+ free_iob(rxq->buf[i]);
+ rxq->buf[i] = NULL;
+ }
+ }
+
+ for (i = 0; i < EFX_TXD_SIZE; i++) {
+ if (txq->buf[i]) {
+ netdev_tx_complete(efx->netdev, txq->buf[i]);
+ txq->buf[i] = NULL;
+ }
+ }
+
+ /* Clear interrupts */
+ efx_hunt_clear_interrupts(efx);
+}