/* SPDX-License-Identifier: GPL-2.0-or-later */ /****************************************************************************** * arch/x86/hypercall.c * * Common x86 hypercall infrastructure. * * Copyright (c) 2015,2016 Citrix Systems Ltd. */ #include #include #define NEXT_ARG(fmt, args) \ ({ \ unsigned long __arg; \ switch ( *(fmt)++ ) \ { \ case 'i': __arg = (unsigned long)va_arg(args, unsigned int); break; \ case 'l': __arg = (unsigned long)va_arg(args, unsigned long); break; \ case 'h': __arg = (unsigned long)va_arg(args, void *); break; \ default: goto bad_fmt; \ } \ __arg; \ }) unsigned long hypercall_create_continuation( unsigned int op, const char *format, ...) { struct vcpu *curr = current; struct mc_state *mcs = &curr->mc_state; const char *p = format; unsigned long arg; unsigned int i; va_list args; curr->hcall_preempted = true; va_start(args, format); if ( mcs->flags & MCSF_in_multicall ) { for ( i = 0; *p != '\0'; i++ ) mcs->call.args[i] = NEXT_ARG(p, args); } else { struct cpu_user_regs *regs = guest_cpu_user_regs(); regs->rax = op; #ifdef CONFIG_COMPAT if ( !curr->hcall_compat ) #else if ( true ) #endif { for ( i = 0; *p != '\0'; i++ ) { arg = NEXT_ARG(p, args); switch ( i ) { case 0: regs->rdi = arg; break; case 1: regs->rsi = arg; break; case 2: regs->rdx = arg; break; case 3: regs->r10 = arg; break; case 4: regs->r8 = arg; break; case 5: regs->r9 = arg; break; } } } else { for ( i = 0; *p != '\0'; i++ ) { arg = NEXT_ARG(p, args); switch ( i ) { case 0: regs->rbx = arg; break; case 1: regs->rcx = arg; break; case 2: regs->rdx = arg; break; case 3: regs->rsi = arg; break; case 4: regs->rdi = arg; break; case 5: regs->rbp = arg; break; } } } } va_end(args); return op; bad_fmt: va_end(args); gprintk(XENLOG_ERR, "Bad hypercall continuation format '%c'\n", *p); ASSERT_UNREACHABLE(); domain_crash(curr->domain); return 0; } #undef NEXT_ARG void arch_hypercall_tasklet_result(struct vcpu *v, long res) { struct cpu_user_regs *regs = &v->arch.user_regs; regs->rax = res; } int hypercall_xlat_continuation(unsigned int *id, unsigned int nr, unsigned int mask, ...) { int rc = 0; struct mc_state *mcs = ¤t->mc_state; struct cpu_user_regs *regs; unsigned int i, cval = 0; unsigned long nval = 0; va_list args; ASSERT(nr <= ARRAY_SIZE(mcs->call.args)); ASSERT(!(mask >> nr)); ASSERT(!id || *id < nr); ASSERT(!id || !(mask & (1U << *id))); va_start(args, mask); if ( mcs->flags & MCSF_in_multicall ) { if ( !current->hcall_preempted ) { va_end(args); return 0; } for ( i = 0; i < nr; ++i, mask >>= 1 ) { if ( mask & 1 ) { nval = va_arg(args, unsigned long); cval = va_arg(args, unsigned int); if ( cval == nval ) mask &= ~1U; else BUG_ON(nval == (unsigned int)nval); } else if ( id && *id == i ) { *id = mcs->call.args[i]; id = NULL; } if ( (mask & 1) && mcs->call.args[i] == nval ) { mcs->call.args[i] = cval; ++rc; } else BUG_ON(mcs->call.args[i] != (unsigned int)mcs->call.args[i]); } } else { regs = guest_cpu_user_regs(); for ( i = 0; i < nr; ++i, mask >>= 1 ) { unsigned long *reg; switch ( i ) { case 0: reg = ®s->rbx; break; case 1: reg = ®s->rcx; break; case 2: reg = ®s->rdx; break; case 3: reg = ®s->rsi; break; case 4: reg = ®s->rdi; break; case 5: reg = ®s->rbp; break; default: BUG(); reg = NULL; break; } if ( (mask & 1) ) { nval = va_arg(args, unsigned long); cval = va_arg(args, unsigned int); if ( cval == nval ) mask &= ~1U; else BUG_ON(nval == (unsigned int)nval); } else if ( id && *id == i ) { *id = *reg; id = NULL; } if ( (mask & 1) && *reg == nval ) { *reg = cval; ++rc; } else BUG_ON(*reg != (unsigned int)*reg); } } va_end(args); return rc; } enum mc_disposition arch_do_multicall_call(struct mc_state *state) { const struct domain *currd = current->domain; if ( is_pv_domain(currd) ) return pv_do_multicall_call(state); if ( is_hvm_domain(currd) ) return hvm_do_multicall_call(state); return mc_exit; } /* * Local variables: * mode: C * c-file-style: "BSD" * c-basic-offset: 4 * tab-width: 4 * indent-tabs-mode: nil * End: */