nuttx/sched/mqueue/mq_notify.c
Marco Casaroli 43694933ce libc, sched: Resolve FDPIC descriptors at module callback entry points.
The base firmware and an FDPIC module disagree about what a function
pointer is.  Firmware is not built FDPIC, so to it a pointer is a code
address and it branches there.  A module passes the address of a two word
descriptor instead, because its code and data are placed independently and
a bare code address would leave the callee unable to find its own data.  A
firmware routine that takes a callback therefore branches into the
module's data segment and faults.

So the ten entry points that can be handed a callback by a module resolve
the descriptor before storing or branching to it: qsort, bsearch,
pthread_create, signal, sigaction, task_create and task_create_with_stack,
task_spawn, pthread_once, scandir, and mq_notify and timer_create with
SIGEV_THREAD.

Which one resolves matters as much as that one does.  Resolving twice would
take an already resolved code address for a descriptor and read two words
from the instruction stream, so each pointer is resolved exactly once, at
the outermost point that sees it.  signal() passes its argument through
untouched because sigaction() and then nxsig_action() will resolve it,
which covers a module calling sigaction() directly as well.  qsort() is
split so that the public entry resolves and the recursive implementation
does not.  scandir() resolves its filter but not its comparison function,
which it hands to qsort().

Whether a caller is a module at all is asked of the PIC base register,
which up_initial_state() sets only for a task that has a D-Space.  A plain
kernel task therefore reads zero and is left alone.

SIGEV_THREAD is the case the register cannot answer, because the callback
runs later on a work queue worker that carries no module's base at all.
The base is captured instead when the notification is registered, in the
module's own context, and installed around the call.

All of it is behind CONFIG_FDPIC, which defaults off.  Built for
mps3-an547:picostest both ways; with it off the entry points compile to
what they were.

Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
2026-09-25 10:46:48 -03:00

226 lines
6.9 KiB
C

/****************************************************************************
* sched/mqueue/mq_notify.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.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <mqueue.h>
#include <sched.h>
#include <string.h>
#include <errno.h>
#include <nuttx/irq.h>
#include <nuttx/sched.h>
#if defined(CONFIG_FDPIC) && defined(CONFIG_SIG_EVTHREAD)
# include <nuttx/fdpic.h>
#endif
#include "sched/sched.h"
#include "mqueue/mqueue.h"
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: mq_notify
*
* Description:
* If "notification" is not NULL, this function connects the task with
* the message queue such that the specified signal will be sent to the
* task whenever the message changes from empty to non-empty. Only one
* notification can be attached to a message queue.
*
* If "notification" is NULL, the attached notification is detached (if
* it was held by the calling task) and the queue is available to attach
* another notification.
*
* When the notification is sent to the registered process, its
* registration will be removed. The message queue will then be
* available for registration.
*
* Input Parameters:
* mqdes - Message queue descriptor
* notification - Real-time signal structure containing:
* sigev_notify - Should be SIGEV_SIGNAL or SIGEV_THREAD
* sigev_signo - The signo to use for the notification
* sigev_value - Value associated with the signal
*
* Returned Value:
* On success mq_notify() returns 0; on error, -1 is returned, with
* errno set to indicate the error.
*
* EBADF The descriptor specified in mqdes is invalid.
* EBUSY Another process has already registered to receive notification
* for this message queue.
* EINVAL sevp->sigev_notify is not one of the permitted values; or
* sevp->sigev_notify is SIGEV_SIGNAL and sevp->sigev_signo is not a
* valid signal number.
* ENOMEM
* Insufficient memory.
*
* Assumptions:
*
* POSIX Compatibility:
* int mq_notify(mqd_t mqdes, const struct sigevent *notification);
*
* The notification will be sent to the registered task even if another
* task is waiting for the message queue to become non-empty. This is
* inconsistent with the POSIX specification which says, "If a process
* has registered for notification of message a arrival at a message
* queue and some process is blocked in [nx]mq_receive() waiting to receive
* a message when a message arrives at the queue, the arriving message
* message shall satisfy [nx]mq_receive()... The resulting behavior is as
* if the message queue remains empty, and no notification shall be sent."
*
****************************************************************************/
int mq_notify(mqd_t mqdes, FAR const struct sigevent *notification)
{
#ifndef CONFIG_DISABLE_MQUEUE_NOTIFICATION
FAR struct mqueue_inode_s *msgq;
FAR struct inode *inode;
FAR struct file *filep;
FAR struct tcb_s *rtcb;
irqstate_t flags;
int errval;
errval = file_get(mqdes, &filep);
if (errval < 0)
{
errval = -errval;
goto errout_without_lock;
}
inode = filep->f_inode;
/* Was a valid message queue descriptor provided? */
if (!inode->i_private)
{
/* No.. return EBADF */
errval = EBADF;
goto errout_with_filep;
}
/* Get a pointer to the message queue */
flags = enter_critical_section();
/* Get the current process ID */
rtcb = this_task();
/* Is there already a notification attached */
msgq = inode->i_private;
if (msgq->ntpid == INVALID_PROCESS_ID)
{
/* No... Have we been asked to establish one? */
if (notification)
{
/* Yes... Was a valid signal number supplied? */
if (!GOOD_SIGNO(notification->sigev_signo))
{
/* No... Return EINVAL */
errval = EINVAL;
goto errout;
}
/* Yes... Assign it to the current task. */
memcpy(&msgq->ntevent, notification,
sizeof(struct sigevent));
msgq->ntpid = rtcb->pid;
#if defined(CONFIG_FDPIC) && defined(CONFIG_SIG_EVTHREAD)
/* Record the callback here, where this still runs in the
* module's context. It fires later on a worker that carries no
* data base, so the base travels with it. The function shares a
* union with the thread ID, so only a SIGEV_THREAD event has one
* to record.
*/
if ((notification->sigev_notify & SIGEV_THREAD) != 0)
{
FAR void *fn = (FAR void *)notification->sigev_notify_function;
fdpic_init(&msgq->ntwork.func, fn);
msgq->ntevent.sigev_notify_function =
(sigev_notify_function_t)fdpic_callback(fn);
}
#endif
}
}
/* Yes... a notification is attached. Does this task own it?
* Is it trying to remove it?
*/
else if ((msgq->ntpid != rtcb->pid) || (notification != NULL))
{
/* This thread does not own the notification OR it is
* not trying to remove it. Return EBUSY.
*/
errval = EBUSY;
goto errout;
}
else
{
/* Yes, the notification belongs to this thread. Allow the
* thread to detach the notification.
*/
memset(&msgq->ntevent, 0, sizeof(struct sigevent));
msgq->ntpid = INVALID_PROCESS_ID;
nxsig_cancel_notification(&msgq->ntwork);
}
leave_critical_section(flags);
file_put(filep);
return OK;
errout:
leave_critical_section(flags);
errout_with_filep:
file_put(filep);
errout_without_lock:
set_errno(errval);
return ERROR;
#else
set_errno(ENOSYS);
return ERROR;
#endif
}