#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: x86_tlb.c,v 1.22 2026/03/17 02:16:39 yamt Exp $");
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/atomic.h>
#include <sys/cpu.h>
#include <sys/intr.h>
#include <uvm/uvm.h>
#include <machine/cpuvar.h>
#include <machine/pmap_private.h>
#ifdef XENPV
#include <xen/xenpmap.h>
#endif
#include <x86/i82489reg.h>
#include <x86/i82489var.h>
#define TP_MAXVA 16
#define TP_ALLVA PAGE_MASK
typedef struct {
uintptr_t tp_store[TP_MAXVA];
} pmap_tlb_packet_t;
#define TP_COUNT 0
#define TP_USERPMAP 1
#define TP_GLOBAL 2
#define TP_DONE 3
#define TP_GET_COUNT(tp) ((tp)->tp_store[TP_COUNT] & PAGE_MASK)
#define TP_GET_USERPMAP(tp) ((tp)->tp_store[TP_USERPMAP] & 1)
#define TP_GET_GLOBAL(tp) ((tp)->tp_store[TP_GLOBAL] & 1)
#define TP_GET_DONE(tp) (atomic_load_relaxed(&(tp)->tp_store[TP_DONE]) & 1)
#define TP_GET_VA(tp, i) ((tp)->tp_store[(i)] & ~PAGE_MASK)
#define TP_INC_COUNT(tp) ((tp)->tp_store[TP_COUNT]++)
#define TP_SET_ALLVA(tp) ((tp)->tp_store[TP_COUNT] |= TP_ALLVA)
#define TP_SET_VA(tp, c, va) ((tp)->tp_store[(c)] |= ((va) & ~PAGE_MASK))
#define TP_SET_USERPMAP(tp) ((tp)->tp_store[TP_USERPMAP] |= 1)
#define TP_SET_GLOBAL(tp) ((tp)->tp_store[TP_GLOBAL] |= 1)
#define TP_SET_DONE(tp) \
do { \
uintptr_t v = atomic_load_relaxed(&(tp)->tp_store[TP_DONE]); \
atomic_store_relaxed(&(tp)->tp_store[TP_DONE], v | 1); \
} while ( 0);
#define TP_CLEAR(tp) memset(__UNVOLATILE(tp), 0, sizeof(*(tp)));
static volatile pmap_tlb_packet_t *volatile pmap_tlb_packet __cacheline_aligned;
static volatile u_int pmap_tlb_pendcount __cacheline_aligned;
static struct evcnt pmap_tlb_evcnt __cacheline_aligned;
#ifdef TLBSTATS
static struct evcnt tlbstat_local[TLBSHOOT__MAX];
static struct evcnt tlbstat_remote[TLBSHOOT__MAX];
static struct evcnt tlbstat_kernel[TLBSHOOT__MAX];
static struct evcnt tlbstat_single_req;
static struct evcnt tlbstat_single_issue;
static const char * tlbstat_name[ ] = {
"REMOVE_ALL",
"KENTER",
"KREMOVE",
"FREE_PTP",
"REMOVE_PTE",
"SYNC_PV",
"WRITE_PROTECT",
"ENTER",
"NVMM",
"BUS_DMA",
"BUS_SPACE",
};
#endif
void
pmap_tlb_init(void)
{
evcnt_attach_dynamic(&pmap_tlb_evcnt, EVCNT_TYPE_INTR,
NULL, "TLB", "shootdown");
#ifdef TLBSTATS
int i;
for (i = 0; i < TLBSHOOT__MAX; i++) {
evcnt_attach_dynamic(&tlbstat_local[i], EVCNT_TYPE_MISC,
NULL, "tlbshoot local", tlbstat_name[i]);
}
for (i = 0; i < TLBSHOOT__MAX; i++) {
evcnt_attach_dynamic(&tlbstat_remote[i], EVCNT_TYPE_MISC,
NULL, "tlbshoot remote", tlbstat_name[i]);
}
for (i = 0; i < TLBSHOOT__MAX; i++) {
evcnt_attach_dynamic(&tlbstat_kernel[i], EVCNT_TYPE_MISC,
NULL, "tlbshoot kernel", tlbstat_name[i]);
}
evcnt_attach_dynamic(&tlbstat_single_req, EVCNT_TYPE_MISC,
NULL, "tlbshoot single page", "requests");
evcnt_attach_dynamic(&tlbstat_single_issue, EVCNT_TYPE_MISC,
NULL, "tlbshoot single page", "issues");
#endif
}
void
pmap_tlb_cpu_init(struct cpu_info *ci)
{
pmap_tlb_packet_t *tp = (pmap_tlb_packet_t *)ci->ci_pmap_data;
memset(tp, 0, sizeof(pmap_tlb_packet_t));
kcpuset_create(&ci->ci_tlb_cpuset, true);
}
static inline void
pmap_tlbstat_count(struct pmap *pm, vaddr_t va, tlbwhy_t why)
{
#ifdef TLBSTATS
const cpuid_t cid = cpu_index(curcpu());
bool local = false, remote = false;
if (va != (vaddr_t)-1LL) {
atomic_inc_64(&tlbstat_single_req.ev_count);
}
if (pm == pmap_kernel()) {
atomic_inc_64(&tlbstat_kernel[why].ev_count);
return;
}
if (va >= VM_MAXUSER_ADDRESS) {
remote = kcpuset_isotherset(pm->pm_kernel_cpus, cid);
local = kcpuset_isset(pm->pm_kernel_cpus, cid);
}
remote |= kcpuset_isotherset(pm->pm_cpus, cid);
local |= kcpuset_isset(pm->pm_cpus, cid);
if (local) {
atomic_inc_64(&tlbstat_local[why].ev_count);
}
if (remote) {
atomic_inc_64(&tlbstat_remote[why].ev_count);
}
#endif
}
static inline void
pmap_tlb_invalidate(volatile pmap_tlb_packet_t *tp)
{
int i = TP_GET_COUNT(tp);
if (i == TP_ALLVA) {
if (TP_GET_GLOBAL(tp) != 0) {
tlbflushg();
} else {
tlbflush();
}
} else {
KASSERT(i != 0);
do {
--i;
pmap_update_pg(TP_GET_VA(tp, i));
} while (i > 0);
}
}
void
pmap_tlb_shootdown(struct pmap *pm, vaddr_t va, pt_entry_t pte, tlbwhy_t why)
{
pmap_tlb_packet_t *tp;
struct cpu_info *ci;
uint8_t count;
int s;
#ifndef XENPV
KASSERT((pte & PTE_G) == 0 || pm == pmap_kernel());
#endif
if (__predict_false(pm->pm_tlb_flush != NULL)) {
(*pm->pm_tlb_flush)(pm);
return;
}
if ((pte & PTE_PS) != 0) {
va &= PTE_LGFRAME;
}
s = splvm();
ci = curcpu();
tp = (pmap_tlb_packet_t *)ci->ci_pmap_data;
if ((pte & PTE_G) != 0) {
TP_SET_GLOBAL(tp);
}
count = TP_GET_COUNT(tp);
if (count < TP_MAXVA && va != (vaddr_t)-1LL) {
TP_SET_VA(tp, count, va);
TP_INC_COUNT(tp);
} else {
TP_SET_ALLVA(tp);
}
if (pm != pmap_kernel()) {
kcpuset_merge(ci->ci_tlb_cpuset, pm->pm_cpus);
if (va >= VM_MAXUSER_ADDRESS) {
kcpuset_merge(ci->ci_tlb_cpuset, pm->pm_kernel_cpus);
}
TP_SET_USERPMAP(tp);
} else {
kcpuset_copy(ci->ci_tlb_cpuset, kcpuset_running);
}
pmap_tlbstat_count(pm, va, why);
splx(s);
}
#ifdef XENPV
static inline void
pmap_tlb_processpacket(volatile pmap_tlb_packet_t *tp, kcpuset_t *target)
{
#ifdef MULTIPROCESSOR
int i = TP_GET_COUNT(tp);
if (i != TP_ALLVA) {
KASSERT(i != 0);
do {
--i;
xen_mcast_invlpg(TP_GET_VA(tp, i), target);
} while (i > 0);
} else {
xen_mcast_tlbflush(target);
}
pmap_tlb_pendcount = 0;
pmap_tlb_packet = NULL;
TP_SET_DONE(tp);
#endif
}
#else
static inline void
pmap_tlb_processpacket(volatile pmap_tlb_packet_t *tp, kcpuset_t *target)
{
#ifdef MULTIPROCESSOR
int err = 0;
if (!kcpuset_match(target, kcpuset_attached)) {
const struct cpu_info * const self = curcpu();
CPU_INFO_ITERATOR cii;
struct cpu_info *lci;
for (CPU_INFO_FOREACH(cii, lci)) {
const cpuid_t lcid = cpu_index(lci);
if (__predict_false(lci == self) ||
!kcpuset_isset(target, lcid)) {
continue;
}
err |= x86_ipi(LAPIC_TLB_VECTOR,
lci->ci_cpuid, LAPIC_DLMODE_FIXED);
}
} else {
err = x86_ipi(LAPIC_TLB_VECTOR, LAPIC_DEST_ALLEXCL,
LAPIC_DLMODE_FIXED);
}
KASSERT(err == 0);
#endif
}
#endif
void
pmap_tlb_shootnow(void)
{
volatile pmap_tlb_packet_t *tp, *ts;
volatile uint8_t stackbuf[sizeof(*tp) + COHERENCY_UNIT];
struct cpu_info *ci;
kcpuset_t *target;
u_int local, rcpucount;
cpuid_t cid;
int s;
KASSERT(kpreempt_disabled());
ci = curcpu();
tp = (pmap_tlb_packet_t *)ci->ci_pmap_data;
if (TP_GET_COUNT(tp) == 0) {
return;
}
s = splvm();
if (TP_GET_COUNT(tp) == 0) {
splx(s);
return;
}
cid = cpu_index(ci);
target = ci->ci_tlb_cpuset;
local = kcpuset_isset(target, cid) ? 1 : 0;
rcpucount = kcpuset_countset(target) - local;
if (rcpucount == 0) {
pmap_tlb_invalidate(tp);
kcpuset_zero(ci->ci_tlb_cpuset);
TP_CLEAR(tp);
splx(s);
return;
}
KASSERT(rcpucount < ncpu);
ts = (void *)roundup2((uintptr_t)stackbuf, COHERENCY_UNIT);
*ts = *tp;
KASSERT(TP_GET_DONE(ts) == 0);
while (atomic_cas_ptr(&pmap_tlb_packet, NULL,
__UNVOLATILE(ts)) != NULL) {
KASSERT(atomic_load_relaxed(&pmap_tlb_packet) != ts);
splx(s);
do {
x86_pause();
} while (atomic_load_relaxed(&pmap_tlb_packet) != NULL);
s = splvm();
if (TP_GET_COUNT(tp) == 0) {
splx(s);
return;
}
KASSERT(local == kcpuset_isset(target, cid) ? 1 : 0);
KASSERT(rcpucount == kcpuset_countset(target) - local);
KASSERT(memcmp(__UNVOLATILE(ts), __UNVOLATILE(tp),
sizeof(*ts)) == 0);
}
pmap_tlb_pendcount = rcpucount;
pmap_tlb_evcnt.ev_count++;
pmap_tlb_processpacket(ts, target);
#ifdef TLBSTATS
if (TP_GET_COUNT(tp) != TP_ALLVA) {
atomic_add_64(&tlbstat_single_issue.ev_count,
TP_GET_COUNT(tp));
}
#endif
kcpuset_zero(ci->ci_tlb_cpuset);
TP_CLEAR(tp);
splx(s);
if (local) {
pmap_tlb_invalidate(ts);
}
while (TP_GET_DONE(ts) == 0) {
x86_pause();
}
}
void
pmap_tlb_intr(void)
{
pmap_tlb_packet_t copy;
volatile pmap_tlb_packet_t *source;
struct cpu_info *ci;
source = pmap_tlb_packet;
copy = *source;
if (atomic_dec_uint_nv(&pmap_tlb_pendcount) == 0) {
atomic_store_relaxed(&pmap_tlb_packet, NULL);
__insn_barrier();
TP_SET_DONE(source);
}
pmap_tlb_invalidate(©);
ci = curcpu();
if (ci->ci_tlbstate == TLBSTATE_LAZY && TP_GET_USERPMAP(©) != 0) {
kcpuset_atomic_clear(ci->ci_pmap->pm_cpus, cpu_index(ci));
ci->ci_tlbstate = TLBSTATE_STALE;
}
}