/* Test for SME longjmp. Copyright (C) 2023 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C 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. The GNU C 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 the GNU C Library; if not, see . */ #include #include #include #include #include #include #include #include #include #include struct blk { void *za_save_buffer; uint16_t num_za_save_slices; char __reserved[6]; }; static unsigned long svl; static uint8_t *za_orig; static uint8_t *za_dump; static uint8_t *za_save; static unsigned long get_svl (void) { register unsigned long x0 asm ("x0"); asm volatile ( ".inst 0x04bf5820 /* rdsvl x0, 1 */\n" : "=r" (x0)); return x0; } /* PSTATE.ZA = 1, set ZA state to active. */ static void start_za (void) { asm volatile ( ".inst 0xd503457f /* smstart za */"); } /* Read SVCR to get SM (bit0) and ZA (bit1) state. */ static unsigned long get_svcr (void) { register unsigned long x0 asm ("x0"); asm volatile ( ".inst 0xd53b4240 /* mrs x0, svcr */\n" : "=r" (x0)); return x0; } /* Load data into ZA byte by byte from p. */ static void __attribute__ ((noinline)) load_za (const void *p) { register unsigned long x15 asm ("x15") = 0; register unsigned long x16 asm ("x16") = (unsigned long)p; register unsigned long x17 asm ("x17") = svl; asm volatile ( ".inst 0xd503437f /* smstart sm */\n" ".L_ldr_loop:\n" ".inst 0xe1006200 /* ldr za[w15, 0], [x16] */\n" "add w15, w15, 1\n" ".inst 0x04305030 /* addvl x16, x16, 1 */\n" "cmp w15, w17\n" "bne .L_ldr_loop\n" ".inst 0xd503427f /* smstop sm */\n" : "+r"(x15), "+r"(x16), "+r"(x17)); } /* Set tpidr2 to BLK. */ static void set_tpidr2 (struct blk *blk) { register unsigned long x0 asm ("x0") = (unsigned long)blk; asm volatile ( ".inst 0xd51bd0a0 /* msr tpidr2_el0, x0 */\n" :: "r"(x0) : "memory"); } /* Returns tpidr2. */ static void * get_tpidr2 (void) { register unsigned long x0 asm ("x0"); asm volatile ( ".inst 0xd53bd0a0 /* mrs x0, tpidr2_el0 */\n" : "=r"(x0) :: "memory"); return (void *) x0; } static void print_data(const char *msg, void *p) { unsigned char *a = p; printf ("%s:\n", msg); for (int i = 0; i < svl; i++) { printf ("%d: ", i); for (int j = 0; j < svl; j++) printf("%02x,", a[i*svl+j]); printf("\n"); } printf(".\n"); fflush (stdout); } __attribute__ ((noinline)) static void do_longjmp (jmp_buf env) { longjmp (env, 1); } __attribute__ ((noinline)) static void do_setcontext (const ucontext_t *p) { setcontext (p); } static void longjmp_test (void) { unsigned long svcr; jmp_buf env; void *p; int r; struct blk blk = {za_save, svl, {0}}; printf ("longjmp test:\n"); p = get_tpidr2 (); printf ("initial tp2 = %p\n", p); if (p != NULL) FAIL_EXIT1 ("tpidr2 is not initialized to 0"); svcr = get_svcr (); if (svcr != 0) FAIL_EXIT1 ("svcr != 0: %lu", svcr); set_tpidr2 (&blk); start_za (); load_za (za_orig); print_data ("za save space", za_save); p = get_tpidr2 (); printf ("before setjmp: tp2 = %p\n", p); if (p != &blk) FAIL_EXIT1 ("tpidr2 is not set to BLK %p", (void *)&blk); if (setjmp (env) == 0) { p = get_tpidr2 (); printf ("before longjmp: tp2 = %p\n", p); if (p != &blk) FAIL_EXIT1 ("tpidr2 is clobbered"); do_longjmp (env); FAIL_EXIT1 ("longjmp returned"); } p = get_tpidr2 (); printf ("after longjmp: tp2 = %p\n", p); if (p != NULL) FAIL_EXIT1 ("tpidr2 is not set to 0"); svcr = get_svcr (); if (svcr != 0) FAIL_EXIT1 ("svcr != 0: %lu", svcr); print_data ("za save space", za_save); r = memcmp (za_orig, za_save, svl*svl); if (r != 0) FAIL_EXIT1 ("saving za failed"); } static void setcontext_test (void) { volatile int setcontext_done = 0; unsigned long svcr; ucontext_t ctx; void *p; int r; struct blk blk = {za_save, svl, {0}}; printf ("setcontext test:\n"); p = get_tpidr2 (); printf ("initial tp2 = %p\n", p); if (p != NULL) FAIL_EXIT1 ("tpidr2 is not initialized to 0"); svcr = get_svcr (); if (svcr != 0) FAIL_EXIT1 ("svcr != 0: %lu", svcr); set_tpidr2 (&blk); start_za (); load_za (za_orig); print_data ("za save space", za_save); p = get_tpidr2 (); printf ("before getcontext: tp2 = %p\n", p); if (p != &blk) FAIL_EXIT1 ("tpidr2 is not set to BLK %p", (void *)&blk); r = getcontext (&ctx); if (r != 0) FAIL_EXIT1 ("getcontext failed"); if (setcontext_done == 0) { p = get_tpidr2 (); printf ("before setcontext: tp2 = %p\n", p); if (p != &blk) FAIL_EXIT1 ("tpidr2 is clobbered"); setcontext_done = 1; do_setcontext (&ctx); FAIL_EXIT1 ("setcontext returned"); } p = get_tpidr2 (); printf ("after setcontext: tp2 = %p\n", p); if (p != NULL) FAIL_EXIT1 ("tpidr2 is not set to 0"); svcr = get_svcr (); if (svcr != 0) FAIL_EXIT1 ("svcr != 0: %lu", svcr); print_data ("za save space", za_save); r = memcmp (za_orig, za_save, svl*svl); if (r != 0) FAIL_EXIT1 ("saving za failed"); } static int do_test (void) { unsigned long hwcap2; hwcap2 = getauxval (AT_HWCAP2); if ((hwcap2 & HWCAP2_SME) == 0) return EXIT_UNSUPPORTED; svl = get_svl (); printf ("svl: %lu\n", svl); if (svl < 16 || svl % 16 != 0 || svl >= (1 << 16)) FAIL_EXIT1 ("invalid svl"); za_orig = xmalloc (svl*svl); za_save = xmalloc (svl*svl); za_dump = xmalloc (svl*svl); memset (za_orig, 1, svl*svl); memset (za_save, 2, svl*svl); memset (za_dump, 3, svl*svl); for (int i = 0; i < svl; i++) for (int j = 0; j < svl; j++) za_orig[i*svl+j] = i*svl+j; print_data ("original data", za_orig); longjmp_test (); memset (za_save, 2, svl*svl); memset (za_dump, 3, svl*svl); setcontext_test (); free (za_orig); free (za_save); free (za_dump); return 0; } #include