| /* |
| * Copyright (C) 2008 Michael Brown <mbrown@fensystems.co.uk>. |
| * Copyright (C) 2008 NetXen, 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. |
| * |
| * This program is distributed in the hope that it will be useful, but |
| * WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| * 02110-1301, USA. |
| * |
| * 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. |
| */ |
| |
| FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); |
| |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <errno.h> |
| #include <assert.h> |
| #include <byteswap.h> |
| #include <ipxe/pci.h> |
| #include <ipxe/io.h> |
| #include <ipxe/malloc.h> |
| #include <ipxe/iobuf.h> |
| #include <ipxe/netdevice.h> |
| #include <ipxe/if_ether.h> |
| #include <ipxe/ethernet.h> |
| #include <ipxe/spi.h> |
| #include <ipxe/settings.h> |
| #include "phantom.h" |
| |
| /** |
| * @file |
| * |
| * NetXen Phantom NICs |
| * |
| */ |
| |
| /** Maximum number of ports */ |
| #define PHN_MAX_NUM_PORTS 8 |
| |
| /** Maximum time to wait for command PEG to initialise |
| * |
| * BUGxxxx |
| * |
| * The command PEG will currently report initialisation complete only |
| * when at least one PHY has detected a link (so that the global PHY |
| * clock can be set to 10G/1G as appropriate). This can take a very, |
| * very long time. |
| * |
| * A future firmware revision should decouple PHY initialisation from |
| * firmware initialisation, at which point the command PEG will report |
| * initialisation complete much earlier, and this timeout can be |
| * reduced. |
| */ |
| #define PHN_CMDPEG_INIT_TIMEOUT_SEC 50 |
| |
| /** Maximum time to wait for receive PEG to initialise */ |
| #define PHN_RCVPEG_INIT_TIMEOUT_SEC 2 |
| |
| /** Maximum time to wait for firmware to accept a command */ |
| #define PHN_ISSUE_CMD_TIMEOUT_MS 2000 |
| |
| /** Maximum time to wait for test memory */ |
| #define PHN_TEST_MEM_TIMEOUT_MS 100 |
| |
| /** Maximum time to wait for CLP command to be issued */ |
| #define PHN_CLP_CMD_TIMEOUT_MS 500 |
| |
| /** Link state poll frequency |
| * |
| * The link state will be checked once in every N calls to poll(). |
| */ |
| #define PHN_LINK_POLL_FREQUENCY 4096 |
| |
| /** Number of RX descriptors */ |
| #define PHN_NUM_RDS 32 |
| |
| /** RX maximum fill level. Must be strictly less than PHN_NUM_RDS. */ |
| #define PHN_RDS_MAX_FILL 16 |
| |
| /** RX buffer size */ |
| #define PHN_RX_BUFSIZE ( 32 /* max LL padding added by card */ + \ |
| ETH_FRAME_LEN ) |
| |
| /** Number of RX status descriptors */ |
| #define PHN_NUM_SDS 32 |
| |
| /** Number of TX descriptors */ |
| #define PHN_NUM_CDS 8 |
| |
| /** A Phantom descriptor ring set */ |
| struct phantom_descriptor_rings { |
| /** RX descriptors */ |
| struct phantom_rds rds[PHN_NUM_RDS]; |
| /** RX status descriptors */ |
| struct phantom_sds sds[PHN_NUM_SDS]; |
| /** TX descriptors */ |
| union phantom_cds cds[PHN_NUM_CDS]; |
| /** TX consumer index */ |
| volatile uint32_t cmd_cons; |
| }; |
| |
| /** RX context creation request and response buffers */ |
| struct phantom_create_rx_ctx_rqrsp { |
| struct { |
| struct nx_hostrq_rx_ctx_s rx_ctx; |
| struct nx_hostrq_rds_ring_s rds; |
| struct nx_hostrq_sds_ring_s sds; |
| } __unm_dma_aligned hostrq; |
| struct { |
| struct nx_cardrsp_rx_ctx_s rx_ctx; |
| struct nx_cardrsp_rds_ring_s rds; |
| struct nx_cardrsp_sds_ring_s sds; |
| } __unm_dma_aligned cardrsp; |
| }; |
| |
| /** TX context creation request and response buffers */ |
| struct phantom_create_tx_ctx_rqrsp { |
| struct { |
| struct nx_hostrq_tx_ctx_s tx_ctx; |
| } __unm_dma_aligned hostrq; |
| struct { |
| struct nx_cardrsp_tx_ctx_s tx_ctx; |
| } __unm_dma_aligned cardrsp; |
| }; |
| |
| /** A Phantom NIC */ |
| struct phantom_nic { |
| /** BAR 0 */ |
| void *bar0; |
| /** Current CRB window */ |
| unsigned long crb_window; |
| /** CRB window access method */ |
| unsigned long ( *crb_access ) ( struct phantom_nic *phantom, |
| unsigned long reg ); |
| |
| |
| /** Port number */ |
| unsigned int port; |
| |
| |
| /** RX context ID */ |
| uint16_t rx_context_id; |
| /** RX descriptor producer CRB offset */ |
| unsigned long rds_producer_crb; |
| /** RX status descriptor consumer CRB offset */ |
| unsigned long sds_consumer_crb; |
| /** RX interrupt mask CRB offset */ |
| unsigned long sds_irq_mask_crb; |
| /** RX interrupts enabled */ |
| unsigned int sds_irq_enabled; |
| |
| /** RX producer index */ |
| unsigned int rds_producer_idx; |
| /** RX consumer index */ |
| unsigned int rds_consumer_idx; |
| /** RX status consumer index */ |
| unsigned int sds_consumer_idx; |
| /** RX I/O buffers */ |
| struct io_buffer *rds_iobuf[PHN_RDS_MAX_FILL]; |
| |
| |
| /** TX context ID */ |
| uint16_t tx_context_id; |
| /** TX descriptor producer CRB offset */ |
| unsigned long cds_producer_crb; |
| |
| /** TX producer index */ |
| unsigned int cds_producer_idx; |
| /** TX consumer index */ |
| unsigned int cds_consumer_idx; |
| /** TX I/O buffers */ |
| struct io_buffer *cds_iobuf[PHN_NUM_CDS]; |
| |
| |
| /** Descriptor rings */ |
| struct phantom_descriptor_rings *desc; |
| |
| |
| /** Last known link state */ |
| uint32_t link_state; |
| /** Link state poll timer */ |
| unsigned long link_poll_timer; |
| |
| |
| /** Non-volatile settings */ |
| struct settings settings; |
| }; |
| |
| /** Interrupt mask registers */ |
| static const unsigned long phantom_irq_mask_reg[PHN_MAX_NUM_PORTS] = { |
| UNM_PCIE_IRQ_MASK_F0, |
| UNM_PCIE_IRQ_MASK_F1, |
| UNM_PCIE_IRQ_MASK_F2, |
| UNM_PCIE_IRQ_MASK_F3, |
| UNM_PCIE_IRQ_MASK_F4, |
| UNM_PCIE_IRQ_MASK_F5, |
| UNM_PCIE_IRQ_MASK_F6, |
| UNM_PCIE_IRQ_MASK_F7, |
| }; |
| |
| /** Interrupt status registers */ |
| static const unsigned long phantom_irq_status_reg[PHN_MAX_NUM_PORTS] = { |
| UNM_PCIE_IRQ_STATUS_F0, |
| UNM_PCIE_IRQ_STATUS_F1, |
| UNM_PCIE_IRQ_STATUS_F2, |
| UNM_PCIE_IRQ_STATUS_F3, |
| UNM_PCIE_IRQ_STATUS_F4, |
| UNM_PCIE_IRQ_STATUS_F5, |
| UNM_PCIE_IRQ_STATUS_F6, |
| UNM_PCIE_IRQ_STATUS_F7, |
| }; |
| |
| /*************************************************************************** |
| * |
| * CRB register access |
| * |
| */ |
| |
| /** |
| * Prepare for access to CRB register via 128MB BAR |
| * |
| * @v phantom Phantom NIC |
| * @v reg Register offset within abstract address space |
| * @ret offset Register offset within PCI BAR0 |
| */ |
| static unsigned long phantom_crb_access_128m ( struct phantom_nic *phantom, |
| unsigned long reg ) { |
| unsigned long offset = ( 0x6000000 + ( reg & 0x1ffffff ) ); |
| uint32_t window = ( reg & 0x2000000 ); |
| uint32_t verify_window; |
| |
| if ( phantom->crb_window != window ) { |
| |
| /* Write to the CRB window register */ |
| writel ( window, phantom->bar0 + UNM_128M_CRB_WINDOW ); |
| |
| /* Ensure that the write has reached the card */ |
| verify_window = readl ( phantom->bar0 + UNM_128M_CRB_WINDOW ); |
| assert ( verify_window == window ); |
| |
| /* Record new window */ |
| phantom->crb_window = window; |
| } |
| |
| return offset; |
| } |
| |
| /** |
| * Prepare for access to CRB register via 32MB BAR |
| * |
| * @v phantom Phantom NIC |
| * @v reg Register offset within abstract address space |
| * @ret offset Register offset within PCI BAR0 |
| */ |
| static unsigned long phantom_crb_access_32m ( struct phantom_nic *phantom, |
| unsigned long reg ) { |
| unsigned long offset = ( reg & 0x1ffffff ); |
| uint32_t window = ( reg & 0x2000000 ); |
| uint32_t verify_window; |
| |
| if ( phantom->crb_window != window ) { |
| |
| /* Write to the CRB window register */ |
| writel ( window, phantom->bar0 + UNM_32M_CRB_WINDOW ); |
| |
| /* Ensure that the write has reached the card */ |
| verify_window = readl ( phantom->bar0 + UNM_32M_CRB_WINDOW ); |
| assert ( verify_window == window ); |
| |
| /* Record new window */ |
| phantom->crb_window = window; |
| } |
| |
| return offset; |
| } |
| |
| /** |
| * Prepare for access to CRB register via 2MB BAR |
| * |
| * @v phantom Phantom NIC |
| * @v reg Register offset within abstract address space |
| * @ret offset Register offset within PCI BAR0 |
| */ |
| static unsigned long phantom_crb_access_2m ( struct phantom_nic *phantom, |
| unsigned long reg ) { |
| static const struct { |
| uint8_t block; |
| uint16_t window_hi; |
| } reg_window_hi[] = { |
| { UNM_CRB_BLK_PCIE, 0x773 }, |
| { UNM_CRB_BLK_CAM, 0x416 }, |
| { UNM_CRB_BLK_ROMUSB, 0x421 }, |
| { UNM_CRB_BLK_TEST, 0x295 }, |
| { UNM_CRB_BLK_PEG_0, 0x340 }, |
| { UNM_CRB_BLK_PEG_1, 0x341 }, |
| { UNM_CRB_BLK_PEG_2, 0x342 }, |
| { UNM_CRB_BLK_PEG_3, 0x343 }, |
| { UNM_CRB_BLK_PEG_4, 0x34b }, |
| }; |
| unsigned int block = UNM_CRB_BLK ( reg ); |
| unsigned long offset = UNM_CRB_OFFSET ( reg ); |
| uint32_t window; |
| uint32_t verify_window; |
| unsigned int i; |
| |
| for ( i = 0 ; i < ( sizeof ( reg_window_hi ) / |
| sizeof ( reg_window_hi[0] ) ) ; i++ ) { |
| |
| if ( reg_window_hi[i].block != block ) |
| continue; |
| |
| window = ( ( reg_window_hi[i].window_hi << 20 ) | |
| ( offset & 0x000f0000 ) ); |
| |
| if ( phantom->crb_window != window ) { |
| |
| /* Write to the CRB window register */ |
| writel ( window, phantom->bar0 + UNM_2M_CRB_WINDOW ); |
| |
| /* Ensure that the write has reached the card */ |
| verify_window = readl ( phantom->bar0 + |
| UNM_2M_CRB_WINDOW ); |
| assert ( verify_window == window ); |
| |
| /* Record new window */ |
| phantom->crb_window = window; |
| } |
| |
| return ( 0x1e0000 + ( offset & 0xffff ) ); |
| } |
| |
| assert ( 0 ); |
| return 0; |
| } |
| |
| /** |
| * Read from Phantom CRB register |
| * |
| * @v phantom Phantom NIC |
| * @v reg Register offset within abstract address space |
| * @ret value Register value |
| */ |
| static uint32_t phantom_readl ( struct phantom_nic *phantom, |
| unsigned long reg ) { |
| unsigned long offset; |
| |
| offset = phantom->crb_access ( phantom, reg ); |
| return readl ( phantom->bar0 + offset ); |
| } |
| |
| /** |
| * Write to Phantom CRB register |
| * |
| * @v phantom Phantom NIC |
| * @v value Register value |
| * @v reg Register offset within abstract address space |
| */ |
| static void phantom_writel ( struct phantom_nic *phantom, uint32_t value, |
| unsigned long reg ) { |
| unsigned long offset; |
| |
| offset = phantom->crb_access ( phantom, reg ); |
| writel ( value, phantom->bar0 + offset ); |
| } |
| |
| /** |
| * Write to Phantom CRB HI/LO register pair |
| * |
| * @v phantom Phantom NIC |
| * @v value Register value |
| * @v lo_offset LO register offset within CRB |
| * @v hi_offset HI register offset within CRB |
| */ |
| static inline void phantom_write_hilo ( struct phantom_nic *phantom, |
| uint64_t value, |
| unsigned long lo_offset, |
| unsigned long hi_offset ) { |
| uint32_t lo = ( value & 0xffffffffUL ); |
| uint32_t hi = ( value >> 32 ); |
| |
| phantom_writel ( phantom, lo, lo_offset ); |
| phantom_writel ( phantom, hi, hi_offset ); |
| } |
| |
| /*************************************************************************** |
| * |
| * Firmware message buffer access (for debug) |
| * |
| */ |
| |
| /** |
| * Read from Phantom test memory |
| * |
| * @v phantom Phantom NIC |
| * @v offset Offset within test memory |
| * @v buf 8-byte buffer to fill |
| * @ret rc Return status code |
| */ |
| static int phantom_read_test_mem_block ( struct phantom_nic *phantom, |
| unsigned long offset, |
| uint32_t buf[2] ) { |
| unsigned int retries; |
| uint32_t test_control; |
| |
| phantom_write_hilo ( phantom, offset, UNM_TEST_ADDR_LO, |
| UNM_TEST_ADDR_HI ); |
| phantom_writel ( phantom, UNM_TEST_CONTROL_ENABLE, UNM_TEST_CONTROL ); |
| phantom_writel ( phantom, |
| ( UNM_TEST_CONTROL_ENABLE | UNM_TEST_CONTROL_START ), |
| UNM_TEST_CONTROL ); |
| |
| for ( retries = 0 ; retries < PHN_TEST_MEM_TIMEOUT_MS ; retries++ ) { |
| test_control = phantom_readl ( phantom, UNM_TEST_CONTROL ); |
| if ( ( test_control & UNM_TEST_CONTROL_BUSY ) == 0 ) { |
| buf[0] = phantom_readl ( phantom, UNM_TEST_RDDATA_LO ); |
| buf[1] = phantom_readl ( phantom, UNM_TEST_RDDATA_HI ); |
| return 0; |
| } |
| mdelay ( 1 ); |
| } |
| |
| DBGC ( phantom, "Phantom %p timed out waiting for test memory\n", |
| phantom ); |
| return -ETIMEDOUT; |
| } |
| |
| /** |
| * Read single byte from Phantom test memory |
| * |
| * @v phantom Phantom NIC |
| * @v offset Offset within test memory |
| * @ret byte Byte read, or negative error |
| */ |
| static int phantom_read_test_mem ( struct phantom_nic *phantom, |
| unsigned long offset ) { |
| static union { |
| uint8_t bytes[8]; |
| uint32_t dwords[2]; |
| } cache; |
| static unsigned long cache_offset = -1UL; |
| unsigned long sub_offset; |
| int rc; |
| |
| sub_offset = ( offset & ( sizeof ( cache ) - 1 ) ); |
| offset = ( offset & ~( sizeof ( cache ) - 1 ) ); |
| |
| if ( cache_offset != offset ) { |
| if ( ( rc = phantom_read_test_mem_block ( phantom, offset, |
| cache.dwords )) !=0 ) |
| return rc; |
| cache_offset = offset; |
| } |
| |
| return cache.bytes[sub_offset]; |
| } |
| |
| /** |
| * Dump Phantom firmware dmesg log |
| * |
| * @v phantom Phantom NIC |
| * @v log Log number |
| * @v max_lines Maximum number of lines to show, or -1 to show all |
| * @ret rc Return status code |
| */ |
| static int phantom_dmesg ( struct phantom_nic *phantom, unsigned int log, |
| unsigned int max_lines ) { |
| uint32_t head; |
| uint32_t tail; |
| uint32_t sig; |
| uint32_t offset; |
| int byte; |
| |
| /* Optimise out for non-debug builds */ |
| if ( ! DBG_LOG ) |
| return 0; |
| |
| /* Locate log */ |
| head = phantom_readl ( phantom, UNM_CAM_RAM_DMESG_HEAD ( log ) ); |
| tail = phantom_readl ( phantom, UNM_CAM_RAM_DMESG_TAIL ( log ) ); |
| sig = phantom_readl ( phantom, UNM_CAM_RAM_DMESG_SIG ( log ) ); |
| DBGC ( phantom, "Phantom %p firmware dmesg buffer %d (%08x-%08x)\n", |
| phantom, log, head, tail ); |
| assert ( ( head & 0x07 ) == 0 ); |
| if ( sig != UNM_CAM_RAM_DMESG_SIG_MAGIC ) { |
| DBGC ( phantom, "Warning: bad signature %08x (want %08lx)\n", |
| sig, UNM_CAM_RAM_DMESG_SIG_MAGIC ); |
| } |
| |
| /* Locate start of last (max_lines) lines */ |
| for ( offset = tail ; offset > head ; offset-- ) { |
| if ( ( byte = phantom_read_test_mem ( phantom, |
| ( offset - 1 ) ) ) < 0 ) |
| return byte; |
| if ( ( byte == '\n' ) && ( max_lines-- == 0 ) ) |
| break; |
| } |
| |
| /* Print lines */ |
| for ( ; offset < tail ; offset++ ) { |
| if ( ( byte = phantom_read_test_mem ( phantom, offset ) ) < 0 ) |
| return byte; |
| DBG ( "%c", byte ); |
| } |
| DBG ( "\n" ); |
| return 0; |
| } |
| |
| /** |
| * Dump Phantom firmware dmesg logs |
| * |
| * @v phantom Phantom NIC |
| * @v max_lines Maximum number of lines to show, or -1 to show all |
| */ |
| static void __attribute__ (( unused )) |
| phantom_dmesg_all ( struct phantom_nic *phantom, unsigned int max_lines ) { |
| unsigned int i; |
| |
| for ( i = 0 ; i < UNM_CAM_RAM_NUM_DMESG_BUFFERS ; i++ ) |
| phantom_dmesg ( phantom, i, max_lines ); |
| } |
| |
| /*************************************************************************** |
| * |
| * Firmware interface |
| * |
| */ |
| |
| /** |
| * Wait for firmware to accept command |
| * |
| * @v phantom Phantom NIC |
| * @ret rc Return status code |
| */ |
| static int phantom_wait_for_cmd ( struct phantom_nic *phantom ) { |
| unsigned int retries; |
| uint32_t cdrp; |
| |
| for ( retries = 0 ; retries < PHN_ISSUE_CMD_TIMEOUT_MS ; retries++ ) { |
| mdelay ( 1 ); |
| cdrp = phantom_readl ( phantom, UNM_NIC_REG_NX_CDRP ); |
| if ( NX_CDRP_IS_RSP ( cdrp ) ) { |
| switch ( NX_CDRP_FORM_RSP ( cdrp ) ) { |
| case NX_CDRP_RSP_OK: |
| return 0; |
| case NX_CDRP_RSP_FAIL: |
| return -EIO; |
| case NX_CDRP_RSP_TIMEOUT: |
| return -ETIMEDOUT; |
| default: |
| return -EPROTO; |
| } |
| } |
| } |
| |
| DBGC ( phantom, "Phantom %p timed out waiting for firmware to accept " |
| "command\n", phantom ); |
| return -ETIMEDOUT; |
| } |
| |
| /** |
| * Issue command to firmware |
| * |
| * @v phantom Phantom NIC |
| * @v command Firmware command |
| * @v arg1 Argument 1 |
| * @v arg2 Argument 2 |
| * @v arg3 Argument 3 |
| * @ret rc Return status code |
| */ |
| static int phantom_issue_cmd ( struct phantom_nic *phantom, |
| uint32_t command, uint32_t arg1, uint32_t arg2, |
| uint32_t arg3 ) { |
| uint32_t signature; |
| int rc; |
| |
| /* Issue command */ |
| signature = NX_CDRP_SIGNATURE_MAKE ( phantom->port, |
| NXHAL_VERSION ); |
| DBGC2 ( phantom, "Phantom %p issuing command %08x (%08x, %08x, " |
| "%08x)\n", phantom, command, arg1, arg2, arg3 ); |
| phantom_writel ( phantom, signature, UNM_NIC_REG_NX_SIGN ); |
| phantom_writel ( phantom, arg1, UNM_NIC_REG_NX_ARG1 ); |
| phantom_writel ( phantom, arg2, UNM_NIC_REG_NX_ARG2 ); |
| phantom_writel ( phantom, arg3, UNM_NIC_REG_NX_ARG3 ); |
| phantom_writel ( phantom, NX_CDRP_FORM_CMD ( command ), |
| UNM_NIC_REG_NX_CDRP ); |
| |
| /* Wait for command to be accepted */ |
| if ( ( rc = phantom_wait_for_cmd ( phantom ) ) != 0 ) { |
| DBGC ( phantom, "Phantom %p could not issue command: %s\n", |
| phantom, strerror ( rc ) ); |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * Issue buffer-format command to firmware |
| * |
| * @v phantom Phantom NIC |
| * @v command Firmware command |
| * @v buffer Buffer to pass to firmware |
| * @v len Length of buffer |
| * @ret rc Return status code |
| */ |
| static int phantom_issue_buf_cmd ( struct phantom_nic *phantom, |
| uint32_t command, void *buffer, |
| size_t len ) { |
| uint64_t physaddr; |
| |
| physaddr = virt_to_bus ( buffer ); |
| return phantom_issue_cmd ( phantom, command, ( physaddr >> 32 ), |
| ( physaddr & 0xffffffffUL ), len ); |
| } |
| |
| /** |
| * Create Phantom RX context |
| * |
| * @v phantom Phantom NIC |
| * @ret rc Return status code |
| */ |
| static int phantom_create_rx_ctx ( struct phantom_nic *phantom ) { |
| struct phantom_create_rx_ctx_rqrsp *buf; |
| int rc; |
| |
| /* Allocate context creation buffer */ |
| buf = malloc_phys ( sizeof ( *buf ), UNM_DMA_BUFFER_ALIGN ); |
| if ( ! buf ) { |
| rc = -ENOMEM; |
| goto out; |
| } |
| memset ( buf, 0, sizeof ( *buf ) ); |
| |
| /* Prepare request */ |
| buf->hostrq.rx_ctx.host_rsp_dma_addr = |
| cpu_to_le64 ( virt_to_bus ( &buf->cardrsp ) ); |
| buf->hostrq.rx_ctx.capabilities[0] = |
| cpu_to_le32 ( NX_CAP0_LEGACY_CONTEXT | NX_CAP0_LEGACY_MN ); |
| buf->hostrq.rx_ctx.host_int_crb_mode = |
| cpu_to_le32 ( NX_HOST_INT_CRB_MODE_SHARED ); |
| buf->hostrq.rx_ctx.host_rds_crb_mode = |
| cpu_to_le32 ( NX_HOST_RDS_CRB_MODE_UNIQUE ); |
| buf->hostrq.rx_ctx.rds_ring_offset = cpu_to_le32 ( 0 ); |
| buf->hostrq.rx_ctx.sds_ring_offset = |
| cpu_to_le32 ( sizeof ( buf->hostrq.rds ) ); |
| buf->hostrq.rx_ctx.num_rds_rings = cpu_to_le16 ( 1 ); |
| buf->hostrq.rx_ctx.num_sds_rings = cpu_to_le16 ( 1 ); |
| buf->hostrq.rds.host_phys_addr = |
| cpu_to_le64 ( virt_to_bus ( phantom->desc->rds ) ); |
| buf->hostrq.rds.buff_size = cpu_to_le64 ( PHN_RX_BUFSIZE ); |
| buf->hostrq.rds.ring_size = cpu_to_le32 ( PHN_NUM_RDS ); |
| buf->hostrq.rds.ring_kind = cpu_to_le32 ( NX_RDS_RING_TYPE_NORMAL ); |
| buf->hostrq.sds.host_phys_addr = |
| cpu_to_le64 ( virt_to_bus ( phantom->desc->sds ) ); |
| buf->hostrq.sds.ring_size = cpu_to_le32 ( PHN_NUM_SDS ); |
| |
| DBGC ( phantom, "Phantom %p creating RX context\n", phantom ); |
| DBGC2_HDA ( phantom, virt_to_bus ( &buf->hostrq ), |
| &buf->hostrq, sizeof ( buf->hostrq ) ); |
| |
| /* Issue request */ |
| if ( ( rc = phantom_issue_buf_cmd ( phantom, |
| NX_CDRP_CMD_CREATE_RX_CTX, |
| &buf->hostrq, |
| sizeof ( buf->hostrq ) ) ) != 0 ) { |
| DBGC ( phantom, "Phantom %p could not create RX context: " |
| "%s\n", phantom, strerror ( rc ) ); |
| DBGC ( phantom, "Request:\n" ); |
| DBGC_HDA ( phantom, virt_to_bus ( &buf->hostrq ), |
| &buf->hostrq, sizeof ( buf->hostrq ) ); |
| DBGC ( phantom, "Response:\n" ); |
| DBGC_HDA ( phantom, virt_to_bus ( &buf->cardrsp ), |
| &buf->cardrsp, sizeof ( buf->cardrsp ) ); |
| goto out; |
| } |
| |
| /* Retrieve context parameters */ |
| phantom->rx_context_id = |
| le16_to_cpu ( buf->cardrsp.rx_ctx.context_id ); |
| phantom->rds_producer_crb = |
| ( UNM_CAM_RAM + |
| le32_to_cpu ( buf->cardrsp.rds.host_producer_crb ) ); |
| phantom->sds_consumer_crb = |
| ( UNM_CAM_RAM + |
| le32_to_cpu ( buf->cardrsp.sds.host_consumer_crb ) ); |
| phantom->sds_irq_mask_crb = |
| ( UNM_CAM_RAM + |
| le32_to_cpu ( buf->cardrsp.sds.interrupt_crb ) ); |
| |
| DBGC ( phantom, "Phantom %p created RX context (id %04x, port phys " |
| "%02x virt %02x)\n", phantom, phantom->rx_context_id, |
| buf->cardrsp.rx_ctx.phys_port, buf->cardrsp.rx_ctx.virt_port ); |
| DBGC2_HDA ( phantom, virt_to_bus ( &buf->cardrsp ), |
| &buf->cardrsp, sizeof ( buf->cardrsp ) ); |
| DBGC ( phantom, "Phantom %p RDS producer CRB is %08lx\n", |
| phantom, phantom->rds_producer_crb ); |
| DBGC ( phantom, "Phantom %p SDS consumer CRB is %08lx\n", |
| phantom, phantom->sds_consumer_crb ); |
| DBGC ( phantom, "Phantom %p SDS interrupt mask CRB is %08lx\n", |
| phantom, phantom->sds_irq_mask_crb ); |
| |
| out: |
| free_phys ( buf, sizeof ( *buf ) ); |
| return rc; |
| } |
| |
| /** |
| * Destroy Phantom RX context |
| * |
| * @v phantom Phantom NIC |
| * @ret rc Return status code |
| */ |
| static void phantom_destroy_rx_ctx ( struct phantom_nic *phantom ) { |
| int rc; |
| |
| DBGC ( phantom, "Phantom %p destroying RX context (id %04x)\n", |
| phantom, phantom->rx_context_id ); |
| |
| /* Issue request */ |
| if ( ( rc = phantom_issue_cmd ( phantom, |
| NX_CDRP_CMD_DESTROY_RX_CTX, |
| phantom->rx_context_id, |
| NX_DESTROY_CTX_RESET, 0 ) ) != 0 ) { |
| DBGC ( phantom, "Phantom %p could not destroy RX context: " |
| "%s\n", phantom, strerror ( rc ) ); |
| /* We're probably screwed */ |
| return; |
| } |
| |
| /* Clear context parameters */ |
| phantom->rx_context_id = 0; |
| phantom->rds_producer_crb = 0; |
| phantom->sds_consumer_crb = 0; |
| |
| /* Reset software counters */ |
| phantom->rds_producer_idx = 0; |
| phantom->rds_consumer_idx = 0; |
| phantom->sds_consumer_idx = 0; |
| } |
| |
| /** |
| * Create Phantom TX context |
| * |
| * @v phantom Phantom NIC |
| * @ret rc Return status code |
| */ |
| static int phantom_create_tx_ctx ( struct phantom_nic *phantom ) { |
| struct phantom_create_tx_ctx_rqrsp *buf; |
| int rc; |
| |
| /* Allocate context creation buffer */ |
| buf = malloc_phys ( sizeof ( *buf ), UNM_DMA_BUFFER_ALIGN ); |
| if ( ! buf ) { |
| rc = -ENOMEM; |
| goto out; |
| } |
| memset ( buf, 0, sizeof ( *buf ) ); |
| |
| /* Prepare request */ |
| buf->hostrq.tx_ctx.host_rsp_dma_addr = |
| cpu_to_le64 ( virt_to_bus ( &buf->cardrsp ) ); |
| buf->hostrq.tx_ctx.cmd_cons_dma_addr = |
| cpu_to_le64 ( virt_to_bus ( &phantom->desc->cmd_cons ) ); |
| buf->hostrq.tx_ctx.capabilities[0] = |
| cpu_to_le32 ( NX_CAP0_LEGACY_CONTEXT | NX_CAP0_LEGACY_MN ); |
| buf->hostrq.tx_ctx.host_int_crb_mode = |
| cpu_to_le32 ( NX_HOST_INT_CRB_MODE_SHARED ); |
| buf->hostrq.tx_ctx.cds_ring.host_phys_addr = |
| cpu_to_le64 ( virt_to_bus ( phantom->desc->cds ) ); |
| buf->hostrq.tx_ctx.cds_ring.ring_size = cpu_to_le32 ( PHN_NUM_CDS ); |
| |
| DBGC ( phantom, "Phantom %p creating TX context\n", phantom ); |
| DBGC2_HDA ( phantom, virt_to_bus ( &buf->hostrq ), |
| &buf->hostrq, sizeof ( buf->hostrq ) ); |
| |
| /* Issue request */ |
| if ( ( rc = phantom_issue_buf_cmd ( phantom, |
| NX_CDRP_CMD_CREATE_TX_CTX, |
| &buf->hostrq, |
| sizeof ( buf->hostrq ) ) ) != 0 ) { |
| DBGC ( phantom, "Phantom %p could not create TX context: " |
| "%s\n", phantom, strerror ( rc ) ); |
| DBGC ( phantom, "Request:\n" ); |
| DBGC_HDA ( phantom, virt_to_bus ( &buf->hostrq ), |
| &buf->hostrq, sizeof ( buf->hostrq ) ); |
| DBGC ( phantom, "Response:\n" ); |
| DBGC_HDA ( phantom, virt_to_bus ( &buf->cardrsp ), |
| &buf->cardrsp, sizeof ( buf->cardrsp ) ); |
| goto out; |
| } |
| |
| /* Retrieve context parameters */ |
| phantom->tx_context_id = |
| le16_to_cpu ( buf->cardrsp.tx_ctx.context_id ); |
| phantom->cds_producer_crb = |
| ( UNM_CAM_RAM + |
| le32_to_cpu(buf->cardrsp.tx_ctx.cds_ring.host_producer_crb)); |
| |
| DBGC ( phantom, "Phantom %p created TX context (id %04x, port phys " |
| "%02x virt %02x)\n", phantom, phantom->tx_context_id, |
| buf->cardrsp.tx_ctx.phys_port, buf->cardrsp.tx_ctx.virt_port ); |
| DBGC2_HDA ( phantom, virt_to_bus ( &buf->cardrsp ), |
| &buf->cardrsp, sizeof ( buf->cardrsp ) ); |
| DBGC ( phantom, "Phantom %p CDS producer CRB is %08lx\n", |
| phantom, phantom->cds_producer_crb ); |
| |
| out: |
| free_phys ( buf, sizeof ( *buf ) ); |
| return rc; |
| } |
| |
| /** |
| * Destroy Phantom TX context |
| * |
| * @v phantom Phantom NIC |
| * @ret rc Return status code |
| */ |
| static void phantom_destroy_tx_ctx ( struct phantom_nic *phantom ) { |
| int rc; |
| |
| DBGC ( phantom, "Phantom %p destroying TX context (id %04x)\n", |
| phantom, phantom->tx_context_id ); |
| |
| /* Issue request */ |
| if ( ( rc = phantom_issue_cmd ( phantom, |
| NX_CDRP_CMD_DESTROY_TX_CTX, |
| phantom->tx_context_id, |
| NX_DESTROY_CTX_RESET, 0 ) ) != 0 ) { |
| DBGC ( phantom, "Phantom %p could not destroy TX context: " |
| "%s\n", phantom, strerror ( rc ) ); |
| /* We're probably screwed */ |
| return; |
| } |
| |
| /* Clear context parameters */ |
| phantom->tx_context_id = 0; |
| phantom->cds_producer_crb = 0; |
| |
| /* Reset software counters */ |
| phantom->cds_producer_idx = 0; |
| phantom->cds_consumer_idx = 0; |
| } |
| |
| /*************************************************************************** |
| * |
| * Descriptor ring management |
| * |
| */ |
| |
| /** |
| * Allocate Phantom RX descriptor |
| * |
| * @v phantom Phantom NIC |
| * @ret index RX descriptor index, or negative error |
| */ |
| static int phantom_alloc_rds ( struct phantom_nic *phantom ) { |
| unsigned int rds_producer_idx; |
| unsigned int next_rds_producer_idx; |
| |
| /* Check for space in the ring. RX descriptors are consumed |
| * out of order, but they are *read* by the hardware in strict |
| * order. We maintain a pessimistic consumer index, which is |
| * guaranteed never to be an overestimate of the number of |
| * descriptors read by the hardware. |
| */ |
| rds_producer_idx = phantom->rds_producer_idx; |
| next_rds_producer_idx = ( ( rds_producer_idx + 1 ) % PHN_NUM_RDS ); |
| if ( next_rds_producer_idx == phantom->rds_consumer_idx ) { |
| DBGC ( phantom, "Phantom %p RDS ring full (index %d not " |
| "consumed)\n", phantom, next_rds_producer_idx ); |
| return -ENOBUFS; |
| } |
| |
| return rds_producer_idx; |
| } |
| |
| /** |
| * Post Phantom RX descriptor |
| * |
| * @v phantom Phantom NIC |
| * @v rds RX descriptor |
| */ |
| static void phantom_post_rds ( struct phantom_nic *phantom, |
| struct phantom_rds *rds ) { |
| unsigned int rds_producer_idx; |
| unsigned int next_rds_producer_idx; |
| struct phantom_rds *entry; |
| |
| /* Copy descriptor to ring */ |
| rds_producer_idx = phantom->rds_producer_idx; |
| entry = &phantom->desc->rds[rds_producer_idx]; |
| memcpy ( entry, rds, sizeof ( *entry ) ); |
| DBGC2 ( phantom, "Phantom %p posting RDS %ld (slot %d):\n", |
| phantom, NX_GET ( rds, handle ), rds_producer_idx ); |
| DBGC2_HDA ( phantom, virt_to_bus ( entry ), entry, sizeof ( *entry ) ); |
| |
| /* Update producer index */ |
| next_rds_producer_idx = ( ( rds_producer_idx + 1 ) % PHN_NUM_RDS ); |
| phantom->rds_producer_idx = next_rds_producer_idx; |
| wmb(); |
| phantom_writel ( phantom, phantom->rds_producer_idx, |
| phantom->rds_producer_crb ); |
| } |
| |
| /** |
| * Allocate Phantom TX descriptor |
| * |
| * @v phantom Phantom NIC |
| * @ret index TX descriptor index, or negative error |
| */ |
| static int phantom_alloc_cds ( struct phantom_nic *phantom ) { |
| unsigned int cds_producer_idx; |
| unsigned int next_cds_producer_idx; |
| |
| /* Check for space in the ring. TX descriptors are consumed |
| * in strict order, so we just check for a collision against |
| * the consumer index. |
| */ |
| cds_producer_idx = phantom->cds_producer_idx; |
| next_cds_producer_idx = ( ( cds_producer_idx + 1 ) % PHN_NUM_CDS ); |
| if ( next_cds_producer_idx == phantom->cds_consumer_idx ) { |
| DBGC ( phantom, "Phantom %p CDS ring full (index %d not " |
| "consumed)\n", phantom, next_cds_producer_idx ); |
| return -ENOBUFS; |
| } |
| |
| return cds_producer_idx; |
| } |
| |
| /** |
| * Post Phantom TX descriptor |
| * |
| * @v phantom Phantom NIC |
| * @v cds TX descriptor |
| */ |
| static void phantom_post_cds ( struct phantom_nic *phantom, |
| union phantom_cds *cds ) { |
| unsigned int cds_producer_idx; |
| unsigned int next_cds_producer_idx; |
| union phantom_cds *entry; |
| |
| /* Copy descriptor to ring */ |
| cds_producer_idx = phantom->cds_producer_idx; |
| entry = &phantom->desc->cds[cds_producer_idx]; |
| memcpy ( entry, cds, sizeof ( *entry ) ); |
| DBGC2 ( phantom, "Phantom %p posting CDS %d:\n", |
| phantom, cds_producer_idx ); |
| DBGC2_HDA ( phantom, virt_to_bus ( entry ), entry, sizeof ( *entry ) ); |
| |
| /* Update producer index */ |
| next_cds_producer_idx = ( ( cds_producer_idx + 1 ) % PHN_NUM_CDS ); |
| phantom->cds_producer_idx = next_cds_producer_idx; |
| wmb(); |
| phantom_writel ( phantom, phantom->cds_producer_idx, |
| phantom->cds_producer_crb ); |
| } |
| |
| /*************************************************************************** |
| * |
| * MAC address management |
| * |
| */ |
| |
| /** |
| * Add/remove MAC address |
| * |
| * @v phantom Phantom NIC |
| * @v ll_addr MAC address to add or remove |
| * @v opcode MAC request opcode |
| * @ret rc Return status code |
| */ |
| static int phantom_update_macaddr ( struct phantom_nic *phantom, |
| const uint8_t *ll_addr, |
| unsigned int opcode ) { |
| union phantom_cds cds; |
| int index; |
| |
| /* Get descriptor ring entry */ |
| index = phantom_alloc_cds ( phantom ); |
| if ( index < 0 ) |
| return index; |
| |
| /* Fill descriptor ring entry */ |
| memset ( &cds, 0, sizeof ( cds ) ); |
| NX_FILL_1 ( &cds, 0, |
| nic_request.common.opcode, UNM_NIC_REQUEST ); |
| NX_FILL_2 ( &cds, 1, |
| nic_request.header.opcode, UNM_MAC_EVENT, |
| nic_request.header.context_id, phantom->port ); |
| NX_FILL_7 ( &cds, 2, |
| nic_request.body.mac_request.opcode, opcode, |
| nic_request.body.mac_request.mac_addr_0, ll_addr[0], |
| nic_request.body.mac_request.mac_addr_1, ll_addr[1], |
| nic_request.body.mac_request.mac_addr_2, ll_addr[2], |
| nic_request.body.mac_request.mac_addr_3, ll_addr[3], |
| nic_request.body.mac_request.mac_addr_4, ll_addr[4], |
| nic_request.body.mac_request.mac_addr_5, ll_addr[5] ); |
| |
| /* Post descriptor */ |
| phantom_post_cds ( phantom, &cds ); |
| |
| return 0; |
| } |
| |
| /** |
| * Add MAC address |
| * |
| * @v phantom Phantom NIC |
| * @v ll_addr MAC address to add or remove |
| * @ret rc Return status code |
| */ |
| static inline int phantom_add_macaddr ( struct phantom_nic *phantom, |
| const uint8_t *ll_addr ) { |
| |
| DBGC ( phantom, "Phantom %p adding MAC address %s\n", |
| phantom, eth_ntoa ( ll_addr ) ); |
| |
| return phantom_update_macaddr ( phantom, ll_addr, UNM_MAC_ADD ); |
| } |
| |
| /** |
| * Remove MAC address |
| * |
| * @v phantom Phantom NIC |
| * @v ll_addr MAC address to add or remove |
| * @ret rc Return status code |
| */ |
| static inline int phantom_del_macaddr ( struct phantom_nic *phantom, |
| const uint8_t *ll_addr ) { |
| |
| DBGC ( phantom, "Phantom %p removing MAC address %s\n", |
| phantom, eth_ntoa ( ll_addr ) ); |
| |
| return phantom_update_macaddr ( phantom, ll_addr, UNM_MAC_DEL ); |
| } |
| |
| /*************************************************************************** |
| * |
| * Link state detection |
| * |
| */ |
| |
| /** |
| * Poll link state |
| * |
| * @v netdev Network device |
| */ |
| static void phantom_poll_link_state ( struct net_device *netdev ) { |
| struct phantom_nic *phantom = netdev->priv; |
| uint32_t xg_state_p3; |
| unsigned int link; |
| |
| /* Read link state */ |
| xg_state_p3 = phantom_readl ( phantom, UNM_NIC_REG_XG_STATE_P3 ); |
| |
| /* If there is no change, do nothing */ |
| if ( phantom->link_state == xg_state_p3 ) |
| return; |
| |
| /* Record new link state */ |
| DBGC ( phantom, "Phantom %p new link state %08x (was %08x)\n", |
| phantom, xg_state_p3, phantom->link_state ); |
| phantom->link_state = xg_state_p3; |
| |
| /* Indicate link state to iPXE */ |
| link = UNM_NIC_REG_XG_STATE_P3_LINK ( phantom->port, |
| phantom->link_state ); |
| switch ( link ) { |
| case UNM_NIC_REG_XG_STATE_P3_LINK_UP: |
| DBGC ( phantom, "Phantom %p link is up\n", phantom ); |
| netdev_link_up ( netdev ); |
| break; |
| case UNM_NIC_REG_XG_STATE_P3_LINK_DOWN: |
| DBGC ( phantom, "Phantom %p link is down\n", phantom ); |
| netdev_link_down ( netdev ); |
| break; |
| default: |
| DBGC ( phantom, "Phantom %p bad link state %d\n", |
| phantom, link ); |
| break; |
| } |
| } |
| |
| /*************************************************************************** |
| * |
| * Main driver body |
| * |
| */ |
| |
| /** |
| * Refill descriptor ring |
| * |
| * @v netdev Net device |
| */ |
| static void phantom_refill_rx_ring ( struct net_device *netdev ) { |
| struct phantom_nic *phantom = netdev->priv; |
| struct io_buffer *iobuf; |
| struct phantom_rds rds; |
| unsigned int handle; |
| int index; |
| |
| for ( handle = 0 ; handle < PHN_RDS_MAX_FILL ; handle++ ) { |
| |
| /* Skip this index if the descriptor has not yet been |
| * consumed. |
| */ |
| if ( phantom->rds_iobuf[handle] != NULL ) |
| continue; |
| |
| /* Allocate descriptor ring entry */ |
| index = phantom_alloc_rds ( phantom ); |
| assert ( PHN_RDS_MAX_FILL < PHN_NUM_RDS ); |
| assert ( index >= 0 ); /* Guaranteed by MAX_FILL < NUM_RDS ) */ |
| |
| /* Try to allocate an I/O buffer */ |
| iobuf = alloc_iob ( PHN_RX_BUFSIZE ); |
| if ( ! iobuf ) { |
| /* Failure is non-fatal; we will retry later */ |
| netdev_rx_err ( netdev, NULL, -ENOMEM ); |
| break; |
| } |
| |
| /* Fill descriptor ring entry */ |
| memset ( &rds, 0, sizeof ( rds ) ); |
| NX_FILL_2 ( &rds, 0, |
| handle, handle, |
| length, iob_len ( iobuf ) ); |
| NX_FILL_1 ( &rds, 1, |
| dma_addr, virt_to_bus ( iobuf->data ) ); |
| |
| /* Record I/O buffer */ |
| assert ( phantom->rds_iobuf[handle] == NULL ); |
| phantom->rds_iobuf[handle] = iobuf; |
| |
| /* Post descriptor */ |
| phantom_post_rds ( phantom, &rds ); |
| } |
| } |
| |
| /** |
| * Open NIC |
| * |
| * @v netdev Net device |
| * @ret rc Return status code |
| */ |
| static int phantom_open ( struct net_device *netdev ) { |
| struct phantom_nic *phantom = netdev->priv; |
| int rc; |
| |
| /* Allocate and zero descriptor rings */ |
| phantom->desc = malloc_phys ( sizeof ( *(phantom->desc) ), |
| UNM_DMA_BUFFER_ALIGN ); |
| if ( ! phantom->desc ) { |
| rc = -ENOMEM; |
| goto err_alloc_desc; |
| } |
| memset ( phantom->desc, 0, sizeof ( *(phantom->desc) ) ); |
| |
| /* Create RX context */ |
| if ( ( rc = phantom_create_rx_ctx ( phantom ) ) != 0 ) |
| goto err_create_rx_ctx; |
| |
| /* Create TX context */ |
| if ( ( rc = phantom_create_tx_ctx ( phantom ) ) != 0 ) |
| goto err_create_tx_ctx; |
| |
| /* Fill the RX descriptor ring */ |
| phantom_refill_rx_ring ( netdev ); |
| |
| /* Add MAC addresses |
| * |
| * BUG5583 |
| * |
| * We would like to be able to enable receiving all multicast |
| * packets (or, failing that, promiscuous mode), but the |
| * firmware doesn't currently support this. |
| */ |
| if ( ( rc = phantom_add_macaddr ( phantom, |
| netdev->ll_broadcast ) ) != 0 ) |
| goto err_add_macaddr_broadcast; |
| if ( ( rc = phantom_add_macaddr ( phantom, |
| netdev->ll_addr ) ) != 0 ) |
| goto err_add_macaddr_unicast; |
| |
| return 0; |
| |
| phantom_del_macaddr ( phantom, netdev->ll_addr ); |
| err_add_macaddr_unicast: |
| phantom_del_macaddr ( phantom, netdev->ll_broadcast ); |
| err_add_macaddr_broadcast: |
| phantom_destroy_tx_ctx ( phantom ); |
| err_create_tx_ctx: |
| phantom_destroy_rx_ctx ( phantom ); |
| err_create_rx_ctx: |
| free_phys ( phantom->desc, sizeof ( *(phantom->desc) ) ); |
| phantom->desc = NULL; |
| err_alloc_desc: |
| return rc; |
| } |
| |
| /** |
| * Close NIC |
| * |
| * @v netdev Net device |
| */ |
| static void phantom_close ( struct net_device *netdev ) { |
| struct phantom_nic *phantom = netdev->priv; |
| struct io_buffer *iobuf; |
| unsigned int i; |
| |
| /* Shut down the port */ |
| phantom_del_macaddr ( phantom, netdev->ll_addr ); |
| phantom_del_macaddr ( phantom, netdev->ll_broadcast ); |
| phantom_destroy_tx_ctx ( phantom ); |
| phantom_destroy_rx_ctx ( phantom ); |
| free_phys ( phantom->desc, sizeof ( *(phantom->desc) ) ); |
| phantom->desc = NULL; |
| |
| /* Flush any uncompleted descriptors */ |
| for ( i = 0 ; i < PHN_RDS_MAX_FILL ; i++ ) { |
| iobuf = phantom->rds_iobuf[i]; |
| if ( iobuf ) { |
| free_iob ( iobuf ); |
| phantom->rds_iobuf[i] = NULL; |
| } |
| } |
| for ( i = 0 ; i < PHN_NUM_CDS ; i++ ) { |
| iobuf = phantom->cds_iobuf[i]; |
| if ( iobuf ) { |
| netdev_tx_complete_err ( netdev, iobuf, -ECANCELED ); |
| phantom->cds_iobuf[i] = NULL; |
| } |
| } |
| } |
| |
| /** |
| * Transmit packet |
| * |
| * @v netdev Network device |
| * @v iobuf I/O buffer |
| * @ret rc Return status code |
| */ |
| static int phantom_transmit ( struct net_device *netdev, |
| struct io_buffer *iobuf ) { |
| struct phantom_nic *phantom = netdev->priv; |
| union phantom_cds cds; |
| int index; |
| |
| /* Get descriptor ring entry */ |
| index = phantom_alloc_cds ( phantom ); |
| if ( index < 0 ) |
| return index; |
| |
| /* Fill descriptor ring entry */ |
| memset ( &cds, 0, sizeof ( cds ) ); |
| NX_FILL_3 ( &cds, 0, |
| tx.opcode, UNM_TX_ETHER_PKT, |
| tx.num_buffers, 1, |
| tx.length, iob_len ( iobuf ) ); |
| NX_FILL_2 ( &cds, 2, |
| tx.port, phantom->port, |
| tx.context_id, phantom->port ); |
| NX_FILL_1 ( &cds, 4, |
| tx.buffer1_dma_addr, virt_to_bus ( iobuf->data ) ); |
| NX_FILL_1 ( &cds, 5, |
| tx.buffer1_length, iob_len ( iobuf ) ); |
| |
| /* Record I/O buffer */ |
| assert ( phantom->cds_iobuf[index] == NULL ); |
| phantom->cds_iobuf[index] = iobuf; |
| |
| /* Post descriptor */ |
| phantom_post_cds ( phantom, &cds ); |
| |
| return 0; |
| } |
| |
| /** |
| * Poll for received packets |
| * |
| * @v netdev Network device |
| */ |
| static void phantom_poll ( struct net_device *netdev ) { |
| struct phantom_nic *phantom = netdev->priv; |
| struct io_buffer *iobuf; |
| unsigned int irq_vector; |
| unsigned int irq_state; |
| unsigned int cds_consumer_idx; |
| unsigned int raw_new_cds_consumer_idx; |
| unsigned int new_cds_consumer_idx; |
| unsigned int rds_consumer_idx; |
| unsigned int sds_consumer_idx; |
| struct phantom_sds *sds; |
| unsigned int sds_handle; |
| unsigned int sds_opcode; |
| |
| /* Occasionally poll the link state */ |
| if ( phantom->link_poll_timer-- == 0 ) { |
| phantom_poll_link_state ( netdev ); |
| /* Reset the link poll timer */ |
| phantom->link_poll_timer = PHN_LINK_POLL_FREQUENCY; |
| } |
| |
| /* Check for interrupts */ |
| if ( phantom->sds_irq_enabled ) { |
| |
| /* Do nothing unless an interrupt is asserted */ |
| irq_vector = phantom_readl ( phantom, UNM_PCIE_IRQ_VECTOR ); |
| if ( ! ( irq_vector & UNM_PCIE_IRQ_VECTOR_BIT( phantom->port ))) |
| return; |
| |
| /* Do nothing unless interrupt state machine has stabilised */ |
| irq_state = phantom_readl ( phantom, UNM_PCIE_IRQ_STATE ); |
| if ( ! UNM_PCIE_IRQ_STATE_TRIGGERED ( irq_state ) ) |
| return; |
| |
| /* Acknowledge interrupt */ |
| phantom_writel ( phantom, UNM_PCIE_IRQ_STATUS_MAGIC, |
| phantom_irq_status_reg[phantom->port] ); |
| phantom_readl ( phantom, UNM_PCIE_IRQ_VECTOR ); |
| } |
| |
| /* Check for TX completions */ |
| cds_consumer_idx = phantom->cds_consumer_idx; |
| raw_new_cds_consumer_idx = phantom->desc->cmd_cons; |
| new_cds_consumer_idx = le32_to_cpu ( raw_new_cds_consumer_idx ); |
| while ( cds_consumer_idx != new_cds_consumer_idx ) { |
| DBGC2 ( phantom, "Phantom %p CDS %d complete\n", |
| phantom, cds_consumer_idx ); |
| /* Completions may be for commands other than TX, so |
| * there may not always be an associated I/O buffer. |
| */ |
| if ( ( iobuf = phantom->cds_iobuf[cds_consumer_idx] ) ) { |
| netdev_tx_complete ( netdev, iobuf ); |
| phantom->cds_iobuf[cds_consumer_idx] = NULL; |
| } |
| cds_consumer_idx = ( ( cds_consumer_idx + 1 ) % PHN_NUM_CDS ); |
| phantom->cds_consumer_idx = cds_consumer_idx; |
| } |
| |
| /* Check for received packets */ |
| rds_consumer_idx = phantom->rds_consumer_idx; |
| sds_consumer_idx = phantom->sds_consumer_idx; |
| while ( 1 ) { |
| sds = &phantom->desc->sds[sds_consumer_idx]; |
| if ( NX_GET ( sds, owner ) == 0 ) |
| break; |
| |
| DBGC2 ( phantom, "Phantom %p SDS %d status:\n", |
| phantom, sds_consumer_idx ); |
| DBGC2_HDA ( phantom, virt_to_bus ( sds ), sds, sizeof (*sds) ); |
| |
| /* Check received opcode */ |
| sds_opcode = NX_GET ( sds, opcode ); |
| if ( ( sds_opcode == UNM_RXPKT_DESC ) || |
| ( sds_opcode == UNM_SYN_OFFLOAD ) ) { |
| |
| /* Sanity check: ensure that all of the SDS |
| * descriptor has been written. |
| */ |
| if ( NX_GET ( sds, total_length ) == 0 ) { |
| DBGC ( phantom, "Phantom %p SDS %d " |
| "incomplete; deferring\n", |
| phantom, sds_consumer_idx ); |
| /* Leave for next poll() */ |
| break; |
| } |
| |
| /* Process received packet */ |
| sds_handle = NX_GET ( sds, handle ); |
| iobuf = phantom->rds_iobuf[sds_handle]; |
| assert ( iobuf != NULL ); |
| iob_put ( iobuf, NX_GET ( sds, total_length ) ); |
| iob_pull ( iobuf, NX_GET ( sds, pkt_offset ) ); |
| DBGC2 ( phantom, "Phantom %p RDS %d complete\n", |
| phantom, sds_handle ); |
| netdev_rx ( netdev, iobuf ); |
| phantom->rds_iobuf[sds_handle] = NULL; |
| |
| /* Update RDS consumer counter. This is a |
| * lower bound for the number of descriptors |
| * that have been read by the hardware, since |
| * the hardware must have read at least one |
| * descriptor for each completion that we |
| * receive. |
| */ |
| rds_consumer_idx = |
| ( ( rds_consumer_idx + 1 ) % PHN_NUM_RDS ); |
| phantom->rds_consumer_idx = rds_consumer_idx; |
| |
| } else { |
| |
| DBGC ( phantom, "Phantom %p unexpected SDS opcode " |
| "%02x\n", phantom, sds_opcode ); |
| DBGC_HDA ( phantom, virt_to_bus ( sds ), |
| sds, sizeof ( *sds ) ); |
| } |
| |
| /* Clear status descriptor */ |
| memset ( sds, 0, sizeof ( *sds ) ); |
| |
| /* Update SDS consumer index */ |
| sds_consumer_idx = ( ( sds_consumer_idx + 1 ) % PHN_NUM_SDS ); |
| phantom->sds_consumer_idx = sds_consumer_idx; |
| wmb(); |
| phantom_writel ( phantom, phantom->sds_consumer_idx, |
| phantom->sds_consumer_crb ); |
| } |
| |
| /* Refill the RX descriptor ring */ |
| phantom_refill_rx_ring ( netdev ); |
| } |
| |
| /** |
| * Enable/disable interrupts |
| * |
| * @v netdev Network device |
| * @v enable Interrupts should be enabled |
| */ |
| static void phantom_irq ( struct net_device *netdev, int enable ) { |
| struct phantom_nic *phantom = netdev->priv; |
| |
| phantom_writel ( phantom, ( enable ? 1 : 0 ), |
| phantom->sds_irq_mask_crb ); |
| phantom_writel ( phantom, UNM_PCIE_IRQ_MASK_MAGIC, |
| phantom_irq_mask_reg[phantom->port] ); |
| phantom->sds_irq_enabled = enable; |
| } |
| |
| /** Phantom net device operations */ |
| static struct net_device_operations phantom_operations = { |
| .open = phantom_open, |
| .close = phantom_close, |
| .transmit = phantom_transmit, |
| .poll = phantom_poll, |
| .irq = phantom_irq, |
| }; |
| |
| /*************************************************************************** |
| * |
| * CLP settings |
| * |
| */ |
| |
| /** Phantom CLP settings scope */ |
| static const struct settings_scope phantom_settings_scope; |
| |
| /** Phantom CLP data |
| * |
| */ |
| union phantom_clp_data { |
| /** Data bytes |
| * |
| * This field is right-aligned; if only N bytes are present |
| * then bytes[0]..bytes[7-N] should be zero, and the data |
| * should be in bytes[7-N+1] to bytes[7]; |
| */ |
| uint8_t bytes[8]; |
| /** Dwords for the CLP interface */ |
| struct { |
| /** High dword, in network byte order */ |
| uint32_t hi; |
| /** Low dword, in network byte order */ |
| uint32_t lo; |
| } dwords; |
| }; |
| #define PHN_CLP_BLKSIZE ( sizeof ( union phantom_clp_data ) ) |
| |
| /** |
| * Wait for Phantom CLP command to complete |
| * |
| * @v phantom Phantom NIC |
| * @ret rc Return status code |
| */ |
| static int phantom_clp_wait ( struct phantom_nic *phantom ) { |
| unsigned int retries; |
| uint32_t status; |
| |
| for ( retries = 0 ; retries < PHN_CLP_CMD_TIMEOUT_MS ; retries++ ) { |
| status = phantom_readl ( phantom, UNM_CAM_RAM_CLP_STATUS ); |
| if ( status & UNM_CAM_RAM_CLP_STATUS_DONE ) |
| return 0; |
| mdelay ( 1 ); |
| } |
| |
| DBGC ( phantom, "Phantom %p timed out waiting for CLP command\n", |
| phantom ); |
| return -ETIMEDOUT; |
| } |
| |
| /** |
| * Issue Phantom CLP command |
| * |
| * @v phantom Phantom NIC |
| * @v port Virtual port number |
| * @v opcode Opcode |
| * @v data_in Data in, or NULL |
| * @v data_out Data out, or NULL |
| * @v offset Offset within data |
| * @v len Data buffer length |
| * @ret len Total transfer length (for reads), or negative error |
| */ |
| static int phantom_clp_cmd ( struct phantom_nic *phantom, unsigned int port, |
| unsigned int opcode, const void *data_in, |
| void *data_out, size_t offset, size_t len ) { |
| union phantom_clp_data data; |
| unsigned int index = ( offset / sizeof ( data ) ); |
| unsigned int last = 0; |
| size_t in_frag_len; |
| uint8_t *in_frag; |
| uint32_t command; |
| uint32_t status; |
| size_t read_len; |
| unsigned int error; |
| size_t out_frag_len; |
| uint8_t *out_frag; |
| int rc; |
| |
| /* Sanity checks */ |
| assert ( ( offset % sizeof ( data ) ) == 0 ); |
| if ( len > 255 ) { |
| DBGC ( phantom, "Phantom %p invalid CLP length %zd\n", |
| phantom, len ); |
| return -EINVAL; |
| } |
| |
| /* Check that CLP interface is ready */ |
| if ( ( rc = phantom_clp_wait ( phantom ) ) != 0 ) |
| return rc; |
| |
| /* Copy data in */ |
| memset ( &data, 0, sizeof ( data ) ); |
| if ( data_in ) { |
| assert ( offset < len ); |
| in_frag_len = ( len - offset ); |
| if ( in_frag_len > sizeof ( data ) ) { |
| in_frag_len = sizeof ( data ); |
| } else { |
| last = 1; |
| } |
| in_frag = &data.bytes[ sizeof ( data ) - in_frag_len ]; |
| memcpy ( in_frag, ( data_in + offset ), in_frag_len ); |
| phantom_writel ( phantom, be32_to_cpu ( data.dwords.lo ), |
| UNM_CAM_RAM_CLP_DATA_LO ); |
| phantom_writel ( phantom, be32_to_cpu ( data.dwords.hi ), |
| UNM_CAM_RAM_CLP_DATA_HI ); |
| } |
| |
| /* Issue CLP command */ |
| command = ( ( index << 24 ) | ( ( data_in ? len : 0 ) << 16 ) | |
| ( port << 8 ) | ( last << 7 ) | ( opcode << 0 ) ); |
| phantom_writel ( phantom, command, UNM_CAM_RAM_CLP_COMMAND ); |
| mb(); |
| phantom_writel ( phantom, UNM_CAM_RAM_CLP_STATUS_START, |
| UNM_CAM_RAM_CLP_STATUS ); |
| |
| /* Wait for command to complete */ |
| if ( ( rc = phantom_clp_wait ( phantom ) ) != 0 ) |
| return rc; |
| |
| /* Get command status */ |
| status = phantom_readl ( phantom, UNM_CAM_RAM_CLP_STATUS ); |
| read_len = ( ( status >> 16 ) & 0xff ); |
| error = ( ( status >> 8 ) & 0xff ); |
| if ( error ) { |
| DBGC ( phantom, "Phantom %p CLP command error %02x\n", |
| phantom, error ); |
| return -EIO; |
| } |
| |
| /* Copy data out */ |
| if ( data_out ) { |
| data.dwords.lo = cpu_to_be32 ( phantom_readl ( phantom, |
| UNM_CAM_RAM_CLP_DATA_LO ) ); |
| data.dwords.hi = cpu_to_be32 ( phantom_readl ( phantom, |
| UNM_CAM_RAM_CLP_DATA_HI ) ); |
| out_frag_len = ( read_len - offset ); |
| if ( out_frag_len > sizeof ( data ) ) |
| out_frag_len = sizeof ( data ); |
| out_frag = &data.bytes[ sizeof ( data ) - out_frag_len ]; |
| if ( out_frag_len > ( len - offset ) ) |
| out_frag_len = ( len - offset ); |
| memcpy ( ( data_out + offset ), out_frag, out_frag_len ); |
| } |
| |
| return read_len; |
| } |
| |
| /** |
| * Store Phantom CLP setting |
| * |
| * @v phantom Phantom NIC |
| * @v port Virtual port number |
| * @v setting Setting number |
| * @v data Data buffer |
| * @v len Length of data buffer |
| * @ret rc Return status code |
| */ |
| static int phantom_clp_store ( struct phantom_nic *phantom, unsigned int port, |
| unsigned int setting, const void *data, |
| size_t len ) { |
| unsigned int opcode = setting; |
| size_t offset; |
| int rc; |
| |
| for ( offset = 0 ; offset < len ; offset += PHN_CLP_BLKSIZE ) { |
| if ( ( rc = phantom_clp_cmd ( phantom, port, opcode, data, |
| NULL, offset, len ) ) < 0 ) |
| return rc; |
| } |
| return 0; |
| } |
| |
| /** |
| * Fetch Phantom CLP setting |
| * |
| * @v phantom Phantom NIC |
| * @v port Virtual port number |
| * @v setting Setting number |
| * @v data Data buffer |
| * @v len Length of data buffer |
| * @ret len Length of setting, or negative error |
| */ |
| static int phantom_clp_fetch ( struct phantom_nic *phantom, unsigned int port, |
| unsigned int setting, void *data, size_t len ) { |
| unsigned int opcode = ( setting + 1 ); |
| size_t offset = 0; |
| int read_len; |
| |
| while ( 1 ) { |
| read_len = phantom_clp_cmd ( phantom, port, opcode, NULL, |
| data, offset, len ); |
| if ( read_len < 0 ) |
| return read_len; |
| offset += PHN_CLP_BLKSIZE; |
| if ( offset >= ( unsigned ) read_len ) |
| break; |
| if ( offset >= len ) |
| break; |
| } |
| return read_len; |
| } |
| |
| /** A Phantom CLP setting */ |
| struct phantom_clp_setting { |
| /** iPXE setting */ |
| const struct setting *setting; |
| /** Setting number */ |
| unsigned int clp_setting; |
| }; |
| |
| /** Phantom CLP settings */ |
| static struct phantom_clp_setting clp_settings[] = { |
| { &mac_setting, 0x01 }, |
| }; |
| |
| /** |
| * Find Phantom CLP setting |
| * |
| * @v setting iPXE setting |
| * @v clp_setting Setting number, or 0 if not found |
| */ |
| static unsigned int |
| phantom_clp_setting ( struct phantom_nic *phantom, |
| const struct setting *setting ) { |
| struct phantom_clp_setting *clp_setting; |
| unsigned int i; |
| |
| /* Search the list of explicitly-defined settings */ |
| for ( i = 0 ; i < ( sizeof ( clp_settings ) / |
| sizeof ( clp_settings[0] ) ) ; i++ ) { |
| clp_setting = &clp_settings[i]; |
| if ( setting_cmp ( setting, clp_setting->setting ) == 0 ) |
| return clp_setting->clp_setting; |
| } |
| |
| /* Allow for use of numbered settings */ |
| if ( setting->scope == &phantom_settings_scope ) |
| return setting->tag; |
| |
| DBGC2 ( phantom, "Phantom %p has no \"%s\" setting\n", |
| phantom, setting->name ); |
| |
| return 0; |
| } |
| |
| /** |
| * Check applicability of Phantom CLP setting |
| * |
| * @v settings Settings block |
| * @v setting Setting |
| * @ret applies Setting applies within this settings block |
| */ |
| static int phantom_setting_applies ( struct settings *settings, |
| const struct setting *setting ) { |
| struct phantom_nic *phantom = |
| container_of ( settings, struct phantom_nic, settings ); |
| unsigned int clp_setting; |
| |
| /* Find Phantom setting equivalent to iPXE setting */ |
| clp_setting = phantom_clp_setting ( phantom, setting ); |
| return ( clp_setting != 0 ); |
| } |
| |
| /** |
| * Store Phantom CLP setting |
| * |
| * @v settings Settings block |
| * @v setting Setting to store |
| * @v data Setting data, or NULL to clear setting |
| * @v len Length of setting data |
| * @ret rc Return status code |
| */ |
| static int phantom_store_setting ( struct settings *settings, |
| const struct setting *setting, |
| const void *data, size_t len ) { |
| struct phantom_nic *phantom = |
| container_of ( settings, struct phantom_nic, settings ); |
| unsigned int clp_setting; |
| int rc; |
| |
| /* Find Phantom setting equivalent to iPXE setting */ |
| clp_setting = phantom_clp_setting ( phantom, setting ); |
| assert ( clp_setting != 0 ); |
| |
| /* Store setting */ |
| if ( ( rc = phantom_clp_store ( phantom, phantom->port, |
| clp_setting, data, len ) ) != 0 ) { |
| DBGC ( phantom, "Phantom %p could not store setting \"%s\": " |
| "%s\n", phantom, setting->name, strerror ( rc ) ); |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * Fetch Phantom CLP setting |
| * |
| * @v settings Settings block |
| * @v setting Setting to fetch |
| * @v data Buffer to fill with setting data |
| * @v len Length of buffer |
| * @ret len Length of setting data, or negative error |
| */ |
| static int phantom_fetch_setting ( struct settings *settings, |
| struct setting *setting, |
| void *data, size_t len ) { |
| struct phantom_nic *phantom = |
| container_of ( settings, struct phantom_nic, settings ); |
| unsigned int clp_setting; |
| int read_len; |
| int rc; |
| |
| /* Find Phantom setting equivalent to iPXE setting */ |
| clp_setting = phantom_clp_setting ( phantom, setting ); |
| assert ( clp_setting != 0 ); |
| |
| /* Fetch setting */ |
| if ( ( read_len = phantom_clp_fetch ( phantom, phantom->port, |
| clp_setting, data, len ) ) < 0 ){ |
| rc = read_len; |
| DBGC ( phantom, "Phantom %p could not fetch setting \"%s\": " |
| "%s\n", phantom, setting->name, strerror ( rc ) ); |
| return rc; |
| } |
| |
| return read_len; |
| } |
| |
| /** Phantom CLP settings operations */ |
| static struct settings_operations phantom_settings_operations = { |
| .applies = phantom_setting_applies, |
| .store = phantom_store_setting, |
| .fetch = phantom_fetch_setting, |
| }; |
| |
| /*************************************************************************** |
| * |
| * Initialisation |
| * |
| */ |
| |
| /** |
| * Map Phantom CRB window |
| * |
| * @v phantom Phantom NIC |
| * @ret rc Return status code |
| */ |
| static int phantom_map_crb ( struct phantom_nic *phantom, |
| struct pci_device *pci ) { |
| unsigned long bar0_start; |
| unsigned long bar0_size; |
| |
| bar0_start = pci_bar_start ( pci, PCI_BASE_ADDRESS_0 ); |
| bar0_size = pci_bar_size ( pci, PCI_BASE_ADDRESS_0 ); |
| DBGC ( phantom, "Phantom %p is " PCI_FMT " with BAR0 at %08lx+%lx\n", |
| phantom, PCI_ARGS ( pci ), bar0_start, bar0_size ); |
| |
| if ( ! bar0_start ) { |
| DBGC ( phantom, "Phantom %p BAR not assigned; ignoring\n", |
| phantom ); |
| return -EINVAL; |
| } |
| |
| switch ( bar0_size ) { |
| case ( 128 * 1024 * 1024 ) : |
| DBGC ( phantom, "Phantom %p has 128MB BAR\n", phantom ); |
| phantom->crb_access = phantom_crb_access_128m; |
| break; |
| case ( 32 * 1024 * 1024 ) : |
| DBGC ( phantom, "Phantom %p has 32MB BAR\n", phantom ); |
| phantom->crb_access = phantom_crb_access_32m; |
| break; |
| case ( 2 * 1024 * 1024 ) : |
| DBGC ( phantom, "Phantom %p has 2MB BAR\n", phantom ); |
| phantom->crb_access = phantom_crb_access_2m; |
| break; |
| default: |
| DBGC ( phantom, "Phantom %p has bad BAR size\n", phantom ); |
| return -EINVAL; |
| } |
| |
| phantom->bar0 = pci_ioremap ( pci, bar0_start, bar0_size ); |
| if ( ! phantom->bar0 ) { |
| DBGC ( phantom, "Phantom %p could not map BAR0\n", phantom ); |
| return -EIO; |
| } |
| |
| /* Mark current CRB window as invalid, so that the first |
| * read/write will set the current window. |
| */ |
| phantom->crb_window = -1UL; |
| |
| return 0; |
| } |
| |
| /** |
| * Unhalt all PEGs |
| * |
| * @v phantom Phantom NIC |
| */ |
| static void phantom_unhalt_pegs ( struct phantom_nic *phantom ) { |
| uint32_t halt_status; |
| |
| halt_status = phantom_readl ( phantom, UNM_PEG_0_HALT_STATUS ); |
| phantom_writel ( phantom, halt_status, UNM_PEG_0_HALT_STATUS ); |
| halt_status = phantom_readl ( phantom, UNM_PEG_1_HALT_STATUS ); |
| phantom_writel ( phantom, halt_status, UNM_PEG_1_HALT_STATUS ); |
| halt_status = phantom_readl ( phantom, UNM_PEG_2_HALT_STATUS ); |
| phantom_writel ( phantom, halt_status, UNM_PEG_2_HALT_STATUS ); |
| halt_status = phantom_readl ( phantom, UNM_PEG_3_HALT_STATUS ); |
| phantom_writel ( phantom, halt_status, UNM_PEG_3_HALT_STATUS ); |
| halt_status = phantom_readl ( phantom, UNM_PEG_4_HALT_STATUS ); |
| phantom_writel ( phantom, halt_status, UNM_PEG_4_HALT_STATUS ); |
| } |
| |
| /** |
| * Initialise the Phantom command PEG |
| * |
| * @v phantom Phantom NIC |
| * @ret rc Return status code |
| */ |
| static int phantom_init_cmdpeg ( struct phantom_nic *phantom ) { |
| uint32_t cold_boot; |
| uint32_t sw_reset; |
| unsigned int retries; |
| uint32_t cmdpeg_state; |
| uint32_t last_cmdpeg_state = 0; |
| |
| /* Check for a previous initialisation. This could have |
| * happened if, for example, the BIOS used the UNDI API to |
| * drive the NIC prior to a full PXE boot. |
| */ |
| cmdpeg_state = phantom_readl ( phantom, UNM_NIC_REG_CMDPEG_STATE ); |
| if ( cmdpeg_state == UNM_NIC_REG_CMDPEG_STATE_INITIALIZE_ACK ) { |
| DBGC ( phantom, "Phantom %p command PEG already initialized\n", |
| phantom ); |
| /* Unhalt the PEGs. Previous firmware (e.g. BOFM) may |
| * have halted the PEGs to prevent internal bus |
| * collisions when the BIOS re-reads the expansion ROM. |
| */ |
| phantom_unhalt_pegs ( phantom ); |
| return 0; |
| } |
| |
| /* If this was a cold boot, check that the hardware came up ok */ |
| cold_boot = phantom_readl ( phantom, UNM_CAM_RAM_COLD_BOOT ); |
| if ( cold_boot == UNM_CAM_RAM_COLD_BOOT_MAGIC ) { |
| DBGC ( phantom, "Phantom %p coming up from cold boot\n", |
| phantom ); |
| sw_reset = phantom_readl ( phantom, UNM_ROMUSB_GLB_SW_RESET ); |
| if ( sw_reset != UNM_ROMUSB_GLB_SW_RESET_MAGIC ) { |
| DBGC ( phantom, "Phantom %p reset failed: %08x\n", |
| phantom, sw_reset ); |
| return -EIO; |
| } |
| } else { |
| DBGC ( phantom, "Phantom %p coming up from warm boot " |
| "(%08x)\n", phantom, cold_boot ); |
| } |
| /* Clear cold-boot flag */ |
| phantom_writel ( phantom, 0, UNM_CAM_RAM_COLD_BOOT ); |
| |
| /* Set port modes */ |
| phantom_writel ( phantom, UNM_CAM_RAM_PORT_MODE_AUTO_NEG_1G, |
| UNM_CAM_RAM_WOL_PORT_MODE ); |
| |
| /* Pass dummy DMA area to card */ |
| phantom_write_hilo ( phantom, 0, |
| UNM_NIC_REG_DUMMY_BUF_ADDR_LO, |
| UNM_NIC_REG_DUMMY_BUF_ADDR_HI ); |
| phantom_writel ( phantom, UNM_NIC_REG_DUMMY_BUF_INIT, |
| UNM_NIC_REG_DUMMY_BUF ); |
| |
| /* Tell the hardware that tuning is complete */ |
| phantom_writel ( phantom, UNM_ROMUSB_GLB_PEGTUNE_DONE_MAGIC, |
| UNM_ROMUSB_GLB_PEGTUNE_DONE ); |
| |
| /* Wait for command PEG to finish initialising */ |
| DBGC ( phantom, "Phantom %p initialising command PEG (will take up to " |
| "%d seconds)...\n", phantom, PHN_CMDPEG_INIT_TIMEOUT_SEC ); |
| for ( retries = 0; retries < PHN_CMDPEG_INIT_TIMEOUT_SEC; retries++ ) { |
| cmdpeg_state = phantom_readl ( phantom, |
| UNM_NIC_REG_CMDPEG_STATE ); |
| if ( cmdpeg_state != last_cmdpeg_state ) { |
| DBGC ( phantom, "Phantom %p command PEG state is " |
| "%08x after %d seconds...\n", |
| phantom, cmdpeg_state, retries ); |
| last_cmdpeg_state = cmdpeg_state; |
| } |
| if ( cmdpeg_state == UNM_NIC_REG_CMDPEG_STATE_INITIALIZED ) { |
| /* Acknowledge the PEG initialisation */ |
| phantom_writel ( phantom, |
| UNM_NIC_REG_CMDPEG_STATE_INITIALIZE_ACK, |
| UNM_NIC_REG_CMDPEG_STATE ); |
| return 0; |
| } |
| mdelay ( 1000 ); |
| } |
| |
| DBGC ( phantom, "Phantom %p timed out waiting for command PEG to " |
| "initialise (status %08x)\n", phantom, cmdpeg_state ); |
| return -ETIMEDOUT; |
| } |
| |
| /** |
| * Read Phantom MAC address |
| * |
| * @v phanton_port Phantom NIC |
| * @v hw_addr Buffer to fill with MAC address |
| */ |
| static void phantom_get_macaddr ( struct phantom_nic *phantom, |
| uint8_t *hw_addr ) { |
| union { |
| uint8_t mac_addr[2][ETH_ALEN]; |
| uint32_t dwords[3]; |
| } u; |
| unsigned long offset; |
| int i; |
| |
| /* Read the three dwords that include this MAC address and one other */ |
| offset = ( UNM_CAM_RAM_MAC_ADDRS + |
| ( 12 * ( phantom->port / 2 ) ) ); |
| for ( i = 0 ; i < 3 ; i++, offset += 4 ) { |
| u.dwords[i] = phantom_readl ( phantom, offset ); |
| } |
| |
| /* Copy out the relevant MAC address */ |
| for ( i = 0 ; i < ETH_ALEN ; i++ ) { |
| hw_addr[ ETH_ALEN - i - 1 ] = |
| u.mac_addr[ phantom->port & 1 ][i]; |
| } |
| DBGC ( phantom, "Phantom %p MAC address is %s\n", |
| phantom, eth_ntoa ( hw_addr ) ); |
| } |
| |
| /** |
| * Check Phantom is enabled for boot |
| * |
| * @v phanton_port Phantom NIC |
| * @ret rc Return status code |
| * |
| * This is something of an ugly hack to accommodate an OEM |
| * requirement. The NIC has only one expansion ROM BAR, rather than |
| * one per port. To allow individual ports to be selectively |
| * enabled/disabled for PXE boot (as required), we must therefore |
| * leave the expansion ROM always enabled, and place the per-port |
| * enable/disable logic within the iPXE driver. |
| */ |
| static int phantom_check_boot_enable ( struct phantom_nic *phantom ) { |
| unsigned long boot_enable; |
| |
| boot_enable = phantom_readl ( phantom, UNM_CAM_RAM_BOOT_ENABLE ); |
| if ( ! ( boot_enable & ( 1 << phantom->port ) ) ) { |
| DBGC ( phantom, "Phantom %p PXE boot is disabled\n", |
| phantom ); |
| return -ENOTSUP; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * Initialise Phantom receive PEG |
| * |
| * @v phantom Phantom NIC |
| * @ret rc Return status code |
| */ |
| static int phantom_init_rcvpeg ( struct phantom_nic *phantom ) { |
| unsigned int retries; |
| uint32_t rcvpeg_state; |
| uint32_t last_rcvpeg_state = 0; |
| |
| DBGC ( phantom, "Phantom %p initialising receive PEG (will take up to " |
| "%d seconds)...\n", phantom, PHN_RCVPEG_INIT_TIMEOUT_SEC ); |
| for ( retries = 0; retries < PHN_RCVPEG_INIT_TIMEOUT_SEC; retries++ ) { |
| rcvpeg_state = phantom_readl ( phantom, |
| UNM_NIC_REG_RCVPEG_STATE ); |
| if ( rcvpeg_state != last_rcvpeg_state ) { |
| DBGC ( phantom, "Phantom %p receive PEG state is " |
| "%08x after %d seconds...\n", |
| phantom, rcvpeg_state, retries ); |
| last_rcvpeg_state = rcvpeg_state; |
| } |
| if ( rcvpeg_state == UNM_NIC_REG_RCVPEG_STATE_INITIALIZED ) |
| return 0; |
| mdelay ( 1000 ); |
| } |
| |
| DBGC ( phantom, "Phantom %p timed out waiting for receive PEG to " |
| "initialise (status %08x)\n", phantom, rcvpeg_state ); |
| return -ETIMEDOUT; |
| } |
| |
| /** |
| * Probe PCI device |
| * |
| * @v pci PCI device |
| * @v id PCI ID |
| * @ret rc Return status code |
| */ |
| static int phantom_probe ( struct pci_device *pci ) { |
| struct net_device *netdev; |
| struct phantom_nic *phantom; |
| struct settings *parent_settings; |
| unsigned int busdevfn; |
| int rc; |
| |
| /* Allocate Phantom device */ |
| netdev = alloc_etherdev ( sizeof ( *phantom ) ); |
| if ( ! netdev ) { |
| rc = -ENOMEM; |
| goto err_alloc_etherdev; |
| } |
| netdev_init ( netdev, &phantom_operations ); |
| phantom = netdev->priv; |
| pci_set_drvdata ( pci, netdev ); |
| netdev->dev = &pci->dev; |
| memset ( phantom, 0, sizeof ( *phantom ) ); |
| phantom->port = PCI_FUNC ( pci->busdevfn ); |
| assert ( phantom->port < PHN_MAX_NUM_PORTS ); |
| settings_init ( &phantom->settings, |
| &phantom_settings_operations, |
| &netdev->refcnt, &phantom_settings_scope ); |
| |
| /* Fix up PCI device */ |
| adjust_pci_device ( pci ); |
| |
| /* Map CRB */ |
| if ( ( rc = phantom_map_crb ( phantom, pci ) ) != 0 ) |
| goto err_map_crb; |
| |
| /* BUG5945 - need to hack PCI config space on P3 B1 silicon. |
| * B2 will have this fixed; remove this hack when B1 is no |
| * longer in use. |
| */ |
| busdevfn = pci->busdevfn; |
| if ( PCI_FUNC ( busdevfn ) == 0 ) { |
| unsigned int i; |
| for ( i = 0 ; i < 8 ; i++ ) { |
| uint32_t temp; |
| pci->busdevfn = |
| PCI_BUSDEVFN ( PCI_SEG ( busdevfn ), |
| PCI_BUS ( busdevfn ), |
| PCI_SLOT ( busdevfn ), i ); |
| pci_read_config_dword ( pci, 0xc8, &temp ); |
| pci_read_config_dword ( pci, 0xc8, &temp ); |
| pci_write_config_dword ( pci, 0xc8, 0xf1000 ); |
| } |
| pci->busdevfn = busdevfn; |
| } |
| |
| /* Initialise the command PEG */ |
| if ( ( rc = phantom_init_cmdpeg ( phantom ) ) != 0 ) |
| goto err_init_cmdpeg; |
| |
| /* Initialise the receive PEG */ |
| if ( ( rc = phantom_init_rcvpeg ( phantom ) ) != 0 ) |
| goto err_init_rcvpeg; |
| |
| /* Read MAC addresses */ |
| phantom_get_macaddr ( phantom, netdev->hw_addr ); |
| |
| /* Skip if boot disabled on NIC */ |
| if ( ( rc = phantom_check_boot_enable ( phantom ) ) != 0 ) |
| goto err_check_boot_enable; |
| |
| /* Register network devices */ |
| if ( ( rc = register_netdev ( netdev ) ) != 0 ) { |
| DBGC ( phantom, "Phantom %p could not register net device: " |
| "%s\n", phantom, strerror ( rc ) ); |
| goto err_register_netdev; |
| } |
| |
| /* Register settings blocks */ |
| parent_settings = netdev_settings ( netdev ); |
| if ( ( rc = register_settings ( &phantom->settings, |
| parent_settings, "clp" ) ) != 0 ) { |
| DBGC ( phantom, "Phantom %p could not register settings: " |
| "%s\n", phantom, strerror ( rc ) ); |
| goto err_register_settings; |
| } |
| |
| return 0; |
| |
| unregister_settings ( &phantom->settings ); |
| err_register_settings: |
| unregister_netdev ( netdev ); |
| err_register_netdev: |
| err_check_boot_enable: |
| err_init_rcvpeg: |
| err_init_cmdpeg: |
| err_map_crb: |
| netdev_nullify ( netdev ); |
| netdev_put ( netdev ); |
| err_alloc_etherdev: |
| return rc; |
| } |
| |
| /** |
| * Remove PCI device |
| * |
| * @v pci PCI device |
| */ |
| static void phantom_remove ( struct pci_device *pci ) { |
| struct net_device *netdev = pci_get_drvdata ( pci ); |
| struct phantom_nic *phantom = netdev->priv; |
| |
| unregister_settings ( &phantom->settings ); |
| unregister_netdev ( netdev ); |
| netdev_nullify ( netdev ); |
| netdev_put ( netdev ); |
| } |
| |
| /** Phantom PCI IDs */ |
| static struct pci_device_id phantom_nics[] = { |
| PCI_ROM ( 0x4040, 0x0100, "nx", "NX", 0 ), |
| }; |
| |
| /** Phantom PCI driver */ |
| struct pci_driver phantom_driver __pci_driver = { |
| .ids = phantom_nics, |
| .id_count = ( sizeof ( phantom_nics ) / sizeof ( phantom_nics[0] ) ), |
| .probe = phantom_probe, |
| .remove = phantom_remove, |
| }; |