| /* | |
| * 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, | |
| }; |