aboutsummaryrefslogtreecommitdiff
path: root/target/arm/translate-m-nocp.c
blob: d9e144e8eb3731fc20aed22cf1bd6c7ce484e90d (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
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
/*
 *  ARM translation: M-profile NOCP special-case instructions
 *
 *  Copyright (c) 2020 Linaro, Ltd.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
 */

#include "qemu/osdep.h"
#include "tcg/tcg-op.h"
#include "tcg/tcg-op-gvec.h"
#include "translate.h"
#include "translate-a32.h"

#include "decode-m-nocp.c.inc"

/*
 * Decode VLLDM and VLSTM are nonstandard because:
 *  * if there is no FPU then these insns must NOP in
 *    Secure state and UNDEF in Nonsecure state
 *  * if there is an FPU then these insns do not have
 *    the usual behaviour that vfp_access_check() provides of
 *    being controlled by CPACR/NSACR enable bits or the
 *    lazy-stacking logic.
 */
static bool trans_VLLDM_VLSTM(DisasContext *s, arg_VLLDM_VLSTM *a)
{
    TCGv_i32 fptr;

    if (!arm_dc_feature(s, ARM_FEATURE_M) ||
        !arm_dc_feature(s, ARM_FEATURE_V8)) {
        return false;
    }

    if (a->op) {
        /*
         * T2 encoding ({D0-D31} reglist): v8.1M and up. We choose not
         * to take the IMPDEF option to make memory accesses to the stack
         * slots that correspond to the D16-D31 registers (discarding
         * read data and writing UNKNOWN values), so for us the T2
         * encoding behaves identically to the T1 encoding.
         */
        if (!arm_dc_feature(s, ARM_FEATURE_V8_1M)) {
            return false;
        }
    } else {
        /*
         * T1 encoding ({D0-D15} reglist); undef if we have 32 Dregs.
         * This is currently architecturally impossible, but we add the
         * check to stay in line with the pseudocode. Note that we must
         * emit code for the UNDEF so it takes precedence over the NOCP.
         */
        if (dc_isar_feature(aa32_simd_r32, s)) {
            unallocated_encoding(s);
            return true;
        }
    }

    /*
     * If not secure, UNDEF. We must emit code for this
     * rather than returning false so that this takes
     * precedence over the m-nocp.decode NOCP fallback.
     */
    if (!s->v8m_secure) {
        unallocated_encoding(s);
        return true;
    }

    s->eci_handled = true;

    /* If no fpu, NOP. */
    if (!dc_isar_feature(aa32_vfp, s)) {
        clear_eci_state(s);
        return true;
    }

    fptr = load_reg(s, a->rn);
    if (a->l) {
        gen_helper_v7m_vlldm(cpu_env, fptr);
    } else {
        gen_helper_v7m_vlstm(cpu_env, fptr);
    }
    tcg_temp_free_i32(fptr);

    clear_eci_state(s);

    /*
     * End the TB, because we have updated FP control bits,
     * and possibly VPR or LTPSIZE.
     */
    s->base.is_jmp = DISAS_UPDATE_EXIT;
    return true;
}

static bool trans_VSCCLRM(DisasContext *s, arg_VSCCLRM *a)
{
    int btmreg, topreg;
    TCGv_i64 zero;
    TCGv_i32 aspen, sfpa;

    if (!dc_isar_feature(aa32_m_sec_state, s)) {
        /* Before v8.1M, fall through in decode to NOCP check */
        return false;
    }

    /* Explicitly UNDEF because this takes precedence over NOCP */
    if (!arm_dc_feature(s, ARM_FEATURE_M_MAIN) || !s->v8m_secure) {
        unallocated_encoding(s);
        return true;
    }

    s->eci_handled = true;

    if (!dc_isar_feature(aa32_vfp_simd, s)) {
        /* NOP if we have neither FP nor MVE */
        clear_eci_state(s);
        return true;
    }

    /*
     * If FPCCR.ASPEN != 0 && CONTROL_S.SFPA == 0 then there is no
     * active floating point context so we must NOP (without doing
     * any lazy state preservation or the NOCP check).
     */
    aspen = load_cpu_field(v7m.fpccr[M_REG_S]);
    sfpa = load_cpu_field(v7m.control[M_REG_S]);
    tcg_gen_andi_i32(aspen, aspen, R_V7M_FPCCR_ASPEN_MASK);
    tcg_gen_xori_i32(aspen, aspen, R_V7M_FPCCR_ASPEN_MASK);
    tcg_gen_andi_i32(sfpa, sfpa, R_V7M_CONTROL_SFPA_MASK);
    tcg_gen_or_i32(sfpa, sfpa, aspen);
    arm_gen_condlabel(s);
    tcg_gen_brcondi_i32(TCG_COND_EQ, sfpa, 0, s->condlabel);

    if (s->fp_excp_el != 0) {
        gen_exception_insn(s, s->pc_curr, EXCP_NOCP,
                           syn_uncategorized(), s->fp_excp_el);
        return true;
    }

    topreg = a->vd + a->imm - 1;
    btmreg = a->vd;

    /* Convert to Sreg numbers if the insn specified in Dregs */
    if (a->size == 3) {
        topreg = topreg * 2 + 1;
        btmreg *= 2;
    }

    if (topreg > 63 || (topreg > 31 && !(topreg & 1))) {
        /* UNPREDICTABLE: we choose to undef */
        unallocated_encoding(s);
        return true;
    }

    /* Silently ignore requests to clear D16-D31 if they don't exist */
    if (topreg > 31 && !dc_isar_feature(aa32_simd_r32, s)) {
        topreg = 31;
    }

    if (!vfp_access_check(s)) {
        return true;
    }

    /* Zero the Sregs from btmreg to topreg inclusive. */
    zero = tcg_const_i64(0);
    if (btmreg & 1) {
        write_neon_element64(zero, btmreg >> 1, 1, MO_32);
        btmreg++;
    }
    for (; btmreg + 1 <= topreg; btmreg += 2) {
        write_neon_element64(zero, btmreg >> 1, 0, MO_64);
    }
    if (btmreg == topreg) {
        write_neon_element64(zero, btmreg >> 1, 0, MO_32);
        btmreg++;
    }
    assert(btmreg == topreg + 1);
    if (dc_isar_feature(aa32_mve, s)) {
        TCGv_i32 z32 = tcg_const_i32(0);
        store_cpu_field(z32, v7m.vpr);
    }

    clear_eci_state(s);
    return true;
}

/*
 * M-profile provides two different sets of instructions that can
 * access floating point system registers: VMSR/VMRS (which move
 * to/from a general purpose register) and VLDR/VSTR sysreg (which
 * move directly to/from memory). In some cases there are also side
 * effects which must happen after any write to memory (which could
 * cause an exception). So we implement the common logic for the
 * sysreg access in gen_M_fp_sysreg_write() and gen_M_fp_sysreg_read(),
 * which take pointers to callback functions which will perform the
 * actual "read/write general purpose register" and "read/write
 * memory" operations.
 */

/*
 * Emit code to store the sysreg to its final destination; frees the
 * TCG temp 'value' it is passed. do_access is true to do the store,
 * and false to skip it and only perform side-effects like base
 * register writeback.
 */
typedef void fp_sysreg_storefn(DisasContext *s, void *opaque, TCGv_i32 value,
                               bool do_access);
/*
 * Emit code to load the value to be copied to the sysreg; returns
 * a new TCG temporary. do_access is true to do the store,
 * and false to skip it and only perform side-effects like base
 * register writeback.
 */
typedef TCGv_i32 fp_sysreg_loadfn(DisasContext *s, void *opaque,
                                  bool do_access);

/* Common decode/access checks for fp sysreg read/write */
typedef enum FPSysRegCheckResult {
    FPSysRegCheckFailed, /* caller should return false */
    FPSysRegCheckDone, /* caller should return true */
    FPSysRegCheckContinue, /* caller should continue generating code */
} FPSysRegCheckResult;

static FPSysRegCheckResult fp_sysreg_checks(DisasContext *s, int regno)
{
    if (!dc_isar_feature(aa32_fpsp_v2, s) && !dc_isar_feature(aa32_mve, s)) {
        return FPSysRegCheckFailed;
    }

    switch (regno) {
    case ARM_VFP_FPSCR:
    case QEMU_VFP_FPSCR_NZCV:
        break;
    case ARM_VFP_FPSCR_NZCVQC:
        if (!arm_dc_feature(s, ARM_FEATURE_V8_1M)) {
            return FPSysRegCheckFailed;
        }
        break;
    case ARM_VFP_FPCXT_S:
    case ARM_VFP_FPCXT_NS:
        if (!arm_dc_feature(s, ARM_FEATURE_V8_1M)) {
            return FPSysRegCheckFailed;
        }
        if (!s->v8m_secure) {
            return FPSysRegCheckFailed;
        }
        break;
    case ARM_VFP_VPR:
    case ARM_VFP_P0:
        if (!dc_isar_feature(aa32_mve, s)) {
            return FPSysRegCheckFailed;
        }
        break;
    default:
        return FPSysRegCheckFailed;
    }

    /*
     * FPCXT_NS is a special case: it has specific handling for
     * "current FP state is inactive", and must do the PreserveFPState()
     * but not the usual full set of actions done by ExecuteFPCheck().
     * So we don't call vfp_access_check() and the callers must handle this.
     */
    if (regno != ARM_VFP_FPCXT_NS && !vfp_access_check(s)) {
        return FPSysRegCheckDone;
    }
    return FPSysRegCheckContinue;
}

static void gen_branch_fpInactive(DisasContext *s, TCGCond cond,
                                  TCGLabel *label)
{
    /*
     * FPCXT_NS is a special case: it has specific handling for
     * "current FP state is inactive", and must do the PreserveFPState()
     * but not the usual full set of actions done by ExecuteFPCheck().
     * We don't have a TB flag that matches the fpInactive check, so we
     * do it at runtime as we don't expect FPCXT_NS accesses to be frequent.
     *
     * Emit code that checks fpInactive and does a conditional
     * branch to label based on it:
     *  if cond is TCG_COND_NE then branch if fpInactive != 0 (ie if inactive)
     *  if cond is TCG_COND_EQ then branch if fpInactive == 0 (ie if active)
     */
    assert(cond == TCG_COND_EQ || cond == TCG_COND_NE);

    /* fpInactive = FPCCR_NS.ASPEN == 1 && CONTROL.FPCA == 0 */
    TCGv_i32 aspen, fpca;
    aspen = load_cpu_field(v7m.fpccr[M_REG_NS]);
    fpca = load_cpu_field(v7m.control[M_REG_S]);
    tcg_gen_andi_i32(aspen, aspen, R_V7M_FPCCR_ASPEN_MASK);
    tcg_gen_xori_i32(aspen, aspen, R_V7M_FPCCR_ASPEN_MASK);
    tcg_gen_andi_i32(fpca, fpca, R_V7M_CONTROL_FPCA_MASK);
    tcg_gen_or_i32(fpca, fpca, aspen);
    tcg_gen_brcondi_i32(tcg_invert_cond(cond), fpca, 0, label);
    tcg_temp_free_i32(aspen);
    tcg_temp_free_i32(fpca);
}

static bool gen_M_fp_sysreg_write(DisasContext *s, int regno,
                                  fp_sysreg_loadfn *loadfn,
                                  void *opaque)
{
    /* Do a write to an M-profile floating point system register */
    TCGv_i32 tmp;
    TCGLabel *lab_end = NULL;

    switch (fp_sysreg_checks(s, regno)) {
    case FPSysRegCheckFailed:
        return false;
    case FPSysRegCheckDone:
        return true;
    case FPSysRegCheckContinue:
        break;
    }

    switch (regno) {
    case ARM_VFP_FPSCR:
        tmp = loadfn(s, opaque, true);
        gen_helper_vfp_set_fpscr(cpu_env, tmp);
        tcg_temp_free_i32(tmp);
        gen_lookup_tb(s);
        break;
    case ARM_VFP_FPSCR_NZCVQC:
    {
        TCGv_i32 fpscr;
        tmp = loadfn(s, opaque, true);
        if (dc_isar_feature(aa32_mve, s)) {
            /* QC is only present for MVE; otherwise RES0 */
            TCGv_i32 qc = tcg_temp_new_i32();
            tcg_gen_andi_i32(qc, tmp, FPCR_QC);
            /*
             * The 4 vfp.qc[] fields need only be "zero" vs "non-zero";
             * here writing the same value into all elements is simplest.
             */
            tcg_gen_gvec_dup_i32(MO_32, offsetof(CPUARMState, vfp.qc),
                                 16, 16, qc);
        }
        tcg_gen_andi_i32(tmp, tmp, FPCR_NZCV_MASK);
        fpscr = load_cpu_field(vfp.xregs[ARM_VFP_FPSCR]);
        tcg_gen_andi_i32(fpscr, fpscr, ~FPCR_NZCV_MASK);
        tcg_gen_or_i32(fpscr, fpscr, tmp);
        store_cpu_field(fpscr, vfp.xregs[ARM_VFP_FPSCR]);
        tcg_temp_free_i32(tmp);
        break;
    }
    case ARM_VFP_FPCXT_NS:
    {
        TCGLabel *lab_active = gen_new_label();

        lab_end = gen_new_label();
        gen_branch_fpInactive(s, TCG_COND_EQ, lab_active);
        /*
         * fpInactive case: write is a NOP, so only do side effects
         * like register writeback before we branch to end
         */
        loadfn(s, opaque, false);
        tcg_gen_br(lab_end);

        gen_set_label(lab_active);
        /*
         * !fpInactive: if FPU disabled, take NOCP exception;
         * otherwise PreserveFPState(), and then FPCXT_NS writes
         * behave the same as FPCXT_S writes.
         */
        if (!vfp_access_check_m(s, true)) {
            /*
             * This was only a conditional exception, so override
             * gen_exception_insn()'s default to DISAS_NORETURN
             */
            s->base.is_jmp = DISAS_NEXT;
            break;
        }
    }
    /* fall through */
    case ARM_VFP_FPCXT_S:
    {
        TCGv_i32 sfpa, control;
        /*
         * Set FPSCR and CONTROL.SFPA from value; the new FPSCR takes
         * bits [27:0] from value and zeroes bits [31:28].
         */
        tmp = loadfn(s, opaque, true);
        sfpa = tcg_temp_new_i32();
        tcg_gen_shri_i32(sfpa, tmp, 31);
        control = load_cpu_field(v7m.control[M_REG_S]);
        tcg_gen_deposit_i32(control, control, sfpa,
                            R_V7M_CONTROL_SFPA_SHIFT, 1);
        store_cpu_field(control, v7m.control[M_REG_S]);
        tcg_gen_andi_i32(tmp, tmp, ~FPCR_NZCV_MASK);
        gen_helper_vfp_set_fpscr(cpu_env, tmp);
        s->base.is_jmp = DISAS_UPDATE_NOCHAIN;
        tcg_temp_free_i32(tmp);
        tcg_temp_free_i32(sfpa);
        break;
    }
    case ARM_VFP_VPR:
        /* Behaves as NOP if not privileged */
        if (IS_USER(s)) {
            loadfn(s, opaque, false);
            break;
        }
        tmp = loadfn(s, opaque, true);
        store_cpu_field(tmp, v7m.vpr);
        s->base.is_jmp = DISAS_UPDATE_NOCHAIN;
        break;
    case ARM_VFP_P0:
    {
        TCGv_i32 vpr;
        tmp = loadfn(s, opaque, true);
        vpr = load_cpu_field(v7m.vpr);
        tcg_gen_deposit_i32(vpr, vpr, tmp,
                            R_V7M_VPR_P0_SHIFT, R_V7M_VPR_P0_LENGTH);
        store_cpu_field(vpr, v7m.vpr);
        s->base.is_jmp = DISAS_UPDATE_NOCHAIN;
        tcg_temp_free_i32(tmp);
        break;
    }
    default:
        g_assert_not_reached();
    }
    if (lab_end) {
        gen_set_label(lab_end);
    }
    return true;
}

static bool gen_M_fp_sysreg_read(DisasContext *s, int regno,
                                 fp_sysreg_storefn *storefn,
                                 void *opaque)
{
    /* Do a read from an M-profile floating point system register */
    TCGv_i32 tmp;
    TCGLabel *lab_end = NULL;
    bool lookup_tb = false;

    switch (fp_sysreg_checks(s, regno)) {
    case FPSysRegCheckFailed:
        return false;
    case FPSysRegCheckDone:
        return true;
    case FPSysRegCheckContinue:
        break;
    }

    if (regno == ARM_VFP_FPSCR_NZCVQC && !dc_isar_feature(aa32_mve, s)) {
        /* QC is RES0 without MVE, so NZCVQC simplifies to NZCV */
        regno = QEMU_VFP_FPSCR_NZCV;
    }

    switch (regno) {
    case ARM_VFP_FPSCR:
        tmp = tcg_temp_new_i32();
        gen_helper_vfp_get_fpscr(tmp, cpu_env);
        storefn(s, opaque, tmp, true);
        break;
    case ARM_VFP_FPSCR_NZCVQC:
        tmp = tcg_temp_new_i32();
        gen_helper_vfp_get_fpscr(tmp, cpu_env);
        tcg_gen_andi_i32(tmp, tmp, FPCR_NZCVQC_MASK);
        storefn(s, opaque, tmp, true);
        break;
    case QEMU_VFP_FPSCR_NZCV:
        /*
         * Read just NZCV; this is a special case to avoid the
         * helper call for the "VMRS to CPSR.NZCV" insn.
         */
        tmp = load_cpu_field(vfp.xregs[ARM_VFP_FPSCR]);
        tcg_gen_andi_i32(tmp, tmp, FPCR_NZCV_MASK);
        storefn(s, opaque, tmp, true);
        break;
    case ARM_VFP_FPCXT_S:
    {
        TCGv_i32 control, sfpa, fpscr;
        /* Bits [27:0] from FPSCR, bit [31] from CONTROL.SFPA */
        tmp = tcg_temp_new_i32();
        sfpa = tcg_temp_new_i32();
        gen_helper_vfp_get_fpscr(tmp, cpu_env);
        tcg_gen_andi_i32(tmp, tmp, ~FPCR_NZCV_MASK);
        control = load_cpu_field(v7m.control[M_REG_S]);
        tcg_gen_andi_i32(sfpa, control, R_V7M_CONTROL_SFPA_MASK);
        tcg_gen_shli_i32(sfpa, sfpa, 31 - R_V7M_CONTROL_SFPA_SHIFT);
        tcg_gen_or_i32(tmp, tmp, sfpa);
        tcg_temp_free_i32(sfpa);
        /*
         * Store result before updating FPSCR etc, in case
         * it is a memory write which causes an exception.
         */
        storefn(s, opaque, tmp, true);
        /*
         * Now we must reset FPSCR from FPDSCR_NS, and clear
         * CONTROL.SFPA; so we'll end the TB here.
         */
        tcg_gen_andi_i32(control, control, ~R_V7M_CONTROL_SFPA_MASK);
        store_cpu_field(control, v7m.control[M_REG_S]);
        fpscr = load_cpu_field(v7m.fpdscr[M_REG_NS]);
        gen_helper_vfp_set_fpscr(cpu_env, fpscr);
        tcg_temp_free_i32(fpscr);
        lookup_tb = true;
        break;
    }
    case ARM_VFP_FPCXT_NS:
    {
        TCGv_i32 control, sfpa, fpscr, fpdscr, zero;
        TCGLabel *lab_active = gen_new_label();

        lookup_tb = true;

        gen_branch_fpInactive(s, TCG_COND_EQ, lab_active);
        /* fpInactive case: reads as FPDSCR_NS */
        TCGv_i32 tmp = load_cpu_field(v7m.fpdscr[M_REG_NS]);
        storefn(s, opaque, tmp, true);
        lab_end = gen_new_label();
        tcg_gen_br(lab_end);

        gen_set_label(lab_active);
        /*
         * !fpInactive: if FPU disabled, take NOCP exception;
         * otherwise PreserveFPState(), and then FPCXT_NS
         * reads the same as FPCXT_S.
         */
        if (!vfp_access_check_m(s, true)) {
            /*
             * This was only a conditional exception, so override
             * gen_exception_insn()'s default to DISAS_NORETURN
             */
            s->base.is_jmp = DISAS_NEXT;
            break;
        }
        tmp = tcg_temp_new_i32();
        sfpa = tcg_temp_new_i32();
        fpscr = tcg_temp_new_i32();
        gen_helper_vfp_get_fpscr(fpscr, cpu_env);
        tcg_gen_andi_i32(tmp, fpscr, ~FPCR_NZCV_MASK);
        control = load_cpu_field(v7m.control[M_REG_S]);
        tcg_gen_andi_i32(sfpa, control, R_V7M_CONTROL_SFPA_MASK);
        tcg_gen_shli_i32(sfpa, sfpa, 31 - R_V7M_CONTROL_SFPA_SHIFT);
        tcg_gen_or_i32(tmp, tmp, sfpa);
        tcg_temp_free_i32(control);
        /* Store result before updating FPSCR, in case it faults */
        storefn(s, opaque, tmp, true);
        /* If SFPA is zero then set FPSCR from FPDSCR_NS */
        fpdscr = load_cpu_field(v7m.fpdscr[M_REG_NS]);
        zero = tcg_const_i32(0);
        tcg_gen_movcond_i32(TCG_COND_EQ, fpscr, sfpa, zero, fpdscr, fpscr);
        gen_helper_vfp_set_fpscr(cpu_env, fpscr);
        tcg_temp_free_i32(zero);
        tcg_temp_free_i32(sfpa);
        tcg_temp_free_i32(fpdscr);
        tcg_temp_free_i32(fpscr);
        break;
    }
    case ARM_VFP_VPR:
        /* Behaves as NOP if not privileged */
        if (IS_USER(s)) {
            storefn(s, opaque, NULL, false);
            break;
        }
        tmp = load_cpu_field(v7m.vpr);
        storefn(s, opaque, tmp, true);
        break;
    case ARM_VFP_P0:
        tmp = load_cpu_field(v7m.vpr);
        tcg_gen_extract_i32(tmp, tmp, R_V7M_VPR_P0_SHIFT, R_V7M_VPR_P0_LENGTH);
        storefn(s, opaque, tmp, true);
        break;
    default:
        g_assert_not_reached();
    }

    if (lab_end) {
        gen_set_label(lab_end);
    }
    if (lookup_tb) {
        gen_lookup_tb(s);
    }
    return true;
}

static void fp_sysreg_to_gpr(DisasContext *s, void *opaque, TCGv_i32 value,
                             bool do_access)
{
    arg_VMSR_VMRS *a = opaque;

    if (!do_access) {
        return;
    }

    if (a->rt == 15) {
        /* Set the 4 flag bits in the CPSR */
        gen_set_nzcv(value);
        tcg_temp_free_i32(value);
    } else {
        store_reg(s, a->rt, value);
    }
}

static TCGv_i32 gpr_to_fp_sysreg(DisasContext *s, void *opaque, bool do_access)
{
    arg_VMSR_VMRS *a = opaque;

    if (!do_access) {
        return NULL;
    }
    return load_reg(s, a->rt);
}

static bool trans_VMSR_VMRS(DisasContext *s, arg_VMSR_VMRS *a)
{
    /*
     * Accesses to R15 are UNPREDICTABLE; we choose to undef.
     * FPSCR -> r15 is a special case which writes to the PSR flags;
     * set a->reg to a special value to tell gen_M_fp_sysreg_read()
     * we only care about the top 4 bits of FPSCR there.
     */
    if (a->rt == 15) {
        if (a->l && a->reg == ARM_VFP_FPSCR) {
            a->reg = QEMU_VFP_FPSCR_NZCV;
        } else {
            return false;
        }
    }

    if (a->l) {
        /* VMRS, move FP system register to gp register */
        return gen_M_fp_sysreg_read(s, a->reg, fp_sysreg_to_gpr, a);
    } else {
        /* VMSR, move gp register to FP system register */
        return gen_M_fp_sysreg_write(s, a->reg, gpr_to_fp_sysreg, a);
    }
}

static void fp_sysreg_to_memory(DisasContext *s, void *opaque, TCGv_i32 value,
                                bool do_access)
{
    arg_vldr_sysreg *a = opaque;
    uint32_t offset = a->imm;
    TCGv_i32 addr;

    if (!a->a) {
        offset = -offset;
    }

    if (!do_access && !a->w) {
        return;
    }

    addr = load_reg(s, a->rn);
    if (a->p) {
        tcg_gen_addi_i32(addr, addr, offset);
    }

    if (s->v8m_stackcheck && a->rn == 13 && a->w) {
        gen_helper_v8m_stackcheck(cpu_env, addr);
    }

    if (do_access) {
        gen_aa32_st_i32(s, value, addr, get_mem_index(s),
                        MO_UL | MO_ALIGN | s->be_data);
        tcg_temp_free_i32(value);
    }

    if (a->w) {
        /* writeback */
        if (!a->p) {
            tcg_gen_addi_i32(addr, addr, offset);
        }
        store_reg(s, a->rn, addr);
    } else {
        tcg_temp_free_i32(addr);
    }
}

static TCGv_i32 memory_to_fp_sysreg(DisasContext *s, void *opaque,
                                    bool do_access)
{
    arg_vldr_sysreg *a = opaque;
    uint32_t offset = a->imm;
    TCGv_i32 addr;
    TCGv_i32 value = NULL;

    if (!a->a) {
        offset = -offset;
    }

    if (!do_access && !a->w) {
        return NULL;
    }

    addr = load_reg(s, a->rn);
    if (a->p) {
        tcg_gen_addi_i32(addr, addr, offset);
    }

    if (s->v8m_stackcheck && a->rn == 13 && a->w) {
        gen_helper_v8m_stackcheck(cpu_env, addr);
    }

    if (do_access) {
        value = tcg_temp_new_i32();
        gen_aa32_ld_i32(s, value, addr, get_mem_index(s),
                        MO_UL | MO_ALIGN | s->be_data);
    }

    if (a->w) {
        /* writeback */
        if (!a->p) {
            tcg_gen_addi_i32(addr, addr, offset);
        }
        store_reg(s, a->rn, addr);
    } else {
        tcg_temp_free_i32(addr);
    }
    return value;
}

static bool trans_VLDR_sysreg(DisasContext *s, arg_vldr_sysreg *a)
{
    if (!arm_dc_feature(s, ARM_FEATURE_V8_1M)) {
        return false;
    }
    if (a->rn == 15) {
        return false;
    }
    return gen_M_fp_sysreg_write(s, a->reg, memory_to_fp_sysreg, a);
}

static bool trans_VSTR_sysreg(DisasContext *s, arg_vldr_sysreg *a)
{
    if (!arm_dc_feature(s, ARM_FEATURE_V8_1M)) {
        return false;
    }
    if (a->rn == 15) {
        return false;
    }
    return gen_M_fp_sysreg_read(s, a->reg, fp_sysreg_to_memory, a);
}

static bool trans_NOCP(DisasContext *s, arg_nocp *a)
{
    /*
     * Handle M-profile early check for disabled coprocessor:
     * all we need to do here is emit the NOCP exception if
     * the coprocessor is disabled. Otherwise we return false
     * and the real VFP/etc decode will handle the insn.
     */
    assert(arm_dc_feature(s, ARM_FEATURE_M));

    if (a->cp == 11) {
        a->cp = 10;
    }
    if (arm_dc_feature(s, ARM_FEATURE_V8_1M) &&
        (a->cp == 8 || a->cp == 9 || a->cp == 14 || a->cp == 15)) {
        /* in v8.1M cp 8, 9, 14, 15 also are governed by the cp10 enable */
        a->cp = 10;
    }

    if (a->cp != 10) {
        gen_exception_insn(s, s->pc_curr, EXCP_NOCP,
                           syn_uncategorized(), default_exception_el(s));
        return true;
    }

    if (s->fp_excp_el != 0) {
        gen_exception_insn(s, s->pc_curr, EXCP_NOCP,
                           syn_uncategorized(), s->fp_excp_el);
        return true;
    }

    return false;
}

static bool trans_NOCP_8_1(DisasContext *s, arg_nocp *a)
{
    /* This range needs a coprocessor check for v8.1M and later only */
    if (!arm_dc_feature(s, ARM_FEATURE_V8_1M)) {
        return false;
    }
    return trans_NOCP(s, a);
}