mirror of
https://github.com/apache/nuttx-apps.git
synced 2026-08-13 16:35:30 +00:00
518 lines
13 KiB
C
518 lines
13 KiB
C
|
|
/****************************************************************************
|
||
|
|
* 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 <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;
|
||
|
|
}
|