#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: if_bypass.c,v 1.10 2023/10/06 14:37:04 msaitoh Exp $");
#include "ixgbe.h"
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;
}
static void
ixgbe_bypass_mutex_clear(struct ixgbe_softc *sc)
{
while (atomic_cas_uint(&sc->bypass.high, 1, 0) != 1)
usec_delay(6000);
while (atomic_cas_uint(&sc->bypass.low, 1, 0) != 1)
usec_delay(6000);
return;
}
static void
ixgbe_bypass_wd_mutex_enter(struct ixgbe_softc *sc)
{
while (atomic_cas_uint(&sc->bypass.high, 0, 1) != 0)
usec_delay(3000);
return;
}
static void
ixgbe_bypass_wd_mutex_clear(struct ixgbe_softc *sc)
{
while (atomic_cas_uint(&sc->bypass.high, 1, 0) != 1)
usec_delay(6000);
return;
}
static void
ixgbe_get_bypass_time(u32 *year, u32 *sec)
{
struct timespec current;
*year = 1970;
nanotime(¤t);
*sec = current.tv_sec;
while (*sec > SEC_THIS_YEAR(*year)) {
*sec -= SEC_THIS_YEAR(*year);
(*year)++;
}
}
static int
ixgbe_bp_version(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 featversion = 0;
u32 cmd;
ixgbe_bypass_mutex_enter(sc);
cmd = BYPASS_PAGE_CTL2 | BYPASS_WE;
cmd |= (BYPASS_EEPROM_VER_ADD << BYPASS_CTL2_OFFSET_SHIFT) &
BYPASS_CTL2_OFFSET_M;
if ((error = hw->mac.ops.bypass_rw(hw, cmd, &featversion) != 0))
goto err;
msec_delay(100);
cmd &= ~BYPASS_WE;
if ((error = hw->mac.ops.bypass_rw(hw, cmd, &featversion) != 0))
goto err;
ixgbe_bypass_mutex_clear(sc);
featversion &= BYPASS_CTL2_DATA_M;
node.sysctl_data = &featversion;
error = sysctl_lookup(SYSCTLFN_CALL(&node));
return (error);
err:
ixgbe_bypass_mutex_clear(sc);
return (error);
}
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;
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;
node.sysctl_data = &state;
error = sysctl_lookup(SYSCTLFN_CALL(&node));
if ((error != 0) || (newp == NULL))
return (error);
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;
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);
}
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;
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;
node.sysctl_data = &timeout;
error = sysctl_lookup(SYSCTLFN_CALL(&node));
if ((error) || (newp == NULL))
return (error);
switch (timeout) {
case BYPASS_NOP:
case BYPASS_NORM:
case BYPASS_BYPASS:
case BYPASS_ISOLATE:
break;
default:
return (EINVAL);
}
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);
}
static int
ixgbe_bp_main_on(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 main_on = 0;
ixgbe_bypass_mutex_enter(sc);
error = hw->mac.ops.bypass_rw(hw, BYPASS_PAGE_CTL0, &main_on);
main_on = (main_on >> BYPASS_MAIN_ON_SHIFT) & 0x3;
ixgbe_bypass_mutex_clear(sc);
if (error)
return (error);
node.sysctl_data = &main_on;
error = sysctl_lookup(SYSCTLFN_CALL(&node));
if ((error) || (newp == NULL))
return (error);
switch (main_on) {
case BYPASS_NOP:
case BYPASS_NORM:
case BYPASS_BYPASS:
case BYPASS_ISOLATE:
break;
default:
return (EINVAL);
}
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);
}
static int
ixgbe_bp_main_off(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 main_off = 0;
ixgbe_bypass_mutex_enter(sc);
error = hw->mac.ops.bypass_rw(hw, BYPASS_PAGE_CTL0, &main_off);
ixgbe_bypass_mutex_clear(sc);
if (error)
return (error);
main_off = (main_off >> BYPASS_MAIN_OFF_SHIFT) & 0x3;
node.sysctl_data = &main_off;
error = sysctl_lookup(SYSCTLFN_CALL(&node));
if ((error) || (newp == NULL))
return (error);
switch (main_off) {
case BYPASS_NOP:
case BYPASS_NORM:
case BYPASS_BYPASS:
case BYPASS_ISOLATE:
break;
default:
return (EINVAL);
}
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);
}
static int
ixgbe_bp_aux_on(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 aux_on = 0;
ixgbe_bypass_mutex_enter(sc);
error = hw->mac.ops.bypass_rw(hw, BYPASS_PAGE_CTL0, &aux_on);
ixgbe_bypass_mutex_clear(sc);
if (error)
return (error);
aux_on = (aux_on >> BYPASS_AUX_ON_SHIFT) & 0x3;
node.sysctl_data = &aux_on;
error = sysctl_lookup(SYSCTLFN_CALL(&node));
if ((error) || (newp == NULL))
return (error);
switch (aux_on) {
case BYPASS_NOP:
case BYPASS_NORM:
case BYPASS_BYPASS:
case BYPASS_ISOLATE:
break;
default:
return (EINVAL);
}
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);
}
static int
ixgbe_bp_aux_off(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 aux_off = 0;
ixgbe_bypass_mutex_enter(sc);
error = hw->mac.ops.bypass_rw(hw, BYPASS_PAGE_CTL0, &aux_off);
ixgbe_bypass_mutex_clear(sc);
if (error)
return (error);
aux_off = (aux_off >> BYPASS_AUX_OFF_SHIFT) & 0x3;
node.sysctl_data = &aux_off;
error = sysctl_lookup(SYSCTLFN_CALL(&node));
if ((error) || (newp == NULL))
return (error);
switch (aux_off) {
case BYPASS_NOP:
case BYPASS_NORM:
case BYPASS_BYPASS:
case BYPASS_ISOLATE:
break;
default:
return (EINVAL);
}
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);
}
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;
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 ((tmp & (0x1 << BYPASS_WDT_ENABLE_SHIFT)) == 0)
timeout = 0;
node.sysctl_data = &timeout;
error = sysctl_lookup(SYSCTLFN_CALL(&node));
if ((error) || (newp == NULL))
return (error);
arg = 0x1 << BYPASS_WDT_ENABLE_SHIFT;
mask = BYPASS_WDT_ENABLE_M | BYPASS_WDT_VALUE_M;
switch (timeout) {
case 0:
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);
}
ixgbe_bypass_mutex_enter(sc);
error = hw->mac.ops.bypass_set(hw, BYPASS_PAGE_CTL0, mask, arg);
ixgbe_bypass_mutex_clear(sc);
return (error);
}
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;
node.sysctl_data = &reset_wd;
error = sysctl_lookup(SYSCTLFN_CALL(&node));
if ((error) || (newp == NULL))
return (error);
cmd = BYPASS_PAGE_CTL1 | BYPASS_WE | BYPASS_CTL1_WDT_PET;
ixgbe_get_bypass_time(&year, &sec);
cmd |= (sec & BYPASS_CTL1_TIME_M) | BYPASS_CTL1_VALID;
cmd |= BYPASS_CTL1_OFFTRST;
ixgbe_bypass_wd_mutex_enter(sc);
error = hw->mac.ops.bypass_rw(hw, cmd, &reset_wd);
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));
reset_wd = 0;
ixgbe_bypass_wd_mutex_clear(sc);
return (error);
}
static int
ixgbe_bp_log(SYSCTLFN_ARGS)
{
struct sysctlnode node = *rnode;
struct ixgbe_softc *sc = (struct ixgbe_softc *)node.sysctl_data;
struct ixgbe_hw *hw = &sc->hw;
u32 cmd, base, head;
u32 log_off, count = 0;
static int status = 0;
u8 data;
struct ixgbe_bypass_eeprom eeprom[BYPASS_MAX_LOGS];
int i, error = 0;
node.sysctl_data = &status;
error = sysctl_lookup(SYSCTLFN_CALL(&node));
if ((error) || (newp == NULL))
return (error);
while (atomic_cas_uint(&sc->bypass.log, 0, 1) != 0)
usec_delay(3000);
ixgbe_bypass_mutex_enter(sc);
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;
msec_delay(100);
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;
log_off = base + (head * 5);
while (count < BYPASS_MAX_LOGS) {
eeprom[count].logs = 0;
eeprom[count].actions = 0;
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);
if (!(eeprom[count].logs & BYPASS_LOG_CLEAR_M))
break;
eeprom[count].clear_off = log_off;
count++;
head = head ? head - 1 : BYPASS_MAX_LOGS;
log_off = base + (head * 5);
}
while (count--) {
int year;
u32 mon, days, hours, min, sec;
u32 time = eeprom[count].logs & BYPASS_LOG_TIME_M;
u32 event = (eeprom[count].logs & BYPASS_LOG_EVENT_M) >>
BYPASS_LOG_EVENT_SHIFT;
u8 action = eeprom[count].actions & BYPASS_LOG_ACTION_M;
u16 day_mon[2][13] = {
{0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365},
{0, 31, 59, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}
};
const char *event_str[] = {"unknown", "main on", "aux on",
"main off", "aux off", "WDT", "user" };
const char *action_str[] = {"ignore", "normal", "bypass",
"isolate",};
if (event < BYPASS_EVENT_MAIN_ON || event > BYPASS_EVENT_USR)
event = 0;
ixgbe_get_bypass_time(&year, &sec);
days = time / SEC_PER_DAY;
for (i = 11; days < day_mon[LEAP_YR(year)][i]; i--)
continue;
mon = i + 1;
time -= (day_mon[LEAP_YR(year)][i] * SEC_PER_DAY);
days = (time / SEC_PER_DAY) + 1;
time %= SEC_PER_DAY;
hours = time / (60 * 60);
time %= (60 * 60);
min = time / 60;
sec = time % 60;
device_printf(sc->dev,
"UT %02d/%02d %02d:%02d:%02d %8.8s -> %7.7s\n",
mon, days, hours, min, sec, event_str[event],
action_str[action]);
cmd = BYPASS_PAGE_CTL2 | BYPASS_WE | BYPASS_CTL2_RW;
cmd |= ((eeprom[count].clear_off + 3)
<< BYPASS_CTL2_OFFSET_SHIFT) & BYPASS_CTL2_OFFSET_M;
cmd |= ((eeprom[count].logs & ~BYPASS_LOG_CLEAR_M) >> 24);
ixgbe_bypass_mutex_enter(sc);
error = hw->mac.ops.bypass_rw(hw, cmd, &status);
msec_delay(100);
ixgbe_bypass_mutex_clear(sc);
if (error)
return (EINVAL);
}
status = 0;
while (atomic_cas_uint(&sc->bypass.log, 1, 0) != 1)
usec_delay(3000);
return (error);
unlock_err:
ixgbe_bypass_mutex_clear(sc);
status = 0;
while (atomic_cas_uint(&sc->bypass.log, 1, 0) != 1)
usec_delay(3000);
return (EINVAL);
}
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;
ixgbe_get_bypass_time(&year, &sec);
mask = BYPASS_CTL1_TIME_M
| BYPASS_CTL1_VALID_M
| BYPASS_CTL1_OFFTRST_M;
value = (sec & BYPASS_CTL1_TIME_M)
| BYPASS_CTL1_VALID
| BYPASS_CTL1_OFFTRST;
ixgbe_bypass_mutex_enter(sc);
hw->mac.ops.bypass_set(hw, BYPASS_PAGE_CTL1, mask, value);
ixgbe_bypass_mutex_clear(sc);
log = &sc->sysctllog;
if ((rnode = ixgbe_sysctl_instance(sc)) == NULL) {
aprint_error_dev(dev, "could not create sysctl root\n");
return;
}
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);
sysctl_createv(log, 0, &rnode, &rnode, 0,
CTLTYPE_NODE, "bypass", SYSCTL_DESCR("Bypass"),
NULL, 0, NULL, 0, CTL_CREATE, CTL_EOL);
sysctl_createv(log, 0, &rnode, &cnode, CTLFLAG_READONLY,
CTLTYPE_INT, "version", SYSCTL_DESCR("Bypass Version"),
ixgbe_bp_version, 0, (void *)sc, 0, CTL_CREATE, CTL_EOL);
sysctl_createv(log, 0, &rnode, &cnode, CTLFLAG_READWRITE,
CTLTYPE_INT, "state", SYSCTL_DESCR("Bypass State"),
ixgbe_bp_set_state, 0, (void *)sc, 0, CTL_CREATE, CTL_EOL);
sysctl_createv(log, 0, &rnode, &cnode, CTLFLAG_READWRITE,
CTLTYPE_INT, "timeout", SYSCTL_DESCR("Bypass Timeout"),
ixgbe_bp_timeout, 0, (void *)sc, 0, CTL_CREATE, CTL_EOL);
sysctl_createv(log, 0, &rnode, &cnode, CTLFLAG_READWRITE,
CTLTYPE_INT, "main_on", SYSCTL_DESCR("Bypass Main On"),
ixgbe_bp_main_on, 0, (void *)sc, 0, CTL_CREATE, CTL_EOL);
sysctl_createv(log, 0, &rnode, &cnode, CTLFLAG_READWRITE,
CTLTYPE_INT, "main_off", SYSCTL_DESCR("Bypass Main Off"),
ixgbe_bp_main_off, 0, (void *)sc, 0, CTL_CREATE, CTL_EOL);
sysctl_createv(log, 0, &rnode, &cnode, CTLFLAG_READWRITE,
CTLTYPE_INT, "aux_on", SYSCTL_DESCR("Bypass Aux On"),
ixgbe_bp_aux_on, 0, (void *)sc, 0, CTL_CREATE, CTL_EOL);
sysctl_createv(log, 0, &rnode, &cnode, CTLFLAG_READWRITE,
CTLTYPE_INT, "aux_off", SYSCTL_DESCR("Bypass Aux Off"),
ixgbe_bp_aux_off, 0, (void *)sc, 0, CTL_CREATE, CTL_EOL);
sysctl_createv(log, 0, &rnode, &cnode, CTLFLAG_READWRITE,
CTLTYPE_INT, "wd_set", SYSCTL_DESCR("Set BP Watchdog"),
ixgbe_bp_wd_set, 0, (void *)sc, 0, CTL_CREATE, CTL_EOL);
sysctl_createv(log, 0, &rnode, &cnode, CTLFLAG_READWRITE,
CTLTYPE_INT, "wd_reset", SYSCTL_DESCR("Bypass WD Reset"),
ixgbe_bp_wd_reset, 0, (void *)sc, 0, CTL_CREATE, CTL_EOL);
sc->feat_en |= IXGBE_FEATURE_BYPASS;
}