doc: smp: use doxygen references

Use :c:func:`..`` where possible to reference documented APIs

Signed-off-by: Anas Nashif <anas.nashif@intel.com>
This commit is contained in:
Anas Nashif 2021-04-15 09:30:22 -04:00
commit fe3fcede10

View file

@ -34,7 +34,7 @@ semaphores used to implement blocking mutual exclusion continue to be
a proper application choice. a proper application choice.
At the lowest level, however, Zephyr code has often used the At the lowest level, however, Zephyr code has often used the
``irq_lock()``/``irq_unlock()`` primitives to implement fine grained :c:func:`irq_lock`/:c:func:`irq_unlock` primitives to implement fine grained
critical sections using interrupt masking. These APIs continue to critical sections using interrupt masking. These APIs continue to
work via an emulation layer (see below), but the masking technique work via an emulation layer (see below), but the masking technique
does not: the fact that your CPU will not be interrupted while you are does not: the fact that your CPU will not be interrupted while you are
@ -45,13 +45,13 @@ data!
Spinlocks Spinlocks
========= =========
SMP systems provide a more constrained ``k_spin_lock()`` primitive SMP systems provide a more constrained :c:func:`k_spin_lock` primitive
that not only masks interrupts locally, as done by ``irq_lock()``, but that not only masks interrupts locally, as done by :c:func:`irq_lock`, but
also atomically validates that a shared lock variable has been also atomically validates that a shared lock variable has been
modified before returning to the caller, "spinning" on the check if modified before returning to the caller, "spinning" on the check if
needed to wait for the other CPU to exit the lock. The default Zephyr needed to wait for the other CPU to exit the lock. The default Zephyr
implementation of ``k_spin_lock()`` and ``k_spin_unlock()`` is built implementation of :c:func:`k_spin_lock` and :c:func:`k_spin_unlock` is built
on top of the pre-existing ``atomic_t`` layer (itself usually on top of the pre-existing :c:struct:`atomic_` layer (itself usually
implemented using compiler intrinsics), though facilities exist for implemented using compiler intrinsics), though facilities exist for
architectures to define their own for performance reasons. architectures to define their own for performance reasons.
@ -76,7 +76,7 @@ Legacy irq_lock() emulation
=========================== ===========================
For the benefit of applications written to the uniprocessor locking For the benefit of applications written to the uniprocessor locking
API, ``irq_lock()`` and ``irq_unlock()`` continue to work compatibly on API, :c:func:`irq_lock` and :c:func:`irq_unlock` continue to work compatibly on
SMP systems with identical semantics to their legacy versions. They SMP systems with identical semantics to their legacy versions. They
are implemented as a single global spinlock, with a nesting count and are implemented as a single global spinlock, with a nesting count and
the ability to be atomically reacquired on context switch into locked the ability to be atomically reacquired on context switch into locked
@ -88,7 +88,7 @@ release to happen.
The overhead involved in this process has measurable performance The overhead involved in this process has measurable performance
impact, however. Unlike uniprocessor apps, SMP apps using impact, however. Unlike uniprocessor apps, SMP apps using
``irq_lock()`` are not simply invoking a very short (often ~1 :c:func:`irq_lock` are not simply invoking a very short (often ~1
instruction) interrupt masking operation. That, and the fact that the instruction) interrupt masking operation. That, and the fact that the
IRQ lock is global, means that code expecting to be run in an SMP IRQ lock is global, means that code expecting to be run in an SMP
context should be using the spinlock API wherever possible. context should be using the spinlock API wherever possible.
@ -104,10 +104,10 @@ kconfig variable, which can associate a specific set of CPUs with each
thread, indicating on which CPUs it can run. thread, indicating on which CPUs it can run.
By default, new threads can run on any CPU. Calling By default, new threads can run on any CPU. Calling
``k_thread_cpu_mask_disable()`` with a particular CPU ID will prevent :c:func:`k_thread_cpu_mask_disable` with a particular CPU ID will prevent
that thread from running on that CPU in the future. Likewise that thread from running on that CPU in the future. Likewise
``k_thread_cpu_mask_enable()`` will re-enable execution. There are also :c:func:`k_thread_cpu_mask_enable` will re-enable execution. There are also
``k_thread_cpu_mask_clear()`` and ``k_thread_cpu_mask_enable_all()`` APIs :c:func:`k_thread_cpu_mask_clear` and :c:func:`k_thread_cpu_mask_enable_all` APIs
available for convenience. For obvious reasons, these APIs are available for convenience. For obvious reasons, these APIs are
illegal if called on a runnable thread. The thread must be blocked or illegal if called on a runnable thread. The thread must be blocked or
suspended, otherwise an ``-EINVAL`` will be returned. suspended, otherwise an ``-EINVAL`` will be returned.
@ -129,25 +129,25 @@ Auxiliary CPUs begin in a disabled state in the architecture layer.
All standard kernel initialization, including device initialization, All standard kernel initialization, including device initialization,
happens on a single CPU before other CPUs are brought online. happens on a single CPU before other CPUs are brought online.
Just before entering the application ``main()`` function, the kernel Just before entering the application :c:func:`main` function, the kernel
calls ``z_smp_init()`` to launch the SMP initialization process. This calls :c:func:`z_smp_init` to launch the SMP initialization process. This
enumerates over the configured CPUs, calling into the architecture enumerates over the configured CPUs, calling into the architecture
layer using ``arch_start_cpu()`` for each one. This function is layer using :c:func:`arch_start_cpu` for each one. This function is
passed a memory region to use as a stack on the foreign CPU (in passed a memory region to use as a stack on the foreign CPU (in
practice it uses the area that will become that CPU's interrupt practice it uses the area that will become that CPU's interrupt
stack), the address of a local ``smp_init_top()`` callback function to stack), the address of a local :c:func:`smp_init_top` callback function to
run on that CPU, and a pointer to a "start flag" address which will be run on that CPU, and a pointer to a "start flag" address which will be
used as an atomic signal. used as an atomic signal.
The local SMP initialization (``smp_init_top()``) on each CPU is then The local SMP initialization (:c:func:`smp_init_top`) on each CPU is then
invoked by the architecture layer. Note that interrupts are still invoked by the architecture layer. Note that interrupts are still
masked at this point. This routine is responsible for calling masked at this point. This routine is responsible for calling
``smp_timer_init()`` to set up any needed stat in the timer driver. On :c:func:`smp_timer_init` to set up any needed stat in the timer driver. On
many architectures the timer is a per-CPU device and needs to be many architectures the timer is a per-CPU device and needs to be
configured specially on auxiliary CPUs. Then it waits (spinning) for configured specially on auxiliary CPUs. Then it waits (spinning) for
the atomic "start flag" to be released in the main thread, to the atomic "start flag" to be released in the main thread, to
guarantee that all SMP initialization is complete before any Zephyr guarantee that all SMP initialization is complete before any Zephyr
application code runs, and finally calls ``z_swap()`` to transfer application code runs, and finally calls :c:func:`z_swap` to transfer
control to the appropriate runnable thread via the standard scheduler control to the appropriate runnable thread via the standard scheduler
API. API.
@ -166,7 +166,7 @@ When running in multiprocessor environments, it is occasionally the
case that state modified on the local CPU needs to be synchronously case that state modified on the local CPU needs to be synchronously
handled on a different processor. handled on a different processor.
One example is the Zephyr ``k_thread_abort()`` API, which cannot return One example is the Zephyr :c:func:`k_thread_abort` API, which cannot return
until the thread that had been aborted is no longer runnable. If it until the thread that had been aborted is no longer runnable. If it
is currently running on another CPU, that becomes difficult to is currently running on another CPU, that becomes difficult to
implement. implement.
@ -180,9 +180,9 @@ handle the newly-runnable load.
So where possible, Zephyr SMP architectures should implement an So where possible, Zephyr SMP architectures should implement an
interprocessor interrupt. The current framework is very simple: the interprocessor interrupt. The current framework is very simple: the
architecture provides a ``arch_sched_ipi()`` call, which when invoked architecture provides a :c:func:`arch_sched_ipi` call, which when invoked
will flag an interrupt on all CPUs (except the current one, though will flag an interrupt on all CPUs (except the current one, though
that is allowed behavior) which will then invoke the ``z_sched_ipi()`` that is allowed behavior) which will then invoke the :c:func:`z_sched_ipi`
function implemented in the scheduler. The expectation is that these function implemented in the scheduler. The expectation is that these
APIs will evolve over time to encompass more functionality APIs will evolve over time to encompass more functionality
(e.g. cross-CPU calls), and that the scheduler-specific calls here (e.g. cross-CPU calls), and that the scheduler-specific calls here
@ -193,7 +193,7 @@ Note that not all SMP architectures will have a usable IPI mechanism
Zephyr provides fallback behavior that is correct, but perhaps Zephyr provides fallback behavior that is correct, but perhaps
suboptimal. suboptimal.
Using this, ``k_thread_abort()`` becomes only slightly more Using this, :c:func:`k_thread_abort` becomes only slightly more
complicated in SMP: for the case where a thread is actually running on complicated in SMP: for the case where a thread is actually running on
another CPU (we can detect this atomically inside the scheduler), we another CPU (we can detect this atomically inside the scheduler), we
broadcast an IPI and spin, waiting for the thread to either become broadcast an IPI and spin, waiting for the thread to either become
@ -239,15 +239,15 @@ running concurrently. Likewise a kernel-provided interrupt stack
needs to be created and assigned for each physical CPU, as does the needs to be created and assigned for each physical CPU, as does the
interrupt nesting count used to detect ISR state. interrupt nesting count used to detect ISR state.
These fields are now moved into a separate ``struct _cpu`` instance These fields are now moved into a separate struct :c:struct:`_cpu` instance
within the ``_kernel`` struct, which has a ``cpus[]`` array indexed by ID. within the :c:struct:`_kernel` struct, which has a ``cpus[]`` array indexed by ID.
Compatibility fields are provided for legacy uniprocessor code trying Compatibility fields are provided for legacy uniprocessor code trying
to access the fields of ``cpus[0]`` using the older syntax and assembly to access the fields of ``cpus[0]`` using the older syntax and assembly
offsets. offsets.
Note that an important requirement on the architecture layer is that Note that an important requirement on the architecture layer is that
the pointer to this CPU struct be available rapidly when in kernel the pointer to this CPU struct be available rapidly when in kernel
context. The expectation is that ``arch_curr_cpu()`` will be context. The expectation is that :c:func:`arch_curr_cpu` will be
implemented using a CPU-provided register or addressing mode that can implemented using a CPU-provided register or addressing mode that can
store this value across arbitrary context switches or interrupts and store this value across arbitrary context switches or interrupts and
make it available to any kernel-mode code. make it available to any kernel-mode code.
@ -263,7 +263,7 @@ a separate field in the thread struct.
Switch-based context switching Switch-based context switching
============================== ==============================
The traditional Zephyr context switch primitive has been ``z_swap()``. The traditional Zephyr context switch primitive has been :c:func:`z_swap`.
Unfortunately, this function takes no argument specifying a thread to Unfortunately, this function takes no argument specifying a thread to
switch to. The expectation has always been that the scheduler has switch to. The expectation has always been that the scheduler has
already made its preemption decision when its state was last modified already made its preemption decision when its state was last modified
@ -278,22 +278,22 @@ Instead, the SMP "switch to" decision needs to be made synchronously
with the swap call, and as we don't want per-architecture assembly with the swap call, and as we don't want per-architecture assembly
code to be handling scheduler internal state, Zephyr requires a code to be handling scheduler internal state, Zephyr requires a
somewhat lower-level context switch primitives for SMP systems: somewhat lower-level context switch primitives for SMP systems:
``arch_switch()`` is always called with interrupts masked, and takes :c:func:`arch_switch` is always called with interrupts masked, and takes
exactly two arguments. The first is an opaque (architecture defined) exactly two arguments. The first is an opaque (architecture defined)
handle to the context to which it should switch, and the second is a handle to the context to which it should switch, and the second is a
pointer to such a handle into which it should store the handle pointer to such a handle into which it should store the handle
resulting from the thread that is being switched out. resulting from the thread that is being switched out.
The kernel then implements a portable ``z_swap()`` implementation on top The kernel then implements a portable :c:func:`z_swap` implementation on top
of this primitive which includes the relevant scheduler logic in a of this primitive which includes the relevant scheduler logic in a
location where the architecture doesn't need to understand it. location where the architecture doesn't need to understand it.
Similarly, on interrupt exit, switch-based architectures are expected Similarly, on interrupt exit, switch-based architectures are expected
to call ``z_get_next_switch_handle()`` to retrieve the next thread to to call :c:func:`z_get_next_switch_handle` to retrieve the next thread to
run from the scheduler, passing in an "interrupted" handle reflecting run from the scheduler, passing in an "interrupted" handle reflecting
the same opaque type used by switch, which the kernel will then save the same opaque type used by switch, which the kernel will then save
in the interrupted thread struct. in the interrupted thread struct.
Note that while SMP requires :option:`CONFIG_USE_SWITCH`, the reverse is not Note that while SMP requires :option:`CONFIG_USE_SWITCH`, the reverse is not
true. A uniprocessor architecture built with :option:`CONFIG_SMP` = n might true. A uniprocessor architecture built with :option:`CONFIG_SMP` set to No might
still decide to implement its context switching using still decide to implement its context switching using
``arch_switch()``. :c:func:`arch_switch`.