Actually make suspending SMP domUs work: the previous commit didn't

bring the other vcpus up correctly.

Signed-off-by: Steven Smith, sos22@cam.ac.uk
This commit is contained in:
sos22@douglas.cl.cam.ac.uk committed 2005-08-18 15:27:55 +00:00
1 parent 0b86412f9d
commit fd148643e4
15 files changed
+310 -34

No files matched your search

@@ -115,20 +115,12 @@ void xen_idle(void)
/* We don't actually take CPU down, just spin without interrupts. */
static inline void play_dead(void)
{
/* Ack it */
__get_cpu_var(cpu_state) = CPU_DEAD;
/* We shouldn't have to disable interrupts while dead, but
* some interrupts just don't seem to go away, and this makes
* it "work" for testing purposes. */
/* Death loop */
while (__get_cpu_var(cpu_state) != CPU_UP_PREPARE)
HYPERVISOR_yield();
local_irq_disable();
__flush_tlb_all();
cpu_set(smp_processor_id(), cpu_online_map);
local_irq_enable();
}
#else
static inline void play_dead(void)
@@ -156,12 +148,23 @@ void cpu_idle (void)
rmb();
if (cpu_is_offline(cpu)) {
printk("<0>Cpu %d going offline.\n",
cpu);
local_irq_disable();
/* Ack it. From this point on until
we get woken up, we're not allowed
to take any locks. In particular,
don't printk. */
__get_cpu_var(cpu_state) = CPU_DEAD;
#if defined(CONFIG_XEN) && defined(CONFIG_HOTPLUG_CPU)
/* Tell hypervisor to take vcpu down. */
HYPERVISOR_vcpu_down(cpu);
#endif
play_dead();
}
local_irq_enable();
printk("<0>Cpu %d back online.\n",
cpu);
}
__get_cpu_var(irq_stat).idle_timestamp = jiffies;
xen_idle();
@@ -129,9 +129,12 @@ static inline int __prepare_ICR2 (unsigned int mask)
DECLARE_PER_CPU(int, ipi_to_evtchn[NR_IPIS]);
unsigned uber_debug;
static inline void __send_IPI_one(unsigned int cpu, int vector)
{
unsigned int evtchn;
int r;
evtchn = per_cpu(ipi_to_evtchn, cpu)[vector];
// printk("send_IPI_mask_bitmask cpu %d vector %d evtchn %d\n", cpu, vector, evtchn);
@@ -142,7 +145,11 @@ static inline void __send_IPI_one(unsigned int cpu, int vector)
synch_test_bit(evtchn, &s->evtchn_mask[0]))
;
#endif
notify_via_evtchn(evtchn);
if (uber_debug)
printk("<0>Notifying on evtchn %d.\n", evtchn);
if ((r = notify_via_evtchn(evtchn)) != 0)
printk("<0>Hypervisor stopped us sending an IPI: %d.\n",
r);
} else
printk("send_IPI to unbound port %d/%d",
cpu, vector);
@@ -161,6 +168,8 @@ void __send_IPI_shortcut(unsigned int shortcut, int vector)
if (cpu == smp_processor_id())
continue;
if (cpu_isset(cpu, cpu_online_map)) {
if (uber_debug)
printk("<0>Sending ipi to %d.\n", cpu);
__send_IPI_one(cpu, vector);
}
}
@@ -1616,3 +1616,19 @@ void smp_resume(void)
smp_intr_init();
local_setup_timer_irq();
}
DECLARE_PER_CPU(int, timer_irq);
void _restore_vcpu(void)
{
int cpu = smp_processor_id();
/* We are the first thing the vcpu runs when it comes back,
and we are supposed to restore the IPIs and timer
interrupts etc. When we return, the vcpu's idle loop will
start up again. */
printk("<0>_restore_vcpu %d.\n", cpu);
_bind_virq_to_irq(VIRQ_TIMER, cpu, per_cpu(timer_irq, cpu));
_bind_virq_to_irq(VIRQ_DEBUG, cpu, per_cpu(ldebug_irq, cpu));
_bind_ipi_to_irq(RESCHEDULE_VECTOR, cpu, per_cpu(resched_irq, cpu) );
_bind_ipi_to_irq(CALL_FUNCTION_VECTOR, cpu, per_cpu(callfunc_irq, cpu) );
}
@@ -745,7 +745,7 @@ static void __init hpet_time_init(void)
#endif
/* Dynamically-mapped IRQ. */
static DEFINE_PER_CPU(int, timer_irq);
DEFINE_PER_CPU(int, timer_irq);
static struct irqaction irq_timer = {
timer_interrupt, SA_INTERRUPT, CPU_MASK_NONE, "timer0",
@@ -512,6 +512,7 @@ no_context:
printk("%08lx\n", regs->eip);
dump_fault_path(address);
die("Oops", regs, error_code);
while(1);
bust_spinlocks(0);
do_exit(SIGKILL);
+75 -2
View File
@@ -134,6 +134,8 @@ void force_evtchn_callback(void)
(void)HYPERVISOR_xen_version(0);
}
extern unsigned uber_debug;
/* NB. Interrupts are disabled on entry. */
asmlinkage void evtchn_do_upcall(struct pt_regs *regs)
{
@@ -145,6 +147,8 @@ asmlinkage void evtchn_do_upcall(struct pt_regs *regs)
vcpu_info->evtchn_upcall_pending = 0;
if (uber_debug && cpu != 0)
printk("<0>evtchn_do_upcall on %d.\n", cpu);
/* NB. No need for a barrier here -- XCHG is a barrier on x86. */
l1 = xchg(&vcpu_info->evtchn_pending_sel, 0);
while ( l1 != 0 )
@@ -158,9 +162,13 @@ asmlinkage void evtchn_do_upcall(struct pt_regs *regs)
l2 &= ~(1 << l2i);
port = (l1i << 5) + l2i;
if ( (irq = evtchn_to_irq[port]) != -1 )
if (uber_debug && cpu != 0)
printk("<0>Port %d.\n", port);
if ( (irq = evtchn_to_irq[port]) != -1 ) {
if (uber_debug && cpu != 0)
printk("<0>irq %d.\n", irq);
do_IRQ(irq, regs);
else
} else
evtchn_device_upcall(port);
}
}
@@ -245,6 +253,71 @@ void unbind_virq_from_irq(int virq)
spin_unlock(&irq_mapping_update_lock);
}
/* This is only used when a vcpu from an xm save. The ipi is expected
to have been bound before we suspended, and so all of the xenolinux
state is set up; we only need to restore the Xen side of things.
The irq number has to be the same, but the evtchn number can
change. */
void _bind_ipi_to_irq(int ipi, int vcpu, int irq)
{
evtchn_op_t op;
int evtchn;
spin_lock(&irq_mapping_update_lock);
op.cmd = EVTCHNOP_bind_ipi;
if ( HYPERVISOR_event_channel_op(&op) != 0 )
panic("Failed to bind virtual IPI %d on cpu %d\n", ipi, vcpu);
evtchn = op.u.bind_ipi.port;
printk("<0>IPI %d, old evtchn %d, evtchn %d.\n",
ipi, per_cpu(ipi_to_evtchn, vcpu)[ipi],
evtchn);
evtchn_to_irq[irq_to_evtchn[irq]] = -1;
irq_to_evtchn[irq] = -1;
evtchn_to_irq[evtchn] = irq;
irq_to_evtchn[irq] = evtchn;
per_cpu(ipi_to_evtchn, vcpu)[ipi] = evtchn;
bind_evtchn_to_cpu(evtchn, vcpu);
spin_unlock(&irq_mapping_update_lock);
clear_bit(evtchn, HYPERVISOR_shared_info->evtchn_mask);
}
void _bind_virq_to_irq(int virq, int cpu, int irq)
{
evtchn_op_t op;
int evtchn;
spin_lock(&irq_mapping_update_lock);
op.cmd = EVTCHNOP_bind_virq;
op.u.bind_virq.virq = virq;
if ( HYPERVISOR_event_channel_op(&op) != 0 )
panic("Failed to bind virtual IRQ %d\n", virq);
evtchn = op.u.bind_virq.port;
evtchn_to_irq[irq_to_evtchn[irq]] = -1;
irq_to_evtchn[irq] = -1;
evtchn_to_irq[evtchn] = irq;
irq_to_evtchn[irq] = evtchn;
per_cpu(virq_to_irq, cpu)[virq] = irq;
bind_evtchn_to_cpu(evtchn, cpu);
spin_unlock(&irq_mapping_update_lock);
return irq;
}
int bind_ipi_to_irq(int ipi)
{
evtchn_op_t op;
+68 -2
View File
@@ -65,10 +65,56 @@ static int shutting_down = SHUTDOWN_INVALID;
#define cpu_up(x) (-EOPNOTSUPP)
#endif
static void save_vcpu_context(int vcpu, vcpu_guest_context_t *ctxt)
{
int r;
int gdt_pages;
r = HYPERVISOR_vcpu_pickle(vcpu, ctxt);
BUG_ON(r != 0);
gdt_pages = (ctxt->gdt_ents + 511) / 512;
ctxt->ctrlreg[3] = machine_to_phys(ctxt->ctrlreg[3]);
for (r = 0; r < gdt_pages; r++) {
ctxt->gdt_frames[r] = mfn_to_pfn(ctxt->gdt_frames[r]);
}
}
void _restore_vcpu(int cpu);
static void restore_vcpu_context(int vcpu, vcpu_guest_context_t *ctxt)
{
int r;
int gdt_pages = (ctxt->gdt_ents + 511) / 512;
/* This is kind of a hack, and implicitly relies on the fact that
the vcpu stops in a place where all of the call clobbered
registers are already dead. */
printk("<0>regs.esp %x.\n", ctxt->user_regs.esp);
ctxt->user_regs.esp -= 4;
((unsigned long *)ctxt->user_regs.esp)[0] = ctxt->user_regs.eip;
ctxt->user_regs.eip = (unsigned long)_restore_vcpu;
ctxt->ctrlreg[3] = phys_to_machine(ctxt->ctrlreg[3]);
for (r = 0; r < gdt_pages; r++) {
ctxt->gdt_frames[r] = pfn_to_mfn(ctxt->gdt_frames[r]);
}
r = HYPERVISOR_boot_vcpu(vcpu, ctxt);
if (r != 0) {
printk(KERN_EMERG "Failed to reboot vcpu %d (%d)\n", vcpu, r);
return;
}
}
/* Whoever decided that printk should call into the scheduler needs to
be taken out and shot */
#define msg(x) HYPERVISOR_console_io(CONSOLEIO_write, sizeof(x), x)
extern unsigned uber_debug;
static int __do_suspend(void *ignore)
{
int i, j;
suspend_record_t *suspend_record;
static vcpu_guest_context_t suspended_cpu_records[NR_CPUS];
/* Hmmm... a cleaner interface to suspend/resume blkdevs would be nice. */
/* XXX SMH: yes it would :-( */
@@ -158,6 +204,10 @@ static int __do_suspend(void *ignore)
__cli();
for (i = 0; i < NR_CPUS; i++)
if (cpu_isset(i, feasible_cpus))
save_vcpu_context(i, &suspended_cpu_records[i]);
#ifdef __i386__
mm_pin_all();
kmem_cache_shrink(pgd_cache);
@@ -173,7 +223,9 @@ static int __do_suspend(void *ignore)
smp_suspend();
#endif
msg("xenbus going down.\n");
xenbus_suspend();
msg("xenbus gone down.\n");
ctrl_if_suspend();
@@ -187,10 +239,11 @@ static int __do_suspend(void *ignore)
memcpy(&suspend_record->resume_info, &xen_start_info,
sizeof(xen_start_info));
msg("Suspending...\n");
/* We'll stop somewhere inside this hypercall. When it returns,
we'll start resuming after the restore. */
HYPERVISOR_suspend(virt_to_machine(suspend_record) >> PAGE_SHIFT);
msg("Back from suspension\n");
shutting_down = SHUTDOWN_INVALID;
@@ -217,7 +270,9 @@ static int __do_suspend(void *ignore)
ctrl_if_resume();
msg("Here comes the xenbus...\n");
xenbus_resume();
msg("xenbus resumed.\n");
#ifdef CONFIG_SMP
smp_resume();
@@ -231,21 +286,32 @@ static int __do_suspend(void *ignore)
usbif_resume();
preempt_enable();
msg("Restoring cpu contexts...\n");
for (i = 0; i < NR_CPUS; i++)
if (cpu_isset(i, feasible_cpus))
restore_vcpu_context(i, &suspended_cpu_records[i]);
msg("All vcpus rebooted.\n");
__sti();
out_reenable_cpus:
msg("Reenabling cpus.\n");
while (!cpus_empty(feasible_cpus)) {
i = first_cpu(feasible_cpus);
printk("<0>Bring up %d/%d.\n", i, num_online_cpus());
printk("<0>17 preempt_count %x.\n", preempt_count());
j = cpu_up(i);
printk("<0>18 preempt_count %x.\n", preempt_count());
if (j != 0) {
printk(KERN_CRIT "Failed to bring cpu %d back up (%d).\n",
i, j);
err = j;
}
printk("<0>%d up.\n", i);
cpu_clear(i, feasible_cpus);
}
msg("Reenabled cpus.\n");
uber_debug = 0;
out:
if ( suspend_record != NULL )
@@ -163,7 +163,7 @@ HYPERVISOR_yield(
TRAP_INSTR
: "=a" (ret), "=b" (ign)
: "0" (__HYPERVISOR_sched_op), "1" (SCHEDOP_yield)
: "memory" );
: "memory", "ecx" );
return ret;
}
@@ -178,7 +178,7 @@ HYPERVISOR_block(
TRAP_INSTR
: "=a" (ret), "=b" (ign1)
: "0" (__HYPERVISOR_sched_op), "1" (SCHEDOP_block)
: "memory" );
: "memory", "ecx" );
return ret;
}
@@ -194,7 +194,7 @@ HYPERVISOR_shutdown(
: "=a" (ret), "=b" (ign1)
: "0" (__HYPERVISOR_sched_op),
"1" (SCHEDOP_shutdown | (SHUTDOWN_poweroff << SCHEDOP_reasonshift))
: "memory" );
: "memory", "ecx" );
return ret;
}
@@ -210,7 +210,7 @@ HYPERVISOR_reboot(
: "=a" (ret), "=b" (ign1)
: "0" (__HYPERVISOR_sched_op),
"1" (SCHEDOP_shutdown | (SHUTDOWN_reboot << SCHEDOP_reasonshift))
: "memory" );
: "memory", "ecx" );
return ret;
}
@@ -228,7 +228,7 @@ HYPERVISOR_suspend(
: "=a" (ret), "=b" (ign1), "=S" (ign2)
: "0" (__HYPERVISOR_sched_op),
"b" (SCHEDOP_shutdown | (SHUTDOWN_suspend << SCHEDOP_reasonshift)),
"S" (srec) : "memory");
"S" (srec) : "memory", "ecx");
return ret;
}
@@ -244,7 +244,7 @@ HYPERVISOR_crash(
: "=a" (ret), "=b" (ign1)
: "0" (__HYPERVISOR_sched_op),
"1" (SCHEDOP_shutdown | (SHUTDOWN_crash << SCHEDOP_reasonshift))
: "memory" );
: "memory", "ecx" );
return ret;
}
@@ -534,7 +534,7 @@ HYPERVISOR_vcpu_down(
: "=a" (ret), "=b" (ign1)
: "0" (__HYPERVISOR_sched_op),
"1" (SCHEDOP_vcpu_down | (vcpu << SCHEDOP_vcpushift))
: "memory" );
: "memory", "ecx", "edx" );
return ret;
}
@@ -550,8 +550,26 @@ HYPERVISOR_vcpu_up(
: "=a" (ret), "=b" (ign1)
: "0" (__HYPERVISOR_sched_op),
"1" (SCHEDOP_vcpu_up | (vcpu << SCHEDOP_vcpushift))
: "memory", "ecx" );
return ret;
}
static inline int
HYPERVISOR_vcpu_pickle(
int vcpu, vcpu_guest_context_t *ctxt)
{
int ret;
unsigned long ign1, ign2;
__asm__ __volatile__ (
TRAP_INSTR
: "=a" (ret), "=b" (ign1), "=c" (ign2)
: "0" (__HYPERVISOR_sched_op),
"1" (SCHEDOP_vcpu_pickle | (vcpu << SCHEDOP_vcpushift)),
"2" (ctxt)
: "memory" );
return ret;
}
#endif /* __HYPERCALL_H__ */
+21 -4
View File
@@ -217,8 +217,16 @@ struct vcpu *arch_alloc_vcpu_struct(void)
return xmalloc(struct vcpu);
}
/* We assume that vcpu 0 is always the last one to be freed in a
domain i.e. if v->vcpu_id == 0, the domain should be
single-processor. */
void arch_free_vcpu_struct(struct vcpu *v)
{
struct vcpu *p;
for_each_vcpu(v->domain, p) {
if (p->next_in_list == v)
p->next_in_list = v->next_in_list;
}
xfree(v);
}
@@ -402,8 +410,10 @@ int arch_set_info_guest(
if ( !(c->flags & VGCF_VMX_GUEST) )
{
if ( ((c->user_regs.cs & 3) == 0) ||
((c->user_regs.ss & 3) == 0) )
return -EINVAL;
((c->user_regs.ss & 3) == 0) ) {
printf("User regs.cs %x, ss %x.\n", c->user_regs.cs, c->user_regs.ss);
return -EINVAL;
}
}
clear_bit(_VCPUF_fpu_initialised, &v->vcpu_flags);
@@ -448,8 +458,10 @@ int arch_set_info_guest(
if ( shadow_mode_refcounts(d) )
{
if ( !get_page(&frame_table[phys_basetab>>PAGE_SHIFT], d) )
if ( !get_page(&frame_table[phys_basetab>>PAGE_SHIFT], d) ) {
printf("Bad phys_basetab %lx.\n", phys_basetab);
return -EINVAL;
}
}
else
{
@@ -457,13 +469,16 @@ int arch_set_info_guest(
if ( !(c->flags & VGCF_VMX_GUEST) )
#endif
if ( !get_page_and_type(&frame_table[phys_basetab>>PAGE_SHIFT], d,
PGT_base_page_table) )
PGT_base_page_table) ) {
printf("Bad phys_basetab2 %lx.\n", phys_basetab);
return -EINVAL;
}
}
if ( (rc = (int)set_gdt(v, c->gdt_frames, c->gdt_ents)) != 0 )
{
put_page_and_type(&frame_table[phys_basetab>>PAGE_SHIFT]);
printf("Failed to set gdt, %d.\n", rc);
return rc;
}
@@ -485,6 +500,8 @@ int arch_set_info_guest(
/* Don't redo final setup */
set_bit(_VCPUF_initialised, &v->vcpu_flags);
printf("Arch set_info_guest succeeded.\n");
return 0;
}
+13 -4
View File
@@ -2631,16 +2631,25 @@ long set_gdt(struct vcpu *v,
int i, nr_pages = (entries + 511) / 512;
unsigned long pfn;
if ( entries > FIRST_RESERVED_GDT_ENTRY )
if ( entries > FIRST_RESERVED_GDT_ENTRY ) {
printf("Too many entries in gdt (%d).\n", entries);
return -EINVAL;
}
shadow_sync_all(d);
/* Check the pages in the new GDT. */
for ( i = 0; i < nr_pages; i++ )
if ( ((pfn = frames[i]) >= max_page) ||
!get_page_and_type(&frame_table[pfn], d, PGT_gdt_page) )
for ( i = 0; i < nr_pages; i++ ) {
pfn = frames[i];
if (pfn >= max_page) {
printf("GDT bad as %ld >= %ld.\n", pfn, max_page);
goto fail;
}
if (!get_page_and_type(&frame_table[pfn], d, PGT_gdt_page) ) {
printf("Frame %ld looks bad.\n", pfn);
goto fail;
}
}
/* Tear down the old GDT. */
destroy_gdt(v);
+1 -1
View File
@@ -831,7 +831,7 @@ static void local_time_calibration(void *unused)
tsc_elapsed32 = (u32)tsc_elapsed64;
/* tsc_elapsed > stime_elapsed */
ASSERT(tsc_elapsed32 != 0);
// ASSERT(tsc_elapsed32 != 0);
while ( tsc_elapsed32 <= stime_elapsed32 )
{
tsc_elapsed32 <<= 1;
+11 -1
View File
@@ -178,6 +178,7 @@ void domain_shutdown(u8 reason)
{
struct domain *d = current->domain;
struct vcpu *v;
int cntr;
if ( d->domain_id == 0 )
{
@@ -208,8 +209,17 @@ void domain_shutdown(u8 reason)
}
/* Put every vcpu to sleep, but don't wait (avoids inter-vcpu deadlock). */
for_each_vcpu ( d, v )
cntr = 0;
printf("Putting %d to sleep.\n", d->domain_id);
for_each_vcpu ( d, v ) {
if (test_bit(_VCPUF_down, &v->vcpu_flags)) {
printf("vcpu %d is down.\n", v->vcpu_id);
} else {
printf("vcpu %d is up.\n", v->vcpu_id);
}
domain_sleep_nosync(v);
}
printf("Put %d vcpus to sleep for domain shutdown.\n", cntr);
}
+17
View File
@@ -292,6 +292,8 @@ static long evtchn_bind_ipi(evtchn_bind_ipi_t *bind)
chn = evtchn_from_port(d, port);
chn->state = ECS_IPI;
chn->notify_vcpu_id = current->vcpu_id;
printf("Bound ipi on vcpu %d to port %d.\n", current->vcpu_id,
port);
}
spin_unlock(&d->evtchn_lock);
@@ -497,9 +499,24 @@ long evtchn_send(int lport)
evtchn_set_pending(rd->vcpu[rchn->notify_vcpu_id], rport);
break;
case ECS_IPI:
if (current->domain->domain_id != 0) {
printf("Set %d pending on %d.\n", lport,
lchn->notify_vcpu_id);
if (lport == 7) {
struct vcpu *v = ld->vcpu[lchn->notify_vcpu_id];
struct domain *d = v->domain;
shared_info_t *s = d->shared_info;
printf("pending %x, mask %x, pending_sel %x, upcall_pending %x.\n",
s->evtchn_pending[0],
s->evtchn_mask[0],
v->vcpu_info->evtchn_pending_sel,
v->vcpu_info->evtchn_upcall_pending);
}
}
evtchn_set_pending(ld->vcpu[lchn->notify_vcpu_id], lport);
break;
default:
printf("Failed to set %d pending: state %d.\n", lport, lchn->state);
ret = -EINVAL;
}
+38 -2
View File
@@ -38,6 +38,8 @@
#include <xen/mm.h>
#include <public/sched_ctl.h>
extern void arch_getdomaininfo_ctxt(struct vcpu *,
struct vcpu_guest_context *);
/* opt_sched: scheduler - default to SEDF */
static char opt_sched[10] = "sedf";
string_param("sched", opt_sched);
@@ -82,7 +84,8 @@ void free_domain_struct(struct domain *d)
int i;
SCHED_OP(free_task, d);
for (i = 0; i < MAX_VIRT_CPUS; i++)
/* vcpu 0 has to be the last one destructed. */
for (i = MAX_VIRT_CPUS-1; i >= 0; i--)
if ( d->vcpu[i] )
arch_free_vcpu_struct(d->vcpu[i]);
@@ -295,10 +298,37 @@ static long do_vcpu_up(int vcpu)
return 0;
}
static long do_vcpu_pickle(int vcpu, unsigned long arg)
{
struct vcpu *v;
vcpu_guest_context_t *c;
int ret = 0;
if (vcpu >= MAX_VIRT_CPUS)
return -EINVAL;
v = current->domain->vcpu[vcpu];
if (!v)
return -ESRCH;
/* Don't pickle vcpus which are currently running */
if (!test_bit(_VCPUF_down, &v->vcpu_flags)) {
printf("Pickling a live vcpu?\n");
return -EBUSY;
}
c = xmalloc(vcpu_guest_context_t);
if (!c)
return -ENOMEM;
arch_getdomaininfo_ctxt(v, c);
if (copy_to_user((vcpu_guest_context_t *)arg,
(const vcpu_guest_context_t *)c, sizeof(*c)))
ret = -EFAULT;
xfree(c);
return ret;
}
/*
* Demultiplex scheduler-related hypercalls.
*/
long do_sched_op(unsigned long op)
long do_sched_op(unsigned long op, unsigned long arg)
{
long ret = 0;
@@ -334,6 +364,12 @@ long do_sched_op(unsigned long op)
ret = do_vcpu_up((int)(op >> SCHEDOP_vcpushift));
break;
}
case SCHEDOP_vcpu_pickle:
{
ret = do_vcpu_pickle((int)(op >> SCHEDOP_vcpushift), arg);
printf("Pickle result %ld.\n", ret);
break;
}
default:
ret = -ENOSYS;
+1
View File
@@ -203,6 +203,7 @@ struct mmuext_op {
#define SCHEDOP_shutdown 2 /* Stop executing this domain. */
#define SCHEDOP_vcpu_down 3 /* make target VCPU not-runnable. */
#define SCHEDOP_vcpu_up 4 /* make target VCPU runnable. */
#define SCHEDOP_vcpu_pickle 5 /* save a vcpu's context to memory. */
#define SCHEDOP_cmdmask 255 /* 8-bit command. */
#define SCHEDOP_reasonshift 8 /* 8-bit reason code. (SCHEDOP_shutdown) */
#define SCHEDOP_vcpushift 8 /* 8-bit VCPU target. (SCHEDOP_up|down) */
Reference in new issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.