blob: 8defa57e02a04f3784bb0078cc2050df0b3377fe [file]
/****************************************************************************
* apps/examples/fdpicxip/modules/callback/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 <dirent.h>
#include <fcntl.h>
#include <mqueue.h>
#include <pthread.h>
#include <sched.h>
#include <signal.h>
#include <spawn.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <time.h>
#include <unistd.h>
/****************************************************************************
* 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;
}