From e44e3482bdb4d0ebde2d8b41830ac2cdb07948fb Mon Sep 17 00:00:00 2001 From: Yang Zhang Date: Fri, 28 Aug 2015 09:58:54 +0800 Subject: Add qemu 2.4.0 Change-Id: Ic99cbad4b61f8b127b7dc74d04576c0bcbaaf4f5 Signed-off-by: Yang Zhang --- qemu/roms/ipxe/src/drivers/net/phantom/phantom.c | 2177 ++++++++++++++++++++++ 1 file changed, 2177 insertions(+) create mode 100644 qemu/roms/ipxe/src/drivers/net/phantom/phantom.c (limited to 'qemu/roms/ipxe/src/drivers/net/phantom/phantom.c') diff --git a/qemu/roms/ipxe/src/drivers/net/phantom/phantom.c b/qemu/roms/ipxe/src/drivers/net/phantom/phantom.c new file mode 100644 index 000000000..e70ded08c --- /dev/null +++ b/qemu/roms/ipxe/src/drivers/net/phantom/phantom.c @@ -0,0 +1,2177 @@ +/* + * Copyright (C) 2008 Michael Brown . + * 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. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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_dma ( 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_dma ( 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_dma ( 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_dma ( 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 ( netdev ); + 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 ( netdev ); + 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 ( netdev ); + int rc; + + /* Allocate and zero descriptor rings */ + phantom->desc = malloc_dma ( 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_dma ( 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 ( netdev ); + 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_dma ( 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 ( netdev ); + 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 ( netdev ); + 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 ( netdev ); + + 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 = ioremap ( 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; + 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 ( netdev ); + 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. + */ + if ( PCI_FUNC ( pci->busdevfn ) == 0 ) { + unsigned int i; + for ( i = 0 ; i < 8 ; i++ ) { + uint32_t temp; + pci->busdevfn = + PCI_BUSDEVFN ( PCI_BUS ( pci->busdevfn ), + PCI_SLOT ( pci->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 = PCI_BUSDEVFN ( PCI_BUS ( pci->busdevfn ), + PCI_SLOT ( pci->busdevfn ), 0 ); + } + + /* 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 ( netdev ); + + 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, +}; -- cgit 1.2.3-korg