blob: 0d48b4178cb5aa0542ba7c507d044ba5546780c0 [file] [log] [blame]
/*
* Copyright (C) 2015 Michael Brown <mbrown@fensystems.co.uk>.
*
* 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 (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*
* You can also choose to distribute this program under the terms of
* the Unmodified Binary Distribution Licence (as given in the file
* COPYING.UBDL), provided that you have satisfied its requirements.
*/
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <ipxe/io.h>
#include <ipxe/netdevice.h>
#include <ipxe/ethernet.h>
#include "intelvf.h"
/** @file
*
* Intel 10/100/1000 virtual function network card driver
*
*/
/******************************************************************************
*
* Mailbox messages
*
******************************************************************************
*/
/**
* Write message to mailbox
*
* @v intel Intel device
* @v msg Message
*/
static void intelvf_mbox_write ( struct intel_nic *intel,
const union intelvf_msg *msg ) {
const struct intelvf_msg_raw *raw = &msg->raw;
unsigned int i;
/* Write message */
DBGC2 ( intel, "INTEL %p sending message", intel );
for ( i = 0 ; i < ( sizeof ( *msg ) / sizeof ( raw->dword[0] ) ) ; i++){
DBGC2 ( intel, "%c%08x", ( i ? ':' : ' ' ), raw->dword[i] );
writel ( raw->dword[i], ( intel->regs + intel->mbox.mem +
( i * sizeof ( raw->dword[0] ) ) ) );
}
DBGC2 ( intel, "\n" );
}
/**
* Read message from mailbox
*
* @v intel Intel device
* @v msg Message
*/
static void intelvf_mbox_read ( struct intel_nic *intel,
union intelvf_msg *msg ) {
struct intelvf_msg_raw *raw = &msg->raw;
unsigned int i;
/* Read message */
DBGC2 ( intel, "INTEL %p received message", intel );
for ( i = 0 ; i < ( sizeof ( *msg ) / sizeof ( raw->dword[0] ) ) ; i++){
raw->dword[i] = readl ( intel->regs + intel->mbox.mem +
( i * sizeof ( raw->dword[0] ) ) );
DBGC2 ( intel, "%c%08x", ( i ? ':' : ' ' ), raw->dword[i] );
}
DBGC2 ( intel, "\n" );
}
/**
* Poll mailbox
*
* @v intel Intel device
* @ret rc Return status code
*
* Note that polling the mailbox may fail if the underlying PF is
* reset.
*/
int intelvf_mbox_poll ( struct intel_nic *intel ) {
struct intel_mailbox *mbox = &intel->mbox;
union intelvf_msg msg;
uint32_t ctrl;
/* Get mailbox status */
ctrl = readl ( intel->regs + mbox->ctrl );
/* Fail if a reset is in progress */
if ( ctrl & INTELVF_MBCTRL_RSTI )
return -EPIPE;
/* Acknowledge (and ignore) any received messages */
if ( ctrl & INTELVF_MBCTRL_PFSTS ) {
intelvf_mbox_read ( intel, &msg );
writel ( INTELVF_MBCTRL_ACK, intel->regs + mbox->ctrl );
}
return 0;
}
/**
* Wait for PF reset to complete
*
* @v intel Intel device
* @ret rc Return status code
*/
int intelvf_mbox_wait ( struct intel_nic *intel ) {
unsigned int i;
int rc;
/* Wait until a poll completes successfully */
for ( i = 0 ; i < INTELVF_MBOX_MAX_WAIT_MS ; i++ ) {
/* Check for successful poll */
if ( ( rc = intelvf_mbox_poll ( intel ) ) == 0 )
return 0;
/* Delay */
mdelay ( 1 );
}
DBGC ( intel, "INTEL %p timed out waiting for reset\n", intel );
return -ETIMEDOUT;
}
/**
* Send/receive mailbox message
*
* @v intel Intel device
* @v msg Message buffer
* @ret rc Return status code
*/
int intelvf_mbox_msg ( struct intel_nic *intel, union intelvf_msg *msg ) {
struct intel_mailbox *mbox = &intel->mbox;
uint32_t ctrl;
uint32_t seen = 0;
unsigned int i;
/* Sanity check */
assert ( ! ( msg->hdr & INTELVF_MSG_RESPONSE ) );
/* Handle mailbox */
for ( i = 0 ; i < INTELVF_MBOX_MAX_WAIT_MS ; i++ ) {
/* Attempt to claim mailbox, if we have not yet sent
* our message.
*/
if ( ! ( seen & INTELVF_MBCTRL_VFU ) )
writel ( INTELVF_MBCTRL_VFU, intel->regs + mbox->ctrl );
/* Get mailbox status and record observed flags */
ctrl = readl ( intel->regs + mbox->ctrl );
seen |= ctrl;
/* If a reset is in progress, clear VFU and abort */
if ( ctrl & INTELVF_MBCTRL_RSTI ) {
writel ( 0, intel->regs + mbox->ctrl );
return -EPIPE;
}
/* Write message to mailbox, if applicable. This
* potentially overwrites a message sent by the PF (if
* the PF has simultaneously released PFU (thus
* allowing our VFU) and asserted PFSTS), but that
* doesn't really matter since there are no
* unsolicited PF->VF messages that require the actual
* message content to be observed.
*/
if ( ctrl & INTELVF_MBCTRL_VFU )
intelvf_mbox_write ( intel, msg );
/* Read message from mailbox, if applicable. */
if ( ( seen & INTELVF_MBCTRL_VFU ) &&
( seen & INTELVF_MBCTRL_PFACK ) &&
( ctrl & INTELVF_MBCTRL_PFSTS ) )
intelvf_mbox_read ( intel, msg );
/* Acknowledge received message (if applicable),
* release VFU lock, and send message (if applicable).
*/
ctrl = ( ( ( ctrl & INTELVF_MBCTRL_PFSTS ) ?
INTELVF_MBCTRL_ACK : 0 ) |
( ( ctrl & INTELVF_MBCTRL_VFU ) ?
INTELVF_MBCTRL_REQ : 0 ) );
writel ( ctrl, intel->regs + mbox->ctrl );
/* Exit successfully if we have received a response */
if ( msg->hdr & INTELVF_MSG_RESPONSE ) {
/* Sanity check */
assert ( seen & INTELVF_MBCTRL_VFU );
assert ( seen & INTELVF_MBCTRL_PFACK );
assert ( seen & INTELVF_MBCTRL_PFSTS );
return 0;
}
/* Delay */
mdelay ( 1 );
}
DBGC ( intel, "INTEL %p timed out waiting for mailbox (seen %08x)\n",
intel, seen );
return -ETIMEDOUT;
}
/**
* Send reset message and get initial MAC address
*
* @v intel Intel device
* @v hw_addr Hardware address to fill in, or NULL
* @ret rc Return status code
*/
int intelvf_mbox_reset ( struct intel_nic *intel, uint8_t *hw_addr ) {
union intelvf_msg msg;
int rc;
/* Send reset message */
memset ( &msg, 0, sizeof ( msg ) );
msg.hdr = INTELVF_MSG_TYPE_RESET;
if ( ( rc = intelvf_mbox_msg ( intel, &msg ) ) != 0 ) {
DBGC ( intel, "INTEL %p reset failed: %s\n",
intel, strerror ( rc ) );
return rc;
}
/* Check response */
if ( ( msg.hdr & INTELVF_MSG_TYPE_MASK ) != INTELVF_MSG_TYPE_RESET ) {
DBGC ( intel, "INTEL %p reset unexpected response:\n", intel );
DBGC_HDA ( intel, 0, &msg, sizeof ( msg ) );
return -EPROTO;
}
/* Fill in MAC address, if applicable */
if ( hw_addr ) {
if ( msg.hdr & INTELVF_MSG_ACK ) {
memcpy ( hw_addr, msg.mac.mac, sizeof ( msg.mac.mac ) );
DBGC ( intel, "INTEL %p reset assigned MAC address "
"%s\n", intel, eth_ntoa ( hw_addr ) );
} else {
eth_random_addr ( hw_addr );
DBGC ( intel, "INTEL %p reset generated MAC address "
"%s\n", intel, eth_ntoa ( hw_addr ) );
}
}
return 0;
}
/**
* Send set MAC address message
*
* @v intel Intel device
* @v ll_addr Link-layer address
* @ret rc Return status code
*/
int intelvf_mbox_set_mac ( struct intel_nic *intel, const uint8_t *ll_addr ) {
union intelvf_msg msg;
int rc;
/* Send set MAC address message */
memset ( &msg, 0, sizeof ( msg ) );
msg.hdr = INTELVF_MSG_TYPE_SET_MAC;
memcpy ( msg.mac.mac, ll_addr, sizeof ( msg.mac.mac ) );
if ( ( rc = intelvf_mbox_msg ( intel, &msg ) ) != 0 ) {
DBGC ( intel, "INTEL %p set MAC address failed: %s\n",
intel, strerror ( rc ) );
return rc;
}
/* Check response */
if ( ( msg.hdr & INTELVF_MSG_TYPE_MASK ) != INTELVF_MSG_TYPE_SET_MAC ) {
DBGC ( intel, "INTEL %p set MAC address unexpected response:\n",
intel );
DBGC_HDA ( intel, 0, &msg, sizeof ( msg ) );
return -EPROTO;
}
/* Check that we were allowed to set the MAC address */
if ( ! ( msg.hdr & INTELVF_MSG_ACK ) ) {
DBGC ( intel, "INTEL %p set MAC address refused\n", intel );
return -EPERM;
}
return 0;
}
/**
* Send set MTU message
*
* @v intel Intel device
* @v mtu Maximum packet size
* @ret rc Return status code
*/
int intelvf_mbox_set_mtu ( struct intel_nic *intel, size_t mtu ) {
union intelvf_msg msg;
int rc;
/* Send set MTU message */
memset ( &msg, 0, sizeof ( msg ) );
msg.hdr = INTELVF_MSG_TYPE_SET_MTU;
msg.mtu.mtu = mtu;
if ( ( rc = intelvf_mbox_msg ( intel, &msg ) ) != 0 ) {
DBGC ( intel, "INTEL %p set MTU failed: %s\n",
intel, strerror ( rc ) );
return rc;
}
/* Check response */
if ( ( msg.hdr & INTELVF_MSG_TYPE_MASK ) != INTELVF_MSG_TYPE_SET_MTU ) {
DBGC ( intel, "INTEL %p set MTU unexpected response:\n",
intel );
DBGC_HDA ( intel, 0, &msg, sizeof ( msg ) );
return -EPROTO;
}
/* Check that we were allowed to set the MTU */
if ( ! ( msg.hdr & INTELVF_MSG_ACK ) ) {
DBGC ( intel, "INTEL %p set MTU refused\n", intel );
return -EPERM;
}
return 0;
}