mirror of https://github.com/ipxe/ipxe.git
1338 lines
34 KiB
C
1338 lines
34 KiB
C
/************************************************* -*- linux-c -*-
|
|
* Myricom 10Gb Network Interface Card Software
|
|
* Copyright 2009, Myricom, Inc.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License,
|
|
* version 2, as published by the Free Software Foundation.
|
|
*
|
|
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
****************************************************************/
|
|
|
|
FILE_LICENCE ( GPL2_ONLY );
|
|
|
|
/*
|
|
* Author: Glenn Brown <glenn@myri.com>
|
|
*/
|
|
|
|
/*
|
|
* General Theory of Operation
|
|
*
|
|
* This is a minimal Myricom 10 gigabit Ethernet driver for network
|
|
* boot.
|
|
*
|
|
* Initialization
|
|
*
|
|
* myri10ge_pci_probe() is called by iPXE during initialization.
|
|
* Minimal NIC initialization is performed to minimize resources
|
|
* consumed when the driver is resident but unused.
|
|
*
|
|
* Network Boot
|
|
*
|
|
* myri10ge_net_open() is called by iPXE before attempting to network
|
|
* boot from the card. Packet buffers are allocated and the NIC
|
|
* interface is initialized.
|
|
*
|
|
* Transmit
|
|
*
|
|
* myri10ge_net_transmit() enqueues frames for transmission by writing
|
|
* discriptors to the NIC's tx ring. For simplicity and to avoid
|
|
* copies, we always have the NIC DMA up the packet. The sent I/O
|
|
* buffer is released once the NIC signals myri10ge_interrupt_handler()
|
|
* that the send has completed.
|
|
*
|
|
* Receive
|
|
*
|
|
* Receives are posted to the NIC's receive ring. The NIC fills a
|
|
* DMAable receive_completion ring with completion notifications.
|
|
* myri10ge_net_poll() polls for these receive notifications, posts
|
|
* replacement receive buffers to the NIC, and passes received frames
|
|
* to netdev_rx().
|
|
*
|
|
* NonVolatile Storage
|
|
*
|
|
* This driver supports NonVolatile Storage (nvs) in the NIC EEPROM.
|
|
* If the last EEPROM block is not otherwise filled, we tell
|
|
* iPXE it may store NonVolatile Options (nvo) there.
|
|
*/
|
|
|
|
/*
|
|
* Debugging levels:
|
|
* - DBG() is for any errors, i.e. failed alloc_iob(), malloc_dma(),
|
|
* TX overflow, corrupted packets, ...
|
|
* - DBG2() is for successful events, like packet received,
|
|
* packet transmitted, and other general notifications.
|
|
* - DBGP() prints the name of each called function on entry
|
|
*/
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <byteswap.h>
|
|
#include <errno.h>
|
|
#include <ipxe/ethernet.h>
|
|
#include <ipxe/if_ether.h>
|
|
#include <ipxe/iobuf.h>
|
|
#include <ipxe/malloc.h>
|
|
#include <ipxe/netdevice.h>
|
|
#include <ipxe/nvo.h>
|
|
#include <ipxe/nvs.h>
|
|
#include <ipxe/pci.h>
|
|
#include <ipxe/timer.h>
|
|
|
|
#include "myri10ge_mcp.h"
|
|
|
|
/****************************************************************
|
|
* Forward declarations
|
|
****************************************************************/
|
|
|
|
/* PCI driver entry points */
|
|
|
|
static int myri10ge_pci_probe ( struct pci_device*,
|
|
const struct pci_device_id* );
|
|
static void myri10ge_pci_remove ( struct pci_device* );
|
|
|
|
/* Network device operations */
|
|
|
|
static void myri10ge_net_close ( struct net_device* );
|
|
static void myri10ge_net_irq ( struct net_device*, int enable );
|
|
static int myri10ge_net_open ( struct net_device* );
|
|
static void myri10ge_net_poll ( struct net_device* );
|
|
static int myri10ge_net_transmit ( struct net_device*, struct io_buffer* );
|
|
|
|
/****************************************************************
|
|
* Constants
|
|
****************************************************************/
|
|
|
|
/* Maximum ring indices, used to wrap ring indices. These must be 2**N-1. */
|
|
|
|
#define MYRI10GE_TRANSMIT_WRAP 1U
|
|
#define MYRI10GE_RECEIVE_WRAP 7U
|
|
#define MYRI10GE_RECEIVE_COMPLETION_WRAP 31U
|
|
|
|
/****************************************************************
|
|
* Driver internal data types.
|
|
****************************************************************/
|
|
|
|
/* Structure holding all DMA buffers for a NIC, which we will
|
|
allocated as contiguous read/write DMAable memory when the NIC is
|
|
initialized. */
|
|
|
|
struct myri10ge_dma_buffers
|
|
{
|
|
/* The NIC DMAs receive completion notifications into this ring */
|
|
|
|
mcp_slot_t receive_completion[1+MYRI10GE_RECEIVE_COMPLETION_WRAP];
|
|
|
|
/* Interrupt details are DMAd here before interrupting. */
|
|
|
|
mcp_irq_data_t irq_data; /* 64B */
|
|
|
|
/* NIC command completion status is DMAd here. */
|
|
|
|
mcp_cmd_response_t command_response; /* 8B */
|
|
};
|
|
|
|
struct myri10ge_private
|
|
{
|
|
/* Interrupt support */
|
|
|
|
uint32 *irq_claim; /* in NIC SRAM */
|
|
uint32 *irq_deassert; /* in NIC SRAM */
|
|
|
|
/* DMA buffers. */
|
|
|
|
struct myri10ge_dma_buffers *dma;
|
|
|
|
/*
|
|
* Transmit state.
|
|
*
|
|
* The counts here are uint32 for easy comparison with
|
|
* priv->dma->irq_data.send_done_count and with each other.
|
|
*/
|
|
|
|
mcp_kreq_ether_send_t *transmit_ring; /* in NIC SRAM */
|
|
uint32 transmit_ring_wrap;
|
|
uint32 transmits_posted;
|
|
uint32 transmits_done;
|
|
struct io_buffer *transmit_iob[1 + MYRI10GE_TRANSMIT_WRAP];
|
|
|
|
/*
|
|
* Receive state.
|
|
*/
|
|
|
|
mcp_kreq_ether_recv_t *receive_post_ring; /* in NIC SRAM */
|
|
unsigned int receive_post_ring_wrap;
|
|
unsigned int receives_posted;
|
|
unsigned int receives_done;
|
|
struct io_buffer *receive_iob[1 + MYRI10GE_RECEIVE_WRAP];
|
|
|
|
/* Address for writing commands to the firmware.
|
|
BEWARE: the value must be written 32 bits at a time. */
|
|
|
|
mcp_cmd_t *command;
|
|
|
|
/*
|
|
* Nonvolatile Storage for configuration options.
|
|
*/
|
|
|
|
struct nvs_device nvs;
|
|
struct nvo_block nvo;
|
|
unsigned int nvo_registered;
|
|
|
|
/* Cached PCI capability locations. */
|
|
|
|
uint8 pci_cap_vs;
|
|
};
|
|
|
|
/****************************************************************
|
|
* Driver internal functions.
|
|
****************************************************************/
|
|
|
|
/* Print ring status when debugging. Use this only after a printed
|
|
value changes. */
|
|
|
|
#define DBG2_RINGS( priv ) \
|
|
DBG2 ( "tx %x/%x rx %x/%x in %s() \n", \
|
|
( priv ) ->transmits_done, ( priv ) -> transmits_posted, \
|
|
( priv ) ->receives_done, ( priv ) -> receives_posted, \
|
|
__FUNCTION__ )
|
|
|
|
/*
|
|
* Return a pointer to the driver private data for a network device.
|
|
*
|
|
* @v netdev Network device created by this driver.
|
|
* @ret priv The corresponding driver private data.
|
|
*/
|
|
static inline struct myri10ge_private *myri10ge_priv ( struct net_device *nd )
|
|
{
|
|
/* Our private data always follows the network device in memory,
|
|
since we use alloc_netdev() to allocate the storage. */
|
|
|
|
return ( struct myri10ge_private * ) ( nd + 1 );
|
|
}
|
|
|
|
/*
|
|
* Convert a Myri10ge driver private data pointer to a netdev pointer.
|
|
*
|
|
* @v p Myri10ge device private data.
|
|
* @ret r The corresponding network device.
|
|
*/
|
|
static inline struct net_device *myri10ge_netdev ( struct myri10ge_private *p )
|
|
{
|
|
return ( ( struct net_device * ) p ) - 1;
|
|
}
|
|
|
|
/*
|
|
* Convert a network device pointer to a PCI device pointer.
|
|
*
|
|
* @v netdev A Network Device.
|
|
* @ret r The corresponding PCI device.
|
|
*/
|
|
static inline struct pci_device *myri10ge_pcidev ( struct net_device *netdev )
|
|
{
|
|
return container_of (netdev->dev, struct pci_device, dev);
|
|
}
|
|
|
|
/*
|
|
* Pass a receive buffer to the NIC to be filled.
|
|
*
|
|
* @v priv The network device to receive the buffer.
|
|
* @v iob The I/O buffer to fill.
|
|
*
|
|
* Receive buffers are filled in FIFO order.
|
|
*/
|
|
static void myri10ge_post_receive ( struct myri10ge_private *priv,
|
|
struct io_buffer *iob )
|
|
{
|
|
unsigned int receives_posted;
|
|
mcp_kreq_ether_recv_t *request;
|
|
|
|
/* Record the posted I/O buffer, to be passed to netdev_rx() on
|
|
receive. */
|
|
|
|
receives_posted = priv->receives_posted;
|
|
priv->receive_iob[receives_posted & MYRI10GE_RECEIVE_WRAP] = iob;
|
|
|
|
/* Post the receive. */
|
|
|
|
request = &priv->receive_post_ring[receives_posted
|
|
& priv->receive_post_ring_wrap];
|
|
request->addr_high = 0;
|
|
wmb();
|
|
request->addr_low = htonl ( virt_to_bus ( iob->data ) );
|
|
priv->receives_posted = ++receives_posted;
|
|
}
|
|
|
|
/*
|
|
* Execute a command on the NIC.
|
|
*
|
|
* @v priv NIC to perform the command.
|
|
* @v cmd The command to perform.
|
|
* @v data I/O copy buffer for parameters/results
|
|
* @ret rc 0 on success, else an error code.
|
|
*/
|
|
static int myri10ge_command ( struct myri10ge_private *priv,
|
|
uint32 cmd,
|
|
uint32 data[3] )
|
|
{
|
|
int i;
|
|
mcp_cmd_t *command;
|
|
uint32 result;
|
|
unsigned int slept_ms;
|
|
volatile mcp_cmd_response_t *response;
|
|
|
|
DBGP ( "myri10ge_command ( ,%d, ) \n", cmd );
|
|
command = priv->command;
|
|
response = &priv->dma->command_response;
|
|
|
|
/* Mark the command as incomplete. */
|
|
|
|
response->result = 0xFFFFFFFF;
|
|
|
|
/* Pass the command to the NIC. */
|
|
|
|
command->cmd = htonl ( cmd );
|
|
command->data0 = htonl ( data[0] );
|
|
command->data1 = htonl ( data[1] );
|
|
command->data2 = htonl ( data[2] );
|
|
command->response_addr.high = 0;
|
|
command->response_addr.low
|
|
= htonl ( virt_to_bus ( &priv->dma->command_response ) );
|
|
for ( i=0; i<36; i+=4 )
|
|
* ( uint32 * ) &command->pad[i] = 0;
|
|
wmb();
|
|
* ( uint32 * ) &command->pad[36] = 0;
|
|
|
|
/* Wait up to 2 seconds for a response. */
|
|
|
|
for ( slept_ms=0; slept_ms<2000; slept_ms++ ) {
|
|
result = response->result;
|
|
if ( result == 0 ) {
|
|
data[0] = ntohl ( response->data );
|
|
return 0;
|
|
} else if ( result != 0xFFFFFFFF ) {
|
|
DBG ( "cmd%d:0x%x\n",
|
|
cmd,
|
|
ntohl ( response->result ) );
|
|
return -EIO;
|
|
}
|
|
udelay ( 1000 );
|
|
rmb();
|
|
}
|
|
DBG ( "cmd%d:timed out\n", cmd );
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
/*
|
|
* Handle any pending interrupt.
|
|
*
|
|
* @v netdev Device being polled for interrupts.
|
|
*
|
|
* This is called periodically to let the driver check for interrupts.
|
|
*/
|
|
static void myri10ge_interrupt_handler ( struct net_device *netdev )
|
|
{
|
|
struct myri10ge_private *priv;
|
|
mcp_irq_data_t *irq_data;
|
|
uint8 valid;
|
|
|
|
priv = myri10ge_priv ( netdev );
|
|
irq_data = &priv->dma->irq_data;
|
|
|
|
/* Return if there was no interrupt. */
|
|
|
|
rmb();
|
|
valid = irq_data->valid;
|
|
if ( !valid )
|
|
return;
|
|
DBG2 ( "irq " );
|
|
|
|
/* Tell the NIC to deassert the interrupt and clear
|
|
irq_data->valid.*/
|
|
|
|
*priv->irq_deassert = 0; /* any value is OK. */
|
|
mb();
|
|
|
|
/* Handle any new receives. */
|
|
|
|
if ( valid & 1 ) {
|
|
|
|
/* Pass the receive interrupt token back to the NIC. */
|
|
|
|
DBG2 ( "rx " );
|
|
*priv->irq_claim = htonl ( 3 );
|
|
wmb();
|
|
}
|
|
|
|
/* Handle any sent packet by freeing its I/O buffer, now that
|
|
we know it has been DMAd. */
|
|
|
|
if ( valid & 2 ) {
|
|
unsigned int nic_done_count;
|
|
|
|
DBG2 ( "snt " );
|
|
nic_done_count = ntohl ( priv->dma->irq_data.send_done_count );
|
|
while ( priv->transmits_done != nic_done_count ) {
|
|
struct io_buffer *iob;
|
|
|
|
iob = priv->transmit_iob [priv->transmits_done
|
|
& MYRI10GE_TRANSMIT_WRAP];
|
|
DBG2 ( "%p ", iob );
|
|
netdev_tx_complete ( netdev, iob );
|
|
++priv->transmits_done;
|
|
}
|
|
}
|
|
|
|
/* Record any statistics update. */
|
|
|
|
if ( irq_data->stats_updated ) {
|
|
|
|
/* Update the link status. */
|
|
|
|
DBG2 ( "stats " );
|
|
if ( ntohl ( irq_data->link_up ) == MXGEFW_LINK_UP )
|
|
netdev_link_up ( netdev );
|
|
else
|
|
netdev_link_down ( netdev );
|
|
|
|
/* Ignore all error counters from the NIC. */
|
|
}
|
|
|
|
/* Wait for the interrupt to be deasserted, as indicated by
|
|
irq_data->valid, which is set by the NIC after the deassert. */
|
|
|
|
DBG2 ( "wait " );
|
|
do {
|
|
mb();
|
|
} while ( irq_data->valid );
|
|
|
|
/* Claim the interrupt to enable future interrupt generation. */
|
|
|
|
DBG2 ( "claim\n" );
|
|
* ( priv->irq_claim + 1 ) = htonl ( 3 );
|
|
mb();
|
|
}
|
|
|
|
/* Constants for reading the STRING_SPECS via the Myricom
|
|
Vendor Specific PCI configuration space capability. */
|
|
|
|
#define VS_EEPROM_READ_ADDR ( vs + 0x04 )
|
|
#define VS_EEPROM_READ_DATA ( vs + 0x08 )
|
|
#define VS_EEPROM_WRITE ( vs + 0x0C )
|
|
#define VS_ADDR ( vs + 0x18 )
|
|
#define VS_DATA ( vs + 0x14 )
|
|
#define VS_MODE ( vs + 0x10 )
|
|
#define VS_MODE_READ32 0x3
|
|
#define VS_MODE_LOCATE 0x8
|
|
#define VS_LOCATE_STRING_SPECS 0x3
|
|
#define VS_MODE_EEPROM_STREAM_WRITE 0xB
|
|
|
|
/*
|
|
* Read MAC address from its 'string specs' via the vendor-specific
|
|
* capability. (This capability allows NIC SRAM and ROM to be read
|
|
* before it is mapped.)
|
|
*
|
|
* @v pci The device.
|
|
* @v vs Offset of the PCI Vendor-Specific Capability.
|
|
* @v mac Buffer to store the MAC address.
|
|
* @ret rc Returns 0 on success, else an error code.
|
|
*/
|
|
static int mac_address_from_string_specs ( struct pci_device *pci,
|
|
unsigned int vs,
|
|
uint8 mac[ETH_ALEN] )
|
|
{
|
|
char string_specs[256];
|
|
char *ptr, *limit;
|
|
char *to = string_specs;
|
|
uint32 addr;
|
|
uint32 len;
|
|
int mac_set = 0;
|
|
|
|
/* Locate the String specs in LANai SRAM. */
|
|
|
|
pci_write_config_byte ( pci, VS_MODE, VS_MODE_LOCATE );
|
|
pci_write_config_dword ( pci, VS_ADDR, VS_LOCATE_STRING_SPECS );
|
|
pci_read_config_dword ( pci, VS_ADDR, &addr );
|
|
pci_read_config_dword ( pci, VS_DATA, &len );
|
|
DBG2 ( "ss@%x,%x\n", addr, len );
|
|
|
|
/* Copy in the string specs. Use 32-bit reads for performance. */
|
|
|
|
if ( len > sizeof ( string_specs ) || ( len & 3 ) ) {
|
|
pci_write_config_byte ( pci, VS_MODE, 0 );
|
|
DBG ( "SS too big\n" );
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
pci_write_config_byte ( pci, VS_MODE, VS_MODE_READ32 );
|
|
while ( len >= 4 ) {
|
|
uint32 tmp;
|
|
|
|
pci_write_config_byte ( pci, VS_ADDR, addr );
|
|
pci_read_config_dword ( pci, VS_DATA, &tmp );
|
|
tmp = ntohl ( tmp );
|
|
memcpy ( to, &tmp, 4 );
|
|
to += 4;
|
|
addr += 4;
|
|
len -= 4;
|
|
}
|
|
pci_write_config_byte ( pci, VS_MODE, 0 );
|
|
|
|
/* Parse the string specs. */
|
|
|
|
DBG2 ( "STRING_SPECS:\n" );
|
|
ptr = string_specs;
|
|
limit = string_specs + sizeof ( string_specs );
|
|
while ( *ptr != '\0' && ptr < limit ) {
|
|
DBG2 ( "%s\n", ptr );
|
|
if ( memcmp ( ptr, "MAC=", 4 ) == 0 ) {
|
|
unsigned int i;
|
|
|
|
ptr += 4;
|
|
for ( i=0; i<6; i++ ) {
|
|
if ( ( ptr + 2 ) > limit ) {
|
|
DBG ( "bad MAC addr\n" );
|
|
return -ENOTSUP;
|
|
}
|
|
mac[i] = strtoul ( ptr, &ptr, 16 );
|
|
ptr += 1;
|
|
}
|
|
mac_set = 1;
|
|
}
|
|
else
|
|
while ( ptr < limit && *ptr++ );
|
|
}
|
|
|
|
/* Verify we parsed all we need. */
|
|
|
|
if ( !mac_set ) {
|
|
DBG ( "no MAC addr\n" );
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
DBG2 ( "MAC %02x:%02x:%02x:%02x:%02x:%02x\n",
|
|
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5] );
|
|
|
|
return 0;
|
|
}
|
|
|
|
/****************************************************************
|
|
* NonVolatile Storage support
|
|
****************************************************************/
|
|
|
|
/*
|
|
* Fill a buffer with data read from nonvolatile storage.
|
|
*
|
|
* @v nvs The NonVolatile Storage device to be read.
|
|
* @v addr The first NonVolatile Storage address to be read.
|
|
* @v _buf Pointer to the data buffer to be filled.
|
|
* @v len The number of bytes to copy.
|
|
* @ret rc 0 on success, else nonzero.
|
|
*/
|
|
static int myri10ge_nvs_read ( struct nvs_device *nvs,
|
|
unsigned int addr,
|
|
void *_buf,
|
|
size_t len )
|
|
{
|
|
struct myri10ge_private *priv =
|
|
container_of (nvs, struct myri10ge_private, nvs);
|
|
struct pci_device *pci = myri10ge_pcidev ( myri10ge_netdev ( priv ) );
|
|
unsigned int vs = priv->pci_cap_vs;
|
|
unsigned char *buf = (unsigned char *) _buf;
|
|
unsigned int data;
|
|
unsigned int i, j;
|
|
|
|
DBGP ( "myri10ge_nvs_read\n" );
|
|
|
|
/* Issue the first read address. */
|
|
|
|
pci_write_config_byte ( pci, VS_EEPROM_READ_ADDR + 3, addr>>16 );
|
|
pci_write_config_byte ( pci, VS_EEPROM_READ_ADDR + 2, addr>>8 );
|
|
pci_write_config_byte ( pci, VS_EEPROM_READ_ADDR + 1, addr );
|
|
addr++;
|
|
|
|
/* Issue all the reads, and harvest the results every 4th issue. */
|
|
|
|
for ( i=0; i<len; ++i,addr++ ) {
|
|
|
|
/* Issue the next read address, updating only the
|
|
bytes that need updating. We always update the
|
|
LSB, which triggers the read. */
|
|
|
|
if ( ( addr & 0xff ) == 0 ) {
|
|
if ( ( addr & 0xffff ) == 0 ) {
|
|
pci_write_config_byte ( pci,
|
|
VS_EEPROM_READ_ADDR + 3,
|
|
addr >> 16 );
|
|
}
|
|
pci_write_config_byte ( pci,
|
|
VS_EEPROM_READ_ADDR + 2,
|
|
addr >> 8 );
|
|
}
|
|
pci_write_config_byte ( pci, VS_EEPROM_READ_ADDR + 1, addr );
|
|
|
|
/* If 4 data bytes are available, read them with a single read. */
|
|
|
|
if ( ( i & 3 ) == 3 ) {
|
|
pci_read_config_dword ( pci,
|
|
VS_EEPROM_READ_DATA,
|
|
&data );
|
|
for ( j=0; j<4; j++ ) {
|
|
buf[i-j] = data;
|
|
data >>= 8;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Harvest any remaining results. */
|
|
|
|
if ( ( i & 3 ) != 0 ) {
|
|
pci_read_config_dword ( pci, VS_EEPROM_READ_DATA, &data );
|
|
for ( j=1; j<=(i&3); j++ ) {
|
|
buf[i-j] = data;
|
|
data >>= 8;
|
|
}
|
|
}
|
|
|
|
DBGP_HDA ( addr - len, _buf, len );
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Write a buffer into nonvolatile storage.
|
|
*
|
|
* @v nvs The NonVolatile Storage device to be written.
|
|
* @v address The NonVolatile Storage address to be written.
|
|
* @v _buf Pointer to the data to be written.
|
|
* @v len Length of the buffer to be written.
|
|
* @ret rc 0 on success, else nonzero.
|
|
*/
|
|
static int myri10ge_nvs_write ( struct nvs_device *nvs,
|
|
unsigned int addr,
|
|
const void *_buf,
|
|
size_t len )
|
|
{
|
|
struct myri10ge_private *priv =
|
|
container_of (nvs, struct myri10ge_private, nvs);
|
|
struct pci_device *pci = myri10ge_pcidev ( myri10ge_netdev ( priv ) );
|
|
unsigned int vs = priv->pci_cap_vs;
|
|
const unsigned char *buf = (const unsigned char *)_buf;
|
|
unsigned int i;
|
|
uint8 verify;
|
|
|
|
DBGP ( "nvs_write " );
|
|
DBGP_HDA ( addr, _buf, len );
|
|
|
|
/* Start erase of the NonVolatile Options block. */
|
|
|
|
DBGP ( "erasing " );
|
|
pci_write_config_dword ( pci, VS_EEPROM_WRITE, ( addr << 8 ) | 0xff );
|
|
|
|
/* Wait for erase to complete. */
|
|
|
|
DBGP ( "waiting " );
|
|
pci_read_config_byte ( pci, VS_EEPROM_READ_DATA, &verify );
|
|
while ( verify != 0xff ) {
|
|
pci_write_config_byte ( pci, VS_EEPROM_READ_ADDR + 1, addr );
|
|
pci_read_config_byte ( pci, VS_EEPROM_READ_DATA, &verify );
|
|
}
|
|
|
|
/* Write the data one byte at a time. */
|
|
|
|
DBGP ( "writing " );
|
|
pci_write_config_byte ( pci, VS_MODE, VS_MODE_EEPROM_STREAM_WRITE );
|
|
pci_write_config_dword ( pci, VS_ADDR, addr );
|
|
for (i=0; i<len; i++, addr++)
|
|
pci_write_config_byte ( pci, VS_DATA, buf[i] );
|
|
pci_write_config_dword ( pci, VS_ADDR, 0xffffffff );
|
|
pci_write_config_byte ( pci, VS_MODE, 0 );
|
|
|
|
DBGP ( "done\n" );
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Initialize NonVolatile storage support for a device.
|
|
*
|
|
* @v priv Device private data for the device.
|
|
* @ret rc 0 on success, else an error code.
|
|
*/
|
|
|
|
static int myri10ge_nv_init ( struct myri10ge_private *priv )
|
|
{
|
|
int rc;
|
|
struct myri10ge_eeprom_header
|
|
{
|
|
uint8 __jump[8];
|
|
uint32 eeprom_len;
|
|
uint32 eeprom_segment_len;
|
|
uint32 mcp1_offset;
|
|
uint32 mcp2_offset;
|
|
uint32 version;
|
|
} hdr;
|
|
uint32 mcp2_len;
|
|
unsigned int nvo_fragment_pos;
|
|
|
|
DBGP ( "myri10ge_nv_init\n" );
|
|
|
|
/* Read the EEPROM header, and byteswap the fields we will use.
|
|
This is safe even though priv->nvs is not yet initialized. */
|
|
|
|
rc = myri10ge_nvs_read ( &priv->nvs, 0, &hdr, sizeof ( hdr ) );
|
|
if ( rc ) {
|
|
DBG ( "EEPROM header unreadable\n" );
|
|
return rc;
|
|
}
|
|
hdr.eeprom_len = ntohl ( hdr.eeprom_len );
|
|
hdr.eeprom_segment_len = ntohl ( hdr.eeprom_segment_len );
|
|
hdr.mcp2_offset = ntohl ( hdr.mcp2_offset );
|
|
hdr.version = ntohl ( hdr.version );
|
|
DBG2 ( "eelen:%xh seglen:%xh mcp2@%xh ver%d\n", hdr.eeprom_len,
|
|
hdr.eeprom_segment_len, hdr.mcp2_offset, hdr.version );
|
|
|
|
/* If the firmware does not support EEPROM writes, simply return. */
|
|
|
|
if ( hdr.version < 1 ) {
|
|
DBG ( "No EEPROM write support\n" );
|
|
return 0;
|
|
}
|
|
|
|
/* Read the length of MCP2. */
|
|
|
|
rc = myri10ge_nvs_read ( &priv->nvs, hdr.mcp2_offset, &mcp2_len, 4 );
|
|
mcp2_len = ntohl ( mcp2_len );
|
|
DBG2 ( "mcp2len:%xh\n", mcp2_len );
|
|
|
|
/* Determine the position of the NonVolatile Options fragment and
|
|
simply return if it overlaps other data. */
|
|
|
|
nvo_fragment_pos = hdr.eeprom_len - hdr.eeprom_segment_len;
|
|
if ( hdr.mcp2_offset + mcp2_len > nvo_fragment_pos ) {
|
|
DBG ( "EEPROM full\n" );
|
|
return 0;
|
|
}
|
|
|
|
/* Initilize NonVolatile Storage state. */
|
|
|
|
priv->nvs.word_len_log2 = 0;
|
|
priv->nvs.size = hdr.eeprom_len;
|
|
priv->nvs.block_size = hdr.eeprom_segment_len;
|
|
priv->nvs.read = myri10ge_nvs_read;
|
|
priv->nvs.write = myri10ge_nvs_write;
|
|
|
|
/* Register the NonVolatile Options storage. */
|
|
|
|
nvo_init ( &priv->nvo,
|
|
&priv->nvs,
|
|
nvo_fragment_pos, 0x200,
|
|
NULL,
|
|
& myri10ge_netdev (priv) -> refcnt );
|
|
rc = register_nvo ( &priv->nvo,
|
|
netdev_settings ( myri10ge_netdev ( priv ) ) );
|
|
if ( rc ) {
|
|
DBG ("register_nvo failed");
|
|
return rc;
|
|
}
|
|
|
|
priv->nvo_registered = 1;
|
|
DBG2 ( "NVO supported\n" );
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
myri10ge_nv_fini ( struct myri10ge_private *priv )
|
|
{
|
|
/* Simply return if nonvolatile access is not supported. */
|
|
|
|
if ( 0 == priv->nvo_registered )
|
|
return;
|
|
|
|
unregister_nvo ( &priv->nvo );
|
|
}
|
|
|
|
/****************************************************************
|
|
* iPXE PCI Device Driver API functions
|
|
****************************************************************/
|
|
|
|
/*
|
|
* Initialize the PCI device.
|
|
*
|
|
* @v pci The device's associated pci_device structure.
|
|
* @v id The PCI device + vendor id.
|
|
* @ret rc Returns zero if successfully initialized.
|
|
*
|
|
* This function is called very early on, while iPXE is initializing.
|
|
* This is a iPXE PCI Device Driver API function.
|
|
*/
|
|
static int myri10ge_pci_probe ( struct pci_device *pci,
|
|
const struct pci_device_id *id __unused )
|
|
{
|
|
static struct net_device_operations myri10ge_operations = {
|
|
.open = myri10ge_net_open,
|
|
.close = myri10ge_net_close,
|
|
.transmit = myri10ge_net_transmit,
|
|
.poll = myri10ge_net_poll,
|
|
.irq = myri10ge_net_irq
|
|
};
|
|
|
|
const char *dbg;
|
|
int rc;
|
|
struct net_device *netdev;
|
|
struct myri10ge_private *priv;
|
|
|
|
DBGP ( "myri10ge_pci_probe: " );
|
|
|
|
netdev = alloc_etherdev ( sizeof ( *priv ) );
|
|
if ( !netdev ) {
|
|
rc = -ENOMEM;
|
|
dbg = "alloc_etherdev";
|
|
goto abort_with_nothing;
|
|
}
|
|
|
|
netdev_init ( netdev, &myri10ge_operations );
|
|
priv = myri10ge_priv ( netdev );
|
|
|
|
pci_set_drvdata ( pci, netdev );
|
|
netdev->dev = &pci->dev;
|
|
|
|
/* Make sure interrupts are disabled. */
|
|
|
|
myri10ge_net_irq ( netdev, 0 );
|
|
|
|
/* Find the PCI Vendor-Specific capability. */
|
|
|
|
priv->pci_cap_vs = pci_find_capability ( pci , PCI_CAP_ID_VNDR );
|
|
if ( 0 == priv->pci_cap_vs ) {
|
|
rc = -ENOTSUP;
|
|
dbg = "no_vs";
|
|
goto abort_with_netdev_init;
|
|
}
|
|
|
|
/* Read the NIC HW address. */
|
|
|
|
rc = mac_address_from_string_specs ( pci,
|
|
priv->pci_cap_vs,
|
|
netdev->hw_addr );
|
|
if ( rc ) {
|
|
dbg = "mac_from_ss";
|
|
goto abort_with_netdev_init;
|
|
}
|
|
DBGP ( "mac " );
|
|
|
|
/* Enable bus master, etc. */
|
|
|
|
adjust_pci_device ( pci );
|
|
DBGP ( "pci " );
|
|
|
|
/* Register the initialized network device. */
|
|
|
|
rc = register_netdev ( netdev );
|
|
if ( rc ) {
|
|
dbg = "register_netdev";
|
|
goto abort_with_netdev_init;
|
|
}
|
|
|
|
/* Initialize NonVolatile Storage support. */
|
|
|
|
rc = myri10ge_nv_init ( priv );
|
|
if ( rc ) {
|
|
dbg = "myri10ge_nv_init";
|
|
goto abort_with_registered_netdev;
|
|
}
|
|
|
|
DBGP ( "done\n" );
|
|
|
|
return 0;
|
|
|
|
abort_with_registered_netdev:
|
|
unregister_netdev ( netdev );
|
|
abort_with_netdev_init:
|
|
netdev_nullify ( netdev );
|
|
netdev_put ( netdev );
|
|
abort_with_nothing:
|
|
DBG ( "%s:%s\n", dbg, strerror ( rc ) );
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* Remove a device from the PCI device list.
|
|
*
|
|
* @v pci PCI device to remove.
|
|
*
|
|
* This is a PCI Device Driver API function.
|
|
*/
|
|
static void myri10ge_pci_remove ( struct pci_device *pci )
|
|
{
|
|
struct net_device *netdev;
|
|
|
|
DBGP ( "myri10ge_pci_remove\n" );
|
|
netdev = pci_get_drvdata ( pci );
|
|
|
|
myri10ge_nv_fini ( myri10ge_priv ( netdev ) );
|
|
unregister_netdev ( netdev );
|
|
netdev_nullify ( netdev );
|
|
netdev_put ( netdev );
|
|
}
|
|
|
|
/****************************************************************
|
|
* iPXE Network Device Driver Operations
|
|
****************************************************************/
|
|
|
|
/*
|
|
* Close a network device.
|
|
*
|
|
* @v netdev Device to close.
|
|
*
|
|
* This is a iPXE Network Device Driver API function.
|
|
*/
|
|
static void myri10ge_net_close ( struct net_device *netdev )
|
|
{
|
|
struct myri10ge_private *priv;
|
|
uint32 data[3];
|
|
|
|
DBGP ( "myri10ge_net_close\n" );
|
|
priv = myri10ge_priv ( netdev );
|
|
|
|
/* disable interrupts */
|
|
|
|
myri10ge_net_irq ( netdev, 0 );
|
|
|
|
/* Reset the NIC interface, so we won't get any more events from
|
|
the NIC. */
|
|
|
|
myri10ge_command ( priv, MXGEFW_CMD_RESET, data );
|
|
|
|
/* Free receive buffers that were never filled. */
|
|
|
|
while ( priv->receives_done != priv->receives_posted ) {
|
|
free_iob ( priv->receive_iob[priv->receives_done
|
|
& MYRI10GE_RECEIVE_WRAP] );
|
|
++priv->receives_done;
|
|
}
|
|
|
|
/* Release DMAable memory. */
|
|
|
|
free_dma ( priv->dma, sizeof ( *priv->dma ) );
|
|
|
|
/* Erase all state from the open. */
|
|
|
|
memset ( priv, 0, sizeof ( *priv ) );
|
|
|
|
DBG2_RINGS ( priv );
|
|
}
|
|
|
|
/*
|
|
* Enable or disable IRQ masking.
|
|
*
|
|
* @v netdev Device to control.
|
|
* @v enable Zero to mask off IRQ, non-zero to enable IRQ.
|
|
*
|
|
* This is a iPXE Network Driver API function.
|
|
*/
|
|
static void myri10ge_net_irq ( struct net_device *netdev, int enable )
|
|
{
|
|
struct pci_device *pci_dev;
|
|
uint16 val;
|
|
|
|
DBGP ( "myri10ge_net_irq\n" );
|
|
pci_dev = ( struct pci_device * ) netdev->dev;
|
|
|
|
/* Adjust the Interrupt Disable bit in the Command register of the
|
|
PCI Device. */
|
|
|
|
pci_read_config_word ( pci_dev, PCI_COMMAND, &val );
|
|
if ( enable )
|
|
val &= ~PCI_COMMAND_INTX_DISABLE;
|
|
else
|
|
val |= PCI_COMMAND_INTX_DISABLE;
|
|
pci_write_config_word ( pci_dev, PCI_COMMAND, val );
|
|
}
|
|
|
|
/*
|
|
* Opens a network device.
|
|
*
|
|
* @v netdev Device to be opened.
|
|
* @ret rc Non-zero if failed to open.
|
|
*
|
|
* This enables tx and rx on the device.
|
|
* This is a iPXE Network Device Driver API function.
|
|
*/
|
|
static int myri10ge_net_open ( struct net_device *netdev )
|
|
{
|
|
const char *dbg; /* printed upon error return */
|
|
int rc;
|
|
struct io_buffer *iob;
|
|
struct myri10ge_private *priv;
|
|
uint32 data[3];
|
|
struct pci_device *pci_dev;
|
|
void *membase;
|
|
|
|
DBGP ( "myri10ge_net_open\n" );
|
|
priv = myri10ge_priv ( netdev );
|
|
pci_dev = ( struct pci_device * ) netdev->dev;
|
|
membase = phys_to_virt ( pci_dev->membase );
|
|
|
|
/* Compute address for passing commands to the firmware. */
|
|
|
|
priv->command = membase + MXGEFW_ETH_CMD;
|
|
|
|
/* Ensure interrupts are disabled. */
|
|
|
|
myri10ge_net_irq ( netdev, 0 );
|
|
|
|
/* Allocate cleared DMAable buffers. */
|
|
|
|
priv->dma = malloc_dma ( sizeof ( *priv->dma ) , 128 );
|
|
if ( !priv->dma ) {
|
|
rc = -ENOMEM;
|
|
dbg = "DMA";
|
|
goto abort_with_nothing;
|
|
}
|
|
memset ( priv->dma, 0, sizeof ( *priv->dma ) );
|
|
|
|
/* Simplify following code. */
|
|
|
|
#define TRY( prefix, base, suffix ) do { \
|
|
rc = myri10ge_command ( priv, \
|
|
MXGEFW_ \
|
|
## prefix \
|
|
## base \
|
|
## suffix, \
|
|
data ); \
|
|
if ( rc ) { \
|
|
dbg = #base; \
|
|
goto abort_with_dma; \
|
|
} \
|
|
} while ( 0 )
|
|
|
|
/* Send a reset command to the card to see if it is alive,
|
|
and to reset its queue state. */
|
|
|
|
TRY ( CMD_, RESET , );
|
|
|
|
/* Set the interrupt queue size. */
|
|
|
|
data[0] = ( (uint32_t)( sizeof ( priv->dma->receive_completion ) )
|
|
| MXGEFW_CMD_SET_INTRQ_SIZE_FLAG_NO_STRICT_SIZE_CHECK );
|
|
TRY ( CMD_SET_ , INTRQ_SIZE , );
|
|
|
|
/* Set the interrupt queue DMA address. */
|
|
|
|
data[0] = virt_to_bus ( &priv->dma->receive_completion );
|
|
data[1] = 0;
|
|
TRY ( CMD_SET_, INTRQ_DMA, );
|
|
|
|
/* Get the NIC interrupt claim address. */
|
|
|
|
TRY ( CMD_GET_, IRQ_ACK, _OFFSET );
|
|
priv->irq_claim = membase + data[0];
|
|
|
|
/* Get the NIC interrupt assert address. */
|
|
|
|
TRY ( CMD_GET_, IRQ_DEASSERT, _OFFSET );
|
|
priv->irq_deassert = membase + data[0];
|
|
|
|
/* Disable interrupt coalescing, which is inappropriate for the
|
|
minimal buffering we provide. */
|
|
|
|
TRY ( CMD_GET_, INTR_COAL, _DELAY_OFFSET );
|
|
* ( ( uint32 * ) ( membase + data[0] ) ) = 0;
|
|
|
|
/* Set the NIC mac address. */
|
|
|
|
data[0] = ( netdev->ll_addr[0] << 24
|
|
| netdev->ll_addr[1] << 16
|
|
| netdev->ll_addr[2] << 8
|
|
| netdev->ll_addr[3] );
|
|
data[1] = ( ( netdev->ll_addr[4] << 8 )
|
|
| netdev->ll_addr[5] );
|
|
TRY ( SET_ , MAC_ADDRESS , );
|
|
|
|
/* Enable multicast receives, because some iPXE clients don't work
|
|
without multicast. . */
|
|
|
|
TRY ( ENABLE_ , ALLMULTI , );
|
|
|
|
/* Disable Ethernet flow control, so the NIC cannot deadlock the
|
|
network under any circumstances. */
|
|
|
|
TRY ( DISABLE_ , FLOW , _CONTROL );
|
|
|
|
/* Compute transmit ring sizes. */
|
|
|
|
data[0] = 0; /* slice 0 */
|
|
TRY ( CMD_GET_, SEND_RING, _SIZE );
|
|
priv->transmit_ring_wrap
|
|
= data[0] / sizeof ( mcp_kreq_ether_send_t ) - 1;
|
|
if ( priv->transmit_ring_wrap
|
|
& ( priv->transmit_ring_wrap + 1 ) ) {
|
|
rc = -EPROTO;
|
|
dbg = "TX_RING";
|
|
goto abort_with_dma;
|
|
}
|
|
|
|
/* Compute receive ring sizes. */
|
|
|
|
data[0] = 0; /* slice 0 */
|
|
TRY ( CMD_GET_ , RX_RING , _SIZE );
|
|
priv->receive_post_ring_wrap = data[0] / sizeof ( mcp_dma_addr_t ) - 1;
|
|
if ( priv->receive_post_ring_wrap
|
|
& ( priv->receive_post_ring_wrap + 1 ) ) {
|
|
rc = -EPROTO;
|
|
dbg = "RX_RING";
|
|
goto abort_with_dma;
|
|
}
|
|
|
|
/* Get NIC transmit ring address. */
|
|
|
|
data[0] = 0; /* slice 0. */
|
|
TRY ( CMD_GET_, SEND, _OFFSET );
|
|
priv->transmit_ring = membase + data[0];
|
|
|
|
/* Get the NIC receive ring address. */
|
|
|
|
data[0] = 0; /* slice 0. */
|
|
TRY ( CMD_GET_, SMALL_RX, _OFFSET );
|
|
priv->receive_post_ring = membase + data[0];
|
|
|
|
/* Set the Nic MTU. */
|
|
|
|
data[0] = ETH_FRAME_LEN;
|
|
TRY ( CMD_SET_, MTU, );
|
|
|
|
/* Tell the NIC our buffer sizes. ( We use only small buffers, so we
|
|
set both buffer sizes to the same value, which will force all
|
|
received frames to use small buffers. ) */
|
|
|
|
data[0] = MXGEFW_PAD + ETH_FRAME_LEN;
|
|
TRY ( CMD_SET_, SMALL_BUFFER, _SIZE );
|
|
data[0] = MXGEFW_PAD + ETH_FRAME_LEN;
|
|
TRY ( CMD_SET_, BIG_BUFFER, _SIZE );
|
|
|
|
/* Tell firmware where to DMA IRQ data */
|
|
|
|
data[0] = virt_to_bus ( &priv->dma->irq_data );
|
|
data[1] = 0;
|
|
data[2] = sizeof ( priv->dma->irq_data );
|
|
TRY ( CMD_SET_, STATS_DMA_V2, );
|
|
|
|
/* Post receives. */
|
|
|
|
while ( priv->receives_posted <= MYRI10GE_RECEIVE_WRAP ) {
|
|
|
|
/* Reserve 2 extra bytes at the start of packets, since
|
|
the firmware always skips the first 2 bytes of the buffer
|
|
so TCP headers will be aligned. */
|
|
|
|
iob = alloc_iob ( MXGEFW_PAD + ETH_FRAME_LEN );
|
|
if ( !iob ) {
|
|
rc = -ENOMEM;
|
|
dbg = "alloc_iob";
|
|
goto abort_with_receives_posted;
|
|
}
|
|
iob_reserve ( iob, MXGEFW_PAD );
|
|
myri10ge_post_receive ( priv, iob );
|
|
}
|
|
|
|
/* Bring up the link. */
|
|
|
|
TRY ( CMD_, ETHERNET_UP, );
|
|
|
|
DBG2_RINGS ( priv );
|
|
return 0;
|
|
|
|
abort_with_receives_posted:
|
|
while ( priv->receives_posted-- )
|
|
free_iob ( priv->receive_iob[priv->receives_posted] );
|
|
abort_with_dma:
|
|
/* Because the link is not up, we don't have to reset the NIC here. */
|
|
free_dma ( priv->dma, sizeof ( *priv->dma ) );
|
|
abort_with_nothing:
|
|
/* Erase all signs of the failed open. */
|
|
memset ( priv, 0, sizeof ( *priv ) );
|
|
DBG ( "%s: %s\n", dbg, strerror ( rc ) );
|
|
return ( rc );
|
|
}
|
|
|
|
/*
|
|
* This function allows a driver to process events during operation.
|
|
*
|
|
* @v netdev Device being polled.
|
|
*
|
|
* This is called periodically by iPXE to let the driver check the status of
|
|
* transmitted packets and to allow the driver to check for received packets.
|
|
* This is a iPXE Network Device Driver API function.
|
|
*/
|
|
static void myri10ge_net_poll ( struct net_device *netdev )
|
|
{
|
|
struct io_buffer *iob;
|
|
struct io_buffer *replacement;
|
|
struct myri10ge_dma_buffers *dma;
|
|
struct myri10ge_private *priv;
|
|
unsigned int length;
|
|
unsigned int orig_receives_posted;
|
|
|
|
DBGP ( "myri10ge_net_poll\n" );
|
|
priv = myri10ge_priv ( netdev );
|
|
dma = priv->dma;
|
|
|
|
/* Process any pending interrupt. */
|
|
|
|
myri10ge_interrupt_handler ( netdev );
|
|
|
|
/* Pass up received frames, but limit ourselves to receives posted
|
|
before this function was called, so we cannot livelock if
|
|
receives are arriving faster than we process them. */
|
|
|
|
orig_receives_posted = priv->receives_posted;
|
|
while ( priv->receives_done != orig_receives_posted ) {
|
|
|
|
/* Stop if there is no pending receive. */
|
|
|
|
length = ntohs ( dma->receive_completion
|
|
[priv->receives_done
|
|
& MYRI10GE_RECEIVE_COMPLETION_WRAP]
|
|
.length );
|
|
if ( length == 0 )
|
|
break;
|
|
|
|
/* Allocate a replacement buffer. If none is available,
|
|
stop passing up packets until a buffer is available.
|
|
|
|
Reserve 2 extra bytes at the start of packets, since
|
|
the firmware always skips the first 2 bytes of the buffer
|
|
so TCP headers will be aligned. */
|
|
|
|
replacement = alloc_iob ( MXGEFW_PAD + ETH_FRAME_LEN );
|
|
if ( !replacement ) {
|
|
DBG ( "NO RX BUF\n" );
|
|
break;
|
|
}
|
|
iob_reserve ( replacement, MXGEFW_PAD );
|
|
|
|
/* Pass up the received frame. */
|
|
|
|
iob = priv->receive_iob[priv->receives_done
|
|
& MYRI10GE_RECEIVE_WRAP];
|
|
iob_put ( iob, length );
|
|
netdev_rx ( netdev, iob );
|
|
|
|
/* We have consumed the packet, so clear the receive
|
|
notification. */
|
|
|
|
dma->receive_completion [priv->receives_done
|
|
& MYRI10GE_RECEIVE_COMPLETION_WRAP]
|
|
.length = 0;
|
|
wmb();
|
|
|
|
/* Replace the passed-up I/O buffer. */
|
|
|
|
myri10ge_post_receive ( priv, replacement );
|
|
++priv->receives_done;
|
|
DBG2_RINGS ( priv );
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This transmits a packet.
|
|
*
|
|
* @v netdev Device to transmit from.
|
|
* @v iobuf Data to transmit.
|
|
* @ret rc Non-zero if failed to transmit.
|
|
*
|
|
* This is a iPXE Network Driver API function.
|
|
*/
|
|
static int myri10ge_net_transmit ( struct net_device *netdev,
|
|
struct io_buffer *iobuf )
|
|
{
|
|
mcp_kreq_ether_send_t *kreq;
|
|
size_t len;
|
|
struct myri10ge_private *priv;
|
|
uint32 transmits_posted;
|
|
|
|
DBGP ( "myri10ge_net_transmit\n" );
|
|
priv = myri10ge_priv ( netdev );
|
|
|
|
/* Confirm space in the send ring. */
|
|
|
|
transmits_posted = priv->transmits_posted;
|
|
if ( transmits_posted - priv->transmits_done
|
|
> MYRI10GE_TRANSMIT_WRAP ) {
|
|
DBG ( "TX ring full\n" );
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
DBG2 ( "TX %p+%zd ", iobuf->data, iob_len ( iobuf ) );
|
|
DBG2_HD ( iobuf->data, 14 );
|
|
|
|
/* Record the packet being transmitted, so we can later report
|
|
send completion. */
|
|
|
|
priv->transmit_iob[transmits_posted & MYRI10GE_TRANSMIT_WRAP] = iobuf;
|
|
|
|
/* Copy and pad undersized frames, because the NIC does not pad,
|
|
and we would rather copy small frames than do a gather. */
|
|
|
|
len = iob_len ( iobuf );
|
|
if ( len < ETH_ZLEN ) {
|
|
iob_pad ( iobuf, ETH_ZLEN );
|
|
len = ETH_ZLEN;
|
|
}
|
|
|
|
/* Enqueue the packet by writing a descriptor to the NIC.
|
|
This is a bit tricky because the HW requires 32-bit writes,
|
|
but the structure has smaller fields. */
|
|
|
|
kreq = &priv->transmit_ring[transmits_posted
|
|
& priv->transmit_ring_wrap];
|
|
kreq->addr_high = 0;
|
|
kreq->addr_low = htonl ( virt_to_bus ( iobuf->data ) );
|
|
( ( uint32 * ) kreq ) [2] = htonl (
|
|
0x0000 << 16 /* pseudo_header_offset */
|
|
| ( len & 0xFFFF ) /* length */
|
|
);
|
|
wmb();
|
|
( ( uint32 * ) kreq ) [3] = htonl (
|
|
0x00 << 24 /* pad */
|
|
| 0x01 << 16 /* rdma_count */
|
|
| 0x00 << 8 /* cksum_offset */
|
|
| ( MXGEFW_FLAGS_SMALL
|
|
| MXGEFW_FLAGS_FIRST
|
|
| MXGEFW_FLAGS_NO_TSO ) /* flags */
|
|
);
|
|
wmb();
|
|
|
|
/* Mark the slot as consumed and return. */
|
|
|
|
priv->transmits_posted = ++transmits_posted;
|
|
DBG2_RINGS ( priv );
|
|
return 0;
|
|
}
|
|
|
|
static struct pci_device_id myri10ge_nics[] = {
|
|
/* Each of these macros must be a single line to satisfy a script. */
|
|
PCI_ROM ( 0x14c1, 0x0008, "myri10ge", "Myricom 10Gb Ethernet Adapter", 0 ) ,
|
|
};
|
|
|
|
struct pci_driver myri10ge_driver __pci_driver = {
|
|
.ids = myri10ge_nics,
|
|
.id_count = ( sizeof ( myri10ge_nics ) / sizeof ( myri10ge_nics[0] ) ) ,
|
|
.probe = myri10ge_pci_probe,
|
|
.remove = myri10ge_pci_remove
|
|
};
|
|
|
|
/*
|
|
* Local variables:
|
|
* c-basic-offset: 8
|
|
* c-indent-level: 8
|
|
* tab-width: 8
|
|
* End:
|
|
*/
|