#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/bitstring.h>
#include <sys/kernel.h>
#include <sys/socket.h>
#include <net/ethernet.h>
#include <net/if.h>
#include <net/if_var.h>
#include <net/iflib.h>
#include "aq_common.h"
#include "aq_device.h"
#include "aq_ring.h"
#include "aq_dbg.h"
#include "aq_hw.h"
#include "aq_hw_llh.h"
#include "aq_fw.h"
int
aq_update_hw_stats(struct aq_dev *aq_dev)
{
struct aq_hw *hw = &aq_dev->hw;
struct aq_hw_stats stats;
memset(&stats, 0, sizeof(stats));
if (aq_hw_mpi_read_stats(hw, &stats) != 0)
return (0);
#define AQ_SDELTA(_N_) do { \
aq_dev->curr_stats._N_ += stats._N_ - aq_dev->last_stats._N_; \
} while (0)
if (aq_dev->linkup) {
AQ_SDELTA(uprc);
AQ_SDELTA(mprc);
AQ_SDELTA(bprc);
AQ_SDELTA(cprc);
AQ_SDELTA(erpt);
AQ_SDELTA(uptc);
AQ_SDELTA(mptc);
AQ_SDELTA(bptc);
AQ_SDELTA(erpr);
AQ_SDELTA(ubrc);
AQ_SDELTA(ubtc);
AQ_SDELTA(mbrc);
AQ_SDELTA(mbtc);
AQ_SDELTA(bbrc);
AQ_SDELTA(bbtc);
AQ_SDELTA(ptc);
AQ_SDELTA(prc);
AQ_SDELTA(dpc);
if (stats.ubrc | stats.mbrc | stats.bbrc)
aq_dev->curr_stats.brc = aq_dev->curr_stats.ubrc +
aq_dev->curr_stats.mbrc + aq_dev->curr_stats.bbrc;
else
AQ_SDELTA(brc);
if (stats.ubtc | stats.mbtc | stats.bbtc)
aq_dev->curr_stats.btc = aq_dev->curr_stats.ubtc +
aq_dev->curr_stats.mbtc + aq_dev->curr_stats.bbtc;
else
AQ_SDELTA(btc);
}
#undef AQ_SDELTA
memcpy(&aq_dev->last_stats, &stats, sizeof(stats));
return (0);
}
#define AQ_THERMAL_HYSTERESIS_MC 18000
#define AQ_THERMAL_RECOVER_MC 90000
#define AQ_THERMAL_SETTLE_POLLS 5
#define AQ_THERMAL_RETRY_SECS 60
#define AQ_INIT_MAX_RETRIES 5
static void
aq_thermal_report_shutdown(struct aq_dev *aq_dev)
{
struct aq_hw *hw = &aq_dev->hw;
int temp_mc, limit_mc;
bool have_temp, have_limit;
have_temp = hw->fw_ops->get_temp(hw, &temp_mc) == 0;
have_limit = hw->fw_ops->get_thermal_limit(hw, &limit_mc) == 0;
if (have_temp)
aq_dev->thermal_temp_mc = temp_mc;
aq_dev->thermal_recover_mc = have_limit ?
limit_mc - AQ_THERMAL_HYSTERESIS_MC : AQ_THERMAL_RECOVER_MC;
if (have_temp && have_limit)
device_printf(aq_dev->dev, "PHY thermal shutdown; "
"limit %d C, temp %d C; holding link down until it cools\n",
limit_mc / 1000, temp_mc / 1000);
else if (have_temp)
device_printf(aq_dev->dev, "PHY thermal shutdown; "
"temp %d C; holding link down until it cools\n",
temp_mc / 1000);
else
device_printf(aq_dev->dev, "PHY thermal shutdown; "
"holding link down until it cools\n");
}
static void
aq_thermal_poll(struct aq_dev *aq_dev)
{
struct aq_hw *hw = &aq_dev->hw;
uint16_t fault;
int temp_mc;
switch (aq_dev->thermal_state) {
case AQ_THERMAL_NORMAL:
if (aq_dev->linkup)
return;
if (hw->fw_ops->get_phy_fault(hw, &fault) != 0 || fault == 0)
return;
if (fault == aq_dev->phy_fault_last)
return;
aq_dev->phy_fault_last = fault;
if (fault != AQ_PHY_FAULT_THERMAL_SHUTDOWN) {
device_printf(aq_dev->dev,
"PHY fault 0x%04x\n", fault);
return;
}
aq_thermal_report_shutdown(aq_dev);
aq_dev->thermal_state = AQ_THERMAL_COOLING;
return;
case AQ_THERMAL_COOLING:
if (hw->fw_ops->get_temp(hw, &temp_mc) != 0 ||
temp_mc > aq_dev->thermal_recover_mc)
return;
aq_dev->thermal_temp_mc = temp_mc;
if (hw->fw_ops->phy_reset != NULL) {
if (hw->fw_ops->phy_reset(hw) != 0)
return;
aq_dev->thermal_settle = 0;
aq_dev->thermal_state = AQ_THERMAL_SETTLING;
return;
}
break;
case AQ_THERMAL_SETTLING:
if (++aq_dev->thermal_settle < AQ_THERMAL_SETTLE_POLLS)
return;
break;
}
if ((int)(ticks - aq_dev->thermal_retry_ticks) < 0)
return;
aq_dev->thermal_retry_ticks = ticks + AQ_THERMAL_RETRY_SECS * hz;
device_printf(aq_dev->dev, "PHY cooled to %d C; restoring "
"link\n", aq_dev->thermal_temp_mc / 1000);
aq_dev->thermal_state = AQ_THERMAL_NORMAL;
aq_dev->reset_pending = true;
iflib_request_reset(aq_dev->ctx);
iflib_admin_intr_deferred(aq_dev->ctx);
}
void
aq_if_update_admin_status(if_ctx_t ctx)
{
struct aq_dev *aq_dev = iflib_get_softc(ctx);
struct aq_hw *hw = &aq_dev->hw;
uint32_t link_speed;
bool running;
struct aq_hw_fc_info fc_neg;
aq_hw_get_link_state(hw, &link_speed, &fc_neg);
running = (if_getdrvflags(iflib_get_ifp(ctx)) & IFF_DRV_RUNNING) != 0;
if (!running || aq_dev->init_failed)
link_speed = 0;
if (link_speed != 0U &&
(!aq_dev->linkup || link_speed != aq_dev->link_speed)) {
if (!aq_dev->linkup) {
if (bootverbose)
device_printf(aq_dev->dev,
"link UP: speed=%d\n", link_speed);
aq_dev->phy_fault_last = 0;
} else
device_printf(aq_dev->dev, "link speed=%d\n",
link_speed);
aq_dev->linkup = 1;
aq_dev->link_speed = link_speed;
rpb_rx_xoff_en_per_tc_set(hw, fc_neg.fc_rx, 0);
iflib_link_state_change(ctx, LINK_STATE_UP, IF_Mbps(link_speed));
aq_mediastatus_update(aq_dev, link_speed, &fc_neg);
aq_hw_interrupt_moderation_set(hw);
} else if (link_speed == 0U && aq_dev->linkup) {
if (bootverbose)
device_printf(aq_dev->dev, "link DOWN\n");
aq_dev->linkup = 0;
aq_dev->link_speed = 0;
rpb_rx_xoff_en_per_tc_set(hw, 0, 0);
iflib_link_state_change(ctx, LINK_STATE_DOWN, 0);
aq_mediastatus_update(aq_dev, link_speed, &fc_neg);
}
if (!running)
return;
if (aq_dev->init_failed) {
if (aq_dev->reset_pending)
return;
if (aq_dev->init_retries < AQ_INIT_MAX_RETRIES) {
aq_dev->init_retries++;
aq_dev->reset_pending = true;
iflib_request_reset(ctx);
} else if (aq_dev->init_retries == AQ_INIT_MAX_RETRIES) {
aq_dev->init_retries++;
device_printf(aq_dev->dev, "initialization failed; "
"giving up after %d retries, link held down\n",
AQ_INIT_MAX_RETRIES);
}
return;
}
if (hw->fw_ops->get_phy_fault != NULL)
aq_thermal_poll(aq_dev);
aq_update_hw_stats(aq_dev);
}
int
aq_isr_rx(void *arg)
{
struct aq_ring *ring = arg;
struct aq_dev *aq_dev = ring->dev;
struct aq_hw *hw = &aq_dev->hw;
itr_irq_status_clearlsw_set(hw, BIT(ring->msix));
AQ_HW_FLUSH(hw);
counter_u64_add(ring->stats.irq, 1);
return (FILTER_SCHEDULE_THREAD);
}
int
aq_linkstat_isr(void *arg)
{
struct aq_dev *aq_dev = arg;
struct aq_hw *hw = &aq_dev->hw;
itr_irq_status_clearlsw_set(hw, BIT(aq_dev->msix));
AQ_HW_FLUSH(hw);
iflib_admin_intr_deferred(aq_dev->ctx);
return (FILTER_HANDLED);
}