diff --git a/include/nuttx/binfmt/fdpic.h b/include/nuttx/binfmt/fdpic.h index 012f817b66f..79b4cdf265f 100644 --- a/include/nuttx/binfmt/fdpic.h +++ b/include/nuttx/binfmt/fdpic.h @@ -212,8 +212,85 @@ static inline FAR void *fdpic_callback(FAR void *fn) return fn; } + +/**************************************************************************** + * Name: fdpic_base + * + * Description: + * The FDPIC data base of the calling context, taken from the FDPIC + * register. Non-zero means the caller is an FDPIC module; zero means base + * firmware. This is the same test fdpic_callback() makes, exposed for a + * caller that must decide whether a pointer it was handed is a descriptor + * before it stores it somewhere the register will no longer be correct -- + * a callback that will run on a work-queue thread, for instance. + * + ****************************************************************************/ + +static inline uintptr_t fdpic_base(void) +{ + uintptr_t base; + + __asm__ __volatile__ ("mov %0, r9" : "=r"(base)); + + return base; +} + +/**************************************************************************** + * Name: fdpic_invoke + * + * Description: + * Call a resolved module entry point with the module's data base installed + * in the FDPIC register, and restore the caller's base afterwards. + * + * This is for the one case the register cannot already be correct: a + * callback that a module registered but that runs on a shared thread -- + * the signal-notification work queue -- which carries no module's base. + * The base is captured at registration (fdpic_base(), in the module's + * context) and installed here around the call. Everywhere else the + * callback runs in a task that inherited the module's data space and only + * the entry point has to be resolved; there fdpic_callback() is enough. + * + * A context switch or interrupt during the call is safe: the FDPIC + * register is REG_PIC in the saved register context, so it is preserved + * and restored across a switch, and base firmware is built with it + * reserved so no interrupt handler disturbs it. + * + * Input Parameters: + * entry - The code address to enter (already resolved from the + * descriptor). + * arg - The single word argument, passed in r0. + * got - The module data base to install in the FDPIC register. + * + ****************************************************************************/ + +static inline void fdpic_invoke(uintptr_t entry, uintptr_t arg, + uintptr_t got) +{ + register uintptr_t r0v __asm__ ("r0") = arg; + + /* arg is pinned in r0 (the first argument and the call's scratch), so the + * asm needs registers only for entry and got -- kept deliberately few so + * the allocator has room on builds that reserve a frame pointer. r9 is + * saved on the stack rather than in a scratch register; r4 rides along + * only to keep the push 8-byte aligned and is restored untouched. + */ + + __asm__ __volatile__ + ( + "push {r4, r9}\n" /* Save the caller's FDPIC register */ + "mov r9, %[got]\n" /* Install the module's data base */ + "blx %[entry]\n" /* Enter the module */ + "pop {r4, r9}\n" /* Restore the caller's FDPIC register */ + : "+r" (r0v) + : [entry] "r" (entry), [got] "r" (got) + : "r1", "r2", "r3", "r12", "lr", "cc", "memory" + ); +} #else # define fdpic_callback(fn) (fn) +# define fdpic_base() (0) +# define fdpic_invoke(entry, arg, got) \ + ((void)(got), (((CODE void (*)(uintptr_t))(uintptr_t)(entry))(arg))) #endif #endif /* __INCLUDE_NUTTX_BINFMT_FDPIC_H */ diff --git a/include/nuttx/signal.h b/include/nuttx/signal.h index 79fa22b39d5..46b3a0e703f 100644 --- a/include/nuttx/signal.h +++ b/include/nuttx/signal.h @@ -68,6 +68,11 @@ struct sigwork_s struct work_s work; /* Work queue structure */ union sigval value; /* Data passed with notification */ sigev_notify_function_t func; /* Notification function */ +#ifdef CONFIG_FDPIC + uintptr_t got; /* FDPIC data base of a module callback, or + * zero. Captured at registration, installed + * around the call on the worker thread. */ +#endif }; #ifdef __cplusplus diff --git a/sched/mqueue/mq_notify.c b/sched/mqueue/mq_notify.c index ea1f35fc279..0b403aac856 100644 --- a/sched/mqueue/mq_notify.c +++ b/sched/mqueue/mq_notify.c @@ -34,6 +34,10 @@ #include #include +#if defined(CONFIG_FDPIC) && defined(CONFIG_SIG_EVTHREAD) +# include +#endif + #include "sched/sched.h" #include "mqueue/mqueue.h" @@ -156,6 +160,19 @@ int mq_notify(mqd_t mqdes, FAR const struct sigevent *notification) sizeof(struct sigevent)); msgq->ntpid = rtcb->pid; + +#if defined(CONFIG_FDPIC) && defined(CONFIG_SIG_EVTHREAD) + /* If a module registered a SIGEV_THREAD callback, capture its data + * base now, while this runs in the module's context. The callback + * fires later on a work-queue worker that has no base of its own; + * nxsig_notification() reads this to install it around the call. + */ + + msgq->ntwork.got = + (fdpic_base() != 0 && + (notification->sigev_notify & SIGEV_THREAD) != 0) ? + fdpic_base() : 0; +#endif } } diff --git a/sched/signal/sig_notification.c b/sched/signal/sig_notification.c index 86909faf62b..1f22bc51222 100644 --- a/sched/signal/sig_notification.c +++ b/sched/signal/sig_notification.c @@ -34,6 +34,10 @@ #include +#ifdef CONFIG_FDPIC +# include +#endif + #include "sched/sched.h" #include "signal/signal.h" @@ -70,7 +74,24 @@ static void nxsig_notification_worker(FAR void *arg) /* Perform the callback */ - work->func(work->value); +#ifdef CONFIG_FDPIC + /* A module's callback runs here on a shared worker thread, which does not + * carry the module's data base. Install the base captured at + * registration around the call so the callback can reach its own globals; + * work->func has already been resolved to the code address. A non-module + * callback has a zero base and is called directly. + */ + + if (work->got != 0) + { + fdpic_invoke((uintptr_t)work->func, (uintptr_t)work->value.sival_ptr, + work->got); + } + else +#endif + { + work->func(work->value); + } } #endif /* CONFIG_SIG_EVTHREAD */ @@ -157,6 +178,22 @@ int nxsig_notification(pid_t pid, FAR struct sigevent *event, work->value = event->sigev_value; work->func = event->sigev_notify_function; +#ifdef CONFIG_FDPIC + /* When the callback is a module's, work->got was set at registration + * to the module's data base (this runs at send or expiry time, whose + * context is not the module's, so it cannot be read here). The + * callback is a descriptor: resolve it to the code address now -- + * reading the descriptor is just a memory access and needs no base -- + * and the worker installs the base around the call. + */ + + if (work->got != 0) + { + work->func = (sigev_notify_function_t) + ((FAR struct fdpic_desc_s *)event->sigev_notify_function)->entry; + } +#endif + /* Then queue the work */ return work_queue(SIG_EVTHREAD_WORK, &work->work, diff --git a/sched/timer/timer_create.c b/sched/timer/timer_create.c index 1811f3e68af..69799381f71 100644 --- a/sched/timer/timer_create.c +++ b/sched/timer/timer_create.c @@ -37,6 +37,10 @@ #include #include +#if defined(CONFIG_FDPIC) && defined(CONFIG_SIG_EVTHREAD) +# include +#endif + #include "sched/sched.h" #include "timer/timer.h" @@ -196,6 +200,19 @@ int timer_create(clockid_t clockid, FAR struct sigevent *evp, /* Yes, copy the entire struct sigevent content */ memcpy(&ret->pt_event, evp, sizeof(struct sigevent)); + +#if defined(CONFIG_FDPIC) && defined(CONFIG_SIG_EVTHREAD) + /* If a module registered a SIGEV_THREAD callback, capture its + * data base now, while this runs in the module's context. The + * callback fires later on a work-queue worker with no base of + * its own; nxsig_notification() installs this around the call. + */ + + ret->pt_work.got = + (fdpic_base() != 0 && + (evp->sigev_notify & SIGEV_THREAD) != 0) ? + fdpic_base() : 0; +#endif } else {