summaryrefslogtreecommitdiff
path: root/xen/arch/arm/vsmc.c
blob: a36db15fffc06247f2a438132586e91d24396850 (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
/*
 * xen/arch/arm/vsmc.c
 *
 * Generic handler for SMC and HVC calls according to
 * ARM SMC calling convention
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */


#include <xen/lib.h>
#include <xen/types.h>
#include <public/arch-arm/smccc.h>
#include <asm/cpuerrata.h>
#include <asm/cpufeature.h>
#include <asm/monitor.h>
#include <asm/regs.h>
#include <asm/smccc.h>
#include <asm/tee/tee.h>
#include <asm/traps.h>
#include <asm/vpsci.h>
#include <asm/platform.h>

/* Number of functions currently supported by Hypervisor Service. */
#define XEN_SMCCC_FUNCTION_COUNT 3

/* Number of functions currently supported by Standard Service Service Calls. */
#define SSSC_SMCCC_FUNCTION_COUNT (3 + VPSCI_NR_FUNCS)

static bool fill_uid(struct cpu_user_regs *regs, xen_uuid_t uuid)
{
    int n;

    /*
     * UID is returned in registers r0..r3, four bytes per register,
     * first byte is stored in low-order bits of a register.
     * (ARM DEN 0028B page 14)
     */
    for (n = 0; n < 4; n++)
    {
        const uint8_t *bytes = uuid.a + n * 4;
        uint32_t r;

        r = bytes[0];
        r |= bytes[1] << 8;
        r |= bytes[2] << 16;
        r |= bytes[3] << 24;

        set_user_reg(regs, n, r);
    }

    return true;
}

static bool fill_revision(struct cpu_user_regs *regs, uint32_t major,
                         uint32_t minor)
{
    /*
     * Revision is returned in registers r0 and r1.
     * r0 stores major part of the version
     * r1 stores minor part of the version
     * (ARM DEN 0028B page 15)
     */
    set_user_reg(regs, 0, major);
    set_user_reg(regs, 1, minor);

    return true;
}

static bool fill_function_call_count(struct cpu_user_regs *regs, uint32_t cnt)
{
    /*
     * Function call count is retuned as any other return value in register r0
     * (ARM DEN 0028B page 17)
     */
    set_user_reg(regs, 0, cnt);

    return true;
}

/* SMCCC interface for ARM Architecture */
static bool handle_arch(struct cpu_user_regs *regs)
{
    uint32_t fid = (uint32_t)get_user_reg(regs, 0);

    switch ( fid )
    {
    case ARM_SMCCC_VERSION_FID:
        set_user_reg(regs, 0, ARM_SMCCC_VERSION_1_1);
        return true;

    case ARM_SMCCC_ARCH_FEATURES_FID:
    {
        uint32_t arch_func_id = get_user_reg(regs, 1);
        int ret = ARM_SMCCC_NOT_SUPPORTED;

        switch ( arch_func_id )
        {
        case ARM_SMCCC_ARCH_WORKAROUND_1_FID:
            if ( cpus_have_cap(ARM_HARDEN_BRANCH_PREDICTOR) )
                ret = 0;
            break;
        case ARM_SMCCC_ARCH_WORKAROUND_2_FID:
            switch ( get_ssbd_state() )
            {
            case ARM_SSBD_UNKNOWN:
            case ARM_SSBD_FORCE_DISABLE:
                break;

            case ARM_SSBD_RUNTIME:
                ret = ARM_SMCCC_SUCCESS;
                break;

            case ARM_SSBD_FORCE_ENABLE:
            case ARM_SSBD_MITIGATED:
                ret = ARM_SMCCC_NOT_REQUIRED;
                break;
            }
            break;
        }

        set_user_reg(regs, 0, ret);

        return true;
    }

    case ARM_SMCCC_ARCH_WORKAROUND_1_FID:
        /* No return value */
        return true;

    case ARM_SMCCC_ARCH_WORKAROUND_2_FID:
    {
        bool enable = (uint32_t)get_user_reg(regs, 1);

        /*
         * ARM_WORKAROUND_2_FID should only be called when mitigation
         * state can be changed at runtime.
         */
        if ( unlikely(get_ssbd_state() != ARM_SSBD_RUNTIME) )
            return true;

        if ( enable )
            get_cpu_info()->flags |= CPUINFO_WORKAROUND_2_FLAG;
        else
            get_cpu_info()->flags &= ~CPUINFO_WORKAROUND_2_FLAG;

        return true;
    }
    }

    return false;
}

/* SMCCC interface for hypervisor. Tell about itself. */
static bool handle_hypervisor(struct cpu_user_regs *regs)
{
    uint32_t fid = (uint32_t)get_user_reg(regs, 0);

    switch ( fid )
    {
    case ARM_SMCCC_CALL_COUNT_FID(HYPERVISOR):
        return fill_function_call_count(regs, XEN_SMCCC_FUNCTION_COUNT);
    case ARM_SMCCC_CALL_UID_FID(HYPERVISOR):
        return fill_uid(regs, XEN_SMCCC_UID);
    case ARM_SMCCC_REVISION_FID(HYPERVISOR):
        return fill_revision(regs, XEN_SMCCC_MAJOR_REVISION,
                             XEN_SMCCC_MINOR_REVISION);
    default:
        return false;
    }
}

/* Existing (pre SMCCC) APIs. This includes PSCI 0.1 interface */
static bool handle_existing_apis(struct cpu_user_regs *regs)
{
    /* Only least 32 bits are significant (ARM DEN 0028B, page 12) */
    uint32_t fid = (uint32_t)get_user_reg(regs, 0);

    return do_vpsci_0_1_call(regs, fid);
}

/* PSCI 0.2 interface and other Standard Secure Calls */
static bool handle_sssc(struct cpu_user_regs *regs)
{
    uint32_t fid = (uint32_t)get_user_reg(regs, 0);

    if ( do_vpsci_0_2_call(regs, fid) )
        return true;

    switch ( fid )
    {
    case ARM_SMCCC_CALL_COUNT_FID(STANDARD):
        return fill_function_call_count(regs, SSSC_SMCCC_FUNCTION_COUNT);

    case ARM_SMCCC_CALL_UID_FID(STANDARD):
        return fill_uid(regs, SSSC_SMCCC_UID);

    case ARM_SMCCC_REVISION_FID(STANDARD):
        return fill_revision(regs, SSSC_SMCCC_MAJOR_REVISION,
                             SSSC_SMCCC_MINOR_REVISION);

    default:
        return false;
    }
}

/*
 * vsmccc_handle_call() - handle SMC/HVC call according to ARM SMCCC.
 * returns true if that was valid SMCCC call (even if function number
 * was unknown).
 */
static bool vsmccc_handle_call(struct cpu_user_regs *regs)
{
    bool handled = false;
    const union hsr hsr = { .bits = regs->hsr };
    uint32_t funcid = get_user_reg(regs, 0);

    /*
     * Check immediate value for HVC32, HVC64 and SMC64.
     * It is not so easy to check immediate value for SMC32,
     * because it is not stored in HSR.ISS field. To get immediate
     * value we need to disassemble instruction at current pc, which
     * is expensive. So we will assume that it is 0x0.
     */
    switch ( hsr.ec )
    {
    case HSR_EC_HVC32:
#ifdef CONFIG_ARM_64
    case HSR_EC_HVC64:
    case HSR_EC_SMC64:
#endif
        if ( (hsr.iss & HSR_XXC_IMM_MASK) != 0)
            return false;
        break;
    case HSR_EC_SMC32:
        break;
    default:
        return false;
    }

    /* 64 bit calls are allowed only from 64 bit domains. */
    if ( smccc_is_conv_64(funcid) && is_32bit_domain(current->domain) )
    {
        set_user_reg(regs, 0, ARM_SMCCC_ERR_UNKNOWN_FUNCTION);
        return true;
    }

    /*
     * Special case: identifier range for existing APIs.
     * This range is described in SMCCC (ARM DEN 0028B, page 16),
     * but it does not conforms to standard function identifier
     * encoding.
     */
    if ( funcid >= ARM_SMCCC_RESERVED_RANGE_START &&
         funcid <= ARM_SMCCC_RESERVED_RANGE_END )
        handled = handle_existing_apis(regs);
    else
    {
        switch ( smccc_get_owner(funcid) )
        {
        case ARM_SMCCC_OWNER_ARCH:
            handled = handle_arch(regs);
            break;
        case ARM_SMCCC_OWNER_HYPERVISOR:
            handled = handle_hypervisor(regs);
            break;
        case ARM_SMCCC_OWNER_STANDARD:
            handled = handle_sssc(regs);
            break;
        case ARM_SMCCC_OWNER_SIP:
            handled = platform_smc(regs);
            break;
        case ARM_SMCCC_OWNER_TRUSTED_APP ... ARM_SMCCC_OWNER_TRUSTED_APP_END:
        case ARM_SMCCC_OWNER_TRUSTED_OS ... ARM_SMCCC_OWNER_TRUSTED_OS_END:
            handled = tee_handle_call(regs);
            break;
        }
    }

    if ( !handled )
    {
        gprintk(XENLOG_INFO, "Unhandled SMC/HVC: %#x\n", funcid);

        /* Inform caller that function is not supported. */
        set_user_reg(regs, 0, ARM_SMCCC_ERR_UNKNOWN_FUNCTION);
    }

    return true;
}

void do_trap_smc(struct cpu_user_regs *regs, const union hsr hsr)
{
    int rc = 0;

    if ( !check_conditional_instr(regs, hsr) )
    {
        advance_pc(regs, hsr);
        return;
    }

    /* If monitor is enabled, let it handle the call. */
    if ( current->domain->arch.monitor.privileged_call_enabled )
        rc = monitor_smc();

    if ( rc == 1 )
        return;

    /*
     * Use standard routines to handle the call.
     * vsmccc_handle_call() will return false if this call is not
     * SMCCC compatible (e.g. immediate value != 0). As it is not
     * compatible, we can't be sure that guest will understand
     * ARM_SMCCC_ERR_UNKNOWN_FUNCTION.
     */
    if ( vsmccc_handle_call(regs) )
        advance_pc(regs, hsr);
    else
        inject_undef_exception(regs, hsr);
}

void do_trap_hvc_smccc(struct cpu_user_regs *regs)
{
    const union hsr hsr = { .bits = regs->hsr };

    /*
     * vsmccc_handle_call() will return false if this call is not
     * SMCCC compatible (e.g. immediate value != 0). As it is not
     * compatible, we can't be sure that guest will understand
     * ARM_SMCCC_ERR_UNKNOWN_FUNCTION.
     */
    if ( !vsmccc_handle_call(regs) )
        inject_undef_exception(regs, hsr);
}

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