summaryrefslogtreecommitdiff
path: root/xen/common/multicall.c
blob: 1f0cc4cb267ce6517ccd8703d91984ee6eeb284b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
/******************************************************************************
 * multicall.c
 */

#include <xen/types.h>
#include <xen/lib.h>
#include <xen/mm.h>
#include <xen/sched.h>
#include <xen/event.h>
#include <xen/multicall.h>
#include <xen/guest_access.h>
#include <xen/hypercall.h>
#include <xen/perfc.h>
#include <xen/trace.h>
#include <asm/current.h>
#include <asm/hardirq.h>

#ifndef COMPAT
typedef long ret_t;
#define xlat_multicall_entry(mcs)

static void __trace_multicall_call(multicall_entry_t *call)
{
    __trace_hypercall(TRC_PV_HYPERCALL_SUBCALL, call->op, call->args);
}
#endif

static void trace_multicall_call(multicall_entry_t *call)
{
    if ( !tb_init_done )
        return;

    __trace_multicall_call(call);
}

ret_t do_multicall(
    XEN_GUEST_HANDLE_PARAM(multicall_entry_t) call_list, uint32_t nr_calls)
{
    struct vcpu *curr = current;
    struct mc_state *mcs = &curr->mc_state;
    uint32_t         i;
    int              rc = 0;
    enum mc_disposition disp = mc_continue;

    if ( unlikely(__test_and_set_bit(_MCSF_in_multicall, &mcs->flags)) )
    {
        gdprintk(XENLOG_INFO, "Multicall reentry is disallowed.\n");
        return -EINVAL;
    }

    if ( unlikely(!guest_handle_okay(call_list, nr_calls)) )
        rc = -EFAULT;

    for ( i = 0; !rc && disp == mc_continue && i < nr_calls; i++ )
    {
        if ( i && hypercall_preempt_check() )
            goto preempted;

        if ( unlikely(__copy_from_guest(&mcs->call, call_list, 1)) )
        {
            rc = -EFAULT;
            break;
        }

        trace_multicall_call(&mcs->call);

        disp = arch_do_multicall_call(mcs);

        /*
         * In the unlikely event that a hypercall has left interrupts,
         * spinlocks, or other things in a bad way, continuing the multicall
         * will typically lead to far more subtle issues to debug.
         */
        ASSERT_NOT_IN_ATOMIC();

#ifndef NDEBUG
        {
            /*
             * Deliberately corrupt the contents of the multicall structure.
             * The caller must depend only on the 'result' field on return.
             */
            struct multicall_entry corrupt;
            memset(&corrupt, 0xAA, sizeof(corrupt));
            (void)__copy_to_guest(call_list, &corrupt, 1);
        }
#endif

        if ( unlikely(disp == mc_exit) )
        {
            if ( __copy_field_to_guest(call_list, &mcs->call, result) )
                /* nothing, best effort only */;
            rc = mcs->call.result;
        }
        else if ( unlikely(__copy_field_to_guest(call_list, &mcs->call,
                                                 result)) )
            rc = -EFAULT;
        else if ( curr->hcall_preempted )
        {
            /* Translate sub-call continuation to guest layout */
            xlat_multicall_entry(mcs);

            /* Copy the sub-call continuation. */
            if ( likely(!__copy_to_guest(call_list, &mcs->call, 1)) )
                goto preempted;
            else
                hypercall_cancel_continuation(curr);
            rc = -EFAULT;
        }
        else
            guest_handle_add_offset(call_list, 1);
    }

    if ( unlikely(disp == mc_preempt) && i < nr_calls )
        goto preempted;

    perfc_add(calls_from_multicall, i);
    mcs->flags = 0;
    return rc;

 preempted:
    perfc_add(calls_from_multicall, i);
    mcs->flags = 0;
    return hypercall_create_continuation(
        __HYPERVISOR_multicall, "hi", call_list, nr_calls-i);
}

/*
 * Local variables:
 * mode: C
 * c-file-style: "BSD"
 * c-basic-offset: 4
 * tab-width: 4
 * indent-tabs-mode: nil
 * End:
 */