#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: hypervisor_machdep.c,v 1.47 2026/01/12 21:42:52 bouyer Exp $");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kmem.h>
#include <sys/cpu.h>
#include <sys/ksyms.h>
#include <uvm/uvm_extern.h>
#include <machine/vmparam.h>
#include <machine/pmap.h>
#include <machine/pmap_private.h>
#include <x86/machdep.h>
#include <x86/cpuvar.h>
#include <xen/xen.h>
#include <xen/intr.h>
#include <xen/hypervisor.h>
#include <xen/evtchn.h>
#include <xen/xenpmap.h>
#include "opt_xen.h"
#include "opt_modular.h"
#include "opt_ddb.h"
#include "isa.h"
#include "pci.h"
#include "ksyms.h"
#ifdef DDB
#include <machine/db_machdep.h>
#include <ddb/db_extern.h>
#include <ddb/db_output.h>
#include <ddb/db_interface.h>
#endif
#ifdef XENPV
static unsigned long * l3_p2m_page;
static unsigned long * l2_p2m_page;
static int l2_p2m_page_size;
static void build_p2m_frame_list_list(void);
static void update_p2m_frame_list_list(void);
#endif
typedef void (*iterate_func_t)(unsigned int, unsigned int,
unsigned int, void *);
static inline void
evt_iterate_bits(volatile unsigned long *pendingl1,
volatile unsigned long *pendingl2,
volatile unsigned long *mask,
iterate_func_t iterate_pending, void *iterate_args)
{
KASSERT(pendingl1 != NULL);
KASSERT(pendingl2 != NULL);
unsigned long l1, l2;
unsigned int l1i, l2i, port;
l1 = xen_atomic_xchg(pendingl1, 0);
while ((l1i = xen_ffs(l1)) != 0) {
l1i--;
l1 &= ~(1UL << l1i);
l2 = pendingl2[l1i] & (mask != NULL ? ~mask[l1i] : -1UL);
l2 &= curcpu()->ci_evtmask[l1i];
if (mask != NULL) xen_atomic_setbits_l(&mask[l1i], l2);
xen_atomic_clearbits_l(&pendingl2[l1i], l2);
while ((l2i = xen_ffs(l2)) != 0) {
l2i--;
l2 &= ~(1UL << l2i);
port = (l1i << LONG_SHIFT) + l2i;
iterate_pending(port, l1i, l2i, iterate_args);
}
}
}
static inline void
evt_set_pending(unsigned int port, unsigned int l1i,
unsigned int l2i, void *args)
{
KASSERT(args != NULL);
int *ret = args;
struct intrhand *ih;
if (evtsource[port]) {
hypervisor_set_ipending(evtsource[port]->ev_imask, l1i, l2i);
evtsource[port]->ev_evcnt.ev_count++;
ih = evtsource[port]->ev_handlers;
while (ih != NULL) {
ih->ih_pending++;
ih = ih->ih_evt_next;
}
if (*ret == 0 && curcpu()->ci_ilevel <
evtsource[port]->ev_maxlevel)
*ret = 1;
}
#ifdef DOM0OPS
else {
xenevt_setipending(l1i, l2i);
}
#endif
}
int stipending(void);
int
stipending(void)
{
volatile shared_info_t *s = HYPERVISOR_shared_info;
struct cpu_info *ci;
volatile struct vcpu_info *vci;
int ret;
kpreempt_disable();
ret = 0;
ci = curcpu();
vci = ci->ci_vcpu;
#if 0
if (HYPERVISOR_shared_info->events)
printf("stipending events %08lx mask %08lx ilevel %d\n",
HYPERVISOR_shared_info->events,
HYPERVISOR_shared_info->events_mask, ci->ci_ilevel);
#endif
#ifdef EARLY_DEBUG_EVENT
if (xen_atomic_test_bit(&s->evtchn_pending[0], debug_port)) {
xen_debug_handler(NULL);
xen_atomic_clear_bit(&s->evtchn_pending[0], debug_port);
}
#endif
while (vci->evtchn_upcall_pending) {
x86_disable_intr();
vci->evtchn_upcall_pending = 0;
evt_iterate_bits(&vci->evtchn_pending_sel,
s->evtchn_pending, s->evtchn_mask,
evt_set_pending, &ret);
x86_enable_intr();
}
kpreempt_enable();
return (ret);
}
static inline void
evt_do_hypervisor_callback(unsigned int port, unsigned int l1i,
unsigned int l2i, void *args)
{
KASSERT(args != NULL);
#ifdef DOM0OPS
struct cpu_info *ci = curcpu();
#endif
struct intrframe *regs = args;
#ifdef PORT_DEBUG
if (port == PORT_DEBUG)
printf("do_hypervisor_callback event %d\n", port);
#endif
if (evtsource[port]) {
KASSERT(cpu_intr_p());
evtchn_do_event(port, regs);
}
#ifdef DOM0OPS
else {
if (ci->ci_ilevel < IPL_HIGH) {
int oipl = ci->ci_ilevel;
ci->ci_ilevel = IPL_HIGH;
KASSERT(cpu_intr_p());
xenevt_event(port);
ci->ci_ilevel = oipl;
} else {
xenevt_setipending(l1i, l2i);
}
}
#endif
}
void
do_hypervisor_callback(struct intrframe *regs)
{
volatile shared_info_t *s = HYPERVISOR_shared_info;
struct cpu_info *ci;
volatile struct vcpu_info *vci;
uint64_t level __diagused;
ci = curcpu();
vci = ci->ci_vcpu;
level = ci->ci_ilevel;
KASSERT(regs != NULL);
ci->ci_xen_clockf_usermode = USERMODE(regs->_INTRFRAME_CS);
ci->ci_xen_clockf_pc = regs->_INTRFRAME_IP;
#ifdef EARLY_DEBUG_EVENT
if (xen_atomic_test_bit(&s->evtchn_pending[0], debug_port)) {
xen_debug_handler(NULL);
xen_atomic_clear_bit(&s->evtchn_pending[0], debug_port);
}
#endif
while (vci->evtchn_upcall_pending) {
vci->evtchn_upcall_pending = 0;
evt_iterate_bits(&vci->evtchn_pending_sel,
s->evtchn_pending, s->evtchn_mask,
evt_do_hypervisor_callback, regs);
}
#ifdef DIAGNOSTIC
if (level != ci->ci_ilevel)
printf("hypervisor done %08x level %" PRIu64 "/%" PRIu64 " ipending %0" PRIx64 "\n",
(uint)vci->evtchn_pending_sel,
level, (uint64_t)ci->ci_ilevel, (uint64_t)ci->ci_ipending);
#endif
}
void
hypervisor_unmask_event(unsigned int ev)
{
KASSERT(ev > 0 && ev < NR_EVENT_CHANNELS);
#ifdef PORT_DEBUG
if (ev == PORT_DEBUG)
printf("hypervisor_unmask_event %d\n", ev);
#endif
evtchn_op_t op;
op.cmd = EVTCHNOP_unmask;
op.u.unmask.port = ev;
if (HYPERVISOR_event_channel_op(&op) != 0)
panic("Failed to unmask event %d\n", ev);
return;
}
void
hypervisor_mask_event(unsigned int ev)
{
volatile shared_info_t *s = HYPERVISOR_shared_info;
#ifdef PORT_DEBUG
if (ev == PORT_DEBUG)
printf("hypervisor_mask_event %d\n", ev);
#endif
xen_atomic_set_bit(&s->evtchn_mask[0], ev);
}
void
hypervisor_clear_event(unsigned int ev)
{
volatile shared_info_t *s = HYPERVISOR_shared_info;
#ifdef PORT_DEBUG
if (ev == PORT_DEBUG)
printf("hypervisor_clear_event %d\n", ev);
#endif
xen_atomic_clear_bit(&s->evtchn_pending[0], ev);
}
static inline void
evt_enable_event(unsigned int port, unsigned int l1i,
unsigned int l2i, void *args)
{
KASSERT(args == NULL);
hypervisor_unmask_event(port);
#if defined(XENPV) && (NPCI > 0 || NISA > 0)
hypervisor_ack_pirq_event(port);
#endif
}
void
hypervisor_enable_sir(unsigned int sir)
{
struct cpu_info *ci = curcpu();
evt_iterate_bits(&ci->ci_isources[sir]->ipl_evt_mask1,
ci->ci_isources[sir]->ipl_evt_mask2, NULL,
evt_enable_event, NULL);
}
void
hypervisor_set_ipending(uint64_t imask, int l1, int l2)
{
KASSERT(x86_read_psl() != 0);
int sir;
struct cpu_info *ci = curcpu();
ci->ci_ipending |= imask;
sir = ffs(imask);
KASSERT(sir > SIR_XENIPL_VM);
sir--;
KASSERT(sir <= SIR_XENIPL_HIGH);
KASSERT(ci->ci_isources[sir] != NULL);
ci->ci_isources[sir]->ipl_evt_mask1 |= 1UL << l1;
ci->ci_isources[sir]->ipl_evt_mask2[l1] |= 1UL << l2;
KASSERT(ci == curcpu());
#if 0
if (__predict_false(ci != curcpu())) {
if (xen_send_ipi(ci, XEN_IPI_HVCB)) {
panic("hypervisor_set_ipending: "
"xen_send_ipi(cpu%d id %d, XEN_IPI_HVCB) failed\n",
(int) ci->ci_cpuid, ci->ci_vcpuid);
}
}
#endif
}
void
hypervisor_machdep_attach(void)
{
#ifdef XENPV
if (!xendomain_is_dom0()) {
build_p2m_frame_list_list();
sysctl_xen_suspend_setup();
}
#endif
}
void
hypervisor_machdep_resume(void)
{
#ifdef XENPV
if (!xendomain_is_dom0())
update_p2m_frame_list_list();
#endif
}
static void
idle_block(void)
{
KASSERT(curcpu()->ci_ipending == 0);
HYPERVISOR_block();
KASSERT(curcpu()->ci_ipending == 0);
}
void
x86_cpu_idle_xen(void)
{
struct cpu_info *ci = curcpu();
KASSERT(ci->ci_ilevel == IPL_NONE);
x86_disable_intr();
if (__predict_false(!ci->ci_want_resched)) {
idle_block();
} else {
x86_enable_intr();
}
}
#ifdef XENPV
static void
build_p2m_frame_list_list(void)
{
int fpp;
unsigned long max_pfn;
max_pfn = xen_start_info.nr_pages;
fpp = PAGE_SIZE / sizeof(xen_pfn_t);
l3_p2m_page = (vaddr_t *)uvm_km_alloc(kernel_map, PAGE_SIZE,
PAGE_SIZE, UVM_KMF_WIRED | UVM_KMF_NOWAIT);
if (l3_p2m_page == NULL)
panic("could not allocate memory for l3_p2m_page");
l2_p2m_page_size = howmany(max_pfn, fpp);
l2_p2m_page = (vaddr_t *)uvm_km_alloc(kernel_map,
l2_p2m_page_size * PAGE_SIZE,
PAGE_SIZE, UVM_KMF_WIRED | UVM_KMF_NOWAIT);
if (l2_p2m_page == NULL)
panic("could not allocate memory for l2_p2m_page");
update_p2m_frame_list_list();
}
static void
update_p2m_frame_list_list(void)
{
int i;
int fpp;
unsigned long max_pfn;
max_pfn = xen_start_info.nr_pages;
fpp = PAGE_SIZE / sizeof(xen_pfn_t);
for (i = 0; i < l2_p2m_page_size; i++) {
if ((i % fpp) == 0) {
l3_p2m_page[i/fpp] = vtomfn(
(vaddr_t)&l2_p2m_page[i]);
}
l2_p2m_page[i] = vtomfn((vaddr_t)
&xpmap_phys_to_machine_mapping[i*fpp]);
}
HYPERVISOR_shared_info->arch.pfn_to_mfn_frame_list_list =
vtomfn((vaddr_t)l3_p2m_page);
HYPERVISOR_shared_info->arch.max_pfn = max_pfn;
}
#endif
void
xen_init_ksyms(void)
{
#if NKSYMS || defined(DDB) || defined(MODULAR)
extern int end;
extern int *esym;
#ifdef DDB
db_machine_init();
#endif
#ifdef XENPV
esym = xen_start_info.mod_start ?
(void *)xen_start_info.mod_start :
(void *)xen_start_info.mfn_list;
#endif
ksyms_addsyms_elf(*(int *)(void *)&end,
((int *)(void *)&end) + 1, esym);
#endif
}