Copyright (c) 2001-2017, Intel Corporation
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the Intel Corporation nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
/************************************************************************
* ixgbe_bypass_mutex_enter
*
* Mutex support for the bypass feature. Using a dual lock
* to facilitate a privileged access to the watchdog update
* over other threads.
************************************************************************/
static void
ixgbe_bypass_mutex_enter(struct ixgbe_softc *sc)
{
while (atomic_cas_uint(&sc->bypass.low, 0, 1) != 0)
usec_delay(3000);
while (atomic_cas_uint(&sc->bypass.high, 0, 1) != 0)
usec_delay(3000);
return;
} /* ixgbe_bypass_mutex_enter */
/************************************************************************
* ixgbe_bp_set_state
*
* Show/Set the Bypass State:
* 1 = NORMAL
* 2 = BYPASS
* 3 = ISOLATE
*
* With no argument the state is displayed,
* passing a value will set it.
************************************************************************/
static int
ixgbe_bp_set_state(SYSCTLFN_ARGS)
{
struct sysctlnode node = *rnode;
struct ixgbe_softc *sc = (struct ixgbe_softc *)node.sysctl_data;
struct ixgbe_hw *hw = &sc->hw;
int error = 0;
static int state = 0;
/* Get the current state */
ixgbe_bypass_mutex_enter(sc);
error = hw->mac.ops.bypass_rw(hw,
BYPASS_PAGE_CTL0, &state);
ixgbe_bypass_mutex_clear(sc);
if (error != 0)
return (error);
state = (state >> BYPASS_STATUS_OFF_SHIFT) & 0x3;
/* Sanity check new state */
switch (state) {
case BYPASS_NORM:
case BYPASS_BYPASS:
case BYPASS_ISOLATE:
break;
default:
return (EINVAL);
}
ixgbe_bypass_mutex_enter(sc);
if ((error = hw->mac.ops.bypass_set(hw, BYPASS_PAGE_CTL0,
BYPASS_MODE_OFF_M, state) != 0))
goto out;
/* Set AUTO back on so FW can receive events */
error = hw->mac.ops.bypass_set(hw, BYPASS_PAGE_CTL0,
BYPASS_MODE_OFF_M, BYPASS_AUTO);
out:
ixgbe_bypass_mutex_clear(sc);
usec_delay(6000);
return (error);
} /* ixgbe_bp_set_state */
/************************************************************************
* The following routines control the operational
* "rules" of the feature, what behavior will occur
* when particular events occur.
* Values are:
* 0 - no change for the event (NOP)
* 1 - go to Normal operation
* 2 - go to Bypass operation
* 3 - go to Isolate operation
* Calling the entry with no argument just displays
* the current rule setting.
************************************************************************/
/************************************************************************
* ixgbe_bp_timeout
*
* This is to set the Rule for the watchdog,
* not the actual watchdog timeout value.
************************************************************************/
static int
ixgbe_bp_timeout(SYSCTLFN_ARGS)
{
struct sysctlnode node = *rnode;
struct ixgbe_softc *sc = (struct ixgbe_softc *)node.sysctl_data;
struct ixgbe_hw *hw = &sc->hw;
int error = 0;
static int timeout = 0;
/* Get the current value */
ixgbe_bypass_mutex_enter(sc);
error = hw->mac.ops.bypass_rw(hw, BYPASS_PAGE_CTL0, &timeout);
ixgbe_bypass_mutex_clear(sc);
if (error)
return (error);
timeout = (timeout >> BYPASS_WDTIMEOUT_SHIFT) & 0x3;
/* Sanity check on the setting */
switch (timeout) {
case BYPASS_NOP:
case BYPASS_NORM:
case BYPASS_BYPASS:
case BYPASS_ISOLATE:
break;
default:
return (EINVAL);
}
/* Set the new state */
ixgbe_bypass_mutex_enter(sc);
error = hw->mac.ops.bypass_set(hw, BYPASS_PAGE_CTL0,
BYPASS_WDTIMEOUT_M, timeout << BYPASS_WDTIMEOUT_SHIFT);
ixgbe_bypass_mutex_clear(sc);
usec_delay(6000);
return (error);
} /* ixgbe_bp_timeout */
/* Sanity check on the setting */
switch (main_on) {
case BYPASS_NOP:
case BYPASS_NORM:
case BYPASS_BYPASS:
case BYPASS_ISOLATE:
break;
default:
return (EINVAL);
}
/* Set the new state */
ixgbe_bypass_mutex_enter(sc);
error = hw->mac.ops.bypass_set(hw, BYPASS_PAGE_CTL0,
BYPASS_MAIN_ON_M, main_on << BYPASS_MAIN_ON_SHIFT);
ixgbe_bypass_mutex_clear(sc);
usec_delay(6000);
return (error);
} /* ixgbe_bp_main_on */
/* Sanity check on the setting */
switch (main_off) {
case BYPASS_NOP:
case BYPASS_NORM:
case BYPASS_BYPASS:
case BYPASS_ISOLATE:
break;
default:
return (EINVAL);
}
/* Set the new state */
ixgbe_bypass_mutex_enter(sc);
error = hw->mac.ops.bypass_set(hw, BYPASS_PAGE_CTL0,
BYPASS_MAIN_OFF_M, main_off << BYPASS_MAIN_OFF_SHIFT);
ixgbe_bypass_mutex_clear(sc);
usec_delay(6000);
return (error);
} /* ixgbe_bp_main_off */
/* Sanity check on the setting */
switch (aux_on) {
case BYPASS_NOP:
case BYPASS_NORM:
case BYPASS_BYPASS:
case BYPASS_ISOLATE:
break;
default:
return (EINVAL);
}
/* Set the new state */
ixgbe_bypass_mutex_enter(sc);
error = hw->mac.ops.bypass_set(hw, BYPASS_PAGE_CTL0,
BYPASS_AUX_ON_M, aux_on << BYPASS_AUX_ON_SHIFT);
ixgbe_bypass_mutex_clear(sc);
usec_delay(6000);
return (error);
} /* ixgbe_bp_aux_on */
/* Sanity check on the setting */
switch (aux_off) {
case BYPASS_NOP:
case BYPASS_NORM:
case BYPASS_BYPASS:
case BYPASS_ISOLATE:
break;
default:
return (EINVAL);
}
/* Set the new state */
ixgbe_bypass_mutex_enter(sc);
error = hw->mac.ops.bypass_set(hw, BYPASS_PAGE_CTL0,
BYPASS_AUX_OFF_M, aux_off << BYPASS_AUX_OFF_SHIFT);
ixgbe_bypass_mutex_clear(sc);
usec_delay(6000);
return (error);
} /* ixgbe_bp_aux_off */
/************************************************************************
* ixgbe_bp_wd_set - Set the Watchdog timer value
*
* Valid settings are:
* - 0 will disable the watchdog
* - 1, 2, 3, 4, 8, 16, 32
* - anything else is invalid and will be ignored
************************************************************************/
static int
ixgbe_bp_wd_set(SYSCTLFN_ARGS)
{
struct sysctlnode node = *rnode;
struct ixgbe_softc *sc = (struct ixgbe_softc *)node.sysctl_data;
struct ixgbe_hw *hw = &sc->hw;
int error, tmp;
static int timeout = 0;
u32 mask, arg;
/* Get the current hardware value */
ixgbe_bypass_mutex_enter(sc);
error = hw->mac.ops.bypass_rw(hw, BYPASS_PAGE_CTL0, &tmp);
ixgbe_bypass_mutex_clear(sc);
if (error)
return (error);
/*
* If armed keep the displayed value,
* else change the display to zero.
*/
if ((tmp & (0x1 << BYPASS_WDT_ENABLE_SHIFT)) == 0)
timeout = 0;
arg = 0x1 << BYPASS_WDT_ENABLE_SHIFT;
mask = BYPASS_WDT_ENABLE_M | BYPASS_WDT_VALUE_M;
switch (timeout) {
case 0: /* disables the timer */
arg = BYPASS_PAGE_CTL0;
mask = BYPASS_WDT_ENABLE_M;
break;
case 1:
arg |= BYPASS_WDT_1_5 << BYPASS_WDT_TIME_SHIFT;
break;
case 2:
arg |= BYPASS_WDT_2 << BYPASS_WDT_TIME_SHIFT;
break;
case 3:
arg |= BYPASS_WDT_3 << BYPASS_WDT_TIME_SHIFT;
break;
case 4:
arg |= BYPASS_WDT_4 << BYPASS_WDT_TIME_SHIFT;
break;
case 8:
arg |= BYPASS_WDT_8 << BYPASS_WDT_TIME_SHIFT;
break;
case 16:
arg |= BYPASS_WDT_16 << BYPASS_WDT_TIME_SHIFT;
break;
case 32:
arg |= BYPASS_WDT_32 << BYPASS_WDT_TIME_SHIFT;
break;
default:
return (EINVAL);
}
/* Set the new watchdog */
ixgbe_bypass_mutex_enter(sc);
error = hw->mac.ops.bypass_set(hw, BYPASS_PAGE_CTL0, mask, arg);
ixgbe_bypass_mutex_clear(sc);
return (error);
} /* ixgbe_bp_wd_set */
/************************************************************************
* ixgbe_bp_wd_reset - Reset the Watchdog timer
*
* To activate this it must be called with any argument.
************************************************************************/
static int
ixgbe_bp_wd_reset(SYSCTLFN_ARGS)
{
struct sysctlnode node = *rnode;
struct ixgbe_softc *sc = (struct ixgbe_softc *)node.sysctl_data;
struct ixgbe_hw *hw = &sc->hw;
u32 sec, year;
int cmd, count = 0, error = 0;
int reset_wd = 0;
/* Read until it matches what we wrote, or we time out */
do {
if (count++ > 10) {
error = IXGBE_BYPASS_FW_WRITE_FAILURE;
break;
}
error = hw->mac.ops.bypass_rw(hw, BYPASS_PAGE_CTL1, &reset_wd);
if (error != 0) {
error = IXGBE_ERR_INVALID_ARGUMENT;
break;
}
} while (!hw->mac.ops.bypass_valid_rd(cmd, reset_wd));
/* Keep the log display single-threaded */
while (atomic_cas_uint(&sc->bypass.log, 0, 1) != 0)
usec_delay(3000);
ixgbe_bypass_mutex_enter(sc);
/* Find Current head of the log eeprom offset */
cmd = BYPASS_PAGE_CTL2 | BYPASS_WE;
cmd |= (0x1 << BYPASS_CTL2_OFFSET_SHIFT) & BYPASS_CTL2_OFFSET_M;
error = hw->mac.ops.bypass_rw(hw, cmd, &status);
if (error)
goto unlock_err;
/* wait for the write to stick */
msec_delay(100);
/* Now read the results */
cmd &= ~BYPASS_WE;
error = hw->mac.ops.bypass_rw(hw, cmd, &status);
if (error)
goto unlock_err;
ixgbe_bypass_mutex_clear(sc);
base = status & BYPASS_CTL2_DATA_M;
head = (status & BYPASS_CTL2_HEAD_M) >> BYPASS_CTL2_HEAD_SHIFT;
/* address of the first log */
log_off = base + (head * 5);
/* extract all the log entries */
while (count < BYPASS_MAX_LOGS) {
eeprom[count].logs = 0;
eeprom[count].actions = 0;
/* Log 5 bytes store in on u32 and a u8 */
for (i = 0; i < 4; i++) {
ixgbe_bypass_mutex_enter(sc);
error = hw->mac.ops.bypass_rd_eep(hw, log_off + i,
&data);
ixgbe_bypass_mutex_clear(sc);
if (error)
return (EINVAL);
eeprom[count].logs += data << (8 * i);
}
ixgbe_bypass_mutex_enter(sc);
error = hw->mac.ops.bypass_rd_eep(hw,
log_off + i, &eeprom[count].actions);
ixgbe_bypass_mutex_clear(sc);
if (error)
return (EINVAL);
/* Quit if not a unread log */
if (!(eeprom[count].logs & BYPASS_LOG_CLEAR_M))
break;
/*
* Log looks good so store the address where it's
* Unread Log bit is so we can clear it after safely
* pulling out all of the log data.
*/
eeprom[count].clear_off = log_off;
count++;
head = head ? head - 1 : BYPASS_MAX_LOGS;
log_off = base + (head * 5);
}
/************************************************************************
* ixgbe_bypass_init - Set up infrastructure for the bypass feature
*
* Do time and sysctl initialization here. This feature is
* only enabled for the first port of a bypass adapter.
************************************************************************/
void
ixgbe_bypass_init(struct ixgbe_softc *sc)
{
struct ixgbe_hw *hw = &sc->hw;
device_t dev = sc->dev;
struct sysctllog **log;
const struct sysctlnode *rnode, *cnode;
u32 mask, value, sec, year;
if (!(sc->feat_cap & IXGBE_FEATURE_BYPASS))
return;
/* First set up time for the hardware */
ixgbe_get_bypass_time(&year, &sec);
/* Now set up the SYSCTL infrastructure */
log = &sc->sysctllog;
if ((rnode = ixgbe_sysctl_instance(sc)) == NULL) {
aprint_error_dev(dev, "could not create sysctl root\n");
return;
}
/*
* The log routine is kept separate from the other
* children so a general display command like:
* `sysctl dev.ix.0.bypass` will not show the log.
*/
sysctl_createv(log, 0, &rnode, &cnode, CTLFLAG_READWRITE,
CTLTYPE_INT, "bypass_log", SYSCTL_DESCR("Bypass Log"),
ixgbe_bp_log, 0, (void *)sc, 0, CTL_CREATE, CTL_EOL);
/* All other setting are hung from the 'bypass' node */
sysctl_createv(log, 0, &rnode, &rnode, 0,
CTLTYPE_NODE, "bypass", SYSCTL_DESCR("Bypass"),
NULL, 0, NULL, 0, CTL_CREATE, CTL_EOL);