/**************************************************************************** * apps/examples/fdpicxip/modules/callback.c * * SPDX-License-Identifier: Apache-2.0 * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. The * ASF licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * ****************************************************************************/ /* Hands a module function to the firmware every way the firmware will * accept one. * * In FDPIC a function pointer is the address of a two-word descriptor, not * a code address, and that descriptor lives in the module's writable * segment in RAM. Firmware that stores such a pointer and later branches to * it therefore jumps into the module's *data* and faults -- unless the entry * point it came through resolved the descriptor first. * * qsort and bsearch have always done that. pthread_create, signal and * task_create did not, which made them look usable and then take the board * down; pthread_once, task_spawn, scandir's filter and sigaction followed * later. This module is what keeps all of them honest. * * scandir is one worth noting: its filter is called by scandir itself and * must be resolved there, while its comparison function is handed to qsort, * which resolves it. Resolving both would be a bug, so both are passed here * at once. * * mq_notify and timer_create with SIGEV_THREAD are the interesting ones: * their callback runs on a work-queue worker that carries no module data * base. The base is captured at registration and installed around the call, * so the callback still reaches the module's own globals. Each is checked * by having the callback write a distinctive value into a module global. * * Everything is checked by observing a side effect the callback itself * produces, because a callback that never runs is the failure being tested * for -- and on this target the alternative failure is a HardFault, which * reports nothing at all. */ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ #define FAIL_QSORT 0x01 #define FAIL_PTHREAD 0x02 #define FAIL_SIGNAL 0x04 #define FAIL_TASK 0x08 #define FAIL_ONCE 0x10 #define FAIL_SPAWN 0x20 #define FAIL_SCANDIR 0x40 #define FAIL_SIGACTION 0x80 /* The mq checks run under "callback mq" and report in their own byte, so the * exit status -- only eight bits survive waitpid -- never has to hold more * than eight flags at once. */ #define FAIL_MQ_THREAD 0x01 #define FAIL_MQ_SIGNAL 0x02 #define FAIL_TIMER_THREAD 0x04 /**************************************************************************** * Private Data ****************************************************************************/ /* Written by the callbacks, read by main. These live in the module's * writable segment, which is what the callback can only reach if it was * entered with this module's data base in the FDPIC register. */ static volatile int g_qsort_calls; static volatile int g_thread_ran; static volatile int g_signal_ran; static volatile int g_task_ran; static volatile int g_once_ran; static volatile int g_spawn_ran; static volatile int g_filter_calls; static volatile int g_compar_calls; static volatile int g_sigaction_ran; static volatile int g_mq_signal_ran; static volatile int g_mq_thread_ran; static volatile int g_timer_thread_ran; /**************************************************************************** * Private Functions ****************************************************************************/ static int cmp_int(const void *a, const void *b) { g_qsort_calls++; return *(const int *)a - *(const int *)b; } static void *thread_main(void *arg) { g_thread_ran = (int)(intptr_t)arg; return NULL; } static void sig_handler(int signo) { g_signal_ran = signo; } static void sigaction_handler(int signo, siginfo_t *info, void *context) { g_sigaction_ran = signo; } static void mq_signal_handler(int signo, siginfo_t *info, void *context) { g_mq_signal_ran = signo; } static void mq_thread_notify(union sigval value) { /* Runs on a work-queue worker, which carries no module data base of its * own. Writing this module global proves the base was installed around * the call -- without it this store would fault or land nowhere. */ g_mq_thread_ran = value.sival_int; } static void timer_thread_notify(union sigval value) { g_timer_thread_ran = value.sival_int; } static int task_main(int argc, char *argv[]) { g_task_ran = 1; return 0; } static void once_routine(void) { g_once_ran++; } static int spawn_main(int argc, char *argv[]) { g_spawn_ran = 1; return 0; } static int dir_filter(const struct dirent *entry) { g_filter_calls++; return 1; } static int dir_compar(const struct dirent **a, const struct dirent **b) { g_compar_calls++; return strcmp((*a)->d_name, (*b)->d_name); } /* The message-queue checks run as a second invocation, "callback mq", so * their two flags report in a byte of their own rather than overflowing the * eight-bit exit status the other checks already fill. */ static int run_mq(void) { struct mq_attr attr; struct sigaction sa; struct sigevent ev; struct itimerspec its; timer_t timer; mqd_t mqd; int fails = 0; int i; attr.mq_maxmsg = 1; attr.mq_msgsize = 1; attr.mq_flags = 0; mqd = mq_open("/cbmq", O_CREAT | O_RDWR, 0644, &attr); if (mqd == (mqd_t)-1) { return FAIL_MQ_THREAD | FAIL_MQ_SIGNAL | FAIL_TIMER_THREAD; } /* mq_notify with SIGEV_THREAD. The callback runs on a work-queue worker * that carries no module data base of its own; the base captured at * registration is installed around the call, so the callback reaches this * module's globals. A distinctive sival marks that the right callback * ran with the right argument. */ memset(&ev, 0, sizeof(ev)); ev.sigev_notify = SIGEV_THREAD; ev.sigev_notify_function = mq_thread_notify; ev.sigev_value.sival_int = 0x5a; if (mq_notify(mqd, &ev) < 0) { fails |= FAIL_MQ_THREAD; } else { char buf[1]; /* The notification fires on a send to an empty queue; drain the * message afterwards so the queue is empty for the next test and the * next send does not block on a full queue. */ mq_send(mqd, "x", 1, 0); for (i = 0; i < 50 && g_mq_thread_ran == 0; i++) { usleep(10000); } mq_receive(mqd, buf, sizeof(buf), NULL); if (g_mq_thread_ran != 0x5a) { fails |= FAIL_MQ_THREAD; } } /* mq_notify with SIGEV_SIGNAL, delivered to this task and caught by a * handler installed through sigaction. */ memset(&sa, 0, sizeof(sa)); sa.sa_sigaction = mq_signal_handler; sa.sa_flags = SA_SIGINFO; sigaction(SIGUSR1, &sa, NULL); memset(&ev, 0, sizeof(ev)); ev.sigev_notify = SIGEV_SIGNAL; ev.sigev_signo = SIGUSR1; if (mq_notify(mqd, &ev) < 0) { fails |= FAIL_MQ_SIGNAL; } else { char buf[1]; mq_send(mqd, "x", 1, 0); for (i = 0; i < 50 && g_mq_signal_ran == 0; i++) { usleep(10000); } mq_receive(mqd, buf, sizeof(buf), NULL); if (g_mq_signal_ran != SIGUSR1) { fails |= FAIL_MQ_SIGNAL; } } mq_close(mqd); mq_unlink("/cbmq"); /* timer_create with SIGEV_THREAD reaches the module the same way: the * expiry callback runs on the worker with the module's base installed. */ memset(&ev, 0, sizeof(ev)); ev.sigev_notify = SIGEV_THREAD; ev.sigev_notify_function = timer_thread_notify; ev.sigev_value.sival_int = 0xa5; if (timer_create(CLOCK_MONOTONIC, &ev, &timer) < 0) { fails |= FAIL_TIMER_THREAD; } else { its.it_value.tv_sec = 0; its.it_value.tv_nsec = 20 * 1000 * 1000; its.it_interval.tv_sec = 0; its.it_interval.tv_nsec = 0; timer_settime(timer, 0, &its, NULL); for (i = 0; i < 50 && g_timer_thread_ran == 0; i++) { usleep(10000); } if (g_timer_thread_ran != 0xa5) { fails |= FAIL_TIMER_THREAD; } timer_delete(timer); } syslog(LOG_INFO, "[callback] mq_thread=%#x mq_signal=%d timer_thread=%#x -- %s\n", g_mq_thread_ran, g_mq_signal_ran, g_timer_thread_ran, fails == 0 ? "PASS" : "FAIL"); return fails; } /**************************************************************************** * Public Functions ****************************************************************************/ int main(int argc, char *argv[]) { int v[5] = { 5, 3, 1, 4, 2 }; struct dirent **namelist; pthread_once_t once = PTHREAD_ONCE_INIT; struct sigaction sa; pthread_t tid; int fails = 0; int n; int i; if (argc > 1 && strcmp(argv[1], "mq") == 0) { return run_mq(); } /* qsort -- the path that always worked */ qsort(v, 5, sizeof(int), cmp_int); if (g_qsort_calls == 0 || v[0] != 1 || v[4] != 5) { fails |= FAIL_QSORT; } /* pthread_create. The thread inherits this module's D-Space, so it can * reach g_thread_ran only if it was entered at the right address. */ if (pthread_create(&tid, NULL, thread_main, (void *)(intptr_t)7) != 0) { fails |= FAIL_PTHREAD; } else { pthread_join(tid, NULL); if (g_thread_ran != 7) { fails |= FAIL_PTHREAD; } } /* signal, delivered to this task */ if (signal(SIGUSR1, sig_handler) == SIG_ERR) { fails |= FAIL_SIGNAL; } else { /* raise() rather than kill(getpid(), ...): getpid is not in the * firmware's export list, and raise targets the calling thread, which * is exactly what this is checking. */ raise(SIGUSR1); for (i = 0; i < 50 && g_signal_ran == 0; i++) { usleep(10000); } if (g_signal_ran != SIGUSR1) { fails |= FAIL_SIGNAL; } } /* task_create. A separate task, but the same D-Space. */ if (task_create("cbtask", SCHED_PRIORITY_DEFAULT, 2048, task_main, NULL) < 0) { fails |= FAIL_TASK; } else { for (i = 0; i < 50 && g_task_ran == 0; i++) { usleep(10000); } if (g_task_ran == 0) { fails |= FAIL_TASK; } } /* sigaction, delivered to this task. signal() above resolves the handler * before calling sigaction; this reaches sigaction directly, which is the * path signal() does not cover. */ memset(&sa, 0, sizeof(sa)); sa.sa_sigaction = sigaction_handler; sa.sa_flags = SA_SIGINFO; if (sigaction(SIGUSR2, &sa, NULL) < 0) { fails |= FAIL_SIGACTION; } else { raise(SIGUSR2); for (i = 0; i < 50 && g_sigaction_ran == 0; i++) { usleep(10000); } if (g_sigaction_ran != SIGUSR2) { fails |= FAIL_SIGACTION; } } /* pthread_once. Runs on this thread, so only the code address matters. */ if (pthread_once(&once, once_routine) != 0 || g_once_ran != 1) { fails |= FAIL_ONCE; } /* task_spawn. Like task_create, a separate task with the same D-Space. */ if (task_spawn("cbspawn", spawn_main, NULL, NULL, NULL, NULL) < 0) { fails |= FAIL_SPAWN; } else { for (i = 0; i < 50 && g_spawn_ran == 0; i++) { usleep(10000); } if (g_spawn_ran == 0) { fails |= FAIL_SPAWN; } } /* scandir takes two callbacks that reach the module by different routes: * the filter is called by scandir itself, while the comparison function is * handed to qsort. Passing both at once is the point -- it is what would * catch the comparison function being resolved twice. */ namelist = NULL; n = scandir("/mnt/xipfs", &namelist, dir_filter, dir_compar); if (n < 0 || g_filter_calls == 0) { fails |= FAIL_SCANDIR; } if (namelist != NULL) { while (n-- > 0) { free(namelist[n]); } free(namelist); } syslog(LOG_INFO, "[callback] qsort=%d pthread=%d signal=%d task=%d sigaction=%d " "once=%d spawn=%d filter=%d compar=%d -- %s\n", g_qsort_calls, g_thread_ran, g_signal_ran, g_task_ran, g_sigaction_ran, g_once_ran, g_spawn_ran, g_filter_calls, g_compar_calls, fails == 0 ? "PASS" : "FAIL"); return fails; }