#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/proc.h>
#include <sys/vmmeter.h>
#include <sys/thread2.h>
#include <sys/sysctl.h>
#include <vm/vm.h>
#include <vm/pmap.h>
#include <vm/vm_object.h>
#include <machine/cputypes.h>
#include <machine/md_var.h>
#include <machine/specialreg.h>
#include <machine/smp.h>
#include <machine/globaldata.h>
#include <machine/pmap.h>
#include <machine/pmap_inval.h>
#include <machine/clock.h>
#if 1
#define LOOPRECOVER
#endif
#define LOOPRECOVER_TIMEOUT1 2
#define LOOPRECOVER_TIMEOUT2 1
#define MAX_INVAL_PAGES 128
struct pmap_inval_info {
vm_offset_t va;
pt_entry_t *ptep;
pt_entry_t opte;
pt_entry_t npte;
enum { INVDONE, INVSTORE, INVCMPSET } mode;
int success;
vm_pindex_t npgs;
cpumask_t done;
cpumask_t mask;
#ifdef LOOPRECOVER
cpumask_t sigmask;
int failed;
tsc_uclock_t tsc_target;
#endif
} __cachealign;
typedef struct pmap_inval_info pmap_inval_info_t;
static pmap_inval_info_t invinfo[MAXCPU];
extern cpumask_t smp_invmask;
#ifdef LOOPRECOVER
#ifdef LOOPMASK_IN
extern cpumask_t smp_in_mask;
#endif
extern cpumask_t smp_smurf_mask;
#endif
static int pmap_inval_watchdog_print;
static int pmap_inval_force_allcpus;
static int pmap_inval_force_nonopt;
SYSCTL_INT(_machdep, OID_AUTO, pmap_inval_watchdog_print, CTLFLAG_RW,
&pmap_inval_watchdog_print, 0, "");
SYSCTL_INT(_machdep, OID_AUTO, pmap_inval_force_allcpus, CTLFLAG_RW,
&pmap_inval_force_allcpus, 0, "");
SYSCTL_INT(_machdep, OID_AUTO, pmap_inval_force_nonopt, CTLFLAG_RW,
&pmap_inval_force_nonopt, 0, "");
static void
pmap_inval_init(pmap_t pmap)
{
cpulock_t olock;
cpulock_t nlock;
crit_enter_id("inval");
if (pmap != kernel_pmap) {
for (;;) {
olock = pmap->pm_active_lock;
cpu_ccfence();
nlock = olock | CPULOCK_EXCL;
if (olock != nlock &&
atomic_cmpset_int(&pmap->pm_active_lock,
olock, nlock)) {
break;
}
lwkt_process_ipiq();
cpu_pause();
}
atomic_add_64(&pmap->pm_invgen, 1);
}
}
static void
pmap_inval_done(pmap_t pmap)
{
if (pmap != kernel_pmap) {
atomic_add_64(&pmap->pm_invgen, 1);
atomic_clear_int(&pmap->pm_active_lock, CPULOCK_EXCL);
}
crit_exit_id("inval");
}
#ifdef LOOPRECOVER
static
__inline
int
loopwdog(struct pmap_inval_info *info)
{
tsc_uclock_t tsc;
tsc = rdtsc();
if ((tsc_sclock_t)(info->tsc_target - tsc) < 0 && tsc_frequency) {
info->tsc_target = tsc + (tsc_frequency * LOOPRECOVER_TIMEOUT2);
return 1;
}
return 0;
}
static
void
loopdebug(const char *msg, pmap_inval_info_t *info)
{
int p;
int cpu = mycpu->gd_cpuid;
if (pmap_inval_watchdog_print == 0)
return;
cpu_lfence();
#ifdef LOOPRECOVER
atomic_add_long(&smp_smurf_mask.ary[0], 0);
#endif
kprintf("ipilost-%s! %d mode=%d m=%08jx d=%08jx "
#ifdef LOOPRECOVER
"s=%08jx "
#endif
#ifdef LOOPMASK_IN
"in=%08jx "
#endif
#ifdef LOOPRECOVER
"smurf=%08jx\n"
#endif
, msg, cpu, info->mode,
info->mask.ary[0],
info->done.ary[0]
#ifdef LOOPRECOVER
, info->sigmask.ary[0]
#endif
#ifdef LOOPMASK_IN
, smp_in_mask.ary[0]
#endif
#ifdef LOOPRECOVER
, smp_smurf_mask.ary[0]
#endif
);
kprintf("mdglob ");
for (p = 0; p < ncpus; ++p)
kprintf(" %d", CPU_prvspace[p]->mdglobaldata.gd_xinvaltlb);
kprintf("\n");
}
#endif
#ifdef CHECKSIG
#define CHECKSIGMASK(info) _checksigmask(info, __FILE__, __LINE__)
static
void
_checksigmask(pmap_inval_info_t *info, const char *file, int line)
{
cpumask_t tmp;
tmp = info->mask;
CPUMASK_ANDMASK(tmp, info->sigmask);
if (CPUMASK_CMPMASKNEQ(tmp, info->mask)) {
kprintf("\"%s\" line %d: bad sig/mask %08jx %08jx\n",
file, line, info->sigmask.ary[0], info->mask.ary[0]);
}
}
#else
#define CHECKSIGMASK(info)
#endif
pt_entry_t
pmap_inval_smp(pmap_t pmap, vm_offset_t va, vm_pindex_t npgs,
pt_entry_t *ptep, pt_entry_t npte)
{
globaldata_t gd = mycpu;
pmap_inval_info_t *info;
pt_entry_t opte = 0;
int cpu = gd->gd_cpuid;
cpumask_t tmpmask;
unsigned long rflags;
if (pmap == NULL)
pmap = kernel_pmap;
if (CPUMASK_CMPMASKEQ(pmap->pm_active, gd->gd_cpumask) &&
pmap_inval_force_nonopt == 0) {
if (pmap->pm_flags & PMAP_MULTI)
pmap_inval_init(pmap);
if (npgs == 1) {
if (ptep)
opte = atomic_swap_long(ptep, npte);
if (va == (vm_offset_t)-1)
cpu_invltlb();
else
cpu_invlpg((void *)va);
} else if (va == (vm_offset_t)-1 || npgs > MAX_INVAL_PAGES) {
if (ptep) {
while (npgs) {
opte = atomic_swap_long(ptep, npte);
++ptep;
--npgs;
}
}
cpu_invltlb();
} else {
while (npgs) {
if (ptep) {
opte = atomic_swap_long(ptep, npte);
++ptep;
}
cpu_invlpg((void *)va);
va += PAGE_SIZE;
--npgs;
}
}
if (pmap->pm_flags & PMAP_MULTI)
pmap_inval_done(pmap);
return opte;
}
pmap_inval_init(pmap);
info = &invinfo[cpu];
while (CPUMASK_TESTNZERO(info->done)) {
#ifdef LOOPRECOVER
if (loopwdog(info)) {
info->failed = 1;
loopdebug("A", info);
CPUMASK_ASSZERO(info->done);
}
#endif
cpu_pause();
}
KKASSERT(info->mode == INVDONE);
cpu_mfence();
ATOMIC_CPUMASK_ORBIT(smp_invmask, cpu);
info->tsc_target = rdtsc() + (tsc_frequency * LOOPRECOVER_TIMEOUT1);
info->va = va;
info->npgs = npgs;
info->ptep = ptep;
info->npte = npte;
info->opte = 0;
#ifdef LOOPRECOVER
info->failed = 0;
#endif
info->mode = INVSTORE;
tmpmask = pmap->pm_active;
if (pmap_inval_force_allcpus)
tmpmask = smp_active_mask;
cpu_ccfence();
CPUMASK_ANDMASK(tmpmask, smp_active_mask);
if (ptep == NULL)
smp_smurf_idleinvlclr(&tmpmask);
CPUMASK_ORBIT(tmpmask, cpu);
info->mask = tmpmask;
#ifdef LOOPRECOVER
info->sigmask = tmpmask;
CHECKSIGMASK(info);
#endif
cpu_sfence();
rflags = read_rflags();
cpu_disable_intr();
ATOMIC_CPUMASK_COPY(info->done, tmpmask);
smp_invlpg(&tmpmask);
opte = info->opte;
KKASSERT(info->mode == INVDONE);
ATOMIC_CPUMASK_NANDBIT(smp_invmask, cpu);
write_rflags(rflags);
pmap_inval_done(pmap);
return opte;
}
int
pmap_inval_smp_cmpset(pmap_t pmap, vm_offset_t va, pt_entry_t *ptep,
pt_entry_t opte, pt_entry_t npte)
{
globaldata_t gd = mycpu;
pmap_inval_info_t *info;
int success;
int cpu = gd->gd_cpuid;
cpumask_t tmpmask;
unsigned long rflags;
if (pmap == NULL)
pmap = kernel_pmap;
if (CPUMASK_CMPMASKEQ(pmap->pm_active, gd->gd_cpumask) &&
pmap_inval_force_nonopt == 0) {
if (pmap->pm_flags & PMAP_MULTI)
pmap_inval_init(pmap);
if (atomic_cmpset_long(ptep, opte, npte)) {
if (va == (vm_offset_t)-1)
cpu_invltlb();
else
cpu_invlpg((void *)va);
if (pmap->pm_flags & PMAP_MULTI)
pmap_inval_done(pmap);
return 1;
} else {
if (pmap->pm_flags & PMAP_MULTI)
pmap_inval_done(pmap);
return 0;
}
}
pmap_inval_init(pmap);
info = &invinfo[cpu];
while (CPUMASK_TESTNZERO(info->done)) {
#ifdef LOOPRECOVER
if (loopwdog(info)) {
info->failed = 1;
loopdebug("B", info);
CPUMASK_ASSZERO(info->done);
}
#endif
cpu_pause();
}
KKASSERT(info->mode == INVDONE);
cpu_mfence();
ATOMIC_CPUMASK_ORBIT(smp_invmask, cpu);
info->tsc_target = rdtsc() + (tsc_frequency * LOOPRECOVER_TIMEOUT1);
info->va = va;
info->npgs = 1;
info->ptep = ptep;
info->npte = npte;
info->opte = opte;
#ifdef LOOPRECOVER
info->failed = 0;
#endif
info->mode = INVCMPSET;
info->success = 0;
tmpmask = pmap->pm_active;
if (pmap_inval_force_allcpus)
tmpmask = smp_active_mask;
cpu_ccfence();
CPUMASK_ANDMASK(tmpmask, smp_active_mask);
CPUMASK_ORBIT(tmpmask, cpu);
info->mask = tmpmask;
#ifdef LOOPRECOVER
info->sigmask = tmpmask;
CHECKSIGMASK(info);
#endif
cpu_sfence();
rflags = read_rflags();
cpu_disable_intr();
ATOMIC_CPUMASK_COPY(info->done, tmpmask);
smp_invlpg(&tmpmask);
success = info->success;
KKASSERT(info->mode == INVDONE);
ATOMIC_CPUMASK_NANDBIT(smp_invmask, cpu);
write_rflags(rflags);
pmap_inval_done(pmap);
return success;
}
void
pmap_inval_bulk_init(pmap_inval_bulk_t *bulk, struct pmap *pmap)
{
bulk->pmap = pmap;
bulk->va_beg = 0;
bulk->va_end = 0;
bulk->count = 0;
}
pt_entry_t
pmap_inval_bulk(pmap_inval_bulk_t *bulk, vm_offset_t va,
pt_entry_t *ptep, pt_entry_t npte)
{
pt_entry_t pte;
if (bulk == NULL) {
pte = atomic_swap_long(ptep, npte);
return pte;
}
if (bulk->pmap != kernel_pmap) {
pte = pmap_inval_smp(bulk->pmap, va, 1, ptep, npte);
return pte;
}
pte = atomic_swap_long(ptep, npte);
if (va == (vm_offset_t)-1) {
bulk->va_beg = va;
} else if (bulk->va_beg == bulk->va_end) {
bulk->va_beg = va;
bulk->va_end = va + PAGE_SIZE;
} else if (va == bulk->va_end) {
bulk->va_end = va + PAGE_SIZE;
} else {
bulk->va_beg = (vm_offset_t)-1;
bulk->va_end = 0;
#if 0
pmap_inval_bulk_flush(bulk);
bulk->count = 1;
if (va == (vm_offset_t)-1) {
bulk->va_beg = va;
bulk->va_end = 0;
} else {
bulk->va_beg = va;
bulk->va_end = va + PAGE_SIZE;
}
#endif
}
++bulk->count;
return pte;
}
void
pmap_inval_bulk_flush(pmap_inval_bulk_t *bulk)
{
if (bulk == NULL)
return;
if (bulk->va_beg != bulk->va_end) {
if (bulk->va_beg == (vm_offset_t)-1) {
pmap_inval_smp(bulk->pmap, bulk->va_beg, 1, NULL, 0);
} else {
vm_pindex_t n;
n = (bulk->va_end - bulk->va_beg) >> PAGE_SHIFT;
pmap_inval_smp(bulk->pmap, bulk->va_beg, n, NULL, 0);
}
}
bulk->va_beg = 0;
bulk->va_end = 0;
bulk->count = 0;
}
int
pmap_inval_intr(cpumask_t *cpumaskp, int toolong)
{
globaldata_t gd = mycpu;
pmap_inval_info_t *info;
int loopme = 0;
int cpu;
cpumask_t cpumask;
cpu_ccfence();
cpu = gd->gd_cpuid;
cpumask = *cpumaskp;
while (CPUMASK_TESTNZERO(cpumask)) {
int n = BSFCPUMASK(cpumask);
#ifdef LOOPRECOVER
KKASSERT(n >= 0 && n < MAXCPU);
#endif
CPUMASK_NANDBIT(cpumask, n);
info = &invinfo[n];
if (!CPUMASK_TESTBIT(info->done, cpu))
continue;
cpu_lfence();
#ifdef LOOPRECOVER
if (toolong) {
kprintf("pm_inval_intr: WARNING, taking too long "
"cpus=%d->%d done=%08jx mask=%08jx "
"mode=%d\n",
cpu, n, info->done.ary[0], info->mask.ary[0],
info->mode);
}
#endif
if (n != cpu) {
if (CPUMASK_TESTBIT(info->mask, cpu)) {
ATOMIC_CPUMASK_NANDBIT(info->mask, cpu);
loopme = 1;
} else if (info->ptep &&
CPUMASK_TESTBIT(info->mask, n)) {
loopme = 1;
} else {
vm_offset_t va = info->va;
vm_pindex_t npgs;
if (va == (vm_offset_t)-1 ||
info->npgs > MAX_INVAL_PAGES) {
cpu_invltlb();
} else {
for (npgs = info->npgs; npgs; --npgs) {
cpu_invlpg((void *)va);
va += PAGE_SIZE;
}
}
ATOMIC_CPUMASK_NANDBIT(info->done, cpu);
}
} else if (CPUMASK_TESTBIT(info->mask, cpu)) {
if (CPUMASK_CMPMASKNEQ(info->mask, gd->gd_cpumask)) {
loopme = 1;
#ifdef LOOPRECOVER
if (loopwdog(info)) {
info->failed = 1;
loopdebug("C", info);
cpu_disable_intr();
ATOMIC_CPUMASK_NANDMASK(smp_smurf_mask,
info->mask);
smp_invlpg(&smp_active_mask);
cpu_enable_intr();
}
#endif
} else {
KKASSERT(info->mode != INVDONE);
if (info->mode == INVSTORE) {
if (info->ptep)
info->opte = atomic_swap_long(info->ptep, info->npte);
CHECKSIGMASK(info);
ATOMIC_CPUMASK_NANDBIT(info->mask, cpu);
CHECKSIGMASK(info);
} else {
if (atomic_cmpset_long(info->ptep,
info->opte, info->npte)) {
info->success = 1;
} else {
info->success = 0;
}
CHECKSIGMASK(info);
ATOMIC_CPUMASK_NANDBIT(info->mask, cpu);
CHECKSIGMASK(info);
}
loopme = 1;
}
} else {
vm_offset_t va = info->va;
vm_pindex_t npgs;
if (va == (vm_offset_t)-1 ||
info->npgs > MAX_INVAL_PAGES) {
cpu_invltlb();
} else {
for (npgs = info->npgs; npgs; --npgs) {
cpu_invlpg((void *)va);
va += PAGE_SIZE;
}
}
info->mode = INVDONE;
ATOMIC_CPUMASK_NANDBIT(info->done, cpu);
}
}
return loopme;
}