summaryrefslogtreecommitdiff
path: root/xen/arch/arm/vpsci.c
blob: d1615be8a63ec5e916f1f51b4ab7dede4433940a (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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include <xen/errno.h>
#include <xen/sched.h>
#include <xen/types.h>

#include <asm/current.h>
#include <asm/vgic.h>
#include <asm/vpsci.h>
#include <asm/event.h>

#include <public/sched.h>

static int do_common_cpu_on(register_t target_cpu, register_t entry_point,
                            register_t context_id)
{
    struct vcpu *v;
    struct domain *d = current->domain;
    struct vcpu_guest_context *ctxt;
    int rc;
    bool is_thumb = entry_point & 1;
    register_t vcpuid;

    vcpuid = vaffinity_to_vcpuid(target_cpu);

    if ( (v = domain_vcpu(d, vcpuid)) == NULL )
        return PSCI_INVALID_PARAMETERS;

    /* THUMB set is not allowed with 64-bit domain */
    if ( is_64bit_domain(d) && is_thumb )
        return PSCI_INVALID_ADDRESS;

    if ( !test_bit(_VPF_down, &v->pause_flags) )
        return PSCI_ALREADY_ON;

    if ( (ctxt = alloc_vcpu_guest_context()) == NULL )
        return PSCI_DENIED;

    vgic_clear_pending_irqs(v);

    memset(ctxt, 0, sizeof(*ctxt));
    ctxt->user_regs.pc64 = (u64) entry_point;
    ctxt->sctlr = SCTLR_GUEST_INIT;
    ctxt->ttbr0 = 0;
    ctxt->ttbr1 = 0;
    ctxt->ttbcr = 0; /* Defined Reset Value */

    /*
     * x0/r0_usr are always updated because for PSCI 0.1 the general
     * purpose registers are undefined upon CPU_on.
     */
    if ( is_32bit_domain(d) )
    {
        ctxt->user_regs.cpsr = PSR_GUEST32_INIT;
        /* Start the VCPU with THUMB set if it's requested by the kernel */
        if ( is_thumb )
        {
            ctxt->user_regs.cpsr |= PSR_THUMB;
            ctxt->user_regs.pc64 &= ~(u64)1;
        }

        ctxt->user_regs.r0_usr = context_id;
    }
#ifdef CONFIG_ARM_64
    else
    {
        ctxt->user_regs.cpsr = PSR_GUEST64_INIT;
        ctxt->user_regs.x0 = context_id;
    }
#endif
    ctxt->flags = VGCF_online;

    domain_lock(d);
    rc = arch_set_info_guest(v, ctxt);
    domain_unlock(d);

    free_vcpu_guest_context(ctxt);

    if ( rc < 0 )
        return PSCI_DENIED;

    vcpu_wake(v);

    return PSCI_SUCCESS;
}

static int32_t do_psci_cpu_on(uint32_t vcpuid, register_t entry_point)
{
    int32_t ret;

    ret = do_common_cpu_on(vcpuid, entry_point, 0);
    /*
     * PSCI 0.1 does not define the return codes PSCI_ALREADY_ON and
     * PSCI_INVALID_ADDRESS.
     * Instead, return PSCI_INVALID_PARAMETERS.
     */
    if ( ret == PSCI_ALREADY_ON || ret == PSCI_INVALID_ADDRESS )
        ret = PSCI_INVALID_PARAMETERS;

    return ret;
}

static int32_t do_psci_cpu_off(uint32_t power_state)
{
    struct vcpu *v = current;
    if ( !test_and_set_bit(_VPF_down, &v->pause_flags) )
        vcpu_sleep_nosync(v);
    return PSCI_SUCCESS;
}

static uint32_t do_psci_0_2_version(void)
{
    /*
     * PSCI is backward compatible from 0.2. So we can bump the version
     * without any issue.
     */
    return PSCI_VERSION(1, 1);
}

static register_t do_psci_0_2_cpu_suspend(uint32_t power_state,
                                          register_t entry_point,
                                          register_t context_id)
{
    struct vcpu *v = current;

    /*
     * Power off requests are treated as performing standby
     * as this simplifies Xen implementation.
     */

    vcpu_block_unless_event_pending(v);
    return PSCI_SUCCESS;
}

static int32_t do_psci_0_2_cpu_off(void)
{
    return do_psci_cpu_off(0);
}

static int32_t do_psci_0_2_cpu_on(register_t target_cpu,
                                  register_t entry_point,
                                  register_t context_id)
{
    return do_common_cpu_on(target_cpu, entry_point, context_id);
}

static const unsigned long target_affinity_mask[] = {
    ( MPIDR_HWID_MASK & AFFINITY_MASK( 0 ) ),
    ( MPIDR_HWID_MASK & AFFINITY_MASK( 1 ) ),
    ( MPIDR_HWID_MASK & AFFINITY_MASK( 2 ) )
#ifdef CONFIG_ARM_64
    ,( MPIDR_HWID_MASK & AFFINITY_MASK( 3 ) )
#endif
};

static int32_t do_psci_0_2_affinity_info(register_t target_affinity,
                                         uint32_t lowest_affinity_level)
{
    struct domain *d = current->domain;
    struct vcpu *v;
    uint32_t vcpuid;
    unsigned long tmask;

    if ( lowest_affinity_level < ARRAY_SIZE(target_affinity_mask) )
    {
        tmask = target_affinity_mask[lowest_affinity_level];
        target_affinity &= tmask;
    }
    else
        return PSCI_INVALID_PARAMETERS;

    for ( vcpuid = 0; vcpuid < d->max_vcpus; vcpuid++ )
    {
        v = d->vcpu[vcpuid];

        if ( ( ( v->arch.vmpidr & tmask ) == target_affinity )
                && ( !test_bit(_VPF_down, &v->pause_flags) ) )
            return PSCI_0_2_AFFINITY_LEVEL_ON;
    }

    return PSCI_0_2_AFFINITY_LEVEL_OFF;
}

static int32_t do_psci_0_2_migrate_info_type(void)
{
    return PSCI_0_2_TOS_MP_OR_NOT_PRESENT;
}

static void do_psci_0_2_system_off( void )
{
    struct domain *d = current->domain;
    domain_shutdown(d,SHUTDOWN_poweroff);
}

static void do_psci_0_2_system_reset(void)
{
    struct domain *d = current->domain;
    domain_shutdown(d,SHUTDOWN_reboot);
}

static int32_t do_psci_1_0_features(uint32_t psci_func_id)
{
    /* /!\ Ordered by function ID and not name */
    switch ( psci_func_id )
    {
    case PSCI_0_2_FN32_PSCI_VERSION:
    case PSCI_0_2_FN32_CPU_SUSPEND:
    case PSCI_0_2_FN64_CPU_SUSPEND:
    case PSCI_0_2_FN32_CPU_OFF:
    case PSCI_0_2_FN32_CPU_ON:
    case PSCI_0_2_FN64_CPU_ON:
    case PSCI_0_2_FN32_AFFINITY_INFO:
    case PSCI_0_2_FN64_AFFINITY_INFO:
    case PSCI_0_2_FN32_MIGRATE_INFO_TYPE:
    case PSCI_0_2_FN32_SYSTEM_OFF:
    case PSCI_0_2_FN32_SYSTEM_RESET:
    case PSCI_1_0_FN32_PSCI_FEATURES:
    case ARM_SMCCC_VERSION_FID:
        return 0;
    default:
        return PSCI_NOT_SUPPORTED;
    }
}

#define PSCI_SET_RESULT(reg, val) set_user_reg(reg, 0, val)
#define PSCI_ARG(reg, n) get_user_reg(reg, n)

#ifdef CONFIG_ARM_64
#define PSCI_ARG32(reg, n) (uint32_t)(get_user_reg(reg, n))
#else
#define PSCI_ARG32(reg, n) PSCI_ARG(reg, n)
#endif

/*
 * PSCI 0.1 calls. It will return false if the function ID is not
 * handled.
 */
bool do_vpsci_0_1_call(struct cpu_user_regs *regs, uint32_t fid)
{
    switch ( (uint32_t)get_user_reg(regs, 0) )
    {
    case PSCI_cpu_off:
    {
        uint32_t pstate = PSCI_ARG32(regs, 1);

        perfc_incr(vpsci_cpu_off);
        PSCI_SET_RESULT(regs, do_psci_cpu_off(pstate));
        return true;
    }
    case PSCI_cpu_on:
    {
        uint32_t vcpuid = PSCI_ARG32(regs, 1);
        register_t epoint = PSCI_ARG(regs, 2);

        perfc_incr(vpsci_cpu_on);
        PSCI_SET_RESULT(regs, do_psci_cpu_on(vcpuid, epoint));
        return true;
    }
    default:
        return false;
    }
}

/*
 * PSCI 0.2 or later calls. It will return false if the function ID is
 * not handled.
 */
bool do_vpsci_0_2_call(struct cpu_user_regs *regs, uint32_t fid)
{
    /*
     * /!\ VPSCI_NR_FUNCS (in asm/vpsci.h) should be updated when
     * adding/removing a function. SCCC_SMCCC_*_REVISION should be
     * updated once per release.
     */
    switch ( fid )
    {
    case PSCI_0_2_FN32_PSCI_VERSION:
        perfc_incr(vpsci_version);
        PSCI_SET_RESULT(regs, do_psci_0_2_version());
        return true;

    case PSCI_0_2_FN32_CPU_OFF:
        perfc_incr(vpsci_cpu_off);
        PSCI_SET_RESULT(regs, do_psci_0_2_cpu_off());
        return true;

    case PSCI_0_2_FN32_MIGRATE_INFO_TYPE:
        perfc_incr(vpsci_migrate_info_type);
        PSCI_SET_RESULT(regs, do_psci_0_2_migrate_info_type());
        return true;

    case PSCI_0_2_FN32_SYSTEM_OFF:
        perfc_incr(vpsci_system_off);
        do_psci_0_2_system_off();
        PSCI_SET_RESULT(regs, PSCI_INTERNAL_FAILURE);
        return true;

    case PSCI_0_2_FN32_SYSTEM_RESET:
        perfc_incr(vpsci_system_reset);
        do_psci_0_2_system_reset();
        PSCI_SET_RESULT(regs, PSCI_INTERNAL_FAILURE);
        return true;

    case PSCI_0_2_FN32_CPU_ON:
    case PSCI_0_2_FN64_CPU_ON:
    {
        register_t vcpuid = PSCI_ARG(regs, 1);
        register_t epoint = PSCI_ARG(regs, 2);
        register_t cid = PSCI_ARG(regs, 3);

        perfc_incr(vpsci_cpu_on);
        PSCI_SET_RESULT(regs, do_psci_0_2_cpu_on(vcpuid, epoint, cid));
        return true;
    }

    case PSCI_0_2_FN32_CPU_SUSPEND:
    case PSCI_0_2_FN64_CPU_SUSPEND:
    {
        uint32_t pstate = PSCI_ARG32(regs, 1);
        register_t epoint = PSCI_ARG(regs, 2);
        register_t cid = PSCI_ARG(regs, 3);

        perfc_incr(vpsci_cpu_suspend);
        PSCI_SET_RESULT(regs, do_psci_0_2_cpu_suspend(pstate, epoint, cid));
        return true;
    }

    case PSCI_0_2_FN32_AFFINITY_INFO:
    case PSCI_0_2_FN64_AFFINITY_INFO:
    {
        register_t taff = PSCI_ARG(regs, 1);
        uint32_t laff = PSCI_ARG32(regs, 2);

        perfc_incr(vpsci_cpu_affinity_info);
        PSCI_SET_RESULT(regs, do_psci_0_2_affinity_info(taff, laff));
        return true;
    }

    case PSCI_1_0_FN32_PSCI_FEATURES:
    {
        uint32_t psci_func_id = PSCI_ARG32(regs, 1);

        perfc_incr(vpsci_features);
        PSCI_SET_RESULT(regs, do_psci_1_0_features(psci_func_id));
        return true;
    }

    default:
        return false;
    }
}

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