#include <sys/atomic.h>
#include <sys/cpuvar.h>
#include <sys/cpu.h>
#include <sys/cpu_event.h>
#include <sys/cmn_err.h>
#include <sys/ddi.h>
#include <sys/kmem.h>
#include <sys/kstat.h>
#include <sys/pci.h>
#include <sys/sunddi.h>
#include <sys/sunndi.h>
#include <sys/synch.h>
#include <sys/sysmacros.h>
#include <sys/fipe.h>
#include <vm/hat.h>
fipe_pm_policy_t fipe_pm_policy = FIPE_PM_POLICY_BALANCE;
int fipe_pm_throttle_level = 1;
#define FIPE_KSTAT_SUPPORT 1
#define FIPE_KSTAT_DETAIL 1
#define FIPE_IOAT_BUILTIN 0
#if defined(FIPE_IOAT_BUILTIN) && (FIPE_IOAT_BUILTIN == 0)
#undef FIPE_IOAT_BUILTIN
#endif
#ifdef FIPE_IOAT_BUILTIN
#define FIPE_IOAT_CHAN_CTRL 0x200
#define FIPE_IOAT_CHAN_STS_LO 0x204
#define FIPE_IOAT_CHAN_STS_HI 0x208
#define FIPE_IOAT_CHAN_ADDR_LO 0x20C
#define FIPE_IOAT_CHAN_ADDR_HI 0x210
#define FIPE_IOAT_CHAN_CMD 0x214
#define FIPE_IOAT_CHAN_ERR 0x228
#else
#include <sys/dcopy.h>
#endif
#define FIPE_MC_GBLACT 0x60
#define FIPE_MC_THRTLOW 0x64
#define FIPE_MC_THRTCTRL 0x67
#define FIPE_MC_THRTCTRL_HUNT 0x1
#define FIPE_MC_MEMORY_OFFSET 1024
#define FIPE_MC_MEMORY_SIZE 128
#define FIPE_IOAT_CMD_NUM 2
#define FIPE_IOAT_RETRY_INTERVAL (15 * 1000 * 1000)
#define FIPE_STAT_INTERVAL (10 * 1000 * 1000)
#define FIPE_PROFILE_FIELD(field) (fipe_profile_curr->field)
#define FIPE_PROF_IDLE_COUNT FIPE_PROFILE_FIELD(idle_count)
#define FIPE_PROF_BUSY_THRESHOLD FIPE_PROFILE_FIELD(busy_threshold)
#define FIPE_PROF_INTR_THRESHOLD FIPE_PROFILE_FIELD(intr_threshold)
#define FIPE_PROF_INTR_BUSY_THRESHOLD FIPE_PROFILE_FIELD(intr_busy_threshold)
#define FIPE_PROF_INTR_BUSY_THROTTLE FIPE_PROFILE_FIELD(intr_busy_throttle)
#define CPU_IDLE_CB_PRIO_FIPE (CPU_IDLE_CB_PRIO_LOW_BASE + 0x4000000)
#pragma align CPU_CACHE_COHERENCE_SIZE(fipe_profiles)
static struct fipe_profile {
uint32_t idle_count;
uint32_t busy_threshold;
uint32_t intr_threshold;
uint32_t intr_busy_threshold;
uint32_t intr_busy_throttle;
} fipe_profiles[FIPE_PM_POLICY_MAX] = {
{ 0, 0, 0, 0, 0 },
{ 5, 30, 20, 50, 5 },
{ 10, 40, 40, 75, 4 },
{ 15, 50, 60, 100, 2 },
};
#pragma align CPU_CACHE_COHERENCE_SIZE(fipe_mc_ctrl)
static struct fipe_mc_ctrl {
ddi_acc_handle_t mc_pci_hdl;
unsigned char mc_thrtctrl;
unsigned char mc_thrtlow;
unsigned char mc_gblact;
dev_info_t *mc_dip;
boolean_t mc_initialized;
} fipe_mc_ctrl;
#pragma align CPU_CACHE_COHERENCE_SIZE(fipe_ioat_ctrl)
static struct fipe_ioat_control {
kmutex_t ioat_lock;
boolean_t ioat_ready;
#ifdef FIPE_IOAT_BUILTIN
boolean_t ioat_reg_mapped;
ddi_acc_handle_t ioat_reg_handle;
uint8_t *ioat_reg_addr;
uint64_t ioat_cmd_physaddr;
#else
dcopy_cmd_t ioat_cmds[FIPE_IOAT_CMD_NUM + 1];
dcopy_handle_t ioat_handle;
#endif
dev_info_t *ioat_dev_info;
uint64_t ioat_buf_physaddr;
char *ioat_buf_virtaddr;
char *ioat_buf_start;
size_t ioat_buf_size;
timeout_id_t ioat_timerid;
boolean_t ioat_failed;
boolean_t ioat_cancel;
boolean_t ioat_try_alloc;
} fipe_ioat_ctrl;
#pragma align CPU_CACHE_COHERENCE_SIZE(fipe_idle_ctrl)
static struct fipe_idle_ctrl {
boolean_t idle_ready;
cpu_idle_callback_handle_t cb_handle;
cpu_idle_prop_handle_t prop_enter;
cpu_idle_prop_handle_t prop_exit;
cpu_idle_prop_handle_t prop_busy;
cpu_idle_prop_handle_t prop_idle;
cpu_idle_prop_handle_t prop_intr;
hrtime_t tick_interval;
} fipe_idle_ctrl;
#pragma align CPU_CACHE_COHERENCE_SIZE(fipe_gbl_ctrl)
static struct fipe_global_ctrl {
kmutex_t lock;
boolean_t pm_enabled;
volatile boolean_t pm_active;
volatile uint32_t cpu_count;
volatile uint64_t io_waiters;
hrtime_t enter_ts;
hrtime_t time_in_pm;
size_t state_size;
char *state_buf;
#ifdef FIPE_KSTAT_SUPPORT
kstat_t *fipe_kstat;
#endif
} fipe_gbl_ctrl;
#define FIPE_CPU_STATE_PAD (128 - \
2 * sizeof (boolean_t) - 4 * sizeof (hrtime_t) - \
2 * sizeof (uint64_t) - 2 * sizeof (uint32_t))
#pragma pack(1)
typedef struct fipe_cpu_state {
boolean_t cond_ready;
boolean_t state_ready;
uint32_t idle_count;
uint32_t throttle_cnt;
hrtime_t throttle_ts;
hrtime_t next_ts;
hrtime_t last_busy;
hrtime_t last_idle;
uint64_t last_intr;
uint64_t last_iowait;
char pad1[FIPE_CPU_STATE_PAD];
} fipe_cpu_state_t;
#pragma pack()
#ifdef FIPE_KSTAT_SUPPORT
#pragma align CPU_CACHE_COHERENCE_SIZE(fipe_kstat)
static struct fipe_kstat_s {
kstat_named_t fipe_enabled;
kstat_named_t fipe_policy;
kstat_named_t fipe_pm_time;
#ifdef FIPE_KSTAT_DETAIL
kstat_named_t ioat_ready;
kstat_named_t pm_tryenter_cnt;
kstat_named_t pm_success_cnt;
kstat_named_t pm_race_cnt;
kstat_named_t cpu_loop_cnt;
kstat_named_t cpu_busy_cnt;
kstat_named_t cpu_idle_cnt;
kstat_named_t cpu_intr_busy_cnt;
kstat_named_t cpu_intr_throttle_cnt;
kstat_named_t bio_busy_cnt;
kstat_named_t ioat_start_fail_cnt;
kstat_named_t ioat_stop_fail_cnt;
#endif
} fipe_kstat = {
{ "fipe_enabled", KSTAT_DATA_INT32 },
{ "fipe_policy", KSTAT_DATA_INT32 },
{ "fipe_pm_time", KSTAT_DATA_UINT64 },
#ifdef FIPE_KSTAT_DETAIL
{ "ioat_ready", KSTAT_DATA_INT32 },
{ "pm_tryenter_cnt", KSTAT_DATA_UINT64 },
{ "pm_success_cnt", KSTAT_DATA_UINT64 },
{ "pm_race_cnt", KSTAT_DATA_UINT64 },
{ "cpu_loop_cnt", KSTAT_DATA_UINT64 },
{ "cpu_busy_cnt", KSTAT_DATA_UINT64 },
{ "cpu_idle_cnt", KSTAT_DATA_UINT64 },
{ "cpu_intr_busy_cnt", KSTAT_DATA_UINT64 },
{ "cpu_intr_thrt_cnt", KSTAT_DATA_UINT64 },
{ "bio_busy_cnt", KSTAT_DATA_UINT64 },
{ "ioat_start_fail_cnt", KSTAT_DATA_UINT64 },
{ "ioat_stop_fail_cnt", KSTAT_DATA_UINT64 }
#endif
};
#define FIPE_KSTAT_INC(v) \
atomic_inc_64(&fipe_kstat.v.value.ui64)
#ifdef FIPE_KSTAT_DETAIL
#define FIPE_KSTAT_DETAIL_INC(v) \
atomic_inc_64(&fipe_kstat.v.value.ui64)
#else
#define FIPE_KSTAT_DETAIL_INC(v)
#endif
#else
#define FIPE_KSTAT_INC(v)
#define FIPE_KSTAT_DETAIL_INC(v)
#endif
static fipe_pm_policy_t fipe_pm_policy_saved = FIPE_PM_POLICY_BALANCE;
static fipe_cpu_state_t *fipe_cpu_states = NULL;
static struct fipe_profile *fipe_profile_curr = NULL;
static void fipe_idle_enter(void *arg, cpu_idle_callback_context_t ctx,
cpu_idle_check_wakeup_t check_func, void* check_arg);
static void fipe_idle_exit(void* arg, cpu_idle_callback_context_t ctx,
int flags);
static cpu_idle_callback_t fipe_idle_cb = {
CPU_IDLE_CALLBACK_VER0,
fipe_idle_enter,
fipe_idle_exit,
};
static int
fipe_mc_change(int throttle)
{
pci_config_put8(fipe_mc_ctrl.mc_pci_hdl, FIPE_MC_THRTCTRL,
fipe_mc_ctrl.mc_thrtctrl & ~FIPE_MC_THRTCTRL_HUNT);
pci_config_put8(fipe_mc_ctrl.mc_pci_hdl, FIPE_MC_GBLACT, 0);
pci_config_put8(fipe_mc_ctrl.mc_pci_hdl, FIPE_MC_THRTLOW, throttle);
pci_config_put8(fipe_mc_ctrl.mc_pci_hdl, FIPE_MC_THRTCTRL,
fipe_mc_ctrl.mc_thrtctrl | FIPE_MC_THRTCTRL_HUNT);
return (0);
}
static void
fipe_mc_restore(void)
{
pci_config_put8(fipe_mc_ctrl.mc_pci_hdl, FIPE_MC_THRTCTRL,
fipe_mc_ctrl.mc_thrtctrl & ~FIPE_MC_THRTCTRL_HUNT);
pci_config_put8(fipe_mc_ctrl.mc_pci_hdl, FIPE_MC_GBLACT,
fipe_mc_ctrl.mc_gblact);
pci_config_put8(fipe_mc_ctrl.mc_pci_hdl, FIPE_MC_THRTLOW,
fipe_mc_ctrl.mc_thrtlow);
pci_config_put8(fipe_mc_ctrl.mc_pci_hdl, FIPE_MC_THRTCTRL,
fipe_mc_ctrl.mc_thrtctrl);
}
static int
fipe_mc_init(dev_info_t *dip)
{
ddi_acc_handle_t handle;
bzero(&fipe_mc_ctrl, sizeof (fipe_mc_ctrl));
ndi_hold_devi(dip);
if (pci_config_setup(dip, &handle) != DDI_SUCCESS) {
cmn_err(CE_WARN,
"!fipe: failed to setup pcicfg handler in mc_init.");
ndi_rele_devi(dip);
return (-1);
}
fipe_mc_ctrl.mc_thrtctrl = pci_config_get8(handle, FIPE_MC_THRTCTRL);
fipe_mc_ctrl.mc_thrtlow = pci_config_get8(handle, FIPE_MC_THRTLOW);
fipe_mc_ctrl.mc_gblact = pci_config_get8(handle, FIPE_MC_GBLACT);
fipe_mc_ctrl.mc_dip = dip;
fipe_mc_ctrl.mc_pci_hdl = handle;
fipe_mc_ctrl.mc_initialized = B_TRUE;
return (0);
}
static void
fipe_mc_fini(void)
{
if (fipe_mc_ctrl.mc_initialized) {
fipe_mc_restore();
pci_config_teardown(&fipe_mc_ctrl.mc_pci_hdl);
ndi_rele_devi(fipe_mc_ctrl.mc_dip);
fipe_mc_ctrl.mc_initialized = B_FALSE;
}
bzero(&fipe_mc_ctrl, sizeof (fipe_mc_ctrl));
}
struct fipe_pci_ioat_id {
uint16_t venid;
uint16_t devid;
uint16_t subvenid;
uint16_t subsysid;
char *unitaddr;
};
static struct fipe_pci_ioat_id fipe_pci_ioat_ids[] = {
{ 0x8086, 0x1a38, 0xffff, 0xffff, NULL },
{ 0x8086, 0x360b, 0xffff, 0xffff, NULL },
};
static int
fipe_search_ioat_dev(dev_info_t *dip, void *arg)
{
char *unit;
struct fipe_pci_ioat_id *id;
int i, max, venid, devid, subvenid, subsysid;
venid = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
"vendor-id", 0xffffffff);
if (venid == 0xffffffff) {
return (DDI_WALK_CONTINUE);
}
devid = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
"device-id", 0xffffffff);
if (devid == 0xffffffff) {
return (DDI_WALK_CONTINUE);
}
subvenid = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
"subsystem-vendor-id", 0xffffffff);
if (subvenid == 0xffffffff) {
return (DDI_WALK_CONTINUE);
}
subsysid = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
"subsystem-id", 0xffffffff);
if (subvenid == 0xffffffff) {
return (DDI_WALK_CONTINUE);
}
if (ddi_prop_lookup_string(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
"unit-address", &unit) != DDI_PROP_SUCCESS) {
return (DDI_WALK_CONTINUE);
}
max = sizeof (fipe_pci_ioat_ids) / sizeof (fipe_pci_ioat_ids[0]);
for (i = 0; i < max; i++) {
id = &fipe_pci_ioat_ids[i];
if ((id->venid == 0xffffu || id->venid == venid) &&
(id->devid == 0xffffu || id->devid == devid) &&
(id->subvenid == 0xffffu || id->subvenid == subvenid) &&
(id->subsysid == 0xffffu || id->subsysid == subsysid) &&
(id->unitaddr == NULL || strcmp(id->unitaddr, unit) == 0)) {
break;
}
}
ddi_prop_free(unit);
if (i >= max) {
return (DDI_WALK_CONTINUE);
}
ndi_hold_devi(dip);
fipe_ioat_ctrl.ioat_dev_info = dip;
return (DDI_WALK_TERMINATE);
}
#ifdef FIPE_IOAT_BUILTIN
static int
fipe_ioat_trigger(void)
{
uint16_t ctrl;
uint32_t err;
uint8_t *addr = fipe_ioat_ctrl.ioat_reg_addr;
ddi_acc_handle_t handle = fipe_ioat_ctrl.ioat_reg_handle;
ctrl = ddi_get16(handle, (uint16_t *)(addr + FIPE_IOAT_CHAN_CTRL));
if (ctrl & 0x100) {
fipe_ioat_ctrl.ioat_ready = B_FALSE;
fipe_ioat_ctrl.ioat_failed = B_TRUE;
FIPE_KSTAT_INC(ioat_start_fail_cnt);
return (-1);
} else {
ddi_put16(handle,
(uint16_t *)(addr + FIPE_IOAT_CHAN_CTRL), 0x100);
}
ddi_put32(handle,
(uint32_t *)(addr + FIPE_IOAT_CHAN_ADDR_LO),
(uint32_t)fipe_ioat_ctrl.ioat_cmd_physaddr);
ddi_put32(handle, (uint32_t *)(addr + FIPE_IOAT_CHAN_ADDR_HI),
(uint32_t)(fipe_ioat_ctrl.ioat_cmd_physaddr >> 32));
err = ddi_get32(handle, (uint32_t *)(addr + FIPE_IOAT_CHAN_ERR));
if (err != 0) {
ddi_put32(handle, (uint32_t *)(addr + FIPE_IOAT_CHAN_ERR), err);
}
ddi_put8(handle, (uint8_t *)(addr + FIPE_IOAT_CHAN_CMD), 0x1);
return (0);
}
static void
fipe_ioat_cancel(void)
{
uint32_t status;
uint8_t *addr = fipe_ioat_ctrl.ioat_reg_addr;
ddi_acc_handle_t handle = fipe_ioat_ctrl.ioat_reg_handle;
while (1) {
ddi_put8(handle, (uint8_t *)(addr + FIPE_IOAT_CHAN_CMD), 0x20);
status = ddi_get32(handle,
(uint32_t *)(addr + FIPE_IOAT_CHAN_STS_LO));
if (status & 0x1) {
break;
} else {
SMT_PAUSE();
}
}
ddi_put16(handle, (uint16_t *)(addr + FIPE_IOAT_CHAN_CTRL), 0);
}
static void
fipe_ioat_alloc(void *arg)
{
int rc = 0, nregs;
dev_info_t *dip;
ddi_device_acc_attr_t attr;
boolean_t fatal = B_FALSE;
mutex_enter(&fipe_ioat_ctrl.ioat_lock);
if (fipe_ioat_ctrl.ioat_try_alloc == B_FALSE) {
fipe_ioat_ctrl.ioat_try_alloc = B_TRUE;
goto out_error;
}
if (fipe_ioat_ctrl.ioat_ready || fipe_ioat_ctrl.ioat_failed ||
fipe_ioat_ctrl.ioat_cancel) {
fipe_ioat_ctrl.ioat_timerid = 0;
mutex_exit(&fipe_ioat_ctrl.ioat_lock);
return;
}
if (fipe_ioat_ctrl.ioat_dev_info == NULL) {
ddi_walk_devs(ddi_root_node(), fipe_search_ioat_dev, NULL);
if (fipe_ioat_ctrl.ioat_dev_info == NULL) {
cmn_err(CE_NOTE,
"!fipe: no IOAT hardware found, disable pm.");
fatal = B_TRUE;
goto out_error;
}
}
ASSERT(fipe_ioat_ctrl.ioat_dev_info != NULL);
ASSERT(fipe_ioat_ctrl.ioat_reg_mapped == B_FALSE);
dip = fipe_ioat_ctrl.ioat_dev_info;
if (ddi_dev_nregs(dip, &nregs) != DDI_SUCCESS || nregs < 2) {
cmn_err(CE_WARN, "!fipe: ioat has not enough register bars.");
fatal = B_TRUE;
goto out_error;
}
attr.devacc_attr_version = DDI_DEVICE_ATTR_V0;
attr.devacc_attr_endian_flags = DDI_NEVERSWAP_ACC;
attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC;
rc = ddi_regs_map_setup(dip, 1,
(caddr_t *)&fipe_ioat_ctrl.ioat_reg_addr,
0, 0, &attr, &fipe_ioat_ctrl.ioat_reg_handle);
if (rc != DDI_SUCCESS) {
cmn_err(CE_WARN, "!fipe: failed to map IOAT registeres.");
fatal = B_TRUE;
goto out_error;
}
fipe_ioat_ctrl.ioat_reg_mapped = B_TRUE;
fipe_ioat_ctrl.ioat_ready = B_TRUE;
fipe_ioat_ctrl.ioat_failed = B_FALSE;
fipe_ioat_ctrl.ioat_timerid = 0;
mutex_exit(&fipe_ioat_ctrl.ioat_lock);
return;
out_error:
fipe_ioat_ctrl.ioat_timerid = 0;
if (!fipe_ioat_ctrl.ioat_ready && !fipe_ioat_ctrl.ioat_cancel) {
if (fatal) {
fipe_ioat_ctrl.ioat_failed = B_TRUE;
if (fipe_ioat_ctrl.ioat_dev_info != NULL) {
ndi_rele_devi(fipe_ioat_ctrl.ioat_dev_info);
fipe_ioat_ctrl.ioat_dev_info = NULL;
}
} else {
fipe_ioat_ctrl.ioat_timerid = timeout(fipe_ioat_alloc,
NULL, drv_usectohz(FIPE_IOAT_RETRY_INTERVAL));
}
}
mutex_exit(&fipe_ioat_ctrl.ioat_lock);
}
static void
fipe_ioat_free(void)
{
mutex_enter(&fipe_ioat_ctrl.ioat_lock);
if (fipe_ioat_ctrl.ioat_timerid != 0) {
fipe_ioat_ctrl.ioat_cancel = B_TRUE;
mutex_exit(&fipe_ioat_ctrl.ioat_lock);
(void) untimeout(fipe_ioat_ctrl.ioat_timerid);
mutex_enter(&fipe_ioat_ctrl.ioat_lock);
fipe_ioat_ctrl.ioat_timerid = 0;
fipe_ioat_ctrl.ioat_cancel = B_FALSE;
}
if (fipe_ioat_ctrl.ioat_reg_mapped) {
ddi_regs_map_free(&fipe_ioat_ctrl.ioat_reg_handle);
fipe_ioat_ctrl.ioat_reg_mapped = B_FALSE;
}
fipe_ioat_ctrl.ioat_ready = B_FALSE;
mutex_exit(&fipe_ioat_ctrl.ioat_lock);
}
#else
static int
fipe_ioat_trigger(void)
{
int idx;
dcopy_cmd_t *cmds = fipe_ioat_ctrl.ioat_cmds;
for (idx = FIPE_IOAT_CMD_NUM; idx > 0; idx--) {
if (dcopy_cmd_post(cmds[idx]) == DCOPY_SUCCESS) {
continue;
} else {
FIPE_KSTAT_DETAIL_INC(ioat_start_fail_cnt);
return (-1);
}
}
return (0);
}
static void
fipe_ioat_cancel(void)
{
if (dcopy_cmd_post(fipe_ioat_ctrl.ioat_cmds[0]) != DCOPY_SUCCESS) {
FIPE_KSTAT_DETAIL_INC(ioat_stop_fail_cnt);
}
}
static void
fipe_ioat_alloc(void *arg)
{
int idx, flags, rc = 0;
uint64_t physaddr;
boolean_t fatal = B_FALSE;
dcopy_query_t info;
dcopy_handle_t handle;
dcopy_cmd_t cmds[FIPE_IOAT_CMD_NUM + 1];
mutex_enter(&fipe_ioat_ctrl.ioat_lock);
if (fipe_ioat_ctrl.ioat_try_alloc == B_FALSE) {
fipe_ioat_ctrl.ioat_try_alloc = B_TRUE;
mutex_exit(&fipe_ioat_ctrl.ioat_lock);
goto out_error;
}
if (fipe_ioat_ctrl.ioat_ready || fipe_ioat_ctrl.ioat_failed ||
fipe_ioat_ctrl.ioat_cancel) {
fipe_ioat_ctrl.ioat_timerid = 0;
mutex_exit(&fipe_ioat_ctrl.ioat_lock);
return;
}
if (fipe_ioat_ctrl.ioat_dev_info == NULL) {
ddi_walk_devs(ddi_root_node(), fipe_search_ioat_dev, NULL);
if (fipe_ioat_ctrl.ioat_dev_info == NULL) {
cmn_err(CE_NOTE,
"!fipe: no IOAT hardware found, disable pm.");
mutex_exit(&fipe_ioat_ctrl.ioat_lock);
fatal = B_TRUE;
goto out_error;
}
}
mutex_exit(&fipe_ioat_ctrl.ioat_lock);
dcopy_query(&info);
if (info.dq_version < DCOPY_QUERY_V0) {
cmn_err(CE_WARN, "!fipe: IOAT driver version mismatch.");
fatal = B_TRUE;
goto out_error;
} else if (info.dq_num_channels == 0) {
goto out_error;
}
rc = dcopy_alloc(DCOPY_NOSLEEP, &handle);
if (rc == DCOPY_NORESOURCES) {
goto out_error;
} else if (rc != DCOPY_SUCCESS) {
cmn_err(CE_WARN, "!fipe: failed to allocate IOAT channel.");
fatal = B_TRUE;
goto out_error;
}
bzero(cmds, sizeof (cmds));
physaddr = fipe_ioat_ctrl.ioat_buf_physaddr;
for (idx = FIPE_IOAT_CMD_NUM; idx >= 0; idx--) {
if (idx == 0 || idx == FIPE_IOAT_CMD_NUM) {
flags = DCOPY_NOSLEEP;
} else {
flags = DCOPY_NOSLEEP | DCOPY_ALLOC_LINK;
cmds[idx] = cmds[idx + 1];
}
rc = dcopy_cmd_alloc(handle, flags, &cmds[idx]);
if (rc == DCOPY_NORESOURCES) {
goto out_freecmd;
} else if (rc != DCOPY_SUCCESS) {
cmn_err(CE_WARN,
"!fipe: failed to allocate IOAT command.");
fatal = B_TRUE;
goto out_freecmd;
}
cmds[idx]->dp_flags = DCOPY_CMD_NOSRCSNP | DCOPY_CMD_NODSTSNP;
if (idx != 0) {
cmds[idx]->dp_flags |= DCOPY_CMD_NOSTAT;
cmds[idx]->dp_flags |= DCOPY_CMD_NOWAIT;
if (idx == 1) {
cmds[idx]->dp_flags |= DCOPY_CMD_LOOP;
} else {
cmds[idx]->dp_flags |= DCOPY_CMD_QUEUE;
}
}
cmds[idx]->dp_cmd = DCOPY_CMD_COPY;
cmds[idx]->dp.copy.cc_source = physaddr;
cmds[idx]->dp.copy.cc_dest = physaddr + FIPE_MC_MEMORY_OFFSET;
if (idx == 0) {
cmds[idx]->dp.copy.cc_size = 1;
} else {
cmds[idx]->dp.copy.cc_size = FIPE_MC_MEMORY_SIZE;
}
}
mutex_enter(&fipe_ioat_ctrl.ioat_lock);
if (!fipe_ioat_ctrl.ioat_ready && !fipe_ioat_ctrl.ioat_cancel) {
fipe_ioat_ctrl.ioat_handle = handle;
for (idx = 0; idx <= FIPE_IOAT_CMD_NUM; idx++) {
fipe_ioat_ctrl.ioat_cmds[idx] = cmds[idx];
}
fipe_ioat_ctrl.ioat_ready = B_TRUE;
fipe_ioat_ctrl.ioat_failed = B_FALSE;
fipe_ioat_ctrl.ioat_timerid = 0;
mutex_exit(&fipe_ioat_ctrl.ioat_lock);
return;
}
mutex_exit(&fipe_ioat_ctrl.ioat_lock);
out_freecmd:
if (cmds[0] != NULL) {
dcopy_cmd_free(&cmds[0]);
}
for (idx = 1; idx <= FIPE_IOAT_CMD_NUM; idx++) {
if (cmds[idx] != NULL) {
dcopy_cmd_free(&cmds[idx]);
break;
}
}
dcopy_free(&handle);
out_error:
mutex_enter(&fipe_ioat_ctrl.ioat_lock);
fipe_ioat_ctrl.ioat_timerid = 0;
if (!fipe_ioat_ctrl.ioat_ready && !fipe_ioat_ctrl.ioat_cancel) {
if (fatal) {
fipe_ioat_ctrl.ioat_failed = B_TRUE;
if (fipe_ioat_ctrl.ioat_dev_info != NULL) {
ndi_rele_devi(fipe_ioat_ctrl.ioat_dev_info);
fipe_ioat_ctrl.ioat_dev_info = NULL;
}
} else {
fipe_ioat_ctrl.ioat_timerid = timeout(fipe_ioat_alloc,
NULL, drv_usectohz(FIPE_IOAT_RETRY_INTERVAL));
}
}
mutex_exit(&fipe_ioat_ctrl.ioat_lock);
}
static void
fipe_ioat_free(void)
{
int idx = 0;
dcopy_cmd_t *cmds = fipe_ioat_ctrl.ioat_cmds;
mutex_enter(&fipe_ioat_ctrl.ioat_lock);
if (fipe_ioat_ctrl.ioat_timerid != 0) {
fipe_ioat_ctrl.ioat_cancel = B_TRUE;
mutex_exit(&fipe_ioat_ctrl.ioat_lock);
(void) untimeout(fipe_ioat_ctrl.ioat_timerid);
mutex_enter(&fipe_ioat_ctrl.ioat_lock);
fipe_ioat_ctrl.ioat_timerid = 0;
fipe_ioat_ctrl.ioat_cancel = B_FALSE;
}
if (fipe_ioat_ctrl.ioat_ready) {
if (cmds[0] != NULL) {
dcopy_cmd_free(&cmds[0]);
}
for (idx = 1; idx <= FIPE_IOAT_CMD_NUM; idx++) {
if (cmds[idx] != NULL) {
dcopy_cmd_free(&cmds[idx]);
break;
}
}
bzero(fipe_ioat_ctrl.ioat_cmds,
sizeof (fipe_ioat_ctrl.ioat_cmds));
dcopy_free(&fipe_ioat_ctrl.ioat_handle);
fipe_ioat_ctrl.ioat_handle = NULL;
fipe_ioat_ctrl.ioat_ready = B_FALSE;
}
if (fipe_ioat_ctrl.ioat_dev_info != NULL) {
ndi_rele_devi(fipe_ioat_ctrl.ioat_dev_info);
fipe_ioat_ctrl.ioat_dev_info = NULL;
}
mutex_exit(&fipe_ioat_ctrl.ioat_lock);
}
#endif
static int
fipe_ioat_init(void)
{
char *buf;
size_t size;
bzero(&fipe_ioat_ctrl, sizeof (fipe_ioat_ctrl));
mutex_init(&fipe_ioat_ctrl.ioat_lock, NULL, MUTEX_DRIVER, NULL);
size = PAGESIZE;
buf = kmem_zalloc(size, KM_SLEEP);
if ((intptr_t)buf & PAGEOFFSET) {
kmem_free(buf, PAGESIZE);
size <<= 1;
buf = kmem_zalloc(size, KM_SLEEP);
}
fipe_ioat_ctrl.ioat_buf_size = size;
fipe_ioat_ctrl.ioat_buf_start = buf;
buf = (char *)P2ROUNDUP((intptr_t)buf, PAGESIZE);
fipe_ioat_ctrl.ioat_buf_virtaddr = buf;
fipe_ioat_ctrl.ioat_buf_physaddr = hat_getpfnum(kas.a_hat, buf);
fipe_ioat_ctrl.ioat_buf_physaddr <<= PAGESHIFT;
#ifdef FIPE_IOAT_BUILTIN
{
uint64_t bufpa;
struct fipe_ioat_cmd_desc {
uint32_t dd_size;
uint32_t dd_ctrl;
uint64_t dd_src_paddr;
uint64_t dd_dest_paddr;
uint64_t dd_next_desc;
uint64_t dd_res4;
uint64_t dd_res5;
uint64_t dd_res6;
uint64_t dd_res7;
} *desc;
buf = fipe_ioat_ctrl.ioat_buf_virtaddr;
bufpa = fipe_ioat_ctrl.ioat_buf_physaddr;
fipe_ioat_ctrl.ioat_cmd_physaddr = bufpa;
desc = (struct fipe_ioat_cmd_desc *)(buf);
desc->dd_size = 128;
desc->dd_ctrl = 0x6;
desc->dd_src_paddr = bufpa + 2048;
desc->dd_dest_paddr = bufpa + 3072;
desc->dd_next_desc = bufpa + 64;
desc = (struct fipe_ioat_cmd_desc *)(buf + 64);
desc->dd_size = 128;
desc->dd_ctrl = 0x6;
desc->dd_src_paddr = bufpa + 2048;
desc->dd_dest_paddr = bufpa + 3072;
desc->dd_next_desc = bufpa;
}
#endif
return (0);
}
static void
fipe_ioat_fini(void)
{
if (fipe_ioat_ctrl.ioat_dev_info != NULL) {
ndi_rele_devi(fipe_ioat_ctrl.ioat_dev_info);
fipe_ioat_ctrl.ioat_dev_info = NULL;
}
if (fipe_ioat_ctrl.ioat_buf_start != NULL) {
ASSERT(fipe_ioat_ctrl.ioat_buf_size != 0);
kmem_free(fipe_ioat_ctrl.ioat_buf_start,
fipe_ioat_ctrl.ioat_buf_size);
}
mutex_destroy(&fipe_ioat_ctrl.ioat_lock);
bzero(&fipe_ioat_ctrl, sizeof (fipe_ioat_ctrl));
}
static int
fipe_idle_start(void)
{
int rc;
if (fipe_idle_ctrl.idle_ready) {
return (0);
}
if (cpu_idle_prop_create_handle(CPU_IDLE_PROP_ENTER_TIMESTAMP,
&fipe_idle_ctrl.prop_enter) != 0) {
cmn_err(CE_WARN, "!fipe: failed to get enter_ts property.");
return (-1);
}
if (cpu_idle_prop_create_handle(CPU_IDLE_PROP_EXIT_TIMESTAMP,
&fipe_idle_ctrl.prop_exit) != 0) {
cmn_err(CE_WARN, "!fipe: failed to get exit_ts property.");
(void) cpu_idle_prop_destroy_handle(fipe_idle_ctrl.prop_enter);
return (-1);
}
if (cpu_idle_prop_create_handle(CPU_IDLE_PROP_TOTAL_IDLE_TIME,
&fipe_idle_ctrl.prop_idle) != 0) {
cmn_err(CE_WARN, "!fipe: failed to get idle_time property.");
(void) cpu_idle_prop_destroy_handle(fipe_idle_ctrl.prop_exit);
(void) cpu_idle_prop_destroy_handle(fipe_idle_ctrl.prop_enter);
return (-1);
}
if (cpu_idle_prop_create_handle(CPU_IDLE_PROP_TOTAL_BUSY_TIME,
&fipe_idle_ctrl.prop_busy) != 0) {
cmn_err(CE_WARN, "!fipe: failed to get busy_time property.");
(void) cpu_idle_prop_destroy_handle(fipe_idle_ctrl.prop_idle);
(void) cpu_idle_prop_destroy_handle(fipe_idle_ctrl.prop_exit);
(void) cpu_idle_prop_destroy_handle(fipe_idle_ctrl.prop_enter);
return (-1);
}
if (cpu_idle_prop_create_handle(CPU_IDLE_PROP_INTERRUPT_COUNT,
&fipe_idle_ctrl.prop_intr) != 0) {
cmn_err(CE_WARN, "!fipe: failed to get intr_count property.");
(void) cpu_idle_prop_destroy_handle(fipe_idle_ctrl.prop_busy);
(void) cpu_idle_prop_destroy_handle(fipe_idle_ctrl.prop_idle);
(void) cpu_idle_prop_destroy_handle(fipe_idle_ctrl.prop_exit);
(void) cpu_idle_prop_destroy_handle(fipe_idle_ctrl.prop_enter);
return (-1);
}
rc = cpu_idle_register_callback(CPU_IDLE_CB_PRIO_FIPE, &fipe_idle_cb,
NULL, &fipe_idle_ctrl.cb_handle);
if (rc != 0) {
cmn_err(CE_WARN, "!fipe: failed to register cpuidle callback.");
(void) cpu_idle_prop_destroy_handle(fipe_idle_ctrl.prop_intr);
(void) cpu_idle_prop_destroy_handle(fipe_idle_ctrl.prop_busy);
(void) cpu_idle_prop_destroy_handle(fipe_idle_ctrl.prop_idle);
(void) cpu_idle_prop_destroy_handle(fipe_idle_ctrl.prop_exit);
(void) cpu_idle_prop_destroy_handle(fipe_idle_ctrl.prop_enter);
return (-1);
}
fipe_idle_ctrl.idle_ready = B_TRUE;
return (0);
}
static int
fipe_idle_stop(void)
{
int rc;
if (fipe_idle_ctrl.idle_ready == B_FALSE) {
return (0);
}
rc = cpu_idle_unregister_callback(fipe_idle_ctrl.cb_handle);
if (rc != 0) {
cmn_err(CE_WARN,
"!fipe: failed to unregister cpuidle callback.");
return (-1);
}
(void) cpu_idle_prop_destroy_handle(fipe_idle_ctrl.prop_intr);
(void) cpu_idle_prop_destroy_handle(fipe_idle_ctrl.prop_busy);
(void) cpu_idle_prop_destroy_handle(fipe_idle_ctrl.prop_idle);
(void) cpu_idle_prop_destroy_handle(fipe_idle_ctrl.prop_exit);
(void) cpu_idle_prop_destroy_handle(fipe_idle_ctrl.prop_enter);
fipe_idle_ctrl.idle_ready = B_FALSE;
return (0);
}
#ifdef FIPE_KSTAT_SUPPORT
static int
fipe_kstat_update(kstat_t *ksp, int rw)
{
struct fipe_kstat_s *sp;
hrtime_t hrt;
if (rw == KSTAT_WRITE) {
return (EACCES);
}
sp = ksp->ks_data;
sp->fipe_enabled.value.i32 = fipe_gbl_ctrl.pm_enabled ? 1 : 0;
sp->fipe_policy.value.i32 = fipe_pm_policy;
hrt = fipe_gbl_ctrl.time_in_pm;
scalehrtime(&hrt);
sp->fipe_pm_time.value.ui64 = (uint64_t)hrt;
#ifdef FIPE_KSTAT_DETAIL
sp->ioat_ready.value.i32 = fipe_ioat_ctrl.ioat_ready ? 1 : 0;
#endif
return (0);
}
#endif
int
fipe_init(dev_info_t *dip)
{
size_t nsize;
hrtime_t hrt;
bzero(&fipe_gbl_ctrl, sizeof (fipe_gbl_ctrl));
mutex_init(&fipe_gbl_ctrl.lock, NULL, MUTEX_DRIVER, NULL);
fipe_pm_policy = ddi_prop_get_int(DDI_DEV_T_ANY, dip, 0,
FIPE_PROP_PM_POLICY, fipe_pm_policy);
if (fipe_pm_policy < 0 || fipe_pm_policy >= FIPE_PM_POLICY_MAX) {
cmn_err(CE_CONT,
"?fipe: invalid power management policy %d.\n",
fipe_pm_policy);
fipe_pm_policy = FIPE_PM_POLICY_BALANCE;
}
fipe_profile_curr = &fipe_profiles[fipe_pm_policy];
hrt = 1ULL << 36;
scalehrtime(&hrt);
fipe_idle_ctrl.tick_interval = FIPE_STAT_INTERVAL * (1ULL << 36) / hrt;
if (fipe_mc_init(dip) != 0) {
cmn_err(CE_WARN, "!fipe: failed to initialize mc state.");
goto out_mc_error;
}
if (fipe_ioat_init() != 0) {
cmn_err(CE_NOTE, "!fipe: failed to initialize ioat state.");
goto out_ioat_error;
}
nsize = max_ncpus * sizeof (fipe_cpu_state_t);
nsize += CPU_CACHE_COHERENCE_SIZE;
fipe_gbl_ctrl.state_buf = kmem_zalloc(nsize, KM_SLEEP);
fipe_gbl_ctrl.state_size = nsize;
fipe_cpu_states = (fipe_cpu_state_t *)P2ROUNDUP(
(intptr_t)fipe_gbl_ctrl.state_buf, CPU_CACHE_COHERENCE_SIZE);
#ifdef FIPE_KSTAT_SUPPORT
fipe_gbl_ctrl.fipe_kstat = kstat_create("fipe", 0, "fipe-pm", "misc",
KSTAT_TYPE_NAMED, sizeof (fipe_kstat) / sizeof (kstat_named_t),
KSTAT_FLAG_VIRTUAL);
if (fipe_gbl_ctrl.fipe_kstat == NULL) {
cmn_err(CE_CONT, "?fipe: failed to create kstat object.\n");
} else {
fipe_gbl_ctrl.fipe_kstat->ks_lock = &fipe_gbl_ctrl.lock;
fipe_gbl_ctrl.fipe_kstat->ks_data = &fipe_kstat;
fipe_gbl_ctrl.fipe_kstat->ks_update = fipe_kstat_update;
kstat_install(fipe_gbl_ctrl.fipe_kstat);
}
#endif
return (0);
out_ioat_error:
fipe_mc_fini();
out_mc_error:
mutex_destroy(&fipe_gbl_ctrl.lock);
bzero(&fipe_gbl_ctrl, sizeof (fipe_gbl_ctrl));
return (-1);
}
int
fipe_fini(void)
{
if (fipe_gbl_ctrl.pm_enabled) {
cmn_err(CE_NOTE, "!fipe: call fipe_fini without stopping PM.");
return (EBUSY);
}
ASSERT(!fipe_gbl_ctrl.pm_active);
fipe_ioat_fini();
fipe_mc_fini();
#ifdef FIPE_KSTAT_SUPPORT
if (fipe_gbl_ctrl.fipe_kstat != NULL) {
kstat_delete(fipe_gbl_ctrl.fipe_kstat);
fipe_gbl_ctrl.fipe_kstat = NULL;
}
#endif
if (fipe_gbl_ctrl.state_buf != NULL) {
ASSERT(fipe_gbl_ctrl.state_size != 0);
kmem_free(fipe_gbl_ctrl.state_buf, fipe_gbl_ctrl.state_size);
fipe_cpu_states = NULL;
}
fipe_profile_curr = NULL;
mutex_destroy(&fipe_gbl_ctrl.lock);
bzero(&fipe_gbl_ctrl, sizeof (fipe_gbl_ctrl));
return (0);
}
int
fipe_start(void)
{
if (fipe_gbl_ctrl.pm_enabled == B_TRUE) {
return (0);
}
bzero(fipe_cpu_states, max_ncpus * sizeof (fipe_cpu_states[0]));
fipe_ioat_alloc(NULL);
if (fipe_idle_start() != 0) {
cmn_err(CE_NOTE, "!fipe: failed to start PM subsystem.");
fipe_ioat_free();
return (-1);
}
fipe_gbl_ctrl.pm_enabled = B_TRUE;
return (0);
}
int
fipe_stop(void)
{
if (fipe_gbl_ctrl.pm_enabled) {
if (fipe_idle_stop() != 0) {
cmn_err(CE_NOTE,
"!fipe: failed to stop PM subsystem.");
return (-1);
}
fipe_ioat_free();
fipe_gbl_ctrl.pm_enabled = B_FALSE;
}
ASSERT(!fipe_gbl_ctrl.pm_active);
return (0);
}
int
fipe_suspend(void)
{
fipe_pm_policy_saved = fipe_pm_policy;
fipe_pm_policy = FIPE_PM_POLICY_DISABLE;
fipe_profile_curr = &fipe_profiles[fipe_pm_policy];
return (0);
}
int
fipe_resume(void)
{
fipe_pm_policy = fipe_pm_policy_saved;
fipe_profile_curr = &fipe_profiles[fipe_pm_policy];
return (0);
}
fipe_pm_policy_t
fipe_get_pmpolicy(void)
{
return (fipe_pm_policy);
}
int
fipe_set_pmpolicy(fipe_pm_policy_t policy)
{
if (policy < 0 || policy >= FIPE_PM_POLICY_MAX) {
return (EINVAL);
}
fipe_pm_policy = policy;
fipe_profile_curr = &fipe_profiles[fipe_pm_policy];
return (0);
}
static void
fipe_enable(int throttle, cpu_idle_check_wakeup_t check_func, void* check_arg)
{
extern void membar_sync(void);
FIPE_KSTAT_DETAIL_INC(pm_tryenter_cnt);
if (check_func != NULL) {
(*check_func)(check_arg);
}
if (mutex_tryenter(&fipe_gbl_ctrl.lock) == 0) {
FIPE_KSTAT_DETAIL_INC(pm_race_cnt);
} else if (fipe_gbl_ctrl.pm_active) {
FIPE_KSTAT_DETAIL_INC(pm_race_cnt);
mutex_exit(&fipe_gbl_ctrl.lock);
} else {
fipe_gbl_ctrl.pm_active = B_TRUE;
membar_sync();
if (fipe_gbl_ctrl.cpu_count != ncpus) {
FIPE_KSTAT_DETAIL_INC(pm_race_cnt);
fipe_gbl_ctrl.pm_active = B_FALSE;
} else if (fipe_ioat_trigger() != 0) {
fipe_gbl_ctrl.pm_active = B_FALSE;
} else if (fipe_gbl_ctrl.cpu_count != ncpus ||
fipe_mc_change(throttle) != 0) {
fipe_gbl_ctrl.pm_active = B_FALSE;
fipe_ioat_cancel();
if (fipe_gbl_ctrl.cpu_count != ncpus) {
FIPE_KSTAT_DETAIL_INC(pm_race_cnt);
}
} else if (fipe_gbl_ctrl.cpu_count != ncpus) {
fipe_gbl_ctrl.pm_active = B_FALSE;
fipe_mc_restore();
fipe_ioat_cancel();
FIPE_KSTAT_DETAIL_INC(pm_race_cnt);
} else {
FIPE_KSTAT_DETAIL_INC(pm_success_cnt);
}
mutex_exit(&fipe_gbl_ctrl.lock);
}
}
static void
fipe_disable(void)
{
while (mutex_tryenter(&fipe_gbl_ctrl.lock) == 0) {
if (fipe_gbl_ctrl.pm_active == B_FALSE) {
return;
} else {
(void) SMT_PAUSE();
}
}
if (fipe_gbl_ctrl.pm_active) {
fipe_gbl_ctrl.pm_active = B_FALSE;
membar_producer();
fipe_mc_restore();
fipe_ioat_cancel();
}
mutex_exit(&fipe_gbl_ctrl.lock);
}
static boolean_t
fipe_check_cpu(struct fipe_cpu_state *sp, cpu_idle_callback_context_t ctx,
hrtime_t ts)
{
if (cpu_flagged_offline(CPU->cpu_flags)) {
sp->cond_ready = B_TRUE;
return (B_TRUE);
} else if (sp->next_ts <= ts) {
uint64_t intr;
hrtime_t idle, busy, diff;
cpu_idle_prop_value_t val;
sp->cond_ready = B_TRUE;
sp->idle_count = 0;
idle = sp->last_idle;
sp->last_idle = cpu_idle_prop_get_hrtime(
fipe_idle_ctrl.prop_idle, ctx);
idle = sp->last_idle - idle;
busy = sp->last_busy;
sp->last_busy = cpu_idle_prop_get_hrtime(
fipe_idle_ctrl.prop_busy, ctx);
busy = sp->last_busy - busy;
if (idle > 0 && busy > 0) {
if (busy * (100 - FIPE_PROF_BUSY_THRESHOLD) >
idle * FIPE_PROF_BUSY_THRESHOLD) {
FIPE_KSTAT_DETAIL_INC(cpu_busy_cnt);
sp->cond_ready = B_FALSE;
} else {
FIPE_KSTAT_DETAIL_INC(cpu_idle_cnt);
}
} else {
FIPE_KSTAT_DETAIL_INC(cpu_busy_cnt);
sp->cond_ready = B_FALSE;
}
diff = sp->next_ts;
sp->next_ts = ts + fipe_idle_ctrl.tick_interval;
diff = sp->next_ts - diff;
intr = sp->last_intr;
if (cpu_idle_prop_get_value(fipe_idle_ctrl.prop_intr, ctx,
&val) == 0) {
sp->last_intr = val.cipv_uint64;
intr = sp->last_intr - intr;
if (diff != 0) {
intr = intr * fipe_idle_ctrl.tick_interval;
intr /= diff;
} else {
intr = FIPE_PROF_INTR_THRESHOLD;
}
} else {
intr = FIPE_PROF_INTR_THRESHOLD;
}
if (intr >= FIPE_PROF_INTR_BUSY_THRESHOLD) {
FIPE_KSTAT_DETAIL_INC(cpu_intr_busy_cnt);
sp->throttle_ts = ts + FIPE_PROF_INTR_BUSY_THROTTLE *
fipe_idle_ctrl.tick_interval;
sp->cond_ready = B_FALSE;
} else if (intr >= FIPE_PROF_INTR_THRESHOLD) {
FIPE_KSTAT_DETAIL_INC(cpu_intr_throttle_cnt);
sp->cond_ready = B_FALSE;
}
} else if (++sp->idle_count >= FIPE_PROF_IDLE_COUNT) {
FIPE_KSTAT_DETAIL_INC(cpu_loop_cnt);
sp->throttle_ts = sp->next_ts + fipe_idle_ctrl.tick_interval;
sp->idle_count = 0;
sp->cond_ready = B_FALSE;
return (B_FALSE);
}
return (sp->cond_ready);
}
static void
fipe_idle_enter(void *arg, cpu_idle_callback_context_t ctx,
cpu_idle_check_wakeup_t check_func, void* check_arg)
{
hrtime_t ts;
uint32_t cnt;
uint64_t iowait;
cpu_t *cp = CPU;
struct fipe_cpu_state *sp;
sp = &fipe_cpu_states[cp->cpu_id];
ts = cpu_idle_prop_get_hrtime(fipe_idle_ctrl.prop_enter, ctx);
if (fipe_pm_policy != FIPE_PM_POLICY_DISABLE &&
fipe_ioat_ctrl.ioat_ready &&
sp->state_ready && sp->throttle_ts <= ts) {
iowait = CPU_STATS(cp, sys.iowait);
if (iowait != sp->last_iowait) {
atomic_add_64(&fipe_gbl_ctrl.io_waiters,
iowait - sp->last_iowait);
sp->last_iowait = iowait;
}
if (fipe_check_cpu(sp, ctx, ts)) {
do {
cnt = fipe_gbl_ctrl.cpu_count;
ASSERT(cnt < ncpus);
} while (atomic_cas_32(&fipe_gbl_ctrl.cpu_count,
cnt, cnt + 1) != cnt);
if (cnt + 1 == ncpus) {
if (fipe_gbl_ctrl.io_waiters == 0) {
fipe_gbl_ctrl.enter_ts = ts;
fipe_enable(fipe_pm_throttle_level,
check_func, check_arg);
} else {
FIPE_KSTAT_DETAIL_INC(bio_busy_cnt);
}
}
}
} else if (fipe_pm_policy == FIPE_PM_POLICY_DISABLE ||
fipe_ioat_ctrl.ioat_ready == B_FALSE) {
if (sp->cond_ready == B_TRUE) {
sp->cond_ready = B_FALSE;
}
} else if (sp->state_ready == B_FALSE) {
sp->cond_ready = B_FALSE;
sp->state_ready = B_TRUE;
sp->throttle_ts = 0;
sp->next_ts = ts + fipe_idle_ctrl.tick_interval;
sp->last_busy = cpu_idle_prop_get_hrtime(
fipe_idle_ctrl.prop_busy, ctx);
sp->last_idle = cpu_idle_prop_get_hrtime(
fipe_idle_ctrl.prop_idle, ctx);
sp->last_intr = cpu_idle_prop_get_hrtime(
fipe_idle_ctrl.prop_intr, ctx);
sp->idle_count = 0;
}
}
static void
fipe_idle_exit(void* arg, cpu_idle_callback_context_t ctx, int flags)
{
uint32_t cnt;
hrtime_t ts;
struct fipe_cpu_state *sp;
sp = &fipe_cpu_states[CPU->cpu_id];
if (sp->cond_ready) {
do {
cnt = fipe_gbl_ctrl.cpu_count;
ASSERT(cnt > 0);
} while (atomic_cas_32(&fipe_gbl_ctrl.cpu_count,
cnt, cnt - 1) != cnt);
if (cnt == ncpus) {
fipe_disable();
ts = cpu_idle_prop_get_hrtime(fipe_idle_ctrl.prop_exit,
ctx);
fipe_gbl_ctrl.time_in_pm += ts - fipe_gbl_ctrl.enter_ts;
}
}
}