diff --git a/doc/source/architecture.rst b/doc/source/architecture.rst index ce7704f3f2..86635b6b64 100644 --- a/doc/source/architecture.rst +++ b/doc/source/architecture.rst @@ -1,54 +1,132 @@ .. _architecture: SO3 Architecture +################ + +SO3 follows a conventional operating-system organisation with a **user space** +and a **kernel space**. It is a *monolithic* kernel, like Linux: all subsystems +and device drivers live in the kernel. + +.. figure:: img/so3_architecture.png + :width: 100% + + Overview of the SO3 environment (standalone configuration). + +The user space is a small set of lightweight applications (``init``, the shell, +``ls``, ``cat``, ``more``, ``ping``, …) built against the **MUSL** C library and +stored in a root filesystem. The kernel provides the usual subsystems — +process/thread management and scheduling, memory management, a virtual +filesystem, IPC, networking — plus a device-tree-driven device and driver model. + +Source tree +=========== + +The kernel source lives under ``so3/`` and is organised by subsystem:: + + so3/ + ├── arch/ # architecture-specific code (arm32, arm64) + │ └── arm64/ # head/boot, exceptions, MMU, context switch, traps + ├── kernel/ # processes, threads, scheduler, syscalls, time + ├── mm/ # page frame allocator, kernel heap + ├── fs/ # VFS, FAT, devfs, ELF loader + ├── ipc/ # signals, pipes, semaphores, completions + ├── net/ # networking, lwIP TCP/IP stack + ├── devices/ # device model + drivers (irq, timer, serial, mmc, fb, …) + ├── dts/ # device trees (*.dts → *.dtb) + ├── avz/ # the AVZ hypervisor (built with CONFIG_AVZ) + ├── soo/ # the SOO framework / SO3 capsules (CONFIG_SOO) + ├── configs/ # defconfig files + └── lib/ # in-kernel helper libraries (libfdt, libroxml, …) + +The user space lives under ``usr/`` and the surrounding tooling (bootloader, +emulator, root filesystem, deployment scripts) at the repository root — see +:ref:`build_system` and :ref:`user_space`. + +Exception levels ================ -The overall architecture of SO3 follows a conventional operating system organization with a user space and a kernel space. -The user space represents all user files and applications; everything is accessible via a *standard* directory tree -through a root filesystem. - -The set of directories/sub-directories are generated by a well-known `buildroot `__ tool used to generate -embedded Linux systems through cross-compilation. It provides a fully configurable (i.e. customizable) well-profiled -user environment and can lead to a minimal sized root filesystem. - -.. figure:: img/SO3_Architecture.png - :scale: 50 % - - Overview of the SO3 environment with user and kernel space - -As depicted on this figure, SO3 is a monolithic OS like Linux where all subsystems and drivers reside in the kernel space. -Major subsystems like Inter-Process Communication (IPC), scheduling, filesystem, networking, etc. including device drivers -exist and provide basic functionalities. The user space is made of a very simple set of lightweight applications (*ls*, -*more*, *echo*, *cat*, etc.) - -SO3 Kernel Space ----------------- - -The kernel space uses the device tree to have information related to hardware configuration like the RAM memory base and -size, device related information, and various system properties. - -SO3 User Space --------------- - -All user space files are located in ``usr/`` directory. Applications and the *libc* is in separated -directories. - - -The MUSL libc user space library --------------------------------- - -SO3 integrates the `MUSL library `__ as *libc* for its user space application. - -Not all functions are available in SO3, but the functions are enabled as soon as there is a necessity to have it. -Furthermore, more complex functions such as those used to manage ``pthreads`` for example are not activated since -only a minimal set of functionalities and features are present. - -Please do not hesitate to start a discussion thread or simply ask for adding a new feature in ``musl`` as it becomes -necessary. - - - -.. _MUSL_libc: https://musl.libc.org -.. _buildroot: https://buildroot.org - - +On ARM64, SO3 uses the architectural exception levels as follows. + +.. figure:: img/so3_exception_levels.png + :width: 95% + + Exception levels in the standalone and AVZ configurations. + +* **Standalone** — user processes run at **EL0**, the SO3 kernel at **EL1**. + EL2 is unused. The kernel owns ``VBAR_EL1`` (exception vectors) and the + ``TTBR0_EL1`` / ``TTBR1_EL1`` translation tables. +* **AVZ** — the **AVZ hypervisor** runs at **EL2** (``VBAR_EL2``, stage-2 + translation via ``VTTBR_EL2``, ``HCR_EL2`` configured to trap and inject + interrupts). Each guest (the agency and any capsule) runs its own SO3 kernel + at EL1 with its user space at EL0, isolated by per-domain stage-2 tables. + +The exact level a given build targets is selected by ``CONFIG_AVZ``. The same +C and assembly files implement both; EL2-specific code is guarded with +``#ifdef CONFIG_AVZ`` (for example the TLB-maintenance instructions in +``arch/arm64/cache.S`` and the vector handlers in ``arch/arm64/exception.S``). + +Virtual address space +===================== + +Each process owns a private set of page tables. On ARM64 the low half of the +address space (``TTBR0_EL1``) maps the current user process, and the high half +(``TTBR1_EL1``) maps the kernel. + +.. figure:: img/so3_memory.png + :width: 100% + + ARM64 virtual address space (standalone, EL1). + +The kernel base address is configured by ``CONFIG_KERNEL_VADDR``: + +.. flat-table:: + :header-rows: 1 + :widths: 30 40 30 + + * - Configuration + - ``CONFIG_KERNEL_VADDR`` + - Notes + * - standalone (EL1) + - ``0xFFFF800000000000`` + - kernel in the high (TTBR1) half + * - AVZ (EL2) + - ``0x0000100000000000`` + - hypervisor address space + +The ``__pa()`` / ``__va()`` macros (``include/memory.h``) translate between +kernel virtual addresses and physical addresses using ``CONFIG_KERNEL_VADDR`` +and the physical RAM base discovered from the device tree. The initial user +program is mapped at ``USER_SPACE_VADDR`` (``0x1000``). See :ref:`kernel` for +the page-frame allocator and the MMU code. + +Boot flow +========= + +SO3 is started by **U-Boot**, which loads a **FIT image** (``.itb``) containing +the kernel, the device-tree blob and the root filesystem. In the AVZ +configuration the FIT also contains the hypervisor and the agency guest. + +.. figure:: img/so3_boot.png + :width: 100% + + Boot flow from U-Boot to the interactive shell. + +1. **U-Boot** loads the FIT image from the boot medium and jumps to the entry + point of the first component. +2. In the **AVZ** configuration, control enters the hypervisor + (``avz_start()`` at EL2); AVZ sets up the stage-2 tables, *loads the guest + domain* and ``eret``\ s into the agency at EL1. In the **standalone** + configuration U-Boot jumps straight to the SO3 kernel entry + (``__start`` → ``kernel_start()``) at EL1. +3. The kernel brings itself up: ``memory_init()`` (frame table + MMU), + ``devices_init()`` (device-tree probe, GIC, timer, serial), ``timer_init()`` + and ``calibrate_delay()``, ``scheduler_init()``, then interrupts are enabled. +4. ``rest_init()`` runs as the first kernel thread and calls + ``create_root_process()``, which maps the ``__root_proc`` trampoline at + ``0x1000`` and enters EL0. +5. The root process ``execve()``\ s ``init.elf`` (the *SO3 Init Program*), which + in turn launches the shell (``sh.elf``) — the familiar ``so3%`` prompt. + +The individual steps are described in :ref:`kernel`; the packaging of the FIT +image and the U-Boot/QEMU setup are covered in :ref:`build_system` and +:ref:`user_guide`. diff --git a/doc/source/avz.rst b/doc/source/avz.rst new file mode 100644 index 0000000000..3b0569c9a0 --- /dev/null +++ b/doc/source/avz.rst @@ -0,0 +1,179 @@ +.. _avz: + +AVZ Hypervisor +############## + +**AVZ** (*Agency VirtualiZer*) is the type-1 hypervisor that ships inside the +SO3 tree. Built with ``CONFIG_AVZ``, the very same code base runs at **EL2** on +ARM64 and hosts guest *domains* at EL1. AVZ is small and Xen-inspired: it +provides stage-2 memory isolation, a domain scheduler, hypercalls, event +channels, grant tables and a virtual GIC, and nothing more. + +AVZ is also the foundation of the :ref:`SO3 capsule ` model: an agency +domain owns the hardware while one or more lightweight capsule (S3C) guests run +beside it. + +.. note:: + + In the demonstration shipped with this repository the agency is an **SO3** + kernel (enough to exercise the hypervisor). The full SO3 Capsule setup uses a + **Linux** agency, which — together with the SOO framework — lives in a + :ref:`separate repository `. + +.. figure:: img/so3_avz.png + :width: 100% + + AVZ: domains isolated by stage-2 tables, and the EL2 services beneath them. + +The code lives under ``so3/avz/`` (kernel, memory, scheduler, hypercalls, grant +tables, capsule build/inject) together with the EL2-specific parts of +``arch/arm64`` (``head.S`` MMU setup, ``exception.S`` EL2 vectors, +``context.S`` stage-2 switch, ``cache.S`` EL2 TLB ops) and the virtual GIC in +``devices/irq/``. + +Boot and guest loading +====================== + +The hypervisor entry point is ``avz_start()`` (``avz/kernel/setup.c``). After +early CPU, memory and device initialisation it prints its banner and *loads the +guest domain*: it parses the FIT image provided by U-Boot, places the agency's +kernel and device tree in RAM, builds the agency's **stage-2** page tables and +sets the guest entry point. AVZ then ``eret``\ s to EL1, and the agency boots as +an ordinary SO3 kernel (``kernel_start()``). The console trace looks like:: + + ********** Smart Object Oriented technology - AVZ Hypervisor ********** + ... + Now bootstraping the hypervisor kernel ... + ***************** Loading Guest Domain ***************** + ... + ********** Smart Object Oriented SO3 Operating System ********** + +Guest memory is organised in **memory slots** (``avz/include/avz/memslot.h``): +slot 0 is AVZ itself, slot 1 the agency, and the remaining slots are capsules. +Each slot maps a guest *intermediate physical address* (IPA) range to real +physical memory; ``ipa_to_pa()`` / ``pa_to_ipa()`` convert between them. + +Domains +======= + +A **domain** (``struct domain``, ``avz/include/avz/domain.h``) is a guest +instance: its virtual CPU state, its event-channel table, a pointer to the +shared info page and its scheduling metadata. Well-known identifiers +(``avz/include/avz/uapi/avz.h``): + +.. flat-table:: + :header-rows: 1 + :widths: 35 65 + + * - Identifier + - Meaning + * - ``DOMID_AGENCY`` (0) + - the primary agency guest (owns the devices) + * - ``DOMID_AGENCY_RT`` (1) + - optional real-time agency subdomain + * - slots 2 … + - capsule domains + * - ``MAX_CAPSULE_DOMAINS`` + - ``2 + 5`` — up to five capsules alongside the agencies + +Each domain shares a page with the hypervisor — the ``avz_shared`` structure — +carrying its domain id, event-channel pending bits, the upcall state and the +guest's device-tree address. + +Hypercalls +========== + +Guests call into AVZ with the ``hvc`` instruction, which traps to the EL2 +synchronous handler (``el12_sync_handler`` in ``arch/arm64/exception.S``) and is +dispatched by ``avz/kernel/hypercalls.c``. The generic hypercalls +(``avz/include/avz/uapi/avz.h``) are: + +* ``AVZ_EVENT_CHANNEL_OP`` — allocate / bind / send / close event channels; +* ``AVZ_CONSOLE_IO_OP`` — console output for guests; +* ``AVZ_DOMAIN_CONTROL_OP`` — domain control (pause / unpause a capsule, …). + +The capsule-management operations (inject, kill, read/write snapshot) used by the +SOO framework are built on top of these — see :ref:`capsules`. + +Domain scheduling +================= + +AVZ runs each domain on a CPU according to its role. The agency uses the +``sched_agency`` policy; capsules are scheduled by ``sched_flip`` +(``avz/kernel/sched_flip.c``), a lightweight round-robin over the capsule +domains. A per-CPU ``current_domain`` pointer tracks the running guest; switching +domains saves and restores the EL1 register banks and reprograms ``VTTBR_EL2`` +through ``__mmu_switch_vttbr()`` (``arch/arm64/context.S``). + +Inter-domain communication +========================== + +Event channels +-------------- + +Each domain has ``NR_EVTCHN`` (128) event-channel ports. A port can be +*unbound* (waiting for a peer), *interdomain* (bound to a remote domain's port) +or bound to a *virtual IRQ*. Sending an event sets a pending bit in the remote +domain's ``avz_shared`` page and, if needed, injects a virtual interrupt so the +guest is woken. Event channels are the signalling half of the split-driver +model. + +Grant tables +------------ + +Grant tables (``avz/kernel/gnttab.c``) let one domain share specific memory +pages with another in a controlled way. A domain reserves a small set of grant +IPA pages; a peer maps a granted page by reference. This is how the shared rings +of the frontend/backend drivers and the capsule framebuffer are set up. + +Virtual GIC +=========== + +Because guests must not touch the physical interrupt controller directly, AVZ +provides a **virtual GIC**. + +* ``HCR_EL2.IMO`` routes all Group-1 physical IRQs to **EL2**. The EL2 IRQ + handler (``avz_el2_irq_handle()`` for GICv3, ``irq_handle()`` for GICv2) + decides what to do with each interrupt. +* The hypervisor's own interrupts — the EL2 timer (CNTHP, PPI 26) and the vGIC + **maintenance** interrupt (PPI 25) — are handled locally. +* All other interrupts destined for a guest are **injected** through the GIC + *list registers* (``ICH_LR*_EL2`` on GICv3, the GICH MMIO frame on GICv2). The + injected entry is *hardware-backed* (``HW = 1``) so that the physical + interrupt is deactivated automatically when the guest writes its own + end-of-interrupt — keeping hypervisor overhead minimal. +* Accesses by a guest to the physical GIC **distributor** are not mapped in the + guest stage-2 tables; they trap to EL2 and are emulated by the vGIC + (``devices/irq/vgic.c``), which forwards most register accesses and translates + SGI (software-generated interrupt) requests into AVZ's targeted-IPI helper. + +EL2 vs EL1 in the shared code +============================= + +Because the standalone and AVZ builds share the same files, a handful of +low-level operations differ by exception level and are guarded with +``#ifdef CONFIG_AVZ``: + +.. flat-table:: + :header-rows: 1 + :widths: 30 35 35 + + * - Operation + - Standalone (EL1) + - AVZ (EL2) + * - TLB maintenance (``cache.S``) + - ``tlbi vmalle1`` / ``vae1is`` + - ``tlbi alle2`` / ``vae2is`` + * - MMU setup (``head.S``) + - ``ttbr0/1_el1``, ``sctlr_el1`` + - ``ttbr0_el2``, ``tcr_el2``, ``sctlr_el2`` + * - GIC CPU interface + - ``EOImode = 0`` + - ``EOImode = 1`` (split priority-drop / deactivate) + * - sync/IRQ vectors (``exception.S``) + - ``el01_sync`` / ``el01_1_irq`` + - ``el12_sync`` / ``el12_2_irq`` + +Getting these guards right is essential: an EL2-only instruction executed at EL1 +(or vice versa) faults immediately at boot. See :ref:`debugging` for how such +issues are diagnosed under QEMU/GDB. diff --git a/doc/source/build_system.rst b/doc/source/build_system.rst new file mode 100644 index 0000000000..64813e6834 --- /dev/null +++ b/doc/source/build_system.rst @@ -0,0 +1,183 @@ +.. _build_system: + +Build System +############ + +SO3's build system is derived from the Linux **Kbuild** infrastructure, so it +will feel familiar to anyone who has built a Linux kernel: ``Kconfig`` files, +``defconfig`` snapshots, ``obj-y`` lists and a recursive descent through the +source tree. + +Configuration (Kconfig) +======================= + +Each subsystem carries a ``Kconfig`` describing its options, and every option +becomes a ``CONFIG_*`` symbol. Configuration is stored in ``so3/.config`` and +exposed to the build as ``include/generated/autoconf.h``. The usual targets +work:: + + cd so3 + make virt64_defconfig # load a defconfig snapshot + make menuconfig # interactive configuration (optional) + make -j$(nproc) # build the kernel + +The selected target architecture (``CONFIG_VIRT64``, ``CONFIG_RPI4_64``, +``CONFIG_VERDIN_IMX8MP`` for ARM64; ``CONFIG_VIRT32``, ``CONFIG_RPI4`` for ARM32) +and the deployment mode (``CONFIG_AVZ``, ``CONFIG_SOO``) are all driven from +``.config``. The cross-compiler is taken from ``CONFIG_CROSS_COMPILE`` — the +kernel uses the bare-metal **aarch64-none-elf** (or **arm-none-eabi**) toolchain. + +Defconfigs and platforms +======================== + +The ready-made configurations live in ``so3/configs/``: + +.. flat-table:: + :header-rows: 1 + :widths: 38 12 12 12 26 + + * - defconfig + - Arch + - AVZ + - SOO + - Purpose + * - ``virt64_defconfig`` + - arm64 + - + - + - standalone, QEMU virt + * - ``virt64_fb_defconfig`` + - arm64 + - + - + - standalone + framebuffer (LVGL) + * - ``virt64_lvperf_defconfig`` + - arm64 + - + - + - LVGL performance build + * - ``virt64_avz_defconfig`` + - arm64 + - ✔ + - + - AVZ hypervisor + agency + * - ``virt64_avz_soo_defconfig`` + - arm64 + - ✔ + - ✔ + - AVZ + agency with SOO + * - ``virt64_capsule_defconfig`` + - arm64 + - + - ✔ + - SO3 capsule (guest) + * - ``rpi4_64_defconfig`` + - arm64 + - + - + - Raspberry Pi 4 (64-bit) + * - ``rpi4_64_avz_defconfig`` + - arm64 + - ✔ + - + - RPi4 AVZ hypervisor + * - ``rpi4_64_avz_soo_defconfig`` + - arm64 + - ✔ + - ✔ + - RPi4 AVZ + SOO + * - ``verdin-imx8mp_avz_defconfig`` + - arm64 + - ✔ + - + - Toradex Verdin i.MX8M Plus + * - ``virt32_defconfig`` / ``…_fb`` / ``…_lvperf`` + - arm32 + - + - + - 32-bit QEMU virt variants + * - ``rpi4_defconfig`` + - arm32 + - + - + - Raspberry Pi 4 (32-bit) + +The active platform is also recorded at the repository root in ``build.conf`` +(``PLATFORM := virt64``); the deployment and launch scripts read it from there. + +Linker script and asm-offsets +============================= + +The kernel is linked with an architecture-specific linker script +(``arch/arm64/so3.lds``). It places the exception vectors, the boot code +(``.head.text``), the code/data/bss, the per-CPU area (``.percpu_data``), the +system page tables, the driver *initcall* sections, the kernel heap and the +per-CPU stacks. Sizes come from configuration symbols (``CONFIG_HEAP_SIZE_MB``, +``CONFIG_NR_CPUS``, the stack sizes) passed to the linker with ``--defsym``; the +kernel base address is ``CONFIG_KERNEL_VADDR``. + +Assembly code needs the byte offsets of C structures (for instance +``cpu_regs``). These are produced by ``arch/arm64/asm-offsets.c``, compiled to a +header of ``#define OFFSET_*`` values that both C and assembly share — the same +mechanism Linux uses. + +Device trees +============ + +Hardware is described by **device trees** in ``so3/dts/``. The ``.dts`` sources +are compiled to ``.dtb`` blobs by the in-tree ``dtc`` (``scripts/dtc/``). The +relevant ``.dtb`` is shipped to the kernel inside the FIT image (below); the +kernel parses it at boot to discover RAM and devices (:ref:`kernel`). + +FIT image and U-Boot +==================== + +SO3 is started by **U-Boot**, which expects a **FIT image** (``.itb``) — a single +file bundling the kernel, the device tree and the root filesystem (and, for AVZ, +the hypervisor and the agency guest). The image is described by an ``.its`` file +in ``target/`` and assembled with ``mkimage``: + +.. flat-table:: + :header-rows: 1 + :widths: 40 60 + + * - ``.its`` (in ``target/``) + - Contents + * - ``virt64.its`` + - standalone: SO3 kernel + DTB + ramfs + * - ``virt64_avz_so3.its`` + - AVZ hypervisor + agency SO3 + DTBs + ramfs + * - ``virt64_capsule.its`` + - a capsule image + * - ``virt64_lvperf.its`` + - LVGL performance build + * - ``rpi4_64*.its`` / ``virt32*.its`` + - the corresponding RPi4 / 32-bit variants + +The helper ``target/mkuboot.sh `` runs ``mkimage -f .its +.itb``. The resulting ``.itb`` is copied into the first partition of the +SD-card image by ``deploy.sh`` (see :ref:`user_guide`). U-Boot's environment +(``u-boot/uEnv.d/uEnv_.txt``) loads ``.itb`` and boots it. + +Building the AVZ hypervisor +=========================== + +The AVZ hypervisor is an **out-of-tree** build of the same source, configured +into the repository-root ``avz/`` directory (whose ``avz/source`` is a symlink to +``so3/``):: + + cd avz + ./build.sh virt64_avz_defconfig # configure + ./build.sh # build -> avz/so3.bin + +The resulting ``avz/so3.bin`` is the hypervisor; the agency guest is an ordinary +``so3/so3.bin``. Both, together with the device trees and the root filesystem, +are packaged by ``virt64_avz_so3.its``. See :ref:`user_guide` for the full AVZ +run procedure. + +User space +========== + +The user-space applications are built separately with **CMake** and the +**MUSL** cross toolchain (``usr/aarch64-linux-musl.cmake`` / +``usr/arm-linux-musl.cmake``). This is covered in :ref:`user_space`. diff --git a/doc/source/capsules.rst b/doc/source/capsules.rst new file mode 100644 index 0000000000..40c5d3860d --- /dev/null +++ b/doc/source/capsules.rst @@ -0,0 +1,113 @@ +.. _capsules: + +SO3 Capsules (SOO framework) +############################ + +An **SO3 Capsule** (acronym **S3C**) is a lightweight, self-contained guest that +runs at EL1 on top of the :ref:`AVZ hypervisor `. A capsule does not own any +hardware; it cooperates with an **agency** domain that owns the devices, through +split (frontend/backend) drivers. + +.. note:: + + *SO3 Capsule* / *S3C* is the current name and acronym for the concept that + older code and papers called a *Mobile Entity* (ME). The source tree now uses + the **S3C** acronym in identifiers (``S3C_desc_t``, ``S3C_state_t``, + ``S3C_domID``, ``MAX_S3C_DOMAINS`` …) and *capsule* in prose. The legacy + ``ME`` spelling no longer appears in the code. + +The agency is a separate repository +=================================== + +The capsule model needs a **Linux** agency: Linux provides the backend drivers +and the higher-level services that capsules talk to. For the time being **the +Linux agency is not part of this (so3) repository** — it lives, together with the +rest of the **SOO framework**, in the separate +`soo repository `__ (also mirrored on +GitHub). + +What *is* in this so3 repository is the **capsule (guest) side** and the +hypervisor support for it: + +* the **frontend** drivers (``soo/drivers/``); +* the **vbus / vbstore** clients and the event-channel / grant-table glue + (``soo/kernel/``); +* the hypervisor-side capsule **build / inject / snapshot** code + (``so3/avz/`` — ``capsule_build.c``, ``injector.c``). + +A capsule-capable guest is produced by ``virt64_capsule_defconfig`` (enabling +``CONFIG_SOO``). The AVZ demonstration shipped in this repository +(``virt64_avz_so3.its``) boots an **SO3** agency, which is enough to exercise the +hypervisor; running actual capsules additionally requires the Linux agency from +the soo repository. + +Split (frontend/backend) drivers +================================= + +Every virtual device a capsule uses is a **split driver**: a **frontend** in the +capsule talks to a **backend** in the Linux agency through a shared memory ring +and an event channel (set up with grant tables and :ref:`event channels `). + +.. figure:: img/so3_capsule.png + :width: 100% + + Split drivers and the vbstore configuration tree. + +The frontends shipped in ``soo/drivers/`` are: + +.. flat-table:: + :header-rows: 1 + :widths: 30 70 + + * - Frontend + - Purpose + * - ``vfbdevfront`` + - virtual framebuffer (display output for the capsule) + * - ``vinputfront`` + - virtual input (keyboard / mouse events) + * - ``vuartfront`` + - virtual serial console + * - ``vuihandlerfront`` + - UI handler / application channel + * - ``vlogsfront`` + - logging service + * - ``vsenseledfront`` / ``vsensejfront`` + - Raspberry Pi Sense HAT LED matrix and joystick + * - ``vdummyfront`` + - reference/test driver + +All frontends share the common machinery in ``soo/drivers/vdevfront.c`` and +register through ``vbus`` as ``vbus_driver``\ s. + +vbus and vbstore +================ + +The frontend and backend find and configure each other through two Xen-inspired +mechanisms: + +vbstore + A small, hierarchical **configuration store** (``soo/kernel/vbstore/``), + analogous to Xenstore. Devices advertise their state and parameters at paths + such as ``device///state``, ``…/ring-ref`` and + ``…/event-channel``. + +vbus + The **virtual bus** (``soo/kernel/vbus/``) that enumerates devices and drives + their state machine. When a frontend and its backend have both published their + ring reference and event channel in vbstore and reached the *Connected* state, + I/O can flow. The same state machine drives *probe*, *suspend* and *resume*. + +Capsule state and the snapshot mechanism +======================================== + +A capsule's state is tracked by ``S3C_state_t`` +(``soo/include/soo/uapi/soo.h``): ``S3C_state_stopped``, ``S3C_state_living``, +``S3C_state_suspended``, ``S3C_state_resuming``, ``S3C_state_awakened``, +``S3C_state_killed``, ``S3C_state_terminated``, ``S3C_state_dead`` (and the +``S3C_state_hibernate`` / ``S3C_state_booting`` intermediates). + +AVZ provides a low-level **snapshot** primitive — ``AVZ_S3C_READ_SNAPSHOT`` and +``AVZ_S3C_WRITE_SNAPSHOT`` — that saves and restores a capsule's memory image and +its vbstore state. This is the building block the SOO framework uses to move a +capsule's execution state; the higher-level orchestration that drives it lives in +the soo repository, not here. diff --git a/doc/source/conf.py b/doc/source/conf.py index 760b811cf1..06c6e5cdac 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -35,15 +35,14 @@ 'sphinx.ext.todo', 'sphinx.ext.ifconfig', 'sphinx.ext.viewcode', - 'sphinxcontrib.plantuml', 'rstFlatTable', -# 'sphinxcontrib.bibtex', 'sphinx.ext.extlinks', 'sphinx.ext.imgmath' ] -plantuml = 'java -jar %s -t' % os.path.join(os.path.dirname(__file__), "../utils", "plantuml.jar") -plantuml_output_format = 'png' +# Diagrams are authored in source/img/so3.drawio and exported to PNG +# (see source/img/gen_so3_diagrams.py and source/img/export_png.sh). The +# PlantUML extension was removed: it was unused and required a missing JAR. # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -60,7 +59,7 @@ # General information about the project. project = u'SO3 Operating System' -copyright = u'2014-2025, HEIG-VD - REDS Institute' +copyright = u'2014-2026, HEIG-VD - REDS Institute' # The version info for the project you're documenting, acts as replacement for diff --git a/doc/source/debugging.rst b/doc/source/debugging.rst new file mode 100644 index 0000000000..d155997ca4 --- /dev/null +++ b/doc/source/debugging.rst @@ -0,0 +1,117 @@ +.. _debugging: + +Debugging SO3 +############# + +Because SO3 is developed primarily on QEMU, the emulator's built-in **GDB stub** +is the day-to-day debugging tool. This chapter shows the QEMU/GDB workflow; for +debugging on real hardware see :ref:`Debugging with JTAG on Raspberry Pi 4 +`. + +Debug symbols +============= + +The linked kernel ``so3/so3`` is an ELF file with full symbols (the binary +flashed into the FIT image is the stripped ``so3.bin``). Point GDB at the ELF to +get source-level debugging while the target runs the matching ``.bin``. + +Attaching GDB to QEMU +===================== + +The launch scripts already expose a GDB stub. ``./st`` (and ``./stv``) start +QEMU with ``-gdb tcp::1234``; add ``-S`` to make QEMU **wait** for the debugger +before executing the first instruction. A minimal headless invocation looks +like: + +.. code-block:: bash + + sudo qemu-system-aarch64 -smp 4 -M virt -cpu cortex-a72 -m 1024 \ + -kernel u-boot/u-boot -nographic -no-reboot \ + -drive if=none,file=filesystem/sdcard.img.virt64,id=hd0,format=raw \ + -device virtio-blk-device,drive=hd0 \ + -S -gdb tcp::1234 + +Then, from the ``so3/`` directory: + +.. code-block:: bash + + gdb-multiarch -q \ + -ex 'file so3' \ + -ex 'target remote :1234' \ + -ex 'break kernel_start' \ + -ex 'continue' + +.. tip:: + + Use **gdb-multiarch** (or an ``aarch64`` GDB). The repository ships a small + ``gdbinit`` with sensible defaults (for example a bounded backtrace limit). + +Useful breakpoints +================== + +A few entry points are handy when tracing a boot or a fault: + +.. flat-table:: + :header-rows: 1 + :widths: 38 62 + + * - Symbol + - What it marks + * - ``kernel_start`` + - start of kernel bring-up + * - ``memory_init`` / ``devices_init`` + - memory and device-tree initialisation + * - ``trap_handle`` / ``trap_handle_error`` + - the synchronous-exception path (faults, syscalls) + * - ``el01_sync_handler`` / ``syscall_handle`` + - the system-call path + * - ``create_root_process`` / ``ret_from_fork`` + - the transition into the first user process + +Inspecting an exception +======================= + +When a fault is taken, the kernel prints the offending ``ELR``, ``FAR`` and +``ESR`` and stops. To find the faulting instruction in the ELF, add the kernel +virtual base to the load offset and disassemble: + +.. code-block:: bash + + aarch64-none-elf-objdump -d so3/so3 | less # search for the address + +The top bits of ``ESR`` give the exception class (``EC``); a value of ``0x15`` is +an ``SVC`` (system call), ``0x20`` / ``0x21`` an instruction abort, ``0x24`` / +``0x25`` a data abort, and ``0x00`` an "unknown reason" trap (often an +instruction that is illegal at the current exception level — a frequent symptom +when EL2-only code runs at EL1). + +.. note:: + + QEMU's GDB stub cannot always read the AArch64 system registers by name. When + ``$elr_el1`` and friends are unavailable, read the **saved exception frame** + off the stack instead: within ``struct cpu_regs`` the offsets are + ``OFFSET_PC = 256``, ``OFFSET_PSTATE = 264`` and ``OFFSET_SP_USR = 272`` + (with ``S_FRAME_SIZE = 288`` in the standalone build). + +Debugging AVZ +============= + +For the hypervisor, launch with ``./stv`` (so EL2 is available) and add ``-S +-gdb tcp::1234`` the same way. Load symbols from the hypervisor ELF +(``avz/so3``) for the EL2 code and from the agency ELF (``so3/so3``) for the EL1 +guest. The AVZ console trace (the *Loading Guest Domain* section and the vGIC +messages) is usually the quickest way to locate a problem before reaching for +the debugger. + +Semihosting +=========== + +The ARM **semihosting** interface (``arch/arm64/semihosting.c``) is available +under the emulator and through a JTAG probe, allowing early ``printf``-style +output and host file access before the console driver is up. + +On real hardware +================ + +For JTAG-based debugging on the Raspberry Pi 4 (OpenOCD + GDB over a hardware +probe), follow the dedicated chapter: :ref:`so3_jtag_rpi4`. diff --git a/doc/source/img/export_png.sh b/doc/source/img/export_png.sh new file mode 100755 index 0000000000..43d842837a --- /dev/null +++ b/doc/source/img/export_png.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# Render every page of so3.drawio to a PNG (so3_.png). +# The drawio CLI is an Electron app, so we run it under a virtual X server. +set -e +cd "$(dirname "$0")" + +names=(modes architecture boot memory exception_levels syscall avz capsule device_model) + +i=0 +for name in "${names[@]}"; do + # drawio's -p page selector is 1-based + xvfb-run -a drawio -x -f png --scale 2 -p "$((i+1))" \ + -o "so3_${name}.png" so3.drawio \ + --no-sandbox --disable-gpu >/dev/null 2>&1 || true + if [ -f "so3_${name}.png" ]; then + echo " [$i] so3_${name}.png ($(stat -c%s "so3_${name}.png") bytes)" + else + echo " [$i] FAILED: so3_${name}.png" + fi + i=$((i+1)) +done diff --git a/doc/source/img/gen_so3_diagrams.py b/doc/source/img/gen_so3_diagrams.py new file mode 100644 index 0000000000..370a024abb --- /dev/null +++ b/doc/source/img/gen_so3_diagrams.py @@ -0,0 +1,315 @@ +#!/usr/bin/env python3 +# Generator for source/img/so3.drawio (multi-page) and the exported PNGs. +# +# The .drawio file is the editable source of truth; this script lets us +# regenerate it from a compact description. After editing, export the PNGs +# with the helper at the bottom of this file (see README in doc/). +# +# python3 gen_so3_diagrams.py # (re)generate so3.drawio +# ./export_png.sh # render every page to PNG +# +import html + +# ---- palette ------------------------------------------------------------- +KERNEL = "fillColor=#dae8fc;strokeColor=#6c8ebf;" +USER = "fillColor=#d5e8d4;strokeColor=#82b366;" +AVZ = "fillColor=#ffe6cc;strokeColor=#d79b00;" +CAPS = "fillColor=#fff2cc;strokeColor=#d6b656;" +HW = "fillColor=#f8cecc;strokeColor=#b85450;" +NEUTRAL= "fillColor=#f5f5f5;strokeColor=#999999;" +WHITE = "fillColor=#ffffff;strokeColor=#666666;" +NONE = "fillColor=none;strokeColor=#444444;dashed=1;" + +BOX = "rounded=1;whiteSpace=wrap;html=1;arcSize=8;" +CONT= "rounded=1;whiteSpace=wrap;html=1;arcSize=4;verticalAlign=top;fontStyle=1;" +NOTE= "shape=note;whiteSpace=wrap;html=1;size=14;" +ARR = ("edgeStyle=orthogonalEdgeStyle;rounded=1;html=1;endArrow=block;" + "strokeColor=#444444;fontSize=10;") +ARR_D = ARR + "dashed=1;" + + +class Page: + def __init__(self, name): + self.name = name + self.cells = [] + self.n = 1 + + def _id(self): + self.n += 1 + return f"c{self.n}" + + @staticmethod + def _v(label): + # drawio renders \n only when encoded as a numeric char-ref + return html.escape(label).replace("\n", " ") + + def box(self, x, y, w, h, label, style=BOX, fontsize=12, fontstyle=0): + i = self._id() + s = f"{style}fontSize={fontsize};" + if fontstyle: + s += f"fontStyle={fontstyle};" + self.cells.append( + f'') + return i + + def label(self, x, y, w, h, text, fontsize=11, bold=False): + st = ("text;html=1;whiteSpace=wrap;align=center;verticalAlign=middle;" + f"fontSize={fontsize};{'fontStyle=1;' if bold else ''}") + i = self._id() + self.cells.append( + f'') + return i + + def edge(self, src, dst, label="", style=ARR): + i = self._id() + self.cells.append( + f'' + f'') + return i + + def xml(self): + body = "".join(self.cells) + return (f'' + f'' + f'{body}' + f'') + + +pages = [] + +# ========================================================================= +# 1. Polymorphic modes +# ========================================================================= +p = Page("modes") +p.label(0, 10, 1080, 30, "SO3 — one code base, three deployment modes", 16, True) +# Standalone +p.box(30, 70, 320, 460, "Standalone OS", CONT, 13, 1) +p.box(60, 120, 260, 70, "User space\n(init, shell, apps)", USER, 11) +p.box(60, 210, 260, 90, "SO3 kernel (EL1)\nmm · sched · vfs · net · drivers", KERNEL, 11) +p.box(60, 320, 260, 60, "Hardware (EL1)\nQEMU virt / RPi4 / i.MX8MP", HW, 11) +p.label(60, 400, 260, 90, "virt64_defconfig\n\nThe kernel owns the machine\ndirectly. No hypervisor.", 10) +# Hypervisor +p.box(390, 70, 320, 460, "Hypervisor mode", CONT, 13, 1) +p.box(420, 120, 120, 70, "Agency\n(SO3, EL1)", USER, 10) +p.box(560, 120, 120, 70, "RT agency\n(optional)", USER, 10) +p.box(420, 210, 260, 80, "AVZ hypervisor (EL2)\ndomains · stage-2 · vGIC", AVZ, 11) +p.box(420, 310, 260, 60, "Hardware (EL2/EL1)", HW, 11) +p.label(420, 390, 260, 110, "virt64_avz_defconfig\n\nAVZ (Agency VirtualiZer) runs\nat EL2 and hosts the agency\nguest at EL1.", 10) +# Hypervisor + capsules +p.box(750, 70, 320, 460, "SOO / capsule mode", CONT, 13, 1) +p.box(780, 120, 90, 60, "Linux\nagency (EL1)", USER, 9) +p.box(880, 120, 55, 60, "S3C\n#1", CAPS, 9) +p.box(945, 120, 55, 60, "S3C\n#2", CAPS, 9) +p.box(1010, 120, 40, 60, "…", CAPS, 9) +p.box(780, 200, 270, 80, "AVZ hypervisor (EL2)\ncapsule scheduling · grant tables · vbstore", AVZ, 10) +p.box(780, 300, 270, 60, "Hardware", HW, 11) +p.label(780, 385, 270, 120, "SO3 capsules (S3C) run as\nlightweight EL1 guests beside a\nLinux agency.\n\nThe Linux agency and the SOO\nframework live in a separate\nrepository.", 9) +pages.append(p) + +# ========================================================================= +# 2. System architecture (standalone) +# ========================================================================= +p = Page("architecture") +p.label(0, 10, 1080, 30, "SO3 system architecture (standalone)", 16, True) +p.box(40, 60, 1000, 120, "User space (EL0)", CONT, 13, 1) +for i, (lbl) in enumerate(["init.elf", "sh.elf", "ls / cat /\nmore / echo", "ping", "LVGL\ndemos", "MicroPython"]): + p.box(70 + i*155, 100, 140, 60, lbl, USER, 10) +p.box(40, 210, 1000, 300, "Kernel space (EL1)", CONT, 13, 1) +# syscall layer +p.box(70, 250, 940, 40, "System call interface (svc → trap_handle → syscall_table)", NEUTRAL, 11) +subs = [ + ("Process &\nThread mgmt", "kernel/ — pcb, tcb,\nround-robin sched"), + ("Memory mgmt", "mm/ — frame table,\nheap, MMU"), + ("VFS", "fs/ — fat, devfs,\nELF loader"), + ("IPC", "ipc/ — signals,\npipes, semaphores"), + ("Networking", "net/ — lwIP\nsockets"), +] +for i, (t, d) in enumerate(subs): + x = 70 + i*188 + p.box(x, 310, 175, 80, f"{t}\n\n{d}", KERNEL, 10) +p.box(70, 410, 940, 80, "Device model & drivers (devices/)\n" + "FDT probe · GIC (irq) · timer · pl011 serial · mmc · framebuffer · input · net (smc911x) · ramdev", + KERNEL, 10) +p.box(40, 540, 1000, 60, "Hardware — described by a Device Tree (dts/*.dtb)", HW, 12) +pages.append(p) + +# ========================================================================= +# 3. Boot flow +# ========================================================================= +p = Page("boot") +p.label(0, 20, 1080, 30, "Boot flow (QEMU virt / U-Boot / FIT image)", 16, True) +EX = ARR + "exitX=1;exitY=0.5;entryX=0;entryY=0.5;" # left→right +ub = p.box(40, 90, 190, 64, "U-Boot\nloads the FIT (.itb)\nfrom the SD-card", NEUTRAL, 10) +fit = p.box(280, 90, 190, 64, "FIT image (ITB)\nkernel + DTB + ramfs\n(target/*.its)", NEUTRAL, 10) +av = p.box(540, 78, 190, 44, "AVZ (EL2) avz_start()", AVZ, 10) +load= p.box(780, 78, 270, 44, "Loading Guest Domain — stage-2 → agency", AVZ, 10) +so = p.box(540, 168, 190, 44, "SO3 kernel (EL1)\n__start → kernel_start()", KERNEL, 10) +p.edge(ub, fit, "", EX) +p.edge(fit, av, "AVZ config") +p.edge(fit, so, "standalone") +p.edge(av, load, "", EX) +p.edge(load, so) +# kernel bring-up — snake: row A left→right, row B right→left +rowA = [ + ("memory_init()", "frame table + MMU"), + ("devices_init()", "FDT probe, GIC,\ntimer, serial"), + ("timer_init()\n+ calibrate_delay()", "tick source"), + ("scheduler_init()", "round-robin\n+ local_irq_enable()"), +] +rowB = [ + ("rest_init()", "first kernel thread"), + ("create_root_process()", "map __root_proc\n@ 0x1000 (EL0)"), + ("execve(\"init.elf\")", "SO3 Init Program"), + ("sh.elf", "so3% shell prompt"), +] +ax = [40, 295, 550, 805] +ids_a = [p.box(ax[i], 280, 235, 80, f"{rowA[i][0]}\n{rowA[i][1]}", KERNEL, 10) for i in range(4)] +ids_b = [p.box(ax[3-i], 410, 235, 80, f"{rowB[i][0]}\n{rowB[i][1]}", + USER if i >= 2 else KERNEL, 10) for i in range(4)] +p.edge(so, ids_a[0]) +for i in range(3): + p.edge(ids_a[i], ids_a[i+1], "", EX) +p.edge(ids_a[3], ids_b[0]) # scheduler → rest_init (drop down) +for i in range(3): # rowB flows right→left + p.edge(ids_b[i], ids_b[i+1], "", + ARR + "exitX=0;exitY=0.5;entryX=1;entryY=0.5;") +pages.append(p) + +# ========================================================================= +# 4. ARM64 address space (standalone, EL1) +# ========================================================================= +p = Page("memory") +p.label(0, 10, 1080, 30, "ARM64 virtual address space (standalone EL1)", 16, True) +# TTBR0 (user) +p.box(120, 70, 360, 500, "TTBR0_EL1 — user space\n0x0000_0000_0000_0000 … 0x0000_FFFF_FFFF_FFFF", CONT, 11, 1) +p.box(150, 120, 300, 50, "0x1000 __root_proc trampoline\n(initial EL0 entry)", USER, 9) +p.box(150, 190, 300, 60, "ELF text / data / bss\n(per-process, from init.elf)", USER, 9) +p.box(150, 270, 300, 50, "heap (brk, grows up)", USER, 9) +p.box(150, 470, 300, 50, "user stack (grows down)", USER, 9) +p.label(150, 330, 300, 120, "Each process owns a private\nL0 page table (pcb->pgtable).\nThe MMU uses TTBR0_EL1 for\nthe low half (bits [47:0]).", 9) +# TTBR1 (kernel) +p.box(620, 70, 360, 500, "TTBR1_EL1 — kernel space\nbase = CONFIG_KERNEL_VADDR = 0xFFFF_8000_0000_0000", CONT, 11, 1) +p.box(650, 120, 300, 45, "0xFFFF_8000_0008_0000\nvectors + .head.text", KERNEL, 9) +p.box(650, 175, 300, 45, ".text / .data / .bss", KERNEL, 9) +p.box(650, 230, 300, 45, "per-CPU data (.percpu_data)", KERNEL, 9) +p.box(650, 285, 300, 45, "system page tables (idmap, linear)", KERNEL, 9) +p.box(650, 340, 300, 45, "kernel heap (CONFIG_HEAP_SIZE_MB)", KERNEL, 9) +p.box(650, 395, 300, 45, "per-CPU kernel stacks", KERNEL, 9) +p.label(650, 450, 300, 100, "__pa(v) = v - KERNEL_VADDR + phys_base\nKERNEL_VADDR differs per mode:\nstandalone 0xFFFF800000000000\nAVZ (EL2) 0x0000100000000000", 9) +pages.append(p) + +# ========================================================================= +# 5. Exception levels +# ========================================================================= +p = Page("exception-levels") +p.label(0, 10, 1080, 30, "ARM64 exception levels: standalone vs. AVZ", 16, True) +# standalone +p.box(60, 70, 440, 420, "Standalone", CONT, 13, 1) +p.box(110, 120, 340, 60, "EL0 — user processes (init, shell, apps)", USER, 11) +p.box(110, 210, 340, 70, "EL1 — SO3 kernel\nVBAR_EL1 vectors · TTBR0/1_EL1", KERNEL, 11) +p.box(110, 320, 340, 60, "EL2 — unused", NONE, 11) +p.box(110, 400, 340, 50, "Hardware", HW, 11) +# AVZ +p.box(620, 70, 440, 420, "AVZ hypervisor", CONT, 13, 1) +p.box(670, 120, 160, 60, "EL0 — agency\nuser space", USER, 10) +p.box(850, 120, 160, 60, "EL0 — capsule\nuser space", CAPS, 10) +p.box(670, 200, 160, 60, "EL1 — agency\nSO3 kernel", USER, 10) +p.box(850, 200, 160, 60, "EL1 — capsule\nSO3 kernel", CAPS, 10) +p.box(670, 290, 340, 70, "EL2 — AVZ hypervisor\nVBAR_EL2 · VTTBR (stage-2) · vGIC · HCR_EL2", AVZ, 10) +p.box(670, 400, 340, 50, "Hardware", HW, 11) +pages.append(p) + +# ========================================================================= +# 6. Syscall path +# ========================================================================= +p = Page("syscall") +p.label(0, 10, 1080, 30, "System call path (AArch64)", 16, True) +a = p.box(60, 90, 200, 70, "EL0 user code\nx8 = syscall nr\nx0..x5 = args\nsvc #0", USER, 10) +b = p.box(320, 90, 220, 70, "VBAR_EL1 + 0x400\n“Lower EL / Synchronous”\nb el01_sync_handler", KERNEL, 10) +c = p.box(600, 90, 220, 70, "el01_sync_handler\nkernel_entry (save regs)\nprepare_to_enter_to_el1", KERNEL, 10) +d = p.box(880, 90, 180, 70, "trap_handle()\ndecode ESR_EL1\n(EC = SVC64?)", KERNEL, 10) +e = p.box(880, 220, 180, 70, "syscall_handle()\nbounds-check nr", KERNEL, 10) +f = p.box(600, 220, 220, 70, "syscall_table[nr]\n(generated from\nsyscall.tbl)", KERNEL, 10) +g = p.box(320, 220, 220, 70, "sys_xxx()\nSYSCALL_DEFINEn()", KERNEL, 10) +h = p.box(60, 220, 200, 70, "return value → x0\nkernel_exit · eret\n→ back to EL0", USER, 10) +for s, t in [(a,b),(b,c),(c,d),(d,e),(e,f),(f,g),(g,h)]: + p.edge(s, t) +p.label(60, 330, 1000, 40, "syscall.tbl is processed by scripts/syscall_gen.sh into " + "generated/syscall_table.h.in, giving the kernel and the MUSL libc a common ABI.", 10) +pages.append(p) + +# ========================================================================= +# 7. AVZ domains + vGIC +# ========================================================================= +p = Page("avz") +p.label(0, 10, 1080, 30, "AVZ: domains, stage-2 isolation and vGIC injection", 16, True) +p.box(60, 70, 980, 230, "Guests (EL1) — isolated by per-domain stage-2 tables (VTTBR)", CONT, 12, 1) +ag = p.box(100, 120, 200, 150, "Agency (DOMID_AGENCY)\nowns the devices\nvbstore server\nruns on CPU 0", USER, 10) +c1 = p.box(330, 120, 180, 150, "Capsule (S3C) slot 2\nSO3 guest\nstage-2 isolated", CAPS, 10) +c2 = p.box(540, 120, 180, 150, "Capsule (S3C) slot 3\nSO3 guest", CAPS, 10) +c3 = p.box(750, 120, 260, 150, "… up to MAX_S3C_DOMAINS\n(2 agency + 5 capsules)\nrun on the capsule CPU", CAPS, 10) +p.box(60, 330, 980, 170, "AVZ hypervisor (EL2)", CONT, 12, 1) +p.box(100, 370, 200, 100, "Domain scheduler\nsched_agency\nsched_flip (capsules)\nper-CPU current_domain", AVZ, 10) +p.box(330, 370, 200, 100, "Hypercalls (hvc #0)\nconsole · event channels\ndomain control · grant tables", AVZ, 10) +p.box(560, 370, 220, 100, "vGIC\nphys IRQ → EL2 (HCR.IMO)\ninject via list registers (HW=1)\nmaintenance IRQ", AVZ, 10) +p.box(810, 370, 200, 100, "Grant tables &\nevent channels\ninter-domain memory\n+ signalling", AVZ, 10) +hw = p.box(60, 530, 980, 50, "Physical GIC · generic timer · UART · RAM", HW, 11) +pages.append(p) + +# ========================================================================= +# 8. Capsule split drivers / vbus / vbstore +# ========================================================================= +p = Page("capsule") +p.label(0, 10, 1080, 30, "SO3 capsules: split drivers (vbus / vbstore)", 16, True) +# vbus split +p.box(60, 70, 470, 330, "Split-driver model (per device)", CONT, 12, 1) +fe = p.box(95, 120, 180, 90, "Capsule — frontend\n(S3C, EL1)\nvfbdev / vinput /\nvuart / vuihandler", CAPS, 10) +be = p.box(335, 120, 160, 90, "Agency — backend\n(Linux, EL1)\nreal device", USER, 10) +ring = p.box(160, 250, 250, 60, "shared ring + event channel\n(pages shared via grant tables)", NEUTRAL, 9) +p.edge(fe, be, "vbstore handshake", ARR_D) +p.edge(fe, ring); p.edge(be, ring) +p.label(95, 330, 400, 50, "Each virtual device is split: the frontend in the\ncapsule talks to a backend in the Linux agency.", 9) +# vbstore +p.box(570, 70, 470, 330, "vbstore (Xenstore-like config tree)", CONT, 12, 1) +p.box(605, 120, 400, 40, "device///state → Connected", WHITE, 9) +p.box(605, 175, 400, 40, "ring-ref · event-channel · protocol params", WHITE, 9) +p.label(605, 230, 400, 90, "Frontend and backend rendez-vous through the\nvbstore tree; the state machine drives probe(),\nconnect, suspend and resume of each device.", 9) +p.box(605, 330, 400, 50, "snapshot: AVZ_S3C_READ/WRITE_SNAPSHOT\nsave & restore a capsule's state", AVZ, 9) +pages.append(p) + +# ========================================================================= +# 9. Device / driver model +# ========================================================================= +p = Page("device-model") +p.label(0, 10, 1080, 30, "Device & driver model (FDT-driven)", 16, True) +a = p.box(60, 90, 200, 80, "Device Tree blob\n(dts/*.dtb)\nloaded by U-Boot", NEUTRAL, 10) +b = p.box(320, 90, 200, 80, "parse_dtb()\nwalk FDT nodes\nmatch “compatible”", KERNEL, 10) +c = p.box(580, 90, 220, 80, "initcall sections\n(.initcall_driver_*\ncore / postcore)\nvia ll_entry", KERNEL, 10) +d = p.box(860, 90, 200, 80, "driver .init(dev,\nfdt_offset)\nprobe hardware", KERNEL, 10) +e = p.box(580, 230, 220, 80, "devclass_register()\nclass + fops + id\n(fb, input, serial,\nnic, char, …)", KERNEL, 10) +f = p.box(320, 230, 200, 80, "devfs\nexposes /dev/", KERNEL, 10) +g = p.box(60, 230, 200, 80, "User space\nopen(\"/dev/...\")\nread/write/ioctl", USER, 10) +for s, t in [(a,b),(b,c),(c,d),(d,e),(e,f),(f,g)]: + p.edge(s, t) +p.label(60, 360, 1000, 60, + "Drivers register with REGISTER_DRIVER_CORE / _POSTCORE, which place an initcall entry in a dedicated " + "linker section. At boot parse_dtb() matches each FDT node's “compatible” string to a driver and calls its " + "init() callback; the driver then publishes a devclass that VFS/devfs makes reachable as a /dev entry.", 10) +pages.append(p) + +# ---- emit ---------------------------------------------------------------- +out = ('' + + "".join(pg.xml() for pg in pages) + "") +with open("so3.drawio", "w") as fh: + fh.write(out) +print(f"wrote so3.drawio with {len(pages)} pages:") +for i, pg in enumerate(pages): + print(f" [{i}] {pg.name}") diff --git a/doc/source/img/so3.drawio b/doc/source/img/so3.drawio new file mode 100644 index 0000000000..8e417eba83 --- /dev/null +++ b/doc/source/img/so3.drawio @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/doc/source/img/so3_architecture.png b/doc/source/img/so3_architecture.png new file mode 100644 index 0000000000..02a031cf87 Binary files /dev/null and b/doc/source/img/so3_architecture.png differ diff --git a/doc/source/img/so3_avz.png b/doc/source/img/so3_avz.png new file mode 100644 index 0000000000..e94c5ea080 Binary files /dev/null and b/doc/source/img/so3_avz.png differ diff --git a/doc/source/img/so3_boot.png b/doc/source/img/so3_boot.png new file mode 100644 index 0000000000..ab2aad99fc Binary files /dev/null and b/doc/source/img/so3_boot.png differ diff --git a/doc/source/img/so3_capsule.png b/doc/source/img/so3_capsule.png new file mode 100644 index 0000000000..1b2e1de024 Binary files /dev/null and b/doc/source/img/so3_capsule.png differ diff --git a/doc/source/img/so3_device_model.png b/doc/source/img/so3_device_model.png new file mode 100644 index 0000000000..d69b991786 Binary files /dev/null and b/doc/source/img/so3_device_model.png differ diff --git a/doc/source/img/so3_exception_levels.png b/doc/source/img/so3_exception_levels.png new file mode 100644 index 0000000000..46efcd691a Binary files /dev/null and b/doc/source/img/so3_exception_levels.png differ diff --git a/doc/source/img/so3_memory.png b/doc/source/img/so3_memory.png new file mode 100644 index 0000000000..2d46501c91 Binary files /dev/null and b/doc/source/img/so3_memory.png differ diff --git a/doc/source/img/so3_modes.png b/doc/source/img/so3_modes.png new file mode 100644 index 0000000000..049bdc7099 Binary files /dev/null and b/doc/source/img/so3_modes.png differ diff --git a/doc/source/img/so3_syscall.png b/doc/source/img/so3_syscall.png new file mode 100644 index 0000000000..3893384123 Binary files /dev/null and b/doc/source/img/so3_syscall.png differ diff --git a/doc/source/index.rst b/doc/source/index.rst index 7c04522b75..5fb27483c7 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -5,71 +5,108 @@ :width: 180px :height: 70px :target: http://reds.heig-vd.ch/en/rad - + .. image:: img/SO3-Logo.png :align: center :width: 200px :height: 150px .. toctree:: - :maxdepth: 4 + :maxdepth: 2 :numbered: :hidden: - + :caption: Concepts & architecture + introduction architecture + kernel + avz + capsules + +.. toctree:: + :maxdepth: 2 + :numbered: + :hidden: + :caption: Building & running + + build_system user_guide + user_space + debugging so3_jtag_rpi4 + +.. toctree:: + :maxdepth: 2 + :numbered: + :hidden: + :caption: Integrations & conventions + lvgl lwip micropython Coding conventions - + ============================================ Smart Object Oriented (SO3) Operating System ============================================ -Smart Object Oriented Operating system is a compact, lightweight, full featured and extensible -operating system particularly well-suited for embedded systems in general. +SO3 is a compact, lightweight, full-featured and extensible operating system, +particularly well suited to embedded systems. From a single code base it can be +built in three ways: + +* as a **standalone OS** running directly on the hardware (EL1 on ARM64); +* as the **AVZ hypervisor** (*Agency VirtualiZer*) running at EL2 and hosting a + guest *agency* domain; +* as an **SO3 capsule** (S3C) — a lightweight guest running on top of AVZ beside + a Linux *agency*, as part of the **SOO** framework. + +.. figure:: img/so3_modes.png + :width: 100% -The documentation is constantly evolving over the time and further details will be -available soon. + The same SO3 code base deployed in its three modes. -SO3 Concepts and Architecture -============================= +This documentation reflects the current state of the code base: ARM 32/64-bit, +multicore, the AVZ hypervisor and SO3 capsules are all supported. -- :ref:`Introduction to SO3 ` -- :ref:`Architecture ` +Where to start +============== -Setup and Environment -===================== +:ref:`Introduction ` + Philosophy, history and the polymorphic nature of SO3. -- :ref:`User Guide ` -- :ref:`Debugging with JTAG on Raspberry Pi 4 ` +:ref:`Architecture ` · :ref:`Kernel internals ` + The user/kernel split, the boot flow, and how the kernel subsystems work. + +:ref:`AVZ hypervisor ` · :ref:`SO3 capsules ` + Virtualization at EL2 and the SOO capsule framework. + +:ref:`Build system ` · :ref:`User guide ` + How the tree is built, configured and packaged, and a step-by-step setup. + +:ref:`User space ` · :ref:`Debugging ` + The MUSL-based user land, and how to debug SO3 under QEMU/GDB or JTAG. - Development flow ================ -The master contains the last released version of the SOO framework. +The ``main`` branch contains the last released version. .. important:: - It is not allowed to push directly to the master. Please do a merge - request as soon as your development is stable. - -If you want to contribute, please first contact `the maintainer `__ and explain your motivation so that -you can be granted as developer. -Each development leads to a new issue with its related branch. You can develop freely, add comments along the issue -and perform a merge request as soon as your development gets stable enough. A review will be done and your contributions -will be merged in the master branch. + Do not push directly to ``main``. Each development leads to an issue with its + own branch; open a merge/pull request as soon as it is stable enough for + review. +If you want to contribute, please first contact `the maintainer `__. Discussion forum ================ -A `dedicated discussion forum `__ -is available for all questions/remarks/suggestions related to SO3. -Do not hesitate to create topics and to contribute. - +A `dedicated discussion forum `__ is +available for all questions, remarks and suggestions related to SO3. + +We would like to thank our sponsors for their generous support in funding the +development of the SO3 ecosystem, especially `HEIG-VD `__ +and the `Hasler Foundation `__. + .. _SOO_mail: info@soo.tech diff --git a/doc/source/introduction.rst b/doc/source/introduction.rst index 40bae40e0e..b9b30dcbac 100644 --- a/doc/source/introduction.rst +++ b/doc/source/introduction.rst @@ -1,82 +1,99 @@ .. _introduction: Introduction to SO3 -=================== - -SO3 (*Smart Object Oriented*) is a compact, extensible and full featured operating system which is particularly suited to embedded systems -such as IoT devices, real-time environment, academic development, etc. - -SO3 Operating System results from several Years of research and development at -`REDS `__ (Reconfigurable Embedded Digital Systems) Institute from `HEIG-VD `__, -in the field of embedded operating systems and execution environment with ARM 32/64-bit multicore systems. SO3 has -been publicly released in early 2020 (see also `HEIG-VD newsletter `__). - -`Prof. Daniel Rossier `__ initiated the development of an operating system in 2013 in the context of a Bachelor -lecture focusing on the port of operating systems on different embedded platforms. The OS has been constantly evolved -and used in other lectures (like operating systems of course) and since 2018 became SO3 to be used as -a full *migrating* execution environment between connected devices; this novel approach leads to the -`SOO framework `__ which is still in development. - -More recently, SO3 is become a *polymorphic* operating system, meaning that it can be configured to -run as an hypervisor, a standalone OS or a guest on top of the hypervisor. The hypervisor is called **AVZ** -(Agency VirtualiZer) and is aimed at running with or without ARM VT support. +################### + +SO3 (*Smart Object Oriented*) is a compact, extensible and full-featured +operating system that is particularly suited to embedded systems such as IoT +devices, real-time environments and academic development. + +SO3 is the result of several years of research and development at the +`REDS `__ (Reconfigurable Embedded Digital Systems) Institute of +`HEIG-VD `__, in the field of embedded operating systems and +execution environments for ARM 32/64-bit multicore systems. SO3 was publicly +released in early 2020 (see also the `HEIG-VD newsletter `__). + +`Prof. Daniel Rossier `__ initiated the development of an operating system +in 2013, in the context of a Bachelor lecture focusing on the port of operating +systems to different embedded platforms. The OS has evolved constantly and is +used in several lectures (operating systems, embedded systems); since 2018 it +became SO3 and grew into a full execution environment for connected devices — +the foundation of the `SOO framework `__. + +A polymorphic operating system +============================== + +The most distinctive feature of SO3 is that it is **polymorphic**: a single +source tree can be configured and built into three different kinds of system. + +Standalone OS + SO3 runs directly on the hardware as a conventional monolithic OS. On ARM64 + the kernel runs at **EL1** and user applications at **EL0**. This is the + configuration used for teaching and for plain embedded products + (``virt64_defconfig``, ``rpi4_64_defconfig``, …). + +AVZ hypervisor + The same tree, built with ``CONFIG_AVZ``, becomes **AVZ** (*Agency + VirtualiZer*) — a lightweight type-1 hypervisor running at **EL2**. AVZ hosts + one or more guest *domains*; the primary guest is the **agency**, a normal + SO3 (or Linux) running at EL1. See :ref:`avz`. + +SO3 capsule + On top of AVZ, the **SOO** framework adds *SO3 capsules* (**S3C**): + lightweight, self-contained guests that run at EL1 beside a Linux *agency* and + cooperate with it through split (frontend/backend) drivers. The capsule + (guest) side lives in this repository; the Linux agency and the rest of the + SOO framework live in a separate repository. See :ref:`capsules`. + +.. note:: + + *SO3 Capsule*, abbreviated **S3C**, is the current name and acronym for what + older code and papers called a *Mobile Entity* (ME). The code base now uses + the ``S3C`` acronym in identifiers and *capsule* in prose; the ``ME`` spelling + no longer appears. Approach and philosophy ------------------------ - -Today, there are plenty of operating systems especially in the area of IoT devices and embedded systems in general. -All these OS oviously have their own characteristics, pros and cons, but it still remains pretty hard to find an -OS which supports most of *traditional* features such as support for a user and kernel space, MMU, rootfs, -realtime feature, etc. while keeping a simple OS with low memory footprint and minimal configuration. - -The philosophy of SO3 is to keep an OS as compact as possible, which can be used not only in an academic environment -to provide students an OS envronment with a reasonible complexity, but in industry oriented projects where some -customization and rapid prototyping is required. +======================= -Of course, it does not consist in reiventing the wheel, but in opposite, to benefit from various experiences with existing -operating systems, emulation and virtualization frameworks. SO3 is mainly based on Linux principles; for example, its build system -fully relies on the KBuild and some portions of code (but not so many) come from Linux (examples are the very nice mechanisms -of linked list (``struct list_head``) and related macros, or *bitops* and other *types* declarations). -It is also one of the main reason why SO3 is released under the GPLv2 licence. +There are countless operating systems for IoT and embedded systems. They all +have their own characteristics, but it remains hard to find an OS that supports +the *traditional* features — user/kernel separation, MMU, a real root +filesystem, real-time behaviour — while staying small, simple and quick to +configure. +The philosophy of SO3 is to keep the OS **as compact as possible**: small +enough to be a reasonable-complexity teaching platform, yet complete enough for +industrial prototyping and customization. -Emulation is a **key** concept ------------------------------- +SO3 does not reinvent the wheel: it draws on long experience with other +operating systems and virtualization frameworks. It is mainly inspired by Linux +— its build system is based on **Kbuild**, and a few well-proven mechanisms +(the ``struct list_head`` linked lists and related macros, *bitops*, common type +definitions) come from Linux. This is also why SO3 is released under the +**GPLv2** licence. -From the beginning, the ``QEMU`` emulator constituted a crucial tool to understand and to grasp concepts in an operating system -like Linux, and it also essential to elaborate and to debug an OS in a deep and affordable way. At the REDS Institute, -we used QEMU since 2006 and have a strong experience in emulated environment. -Now, the emulated *vExpress* machine in QEMU is used as a reference platform for the 32-bit configuration (virt64 will -be used as soon as the AArch64 support will be integrated). +Emulation is a key concept +========================== -Besides emulation, the Raspberry Pi 4 is currently our platform of choice as real hardware. Its quadcore Cortex-A72 -can be used in a wide range of applications and brings along many interesting features. +From the very beginning, the **QEMU** emulator has been a crucial tool to grasp +operating-system concepts and to develop and debug SO3 in a deep yet affordable +way. REDS has used QEMU since 2006. +The reference emulated platform is QEMU's **virt** machine: ``virt64`` (ARM64, +Cortex-A72) for the 64-bit configuration and ``virt32`` (ARM, Cortex-A15) for +the 32-bit one. As real hardware, the **Raspberry Pi 4** (quad-core Cortex-A72) +is the main target; the **Toradex Verdin i.MX8M Plus** is also supported. SO3 as an experimental environment ----------------------------------- +================================== -One of the major advantage with SO3 is its time of compilation. The kernel is compiled (from scratch) in less than 10 secs -and the user space less than 20 secs depending on what is compiled. -Therefore, SO3 can be used a very nice environment to experiment different things; testing a kernel function, -processor features, or compilation tricks are easily achieved. - -Ongoing work with SO3 ---------------------- - -SO3 is evolving constantly. Here is a non-exhaustive list of future work: - -- Improvement of the networking based on lwIP -- Support of 64-bit (AArch64) -- Support of multi-cores -- Porting SO3 on a RISC-V architecture (Diploma work) - -And finally, we are looking for passoniated contributors :-) Please do not hesitate to `contact us `__. +A major advantage of SO3 is its **compilation time**. The kernel builds from +scratch in a few seconds, and the user space in a few tens of seconds depending +on what is selected. SO3 is therefore an excellent environment to experiment: +trying out a kernel function, a processor feature or a compilation trick is +quick and cheap. .. _REDS: http://www.reds.ch .. _HEIG-VD: http://www.heig-vd.ch -.. _heig-vd_news: https://heig-vd.ch/actualites?utm_medium=email&utm_campaign=Newsletter%20externe%2039&utm_content=Newsletter%20externe%2039+CID_db69309487920998ee2eaa75dc3cab5a&utm_source=heig%20vd&utm_term=PLUS%20DINFORMATIONS#/2020/02/11/so3systemeexploitation +.. _heig-vd_news: https://heig-vd.ch/ .. _DRE: https://reds.heig-vd.ch/en/team/details/daniel.rossier -.. _DRE_mail: info@soo.tech - - diff --git a/doc/source/kernel.rst b/doc/source/kernel.rst new file mode 100644 index 0000000000..78298d83db --- /dev/null +++ b/doc/source/kernel.rst @@ -0,0 +1,210 @@ +.. _kernel: + +Kernel Internals +################ + +This chapter describes the main subsystems of the SO3 kernel as they exist in +the source tree. Unless stated otherwise the discussion targets the ARM64 +(``arch/arm64``) port; the ARM32 port follows the same structure with 2-level +page tables and the classic AArch32 register model. + +Memory management +================= + +Page-frame allocator +-------------------- + +Physical memory is tracked by a **frame table** (``mm/memory.c``): an array with +one entry per physical page, recording whether the page is free and its +reference count. The amount of RAM and its base address are obtained from the +device tree at boot (``get_mem_info()``). ``get_free_page()`` returns a free +physical page (the table is protected by a spinlock); pages are released back to +the table when their reference count drops to zero. + +Kernel heap +----------- + +Dynamic kernel allocations use a **quick-fit** heap (``mm/heap.c``) sized by +``CONFIG_HEAP_SIZE_MB`` and reserved by the linker script. ``malloc()`` / +``free()`` operate on this heap; chunks carry small metadata headers and are +kept on free lists by size. + +MMU and address translation +--------------------------- + +The page tables are built in ``arch/arm64/mmu.c``. On ARM64 SO3 uses up to +**four levels** (L0–L3) with 4 KiB granularity; the presence of the top L0 level +depends on ``CONFIG_VA_BITS_48``. ``create_mapping()`` installs a virtual→ +physical mapping in a given page table, allocating intermediate tables as needed +and choosing block (1 GiB / 2 MiB) or page (4 KiB) descriptors according to +alignment and size. The kernel runs from the high half (``TTBR1_EL1``); each +process has its own low-half table (``TTBR0_EL1``). See +:ref:`the address-space section ` for the layout and the +``CONFIG_KERNEL_VADDR`` values. + +Threads and scheduling +====================== + +The unit of execution is a **thread**, described by a *Task Control Block* +(``tcb_t``, ``include/thread.h``): thread id, priority, state +(``READY`` / ``RUNNING`` / ``WAITING`` / ``ZOMBIE`` …), a saved CPU context +(``cpu_regs_t``) and a stack slot. Threads are either **kernel threads** +(``kernel_thread()``) or **user threads** (``user_thread()``) attached to a +process. + +The scheduler lives in ``kernel/schedule.c``. The default policy is +**round-robin** (``CONFIG_SCHED_RR``) with timer-driven preemption +(``CONFIG_SCHED_FREQ_PREEMPTION``); a fixed-priority policy +(``CONFIG_SCHED_PRIO``) is also available. ``schedule()`` selects the next +runnable thread and calls ``__switch_to()`` (``arch/arm64/context.S``), which +saves the callee-saved registers (x19–x29, ``sp``, ``lr``) of the outgoing +thread into its TCB and restores them from the incoming one. + +Processes +========= + +A **process** (``pcb_t``, ``include/process.h``) owns an address space +(its L0 page table), a heap, a set of file descriptors and one or more threads. +Processes are created by the usual ``fork()`` / ``execve()`` pair. + +The very first process is built by ``create_root_process()`` +(``kernel/process.c``). It maps the compiled-in ``.root_proc.text`` trampoline +(``__root_proc`` in ``arch/arm64/context.S``) at ``USER_SPACE_VADDR`` +(``0x1000``) and starts a user thread there. The trampoline immediately issues +an ``execve("init.elf")`` so that the first *real* program is the init process. + +ELF binaries are parsed and loaded by ``fs/elf.c`` (``elf_load_buffer()`` reads +the file through the VFS; the loadable segments are then mapped into the +process' address space). + +System calls +============ + +User code enters the kernel with the ``svc`` instruction. The system-call number +is passed in **x8** and the arguments in **x0–x5** (AArch64 ABI). + +.. figure:: img/so3_syscall.png + :width: 100% + + The AArch64 system-call path. + +The ``svc`` traps to the *Lower EL / Synchronous* slot of the EL1 vector table +(``VBAR_EL1 + 0x400``, ``arch/arm64/exception.S``), which branches to +``el01_sync_handler``. That handler saves the user context and calls +``trap_handle()`` (``arch/arm64/traps.c``), which decodes ``ESR_EL1``; for an +``SVC64`` exception it forwards to ``syscall_handle()`` (``kernel/syscalls.c``). + +The dispatch table ``syscall_table[]`` is **generated at build time** from +``syscall.tbl`` by ``scripts/syscall_gen.sh`` (producing +``generated/syscall_table.h.in``), giving the kernel and the MUSL libc a common +ABI. Each entry is a ``sys_xxx()`` function declared with the +``SYSCALL_DEFINEn()`` macros. The return value is placed back in ``x0`` and the +handler ``eret``\ s to EL0. + +.. warning:: + + The vector table must contain **all 16** architectural slots, correctly + spaced. Omitting a slot (for instance behind a bare ``#ifdef CONFIG_AVZ``) + shifts the lower-EL vectors and silently misroutes ``svc`` to the wrong + handler — see the SError slot in ``arch/arm64/exception.S``. + +Inter-process communication +=========================== + +The ``ipc/`` directory provides: + +* **signals** (``signal.c``) — POSIX-like signals checked on return to user + space; ``rt_sigaction()``, ``kill()``, signal masks, default actions; +* **pipes** (``pipe.c``) — in-kernel FIFOs with blocking read/write backed by + completions; +* **semaphores** (``semaphore.c``) and **completions** (``completion.c``) — the + kernel's synchronisation primitives, also used internally by drivers and the + scheduler. + +Virtual filesystem +================== + +The **VFS** (``fs/vfs.c``) maintains a global file-descriptor table; each +process maps its local descriptors (``pcb->fd_array``) onto global ones. File +operations are dispatched to the filesystem that owns the file. The kernel +registers: + +* **FAT** (``fs/fat/``) — a FAT driver used for the root filesystem, whether it + is a RAM disk (``CONFIG_ROOTFS_RAMDEV``) packed in the FIT image, or an MMC + card (``CONFIG_ROOTFS_MMC``); +* **devfs** (``fs/devfs/``) — a virtual filesystem that exposes registered + devices under ``/dev``. + +``vfs_init()`` is called early in ``kernel_start()`` and sets up these +filesystems before the root process is launched. + +Device and driver model +======================== + +SO3 uses a **device-tree-driven** driver model. + +.. figure:: img/so3_device_model.png + :width: 100% + + From the device tree to a ``/dev`` entry. + +Drivers register with the ``REGISTER_DRIVER_CORE`` / ``REGISTER_DRIVER_POSTCORE`` +macros, which place an *initcall* entry into a dedicated linker section +(``.initcall_driver_initcall_t_core`` / ``…_postcore`` in the linker script, +reached at runtime through the ``ll_entry_*`` helpers). At boot, +``parse_dtb()`` (``devices/``) walks the flattened device tree, matches each +node's ``compatible`` string against the registered drivers and calls the +driver's ``init(dev, fdt_offset)`` callback. A driver then publishes a +**device class** (``devclass``) with its file operations and a device-id range; +``devfs`` makes it reachable as ``/dev/``. + +The tree contains drivers for the main device classes: + +.. flat-table:: + :header-rows: 1 + :widths: 25 75 + + * - Class (``devices/``) + - Drivers + * - interrupt controller (``irq/``) + - ARM GIC (v2/v3), and the virtual GIC for AVZ (see :ref:`avz`) + * - timer (``timer/``) + - ARM generic timer (``arm_timer``) + * - serial (``serial/``) + - PL011, NS16550, i.MX UART + * - storage (``mmc/``, ``ramdev/``) + - SD/MMC controller, in-memory RAM disk + * - framebuffer (``fb/``) + - PL111, ramfb, virtfb (used by LVGL) + * - input (``input/``) + - PS/2 (PL050, KMI), virtio keyboard/mouse + * - network (``net/``) + - smc911x (``smsc,smc911x``), wired to lwIP — optional (``CONFIG_NET``) + * - i2c, rpisense (``i2c/``, ``rpisense/``) + - I²C bus and Raspberry Pi Sense HAT + +Interrupts and time +=================== + +The exception vectors are installed at ``VBAR_EL1`` (``arch/arm64/exception.S``). +Hardware interrupts are handled by the **GIC** driver (``devices/irq/gic.c``): +on an IRQ the handler reads the interrupt acknowledge register, dispatches to the +registered handler and signals end-of-interrupt. In the standalone (EL1) +configuration the CPU interface uses ``EOImode = 0`` (a single ``EOIR`` write +both drops priority and deactivates the interrupt). The AVZ (EL2) configuration +uses ``EOImode = 1`` and the virtual GIC to forward interrupts to guests — see +:ref:`avz`. + +Time is provided by the ARM generic timer (``devices/timer/arm_timer.c``). A +periodic tick drives the scheduler; ``calibrate_delay()`` (run once during +bring-up) waits for the first ticks to compute the busy-loop delay constant. + +Networking +========== + +SO3 integrates the **lwIP** TCP/IP stack (``net/lwip/``). The kernel exposes a +BSD-style socket API; the ``net/`` glue maps VFS file descriptors onto lwIP +sockets so that ``socket()`` / ``bind()`` / ``connect()`` / ``send()`` / +``recv()`` work from user space. Networking is **opt-in** (``CONFIG_NET``, off in +the default ``virt64_defconfig``); the NIC driver in ``devices/net/`` is an +smc911x (``smsc,smc911x``) MAC. See :ref:`lwip`. diff --git a/doc/source/lvgl.rst b/doc/source/lvgl.rst index 33bf2f3e76..3a0da27b17 100644 --- a/doc/source/lvgl.rst +++ b/doc/source/lvgl.rst @@ -1,41 +1,42 @@ +.. _lvgl: -LVGL - Light and Versatile Embedded Graphics Library -==================================================== +LVGL — Light and Versatile Embedded Graphics Library +#################################################### -LVGL is a free and open-source library providing an efficient GUI for embedded systems. -More information is available on the `official site `__. +`LVGL `__ is a free and open-source library providing an +efficient GUI for embedded systems. Integration of LVGL in SO3 --------------------------- +========================== -The major work of porting LVGL in SO3 has been done by Nikolaos Garanis in the context of -his `Bachelor project `_. - -Some details about the porting can be found in our `discussion forum `__. - -There is `a small video `__ to show LVGL running in the QEMU/vExpress framebuffer emulated environment. - -In addition, from LVGL v8, the ``lv_demo_widgets`` is now fully supported. And yes, SO3 integrates LVGL v8. - -This part will be completed very soon... +The initial port of LVGL to SO3 was done by Nikolaos Garanis in the context of +his `Bachelor project `__; some +details are available on our `discussion forum +`__ and in +`a short video `__. +SO3 integrates **LVGL v8**, and the ``lv_demo_widgets`` demo is fully supported. +On the kernel side LVGL draws to the SO3 framebuffer device (``devices/fb/``, +exposed through ``/dev`` — see :ref:`kernel`); the LVGL library itself is built +into the user space (``usr/lib/lvgl``) and used by the ``lvgl_demo``, +``lvgl_perf`` and ``lvgl_benchmark`` applications. .. _LVGL_qemu: https://youtu.be/skn_mp3ZBhI -Using LVGL in the emulated environment --------------------------------------- +Running LVGL under QEMU +======================= .. note:: - First, make sure you compiled the kernel with a configuration - which has the framebuffer enabled (for example, *vexpress_fb_defconfig*) - -In order to have a graphical framebuffer in the emulated QEMU/vExpress -environment, it is necessary to start the emulator with the ``stg`` script: + Build the kernel with a framebuffer-enabled configuration, for example + ``virt64_fb_defconfig``, and the user space with the LVGL apps. + +The framebuffer needs a real display window, so start the emulator with the +graphical launch script instead of ``./st``: .. code-block:: bash ./stg - -QEMU will start a new GUI window used as framebuffer. +QEMU opens a window that acts as the framebuffer; launch an LVGL application from +the ``so3%`` prompt to draw into it. diff --git a/doc/source/lwip.rst b/doc/source/lwip.rst index 6cb3e2c718..987a0811b1 100644 --- a/doc/source/lwip.rst +++ b/doc/source/lwip.rst @@ -1,16 +1,37 @@ -lwIP - Lightweigth IP -===================== +.. _lwip: -lwIP is a small independent implementation of the TCP/IP protocol suite. +lwIP — Lightweight IP +##################### -Information about lwIP can be obtained on the `official site `__. +`lwIP `__ is a small, independent +implementation of the TCP/IP protocol suite, designed for embedded systems with +limited resources. Integration of lwIP in SO3 --------------------------- +========================== -The major work of porting lwIP in SO3 has been done by Julien Quartier in the context of his Diploma work. +The port of lwIP to SO3 was originally done by Julien Quartier as a Diploma work. +The stack lives under ``net/lwip/`` and provides TCP, UDP, IPv4/IPv6 and a DNS +resolver. +SO3 exposes a BSD-style **socket API** to user space: the ``net/`` glue maps VFS +file descriptors onto lwIP sockets so that ``socket()``, ``bind()``, +``connect()``, ``send()`` and ``recv()`` work from applications. The network +interface driver lives in ``devices/net/`` — currently an **smc911x** +(``smsc,smc911x``) MAC. See the :ref:`networking section ` of the kernel +chapter. +.. note:: -This part will be completed very soon... + Networking is **opt-in**: it is enabled with ``CONFIG_NET`` and is *off* in + the default ``virt64_defconfig``. The smc911x MAC is present on boards such as + the ARM Versatile Express; QEMU's ``virt`` machine does not provide one, so a + different NIC driver is needed to exercise the stack there. +Trying it out +============= + +With ``CONFIG_NET`` enabled and a supported NIC, the ``ping`` application +exercises the stack end to end. Under QEMU the launch scripts attach a tap +network device (``scripts/qemu-ifup.sh`` / ``qemu-ifdown.sh``), so the guest can +reach the host network once the tap bridge is configured. diff --git a/doc/source/micropython.rst b/doc/source/micropython.rst index f46fe373b1..0e02c164a1 100644 --- a/doc/source/micropython.rst +++ b/doc/source/micropython.rst @@ -1,3 +1,4 @@ +.. _micropython: MicroPython =========== diff --git a/doc/source/user_guide.rst b/doc/source/user_guide.rst index 6451313caa..b0f9a0fe6c 100644 --- a/doc/source/user_guide.rst +++ b/doc/source/user_guide.rst @@ -3,310 +3,203 @@ User Guide ########## -The following instructions have been validated with Ubuntu 22.04, but for sure -it will work with lower release. +This guide walks through setting up SO3 from scratch and running it in QEMU. +The instructions were validated on Ubuntu 22.04 but work on other recent +distributions. The reference platform is **virt64** (QEMU's ``virt`` machine, +Cortex-A72, ARM64). -Pre-requisite -************* +Prerequisites +============= -We need to run some i386 executables, and we need to install some i386 -libraries too. - -.. code:: bash - - sudo dpkg --add-architecture i386 - sudo apt-get update - sudo apt-get install libc6:i386 libncurses5:i386 libstdc++6:i386 - sudo apt-get install lib32z1-dev - sudo apt-get install zlib1g:i386 - -Various other packages are required: - -.. code:: bash - - sudo apt-get install pkg-config libgtk2.0-dev bridge-utils - sudo apt-get install unzip bc - sudo apt-get install elfutils u-boot-tools - sudo apt-get install device-tree-compiler - sudo apt-get install fdisk - sudo apt-get install libncurses-dev - -The following packets are not mandatory, but they can be installed to -prevent annoying warnings: - -.. code:: bash - - sudo apt-get install bison flex - -Files and directory organization -******************************** - -SO3 root directory (main subdirs):: - - so3 - ├── usr - ├── src - ├── filesystem - ├── target - ├── u-boot - └── qemu - -- The SO3 tree is organized in two main parts: **kernel** and **user** - space files. All kernel space related files are located in ``so3/`` - while the user space files resides in ``usr/``. - -- The ``filesystem`` directory contains the virtual SD-card image which will - be attached to the QEMU environment. - -- The ``target`` directory contains the set of ``.its`` files which are used - by the *U-boot* bootloader. - -- The ``u-boot`` and ``qemu`` directories contain the *bootloader* and - the *emulator* respectively. - -Build of the environment -************************ - -Kernel toolchain -================ - -We use the ``arm-none-eabi`` toolchain which has no dependencies on a libc. - -The following package can be installed: +Install the host packages used to build the tree, the device trees, the FIT +images and the disk images: .. code-block:: bash - - apt install gcc-arm-none-eabi -User space toolchain -==================== + sudo apt-get update + sudo apt-get install build-essential bc unzip flex bison + sudo apt-get install device-tree-compiler u-boot-tools + sudo apt-get install fdisk dosfstools + sudo apt-get install qemu-system-arm libncurses-dev + sudo apt-get install bridge-utils # for QEMU tap networking -The user space uses MUSL as libc. The Musl toolchains can be generated with -``toolchains/build-toolchain.sh`` script. +Toolchains +========== -.. code-block:: bash +SO3 uses **two** cross toolchains: - $ sudo ./build-toolchain.sh +Kernel + A *bare-metal* toolchain with no libc dependency: + ``aarch64-none-elf`` for ARM64 and ``arm-none-eabi`` for ARM32. The kernel's + ``CONFIG_CROSS_COMPILE`` points at it (``aarch64-none-elf-`` by default). -By default, it generates ``aarch64-linux-musl`` and ``arm-linux-musleabihf`` -folder in the ``toolchains`` directory +User space + The user applications link against **MUSL**. The MUSL toolchains + (``aarch64-linux-musl`` and ``arm-linux-musleabihf``) are produced by: -.. note:: + .. code-block:: bash - The output directory (by default ``toolchains`` folder) can be changed by setting - the ``OUTPUT_PATH`` variable in the ``build-toolchain.sh`` script + cd toolchains + ./build-toolchain.sh -Quick setup & early test -======================== + By default this generates the toolchain directories under ``toolchains/``. -The following commands is helpful to have quick up-and-running -environment with SO3, i.e. a shell running on top of the kernel in the -emulated *virt32* environment. +Make sure both toolchains are on your ``PATH``. -Setting up QEMU -=============== +Repository layout +================= -The QEMU emulator can be installed via apt for 32-bit and 64-bit versions, -as follows: +The most relevant top-level directories are:: -.. code-block:: bash + so3/ # the kernel source (and the avz/ hypervisor, soo/ framework) + usr/ # user-space applications (CMake + MUSL) + rootfs/ # root filesystem image (ramfs) and its creation script + target/ # FIT image descriptions (*.its) and mkuboot.sh + filesystem/ # the virtual SD-card image used by QEMU + u-boot/ # the U-Boot bootloader + avz/ # out-of-tree build of the AVZ hypervisor + build.conf # selects the active PLATFORM + deploy.sh # deploys kernel/dtb/ramfs/apps into the SD-card image - apt-get install qemu-system-arm - apt-get install qemu-system-aarch64 +Quick start (standalone, virt64) +================================ -Compiling U-boot -================ +The fastest path to a shell on top of the standalone kernel. -U-boot is used as initial bootloader. The kernel image, the device tree and -its root filesystem will be embedded in a single ITB image file. +**1. Select the platform.** Edit ``build.conf`` so that ``PLATFORM := virt64`` +(the default). -In u-boot/ directory: +**2. Build U-Boot** (once): .. code-block:: bash cd u-boot - make virt32_defconfig - make -j8 - -Creating the virtual disk image -=============================== + make virt64_defconfig + make -j$(nproc) -In *filesystem/* directory, create a virtual disk image with the -following script: +**3. Create the virtual SD-card** (once), in ``filesystem/``: .. code-block:: bash cd filesystem - ./create_img.sh vexpress - -This script will create two FAT32 partitions, but only the first one will -be used currently (there is no support to mount the filesystem on the -second partition). - -Compiling the user space -======================== - -The user space build system is based on cmake (CMakeList.txt files). + ./create_img.sh virt64 -To build the user space applications, go to ``usr/`` directory and simply -do make: +**4. Build the user space** and pack the ramfs: .. code-block:: bash cd usr ./build.sh - -In order to support the configuration with an embedded ``ramfs``, you also need to create -a FAT-32 image which will contain the user apps. This is achieved with -the following script: + cd ../rootfs + ./create_ramfs.sh # once, creates rootfs.fat -.. code-block:: bash - - cd rootfs - ./create_ramfs.sh - -The deployment of user applications into this *ramfs* will be done below during -the deployment into the SD-card (option ``-u`` of the ``deploy.sh`` script at -the root directory). - -Compiling the kernel space -========================== - -The kernel has to be compiled in ``*so3*/`` after choosing a configuration: +**5. Build the kernel:** .. code-block:: bash cd so3 - make virt32_ramfs_defconfig - make - -In this example, we are working with an embedded *ramfs* which will be packed -in the ITB image file. + make virt64_defconfig + make -j$(nproc) -Deployment into the SD-card -=========================== - -At this point, all necessary components have been built. Now comes the -phase of deployment in the virtual disk. This is done by means of the -``deploy.sh`` script located at the root tree. -Currently, you should only use option ``-b`` and ``-u`` to deploy the **kernel**, -the **device tree** and the **ramfs** into the ITB file. This image file is -then copied in the first partition of the SD-card. +**6. Deploy** kernel, device tree, ramfs and the user apps into the SD-card +image (from the repository root): .. code-block:: bash ./deploy.sh -bu -Starting SO3 -************ +The ``-b`` option deploys the boot components (it builds the FIT image and copies +it into the first partition); ``-u`` deploys the user applications into the +ramfs. -Simply invoking the script st as following: +**7. Run:** .. code-block:: bash ./st -and you should run into the shell… +You should land at the ``so3%`` prompt. .. note:: - To quit QEMU, type ``Ctrl+x`` followed by ``a`` (not Ctrl+a). - - - -SO3 Configuration and ITS files -******************************* - -This section describes the default configurations of the SO3 kernel -which are present in ``*so3/configs/*`` and in ``target/`` - -SO3 works with the following plaforms: ``virt32``, ``virt64``, ``rpi4``, ``rpi4_64`` + To quit QEMU, type ``Ctrl-x`` followed by ``a`` (not ``Ctrl-a``). -+----------------------------+--------------------+----------------------+--------+--------+------+---------+-----+----+----+-------------+--------+------------+ -| ITS | DTS | Config | virt32 | virt64 | rpi4 | rpi4_64 | avz | pv | vt | soo (linux) | rootfs | Validation | -+============================+====================+======================+========+========+======+=========+=====+====+====+=============+========+============+ -| | | virt32_defconfig | | | | | | | | | | | -+----------------------------+--------------------+----------------------+--------+--------+------+---------+-----+----+----+-------------+--------+------------+ -| | | virt64_defconfig | | X | | | | | | | X | 27.09.23 | -+----------------------------+--------------------+----------------------+--------+--------+------+---------+-----+----+----+-------------+--------+------------+ -| virt32_avz.its | virt32.dts | virt32_pv_defconfig | X | | | | X | X | | | X | 13.12.23 | -+----------------------------+--------------------+----------------------+--------+--------+------+---------+-----+----+----+-------------+--------+------------+ -| rpi4.its | rpi4.dts | rpi4_defconfig | | | X | | | | | | X | 30.11.23 | -+----------------------------+--------------------+----------------------+--------+--------+------+---------+-----+----+----+-------------+--------+------------+ -| rpi4_64_avz_so3_pv.its | rpi4_64_avz_pv.dts | rpi4_64_pv_defconfig | | | | X | X | X | | | X | 27.09.23 | -+----------------------------+--------------------+----------------------+--------+--------+------+---------+-----+----+----+-------------+--------+------------+ -| rpi4_64_so3_standalone.its | rpi4_64.dts | rpi4_64_defconfig | | | | X | | | | | X | 26.09.23 | -+----------------------------+--------------------+----------------------+--------+--------+------+---------+-----+----+----+-------------+--------+------------+ -| virt64_avz_so3_pv.its | virt64.dts | virt64_pv_defconfig | | X | | | X | X | | | X | 26.09.23 | -+----------------------------+--------------------+----------------------+--------+--------+------+---------+-----+----+----+-------------+--------+------------+ -| rpi4_64_avz_so3_vt.its | | rpi4_64_defconfig | | | | X | X | | X | | X | 27.09.23 | -+----------------------------+--------------------+----------------------+--------+--------+------+---------+-----+----+----+-------------+--------+------------+ -| (soo) virt64.its | virt64_avz_pv | virt64_pv_defconfig | | X | | | | X | | X | | 07.10.23 | -+----------------------------+--------------------+----------------------+--------+--------+------+---------+-----+----+----+-------------+--------+------------+ -| (soo) rpi4_64.its | rpi4_64_avz_pv | virt64_pv_defconfig | | | | X | X | X | | X | | 08.10.23 | -+----------------------------+--------------------+----------------------+--------+--------+------+---------+-----+----+----+-------------+--------+------------+ +Launch scripts +============== +Three launch scripts wrap QEMU with the right options: -*To be completed* +.. flat-table:: + :header-rows: 1 + :widths: 12 88 -Deployment of a *Hello World* application -***************************************** + * - Script + - Use + * - ``./st`` + - **standalone** SO3 (``-M virt``, no virtualization). The kernel runs at + EL1. + * - ``./stv`` + - **AVZ** (``-M virt,gic-version=2,virtualization=on``). EL2 is available, + so the hypervisor can run. + * - ``./stg`` + - **graphical** run (a display window instead of ``-nographic``), used for + the framebuffer / LVGL configurations. -Using a *ramfs* configuration -============================= +All three read ``PLATFORM`` from ``build.conf`` and attach the SD-card image, +a virtio block device and a tap network device. -All user applications reside in ``usr/src`` directory. Adding a C file requires to update -the ``CMakeLists.txt`` file. - -All binaries are produced in the ``usr/build`` directory by *cmake*. And all files which -will be deployed by the deployment script are stored in ``usr/build/deploy``. - -.. note:: - - Currently, the ``cd`` command is not implemented in the shell of SO3. - For this reason, all executables (and other files) are stored in the root directory, - except the entries of ``dev/`` used to access the drivers. +Running the AVZ hypervisor +========================== -The deployment into the virtual SD-card is simply done with the ``deploy.sh`` script -at the root dir as follows: +To run the hypervisor with an agency guest on virt64: .. code-block:: bash - ./deploy.sh -u - -.. note:: - - This manner of deploying user applications requires to have a ramfs - configuration. All user apps are actually transfered into the *itb* file - which is deployed in the unique partition of the SD-card. - - The next section shows how you should deploy with the MMC configuration. - - -Installation and run with SO3 docker -************************************ + # 1. Build the hypervisor (out-of-tree into avz/) + cd avz + ./build.sh virt64_avz_defconfig + ./build.sh -It is also possible to start SO3 within a docker container. -The ``Dockerfile`` is located at the root directory and two scripts -``drun`` and ``drunit`` (for interactive mode) are available to start -the execution. + # 2. Build the agency guest (a standalone SO3) and pack the FIT image + cd ../so3 + make virt64_defconfig + make -j$(nproc) + cd ../target + ./mkuboot.sh virt64_avz_so3 # produces virt64_avz_so3.itb -For example, building of a container named ``so3/virt32`` can achieved like this: + # 3. Deploy that .itb as the image U-Boot loads (virt64.itb) and run + # (see deploy.sh / the filesystem mount scripts), then: + cd .. + ./stv -.. code-block::bash +A successful run shows the **AVZ Hypervisor** banner, the *Loading Guest Domain* +trace and finally the agency reaching the ``so3%`` prompt. See :ref:`avz` for +what happens under the hood. - docker build -t so3/virt32 . +Running with Docker +=================== -The, starting the execution of the container: +SO3 can also be built and run inside a Docker container. The ``Dockerfile`` is +at the repository root, and two helpers start the container: -.. code-block::bash +.. code-block:: bash - ./drun - + docker build -t so3/virt64 . + ./drun # run + ./drunit # run, interactive +Deploying a *Hello World* +========================= - +User applications live in ``usr/src/``; adding a C file means adding it to the +relevant ``CMakeLists.txt``. Binaries are produced under ``usr/build/`` and the +files to be deployed are gathered in ``usr/build/deploy/``. After building the +user space, deploy the apps into the ramfs with: +.. code-block:: bash + ./deploy.sh -u - +This works with a ramfs configuration: the user applications are transferred +into the ``.itb`` image that is written to the single partition of the SD-card. +See :ref:`user_space` for the user-space build details. diff --git a/doc/source/user_space.rst b/doc/source/user_space.rst new file mode 100644 index 0000000000..f9689902a0 --- /dev/null +++ b/doc/source/user_space.rst @@ -0,0 +1,112 @@ +.. _user_space: + +User Space +########## + +The SO3 user space is a small, self-contained set of applications built against +the **MUSL** C library. Everything lives under ``usr/``. + +The MUSL C library +================== + +SO3 uses `MUSL `__ as its libc for user applications — a small, clean and +static-friendly implementation well suited to embedded systems. Not every libc +function is enabled; functions are pulled in as the need arises. More complex +facilities (for example full ``pthreads``) are intentionally kept minimal. + +Applications are linked **statically** against MUSL, so each executable is +self-contained. + +Build system (CMake) +==================== + +The user space is built with **CMake** and the MUSL cross toolchain. The build +is driven by ``usr/build.sh``, which: + +* reads ``PLATFORM`` from ``../build.conf`` and selects the matching toolchain + file — ``usr/aarch64-linux-musl.cmake`` for 64-bit platforms (virt64, + rpi4_64), ``usr/arm-linux-musl.cmake`` for 32-bit ones; +* configures and builds the tree under ``usr/build/``; +* separates debug information (a stripped ``*.elf`` plus a ``*.elf.debug``) and + collects the deployable binaries in ``usr/build/deploy/``. + +.. code-block:: bash + + cd usr + ./build.sh + +Adding an application means dropping a C file in ``usr/src/`` and referencing it +from the relevant ``CMakeLists.txt``. + +Applications +============ + +The standard applications in ``usr/src/`` include: + +.. flat-table:: + :header-rows: 1 + :widths: 22 78 + + * - Program + - Role + * - ``init.elf`` + - the **init** process: reads ``commands.ini`` and launches the shell + * - ``sh.elf`` + - the interactive **shell** (the ``so3%`` prompt) + * - ``ls`` / ``cat`` / ``more`` / ``echo`` + - basic file utilities + * - ``ping`` + - ICMP ping (exercises the lwIP stack) + * - ``time`` + - simple timing utility + * - ``hello-world`` + - minimal example + * - ``thread_example`` / ``logs_example`` / ``mydev_test`` + - API and subsystem demonstrations + * - ``lvgl_demo`` / ``lvgl_perf`` / ``lvgl_benchmark`` + - LVGL graphical demos (framebuffer builds — see :ref:`lvgl`) + * - MicroPython + - the MicroPython interpreter (ARM64 — see :ref:`micropython`) + +User-space libraries used by the applications live in ``usr/lib/`` (the LVGL +graphics library, logging helpers, and so on). + +Init and the shell +================== + +When the kernel hands over to user space, the root process ``execve()``\ s +``init.elf``. Init reads a small ``commands.ini`` script and runs it line by +line; a typical script prints a banner and starts the shell: + +.. code-block:: text + + echo SO3 Init Program :) + shell + +The shell then presents the ``so3%`` prompt and runs commands by forking and +``execve()``\ -ing the corresponding ``.elf`` from the root filesystem. + +.. note:: + + The shell does not implement ``cd``: all executables and data files live in + the root directory of the filesystem (except the device nodes under + ``dev/``). + +Root filesystem +=============== + +The applications are delivered through a root filesystem. In the default +(ramfs) configuration this is a FAT image, ``rootfs/rootfs.fat``, created by: + +.. code-block:: bash + + cd rootfs + ./create_ramfs.sh + +``deploy.sh -u`` copies the freshly built user binaries from +``usr/build/deploy/`` into this image, which is then packed into the FIT image +and written to the SD-card (:ref:`user_guide`). The same FAT image can be +inspected on the host by mounting its partition — useful when debugging what +actually ended up on the target. + +.. _MUSL: https://musl.libc.org diff --git a/so3/Kconfig b/so3/Kconfig index 18231bee5f..941eb203f7 100644 --- a/so3/Kconfig +++ b/so3/Kconfig @@ -90,7 +90,7 @@ config LOG_LEVEL if !AVZ && !ARCH_ARM32 config SOO - bool "Configure SO3 as a Mobile Entity (ME)" + bool "Configure SO3 as a SO3 capsule (capsule)" endif if AVZ diff --git a/so3/Makefile b/so3/Makefile index 44e22a3d49..ec6ffbf5cf 100644 --- a/so3/Makefile +++ b/so3/Makefile @@ -9,6 +9,7 @@ # o use make's built-in rules and variables # (this increases performance and avoids hard-to-debug behaviour); # o print "Entering directory ..."; + MAKEFLAGS += -rR --no-print-directory # To put more focus on warnings, be less verbose as default @@ -42,10 +43,12 @@ _all: # KBUILD_SRC is set on invocation of make in OBJ directory # KBUILD_SRC is not intended to be used by the regular user (for now) + ifeq ($(KBUILD_SRC),) # OK, Make called in directory where kernel src resides # Do we want to locate output files in a separate directory? + ifeq ("$(origin O)", "command line") KBUILD_OUTPUT := $(O)/ endif @@ -60,6 +63,7 @@ $(CURDIR)/Makefile Makefile: ; ifneq ($(KBUILD_OUTPUT),) # Invoke a second make in the output directory, passing relevant variables # check that the output directory actually exists + saved-output := $(KBUILD_OUTPUT) export saved-output KBUILD_OUTPUT := $(shell cd $(KBUILD_OUTPUT) && /bin/pwd)/ @@ -89,6 +93,7 @@ ifeq ($(skip-makefile),) # If building an external module we do not care about the all: rule # but instead _all depend on modules + PHONY += all _all: all @@ -189,6 +194,7 @@ PHONY += outputmakefile # outputmakefile generates a Makefile in the output directory, if using a # separate output directory. This allows convenient use of make in the # output directory. + outputmakefile: ifneq ($(KBUILD_SRC),) $(Q)ln -fsn $(srctree) source @@ -245,6 +251,7 @@ ifeq ($(config-targets),1) # KBUILD_DEFCONFIG may point out an alternative default configuration # used for 'make defconfig' #include $(srctree)/arch/$(SRCARCH)/Makefile + export KBUILD_DEFCONFIG KBUILD_KCONFIG config: scripts_basic outputmakefile FORCE @@ -265,6 +272,7 @@ ifeq ($(dot-config),1) # Read in dependencies to all Kconfig* files, make sure to run # oldconfig if changes are detected. + -include include/config/auto.conf.cmd # To avoid any implicit rule to kick in, define an empty command @@ -274,6 +282,7 @@ $(KCONFIG_CONFIG) include/config/auto.conf.cmd: ; # with it and forgot to run make oldconfig. # if auto.conf.cmd is missing then we are probably in a cleaned tree so # we execute the config step to be sure to catch updated Kconfig files + include/config/%.conf: $(KCONFIG_CONFIG) include/config/auto.conf.cmd $(Q)set -e $(Q)$(MAKE) -f $(srctree)/Makefile silentoldconfig @@ -323,8 +332,14 @@ DEFSYM_FLAGS = \ --defsym=CONFIG_MAX_THREADS=$(CONFIG_MAX_THREADS) \ --defsym=CONFIG_THREAD_STACK_SIZE_KB=$(CONFIG_THREAD_STACK_SIZE_KB) \ --defsym=ENTRY_ADDR=$(CONFIG_KERNEL_VADDR) - - + +#ifeq ($(CONFIG_VERDIN_IMX8MP),y) +#LDFLAGS += --defsym=LOAD_OFFSET=0x200000 +#else + +LDFLAGS += --defsym=LOAD_OFFSET=0x80000 +#endif + # Specific CFLAGS/AFLAGS according to ARCH @@ -365,6 +380,9 @@ endif ifeq ($(CONFIG_VIRT64),y) TARGET = virt64 endif +ifeq ($(CONFIG_VERDIN_IMX8MP),y) +TARGET = verdin-imx8mp +endif # The all: target is the default when no target is given on the @@ -402,6 +420,7 @@ so3-all := $(so3-objs) $(so3-libs) #quiet_cmd_so3 = LD $@ # cmd_so3 = $(CC) $(LDFLAGS) -o $@ \ # -Wl,--start-group $(so3-libs) $(so3-objs) -Wl,--end-group + LIBGCC_PATH := $(shell dirname `$(CC) -print-libgcc-file-name`) quiet_cmd_so3 = LD $@ @@ -457,6 +476,7 @@ $(ELF): $(so3-all) arch/$(SRCARCH)/so3.lds # The actual objects are generated when descending, # make sure no implicit rule kicks in + $(sort $(so3-all)): $(so3-dirs) ; # Handle descending into subdirectories listed in $(so3-dirs) @@ -492,6 +512,7 @@ MRPROPER_FILES += .config .config.old tags TAGS cscope* GPATH GTAGS GRTAGS GSYMS # clean - Delete most, but leave enough to build external modules # + clean: rm-dirs := $(CLEAN_DIRS) clean: rm-files := $(CLEAN_FILES) clean-dirs := $(addprefix _clean_, $(so3-dirs)) @@ -511,6 +532,7 @@ clean: $(clean-dirs) # mrproper - Delete all generated files, including .config # + mrproper: rm-dirs := $(wildcard $(MRPROPER_DIRS)) mrproper: rm-files := $(wildcard $(MRPROPER_FILES)) mrproper-dirs := $(addprefix _mrproper_, scripts) @@ -525,6 +547,7 @@ mrproper: clean $(mrproper-dirs) # distclean # + PHONY += distclean distclean: mrproper @find $(srctree) $(RCS_FIND_IGNORE) \ @@ -547,6 +570,7 @@ quiet_cmd_rmfiles = $(if $(wildcard $(rm-files)),CLEAN $(wildcard $(rm-files)) # Shorthand for $(Q)$(MAKE) -f scripts/Makefile.clean obj=dir # Usage: # $(Q)$(MAKE) $(clean)=dir + clean := -f $(if $(KBUILD_SRC),$(srctree)/)scripts/Makefile.clean obj @@ -613,4 +637,5 @@ FORCE: # Declare the contents of the .PHONY variable as phony. We keep that # information in a variable so we can use it in if_changed and friends. + .PHONY: $(PHONY) diff --git a/so3/apps/refso3/callbacks.c b/so3/apps/refso3/callbacks.c index 05fe33f7f5..b6ecfdc8ba 100644 --- a/so3/apps/refso3/callbacks.c +++ b/so3/apps/refso3/callbacks.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2023 Daniel Rossier + * Copyright (C) 2014-2026 Daniel Rossier * Copyright (C) March 2018 Baptiste Delporte * * This program is free software; you can redistribute it and/or modify @@ -31,7 +31,7 @@ #include #include -#include +#include #include @@ -49,19 +49,19 @@ sh_refso3_t *sh_refso3; */ void cb_pre_suspend(soo_domcall_arg_t *args) { - DBG(">> ME %d: cb_pre_suspend...\n", ME_domID()); + DBG(">> capsule %d: cb_pre_suspend...\n", S3C_domID()); } /** * PRE_RESUME * - * This callback is executed right before resuming the frontend drivers, right after ME activation + * This callback is executed right before resuming the frontend drivers, right after capsule activation * * Returns 0 if no propagation to the user space is required, 1 otherwise */ void cb_pre_resume(soo_domcall_arg_t *args) { - DBG(">> ME %d: cb_pre_resume...\n", ME_domID()); + DBG(">> capsule %d: cb_pre_resume...\n", S3C_domID()); } /** @@ -74,7 +74,7 @@ void cb_post_activate(soo_domcall_arg_t *args) static uint32_t count = 0; #endif - DBG(">> ME %d: cb_post_activate...\n", ME_domID()); + DBG(">> capsule %d: cb_post_activate...\n", S3C_domID()); } /** @@ -86,14 +86,14 @@ void cb_post_activate(soo_domcall_arg_t *args) void cb_shutdown(void) { - DBG(">> ME %d: cb_shutdown...\n", ME_domID()); - DBG("ME state: %d\n", get_ME_state()); + DBG(">> capsule %d: cb_shutdown...\n", S3C_domID()); + DBG("capsule state: %d\n", get_S3C_state()); - /* We do nothing particular here for this ME, + /* We do nothing particular here for this capsule, * however we proceed with the normal termination of execution. */ - set_ME_state(ME_state_terminated); + set_S3C_state(S3C_state_terminated); } void callbacks_init(void) diff --git a/so3/apps/refso3/refso3.c b/so3/apps/refso3/refso3.c index dea159b674..cc5715c304 100644 --- a/so3/apps/refso3/refso3.c +++ b/so3/apps/refso3/refso3.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2020 Daniel Rossier + * Copyright (C) 2016-2026 Daniel Rossier * Copyright (C) 2016-2019 Baptiste Delporte * * This program is free software; you can redistribute it and/or modify @@ -38,13 +38,13 @@ #include #include -#include +#include /** - * This ME does nothing particular. It is aimed at giving a template to develop - * a new ME. + * This capsule does nothing particular. It is aimed at giving a template to develop + * a new capsule. * - * Please, have a look at the SOO.ledctrl which is an example of ME intended to + * Please, have a look at the SOO.ledctrl which is an example of capsule intended to * pilot LEDs on the Sense HAT extension. * * Note that SOO.refso3 can be configured with a rootfs (ramfs) which contains @@ -58,7 +58,7 @@ void *app_thread_main(void *args) while (1) { msleep(500); - lprintk("(%d)", ME_domID()); + lprintk("(%d)", S3C_domID()); printk("%c ", sh_refso3->cur_letter); } diff --git a/so3/arch/arm32/Kconfig b/so3/arch/arm32/Kconfig index 0f47b2af72..7ac8329987 100644 --- a/so3/arch/arm32/Kconfig +++ b/so3/arch/arm32/Kconfig @@ -12,9 +12,11 @@ choice config VIRT32 bool "QEMU virt 32-bit machine" - - config RPI4 + select GIC_V2 + + config RPI4 bool "Raspberry Pi 4 Model B support" + select GIC_V2 endchoice diff --git a/so3/arch/arm32/include/asm/migration.h b/so3/arch/arm32/include/asm/migration.h index 65da986fdd..1ae42328e0 100644 --- a/so3/arch/arm32/include/asm/migration.h +++ b/so3/arch/arm32/include/asm/migration.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Daniel Rossier + * Copyright (C) 2016-2026 Daniel Rossier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -19,7 +19,7 @@ #ifndef ASM_MIGRATION_H #define ASM_MIGRATION_H -void fix_kernel_boot_page_table_ME(unsigned int ME_slotID); +void fix_kernel_boot_page_table_S3C(unsigned int S3C_slotID); extern volatile long pfn_offset; diff --git a/so3/arch/arm32/include/asm/mmu.h b/so3/arch/arm32/include/asm/mmu.h index 0817a0fcc5..b6f343ba56 100644 --- a/so3/arch/arm32/include/asm/mmu.h +++ b/so3/arch/arm32/include/asm/mmu.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015-2017 Daniel Rossier + * Copyright (C) 2015-2026 Daniel Rossier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -28,7 +28,7 @@ #ifdef CONFIG_AVZ #define AGENCY_VOFFSET UL(0xc0000000) -#define ME_VOFFSET UL(0xc0000000) +#define S3C_VOFFSET UL(0xc0000000) #endif diff --git a/so3/arch/arm32/so3.lds b/so3/arch/arm32/so3.lds index 40ef32fe29..3326dff6c8 100644 --- a/so3/arch/arm32/so3.lds +++ b/so3/arch/arm32/so3.lds @@ -67,27 +67,27 @@ SECTIONS *(.sbss*) } - .bss (NOLOAD) : - { - *(.bss*) - - . = ALIGN(4096); + .bss (NOLOAD) : + { + *(.bss .bss.*) + + . = ALIGN(4096); __per_cpu_start = .; - - *(.bss.percpu) - + + *(.percpu_data) + . = ALIGN(128); - *(.bss.percpu.read_mostly) - + *(.percpu_data.read_mostly) + . = ALIGN(128); - + __per_cpu_data_end = .; - + . = ALIGN(128); - + __per_cpu_end = .; - + } :data __bss_end = .; diff --git a/so3/arch/arm64/Kconfig b/so3/arch/arm64/Kconfig index 283fd926c7..42f19898b7 100644 --- a/so3/arch/arm64/Kconfig +++ b/so3/arch/arm64/Kconfig @@ -8,26 +8,21 @@ menu "Platform" config VIRT64 bool "Generic QEMU virt64 model" - + select GIC_V2 + config RPI4_64 bool "Raspberry Pi 4 64-bit" - + select GIC_V2 + + config VERDIN_IMX8MP + bool "Toradex Verdin iMX8MP" + select GIC_V3 + endchoice - choice - prompt "Virtual address space size" - help - Allows choosing one of multiple possible virtual address - space sizes. The level of translation table is determined by - a combination of page size and virtual address space size. - - config VA_BITS_39 - bool "39-bit" - - config VA_BITS_48 - bool "48-bit" - - endchoice + config VA_BITS_48 + default y + bool "48-bit virtual address space" endmenu diff --git a/so3/arch/arm64/Makefile b/so3/arch/arm64/Makefile index 273b8a92aa..22526119f4 100644 --- a/so3/arch/arm64/Makefile +++ b/so3/arch/arm64/Makefile @@ -10,7 +10,7 @@ obj-y += backtrace.o backtrace_asm.o obj-y += cache_v8.o cache.o context.o obj-y += semihosting.o semicall.o -obj-$(CONFIG_AVZ) += domain.o mmio.o #smmu.o +obj-$(CONFIG_AVZ) += domain.o mmio.o obj-y += smccc-call.o diff --git a/so3/arch/arm64/cache.S b/so3/arch/arm64/cache.S index 844bca455b..3059a732e7 100644 --- a/so3/arch/arm64/cache.S +++ b/so3/arch/arm64/cache.S @@ -14,10 +14,16 @@ ENTRY(__flush_tlb_all) dsb nshst - tlbi vmalle1 +#ifdef CONFIG_AVZ + tlbi alle2 /* AVZ runs at EL2 */ +#else + tlbi vmalle1 /* standalone SO3 runs at EL1 */ +#endif dsb nsh isb + ret + /* * Invalidate the TLB entry corresponding to a VA (stored in x0) @@ -26,9 +32,12 @@ ENTRY(__asm_invalidate_tlb) dsb ish // Barrier instructions - tlbi vaae1is, x0 // Invalidate VA specified by X0, in EL0/1 - // virtual address space for all ASIDs - dsb ish // Barrier instructions - not covered in this guide +#ifdef CONFIG_AVZ + tlbi vae2is, x0 // AVZ runs at EL2: invalidate EL2 VA, inner shareable +#else + tlbi vaae1is, x0 // standalone SO3 runs at EL1: invalidate VA for all ASIDs +#endif + dsb ish // Barrier instructions isb // Synchronize context on this processor ret @@ -43,7 +52,11 @@ ENTRY(__asm_invalidate_tlb) ENTRY(__asm_invalidate_tlb_all) dsb sy - tlbi vmalle1is +#ifdef CONFIG_AVZ + tlbi alle2is /* AVZ runs at EL2: invalidate all EL2 TLBs, inner shareable */ +#else + tlbi vmalle1is /* standalone SO3 runs at EL1: invalidate all EL1 TLBs, inner shareable */ +#endif dsb ish isb diff --git a/so3/arch/arm64/cache_v8.c b/so3/arch/arm64/cache_v8.c index 41cdae414c..9a316ebe80 100644 --- a/so3/arch/arm64/cache_v8.c +++ b/so3/arch/arm64/cache_v8.c @@ -43,20 +43,18 @@ void mmu_setup(void *pgtable) tcr = TCR_CACHE_FLAGS | TCR_SMP_FLAGS | TCR_TG_FLAGS | TCR_ASID16 | TCR_A1; -#ifdef CONFIG_VA_BITS_48 tcr |= TCR_TxSZ(48) | (TCR_PS_BITS_256TB << TCR_IPS_SHIFT); -#elif CONFIG_VA_BITS_39 - tcr |= TCR_TxSZ(39) | (TCR_PS_BITS_1TB << TCR_IPS_SHIFT); -#else -#error "Wrong VA_BITS configuration." -#endif #ifdef CONFIG_AVZ asm volatile("msr tcr_el2, %0" : : "r"(tcr) : "memory"); - /* Prepare the stage-2 configuration */ - tcr = VTCR_T0SZ_VAL(48) | VTCR_SL0_L0 | TCR_PS_BITS_256TB | (TCR_ORGN0_WBWA << TCR_IRGN0_SHIFT) | - (TCR_ORGN0_WBWA << TCR_ORGN0_SHIFT) | TCR_SH0_INNER | VTCR_RES1; + /* Stage-2 configuration for Cortex-A53 (PARange=0b0010 = 40-bit PA). + * SL0=L0 (starting level 0) requires ARMv8.2-LPA which Cortex-A53 lacks — + * use SL0=L1 (starting level 1) instead. T0SZ=25 gives a 39-bit IPA space + * (single L1 root table, no concatenation), which covers all iMX8MP physical + * addresses. VTTBR_EL2 must therefore point to the L1 table directly. + */ + tcr = VTCR_T0SZ_VAL(39) | VTCR_SL0_L1 | VTCR_PS_40BITS | TCR_IRGN0_WBWA | TCR_ORGN0_WBWA | TCR_SH0_INNER | VTCR_RES1; asm volatile("msr vtcr_el2, %0" : : "r"(tcr) : "memory"); asm volatile("isb"); @@ -152,7 +150,7 @@ void flush_dcache_range(unsigned long start, unsigned long end) void flush_pte_entry(addr_t va, u64 *pte) { __asm_invalidate_tlb(va); - invalidate_dcache_range((u64) pte, (u64) (pte + 1)); + flush_dcache_range((u64) pte, (u64) (pte + 1)); } void dcache_enable(void) diff --git a/so3/arch/arm64/domain.c b/so3/arch/arm64/domain.c index 814fdb060f..c6fba13953 100644 --- a/so3/arch/arm64/domain.c +++ b/so3/arch/arm64/domain.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2024 Daniel Rossier + * Copyright (C) 2014-2026 Daniel Rossier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -26,9 +26,7 @@ #include #include -#ifdef CONFIG_AVZ #include -#endif #ifdef CONFIG_SOO #include @@ -111,10 +109,7 @@ void __setup_dom_pgtable(struct domain *d, addr_t paddr_start, unsigned long map printk(" Map size (bytes) : 0x%lx\n", map_size); printk(" Intermediate phys address : 0x%lx\n", memslot[slotID].ipa_addr); - printk(" Stage-2 vttbr : (va) 0x%p - (pa) 0x%lx\n", new_pt, __pa(new_pt)); - - d->pagetable_vaddr = (addr_t) new_pt; - d->pagetable_paddr = __pa(new_pt); + printk(" Stage-2 L0 root (unused as VTTBR): (va) 0x%p - (pa) 0x%lx\n", new_pt, __pa(new_pt)); /* Prepare the IPA -> PA translation for this domain */ __create_mapping(new_pt, memslot[slotID].ipa_addr, paddr_start, map_size, false, S2); @@ -122,12 +117,19 @@ void __setup_dom_pgtable(struct domain *d, addr_t paddr_start, unsigned long map if (d->avz_shared->domID == DOMID_AGENCY) do_ipamap(new_pt, agency_ipamap, ARRAY_SIZE(agency_ipamap)); else - do_ipamap(new_pt, capsule_ipamap, ARRAY_SIZE(capsule_ipamap)); + do_ipamap(new_pt, S3C_ipamap, ARRAY_SIZE(S3C_ipamap)); /* Map the shared page in the IPA space; the shared page is located right after the domain area * in the IPA space, and if any, the RT shared page follows the shared page (in IPA space). */ - __create_mapping(new_pt, memslot[slotID].ipa_addr + map_size, __pa(d->avz_shared), PAGE_SIZE, false, S2); + __create_mapping(new_pt, memslot[slotID].ipa_addr + map_size, __pa(d->avz_shared), PAGE_SIZE, true, S2); + + /* VTCR_EL2 uses SL0=L1 (Cortex-A53 requires this; SL0=L0 needs LVA extension). + * VTTBR_EL2 must therefore point to the L1 table, not the L0 root. + * All iMX8MP IPAs have bits[47:39]=0, so L0[0] is the sole L0 entry. + */ + d->pagetable_paddr = *((u64 *) new_pt) & TTB_L0_TABLE_ADDR_MASK; + d->pagetable_vaddr = (addr_t) __va(d->pagetable_paddr); #ifdef CONFIG_SOO /* Initialize the grant pfn (ipa address) area */ @@ -136,7 +138,7 @@ void __setup_dom_pgtable(struct domain *d, addr_t paddr_start, unsigned long map d->grant_pfn[i].free = true; } - /* Set staring framebuffer ipa address after the grant pfn area */ + /* Set staring framebuffer IPA address after the grant pfn area */ d->fbdev_start_pfn = phys_to_pfn(memslot[slotID].ipa_addr + map_size + 2 * PAGE_SIZE) + NR_GRANT_PFN; fbdev_ipamap_domain(d, slotID); #endif /* CONFIG_SOO */ diff --git a/so3/arch/arm64/exception.S b/so3/arch/arm64/exception.S index d250d3b4bc..b5679c32a8 100644 --- a/so3/arch/arm64/exception.S +++ b/so3/arch/arm64/exception.S @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021-2024 Daniel Rossier + * Copyright (C) 2021-2026 Daniel Rossier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -32,7 +32,7 @@ #include -.global cpu_entrypoint +.global cpu_entrypoints #endif /* CONFIG_AVZ */ @@ -41,6 +41,8 @@ .extern current_thread .extern __sync_serror +.extern avz_handle_el1_serror +.extern avz_handle_el2_serror .extern do_exit .extern sig_check @@ -92,7 +94,21 @@ */ .section ".vectors.text", "ax" - b __start // To be compliant with reset vector (unavailable in aarch64) + b __start /* ARM64 Image header code0 / reset vector */ + .long 0 /* code1: reserved */ +#ifdef CONFIG_VERDIN_IMX8MP + .quad LOAD_OFFSET /* text_offset: DRAM_base + LOAD_OFFSET = load address */ + .quad 0x800000 /* image_size: non-zero → U-Boot reads text_offset */ +#else + .quad 0 /* text_offset: U-Boot uses hardcoded 0x80000 */ + .quad 0 /* image_size: 0 → U-Boot uses hardcoded defaults */ +#endif + .quad 0 /* flags: reserved */ + .quad 0 /* reserved */ + .quad 0 /* reserved */ + .quad 0 /* reserved */ + .ascii "ARM\x64" /* magic = 0x644d5241 at offset 56 */ + .long 0 /* reserved */ .align 12 ENTRY(__vectors) @@ -145,10 +161,17 @@ ENTRY(__vectors) // Current EL with SPx / SError .align 7 +#ifdef CONFIG_AVZ + b avz_el2_serror_handler +#else + /* This slot MUST be emitted in every build: omitting it (e.g. behind + * a bare #ifdef CONFIG_AVZ) shifts every subsequent Lower-EL vector up + * by 0x80, so an EL0 `svc` lands in the IRQ handler instead of + * el01_sync_handler and no syscall is ever serviced. */ mov x0, lr mrs x1, esr_el1 - b __sync_serror +#endif /* CONFIG_AVZ */ // Lower EL using AArch64 / Synchronous .align 7 @@ -179,8 +202,12 @@ ENTRY(__vectors) // Lower EL using AArch64 / SError .align 7 +#ifdef CONFIG_AVZ + b el1_serror_handler +#else mov x0, lr b trap_handle_error +#endif // Lower EL using AArch32 / Synchronous .align 7 @@ -262,29 +289,86 @@ ENTRY(pre_ret_to_el1_spin) #ifdef CONFIG_CPU_PSCI ENTRY(pre_ret_to_el1) - wfi + /* + * Bao-inspired per-CPU entrypoint pattern: + * Each secondary CPU reads its own slot in cpu_entrypoints[] indexed + * by the Aff0 field of MPIDR_EL1. CPU0 writes only the target CPU's + * slot before sending a targeted IPI via smp_trigger_event(), so no + * atomic operations are needed and no other CPU races for the same slot. + */ + mrs x1, mpidr_el1 + and x1, x1, #0xff /* Aff0 = logical CPU index */ + + /* pre_ret_debug_wfi(cpu_id) */ + mov x0, x1 + bl pre_ret_debug_wfi + mrs x1, mpidr_el1 + and x1, x1, #0xff + + /* Busy-poll on cpu_entrypoints[cpu_id] until CPU0's PSCI_CPU_ON handler + * writes the entry point. Using a spin loop (no WFI) to diagnose + * whether WFI wakeup is the bottleneck. */ +1: + dsb ish + isb - ldr x0, cpu_entrypoint - msr elr_el2, x0 + adr x2, cpu_entrypoints + ldr x0, [x2, x1, lsl #3] /* cpu_entrypoints[cpu_id] */ + cbz x0, 1b /* not yet written — spin */ - // Set the CPU in EL1 mode to proceed with - // the bootstrap of the domain + /* pre_ret_debug_woke(cpu_id, ep) — x0=ep, x1=cpu_id */ + mov x19, x0 /* save ep across the call */ + mov x20, x1 /* save cpu_id across the call */ + mov x1, x0 + mov x0, x20 + bl pre_ret_debug_woke + mov x0, x19 /* restore ep */ + mov x1, x20 /* restore cpu_id (not needed further) */ - mov x2, #PSR_MODE_EL1h + /* + * Drain any SError pending from flush_dcache_all() inside + * __mmu_switch_vttbr (called by secondary_start_kernel). + * On Cortex-A53, DC CISW cache maintenance can leave a deferred + * physical SError. With HCR_EL2.AMO=1 it would otherwise fire + * the instant we ERET to EL1, hitting el1_serror_handler before + * Linux is initialised. Force it here at EL2 instead so it is + * consumed by avz_el2_serror_handler and the EL1 entry is clean. + * + * NOTE: the SError handler overwrites ELR_EL2, so we must set + * ELR_EL2 AFTER the drain. x0 (entrypoint) is preserved through + * kernel_entry/kernel_exit used by avz_el2_serror_handler. + */ + msr daifclr, #4 /* unmask SErrors at EL2 */ + isb /* pending SError fires → avz_el2_serror_handler */ + msr daifset, #4 /* re-mask */ + isb - // Make sure no interrupt coming from CPU #0 is - // interferring with other CPU bootstrap - orr x2, x2, #PSR_I_BIT + /* Set ELR_EL2 after the drain so the SError handler cannot clobber it */ + msr elr_el2, x0 + /* EL1h mode, all exceptions masked on EL1 entry */ + mov x2, #(PSR_MODE_EL1h | PSR_F_BIT | PSR_I_BIT | PSR_A_BIT | PSR_D_BIT) msr spsr_el2, x2 - // According to boot protocol - mov x1, #0 + /* Initialise the virtual CPU interface for this secondary CPU so it + * can receive vIRQs (timer PPI 27, IPIs, SPIs). Without this, the + * vGIC on secondaries stays disabled (HCR.EN=0) and Linux ends up + * with no scheduler tick, hitting an RCU stall a few seconds in. + * gich_secondary_init() is a leaf call that preserves callee-saved + * registers (x19=ep) but clobbers x0-x18. */ + bl gich_secondary_init + + /* Disable EL1 physical timer (CNTP) to clear any stale U-Boot compare + * value; prevents spurious id=27 storm before Linux programs its own CVAL. */ + msr cntp_ctl_el0, xzr + + /* ARM64 PSCI secondary boot protocol: x0–x3 = 0 */ + mov x0, #0 mov x1, #0 mov x2, #0 mov x3, #0 - // Ready to jump into the Linux domain... + /* Ready to jump into the Linux domain... */ eret @@ -329,7 +413,8 @@ ENTRY(pre_ret_to_el1) .endm -// Exit macro at the end of an exception routine to restore all saved registers +// Exit macro at the end of an exception routine +// It restores the sp_el0 as well. .macro prepare_to_exit_to_el1 ldr x0, [sp, #OFFSET_PC] msr elr_el2, x0 @@ -386,15 +471,67 @@ el12_2_irq_handler: // Make sure r0 refers to the base of the stack frame mov x0, sp +#ifdef CONFIG_GIC_V3 + bl avz_el2_irq_handle +#else bl irq_handle +#endif prepare_to_exit_to_el1 kernel_exit eret -cpu_entrypoint: - .quad 0x0 +#ifdef CONFIG_AVZ +/* + * SError while AVZ itself is running at EL2 (Current EL with SPx). + * Save registers, log ESR_EL2/ELR_EL2, and ERET to resume the + * interrupted EL2 code (e.g. the scheduler). + */ +.align 5 +avz_el2_serror_handler: + + kernel_entry + + mov x0, sp + bl avz_handle_el2_serror + + kernel_exit + + eret + + dsb nsh + isb + + nop + nop + nop +#endif /* CONFIG_AVZ */ + +.align 5 +el1_serror_handler: + + kernel_entry + prepare_to_enter_to_el2 + + mov x0, sp + bl avz_handle_el1_serror + + prepare_to_exit_to_el1 + kernel_exit + + eret + + dsb nsh + isb + + nop + nop + nop + +/* Per-CPU PSCI entry points — indexed by Aff0 of MPIDR_EL1 */ +cpu_entrypoints: + .quad 0x0, 0x0, 0x0, 0x0 __prepare_to_sig_el1_handler: @@ -418,7 +555,7 @@ __prepare_to_sig_el1_handler: /* * This glue code will be called to prepare a resuming - * of a ME. + * of a capsule. */ ENTRY(resume_to_guest) @@ -479,6 +616,14 @@ ENTRY(pre_ret_to_user) kernel_exit + // Ensure all page-table writes are visible to the hardware table walker + // before handing off to the guest at EL1. + dsb sy + isb + + /* Disable EL1 physical timer (CNTP) to clear stale U-Boot compare value. */ + msr cntp_ctl_el0, xzr + // Ready to jump into the domain... eret diff --git a/so3/arch/arm64/fault.c b/so3/arch/arm64/fault.c index a16c450c3b..ad967a7342 100644 --- a/so3/arch/arm64/fault.c +++ b/so3/arch/arm64/fault.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2019 Daniel Rossier + * Copyright (C) 2014-2026 Daniel Rossier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -40,6 +40,31 @@ void __sync_serror(addr_t lr) kernel_panic(); } +/* Called from pre_ret_to_el1 asm to log secondary CPU state before and after drain */ +void pre_ret_to_el1_diag(unsigned long cpu_id, unsigned long entry_point, int phase) +{ + (void) cpu_id; + (void) entry_point; + (void) phase; +} + +/* SError at EL2 (AVZ running): log and return so the scheduler can continue */ +void avz_handle_el2_serror(void *regs) +{ + /* Consume EL2 SError silently; return to resume interrupted EL2 code */ +} + +/* SError from EL1 trapped to EL2 via HCR_EL2.AMO=1 */ +void avz_handle_el1_serror(void *regs) +{ + unsigned long elr = read_sysreg(elr_el2); + unsigned long esr = read_sysreg(esr_el2); + unsigned long far = read_sysreg(far_el2); + + lprintk("### EL1 SError at EL2: ELR=0x%lx ESR=0x%lx FAR=0x%lx ###\n", elr, esr, far); + /* Consume the SError and return to EL1 so Linux can continue */ +} + void __sync_el2_fault(addr_t lr, addr_t sp) { lprintk("### Got a sync interrupt in EL2 / lr: 0x%lx sp: 0x%lx ###\n", lr, sp); diff --git a/so3/arch/arm64/head.S b/so3/arch/arm64/head.S index 1996af76e4..e1cf33549e 100644 --- a/so3/arch/arm64/head.S +++ b/so3/arch/arm64/head.S @@ -226,7 +226,7 @@ agency: ldr x0, =HCR_AGENCY_FLAGS b 99f guest: - ldr x0, =HCR_ME_FLAGS + ldr x0, =HCR_S3C_FLAGS 99: msr hcr_el2, x0 diff --git a/so3/arch/arm64/include/asm/arm_timer.h b/so3/arch/arm64/include/asm/arm_timer.h index a6b990a96d..aadc12bf8d 100644 --- a/so3/arch/arm64/include/asm/arm_timer.h +++ b/so3/arch/arm64/include/asm/arm_timer.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2017 Daniel Rossier + * Copyright (C) 2014-2026 Daniel Rossier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as diff --git a/so3/arch/arm64/include/asm/migration.h b/so3/arch/arm64/include/asm/migration.h index 19f9e76965..b35baf6a0c 100644 --- a/so3/arch/arm64/include/asm/migration.h +++ b/so3/arch/arm64/include/asm/migration.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Daniel Rossier + * Copyright (C) 2016-2026 Daniel Rossier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -19,10 +19,10 @@ #ifndef ASM_MIGRATION_H #define ASM_MIGRATION_H -void fix_kernel_boot_page_table_ME(unsigned int ME_slotID); +void fix_kernel_boot_page_table_S3C(unsigned int S3C_slotID); -/* Start of ME RAM in virtual address space of idle domain */ -extern unsigned long vaddr_start_ME; +/* Start of capsule RAM in virtual address space of idle domain */ +extern unsigned long vaddr_start_S3C; extern volatile long pfn_offset; diff --git a/so3/arch/arm64/include/asm/mmu.h b/so3/arch/arm64/include/asm/mmu.h index cac4fa3046..203a3727cd 100644 --- a/so3/arch/arm64/include/asm/mmu.h +++ b/so3/arch/arm64/include/asm/mmu.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015-2017 Daniel Rossier + * Copyright (C) 2015-2026 Daniel Rossier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -29,7 +29,6 @@ #define USER_SPACE_VADDR UL(0x1000) -#ifdef CONFIG_VA_BITS_48 #define RAMDEV_VADDR UL(0xffffa00000000000) /* Fixmap page used for temporary mapping */ @@ -42,22 +41,6 @@ * as main L0 page table. */ #define USER_STACK_TOP_VADDR UL(0x0001000000000000) -#elif CONFIG_VA_BITS_39 -#define RAMDEV_VADDR UL(0xffffffd000000000) - -/* Fixmap page used for temporary mapping */ -#define FIXMAP_MAPPING UL(0xffffffd800000000) - -/* Anonymous start virtual address */ -#define USER_ANONYMOUS_VADDR UL(0x0000000100000000) - -/* The user space can be up to bits [38:0] and uses ttbr0_el1 - * as main L0 page table. - */ -#define USER_STACK_TOP_VADDR UL(0x0000008000000000) -#else -#error "Wrong VA_BITS configuration." -#endif #define SZ_256G (256UL * SZ_1G) @@ -69,18 +52,11 @@ #ifdef CONFIG_AVZ /* Start of the container memory base */ -#define ME_BASE UL(0x0000200000000000) -#define ME_ID_SHIFT 32 +#define S3C_BASE UL(0x0000200000000000) +#define S3C_ID_SHIFT 32 -#ifdef CONFIG_VA_BITS_48 #define AGENCY_VOFFSET UL(0x0000110000000000) -#elif CONFIG_VA_BITS_39 -#define AGENCY_VOFFSET UL(0xffffffc010000000) -#else -#error "Wrong VA_BITS configuration." -#endif - #endif /* CONFIG_AVZ */ /* Order of size which makes sense in block mapping */ @@ -467,18 +443,10 @@ enum dcache_option { #define pte_index_to_vaddr(i0, i1, i2, i3) ((i0 << TTB_I0_SHIFT) | i1 << TTB_I1_SHIFT) | (i2 << TTB_I2_SHIFT) | (i3 << TTB_I3_SHIFT)) -#ifdef CONFIG_VA_BITS_48 #define l0pte_offset(pgtable, addr) ((u64 *) ((u64 *) pgtable + l0pte_index(addr))) #define l1pte_offset(l0pte, addr) ((u64 *) (__va(*l0pte & TTB_L0_TABLE_ADDR_MASK)) + l1pte_index(addr)) #define l2pte_offset(l1pte, addr) ((u64 *) (__va(*l1pte & TTB_L1_TABLE_ADDR_MASK)) + l2pte_index(addr)) #define l3pte_offset(l2pte, addr) ((u64 *) (__va(*l2pte & TTB_L2_TABLE_ADDR_MASK)) + l3pte_index(addr)) -#elif CONFIG_VA_BITS_39 -#define l1pte_offset(pgtable, addr) ((u64 *) ((u64 *) pgtable + l1pte_index(addr))) -#define l2pte_offset(l1pte, addr) ((u64 *) (__va(*l1pte & TTB_L1_TABLE_ADDR_MASK)) + l2pte_index(addr)) -#define l3pte_offset(l2pte, addr) ((u64 *) (__va(*l2pte & TTB_L2_TABLE_ADDR_MASK)) + l3pte_index(addr)) -#else -#error "Wrong VA_BITS configuration." -#endif #define l1pte_first(l0pte) ((u64 *) __va(*l0pte & TTB_L0_TABLE_ADDR_MASK)) #define l2pte_first(l1pte) ((u64 *) __va(*l1pte & TTB_L1_TABLE_ADDR_MASK)) diff --git a/so3/arch/arm64/include/asm/processor.h b/so3/arch/arm64/include/asm/processor.h index fa63a3c64e..5b1968a52e 100644 --- a/so3/arch/arm64/include/asm/processor.h +++ b/so3/arch/arm64/include/asm/processor.h @@ -2,7 +2,7 @@ * linux/include/asm-arm/processor.h * * Copyright (C) 1995-2002 Russell King - * Copyright (C) 2014-2019 Daniel Rossier + * Copyright (C) 2014-2026 Daniel Rossier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -103,6 +103,7 @@ #define ESR_ELx_EC_UNKNOWN (0x00) #define ESR_ELx_EC_WFx (0x01) + /* Unallocated EC: 0x02 */ #define ESR_ELx_EC_CP15_32 (0x03) #define ESR_ELx_EC_CP15_64 (0x04) @@ -111,14 +112,17 @@ #define ESR_ELx_EC_FP_ASIMD (0x07) #define ESR_ELx_EC_CP10_ID (0x08) /* EL2 only */ #define ESR_ELx_EC_PAC (0x09) /* EL2 and above */ + /* Unallocated EC: 0x0A - 0x0B */ #define ESR_ELx_EC_CP14_64 (0x0C) #define ESR_ELx_EC_BTI (0x0D) #define ESR_ELx_EC_ILL (0x0E) + /* Unallocated EC: 0x0F - 0x10 */ #define ESR_ELx_EC_SVC32 (0x11) #define ESR_ELx_EC_HVC32 (0x12) /* EL2 only */ #define ESR_ELx_EC_SMC32 (0x13) /* EL2 and above */ + /* Unallocated EC: 0x14 */ #define ESR_ELx_EC_SVC64 (0x15) #define ESR_ELx_EC_HVC64 (0x16) /* EL2 and above */ @@ -126,6 +130,7 @@ #define ESR_ELx_EC_SYS64 (0x18) #define ESR_ELx_EC_SVE (0x19) #define ESR_ELx_EC_ERET (0x1a) /* EL2 only */ + /* Unallocated EC: 0x1B */ #define ESR_ELx_EC_FPAC (0x1C) /* EL1 and above */ /* Unallocated EC: 0x1D - 0x1E */ @@ -133,10 +138,12 @@ #define ESR_ELx_EC_IABT_LOW (0x20) #define ESR_ELx_EC_IABT_CUR (0x21) #define ESR_ELx_EC_PC_ALIGN (0x22) + /* Unallocated EC: 0x23 */ #define ESR_ELx_EC_DABT_LOW (0x24) #define ESR_ELx_EC_DABT_CUR (0x25) #define ESR_ELx_EC_SP_ALIGN (0x26) + /* Unallocated EC: 0x27 */ #define ESR_ELx_EC_FP_EXC32 (0x28) /* Unallocated EC: 0x29 - 0x2B */ @@ -149,6 +156,7 @@ #define ESR_ELx_EC_SOFTSTP_CUR (0x33) #define ESR_ELx_EC_WATCHPT_LOW (0x34) #define ESR_ELx_EC_WATCHPT_CUR (0x35) + /* Unallocated EC: 0x36 - 0x37 */ #define ESR_ELx_EC_BKPT32 (0x38) /* Unallocated EC: 0x39 */ @@ -553,6 +561,8 @@ #define SYS_ICH_VSEIR_EL2 sys_reg(3, 4, 12, 9, 4) #define SYS_ICC_SRE_EL2 sys_reg(3, 4, 12, 9, 5) +#define SYS_ICC_CTLR_EL2 sys_reg(3, 4, 12, 12, 4) +#define ICC_CTLR_EL2_EOImode_BIT (1 << 1) #define SYS_ICH_HCR_EL2 sys_reg(3, 4, 12, 11, 0) #define SYS_ICH_VTR_EL2 sys_reg(3, 4, 12, 11, 1) #define SYS_ICH_MISR_EL2 sys_reg(3, 4, 12, 11, 2) @@ -1266,6 +1276,7 @@ void ret_to_user(void); void pre_ret_to_user(void); void pre_ret_to_el1(void); void pre_ret_to_el1_spin(addr_t release_addr); +void pre_ret_to_el1_diag(unsigned long cpu_id, unsigned long entry_point, int phase); #endif /* CONFIG_AVZ */ diff --git a/so3/arch/arm64/include/asm/virt.h b/so3/arch/arm64/include/asm/virt.h index 63e652c49f..770d8d1429 100644 --- a/so3/arch/arm64/include/asm/virt.h +++ b/so3/arch/arm64/include/asm/virt.h @@ -191,9 +191,15 @@ * FMO: Override CPSR.F and enable signaling with VF * SWIO: Turn set/way invalidates into set/way clean+invalidate */ -#define HCR_AGENCY_FLAGS (HCR_VM_MASK | HCR_API_MASK | HCR_APK_MASK | HCR_RW_MASK) - -#define HCR_ME_FLAGS (HCR_VM_MASK | HCR_API_MASK | HCR_APK_MASK | HCR_AMO_MASK | HCR_RW_MASK | HCR_FMO_MASK | HCR_IMO_MASK) +/* FMO=1 is required so that ICV_AP0Rn_EL1 (virtual Group 0 active priority + * registers) are DEFINED at NS EL1. Per GICv3 spec, ICV_AP0Rn_EL1 at NS EL1 + * requires HCR_EL2.FMO=1 when SCR_EL3.FIQ=1 (which OPTEE sets on this + * platform). Physical FIQs still go to EL3 due to SCR_EL3.FIQ=1 so AVZ + * never actually handles a physical FIQ; FMO=1 only enables the virtual + * Group 0 channel in the CPU interface. */ +#define HCR_AGENCY_FLAGS (HCR_VM_MASK | HCR_API_MASK | HCR_APK_MASK | HCR_RW_MASK | HCR_AMO_MASK | HCR_IMO_MASK | HCR_FMO_MASK) + +#define HCR_S3C_FLAGS (HCR_VM_MASK | HCR_API_MASK | HCR_APK_MASK | HCR_AMO_MASK | HCR_RW_MASK | HCR_FMO_MASK | HCR_IMO_MASK) #define HCR_GUEST_FLAGS \ (HCR_TSC | HCR_TSW | HCR_TWE | HCR_TWI | HCR_VM | HCR_TVM | HCR_BSU_IS | HCR_FB | HCR_TAC | HCR_AMO | HCR_SWIO | \ diff --git a/so3/arch/arm64/mmio.c b/so3/arch/arm64/mmio.c index fb6d5a0d29..c664ebbefc 100644 --- a/so3/arch/arm64/mmio.c +++ b/so3/arch/arm64/mmio.c @@ -23,6 +23,7 @@ #include #include +#include /* * These definitions are heavly borrowed from jailhouse hypervisor @@ -80,8 +81,18 @@ void mmio_perform_access(void *base, struct mmio_access *mmio) */ enum mmio_result mmio_handle_access(struct mmio_access *mmio) { - /* Currently, only GIC access is handled via a data abort exception */ - mmio->address -= (addr_t) gic->gicd_paddr; + addr_t gicd_base = (addr_t) gic->gicd_paddr; + + /* Only GIC distributor accesses are handled via trap-and-emulate. + * Without this guard a Stage-2 fault on any other IPA (e.g. PCIe + * high MMIO beyond the 39-bit IPA limit, or a fault that hardware + * reports with HPFAR=0) underflows the subtraction below and + * dereferences a wild EL2 VA in mmio_perform_access — turning a + * guest fault into an EL2 panic. */ + if (mmio->address < gicd_base || mmio->address >= gicd_base + 0x10000) + return MMIO_UNHANDLED; + + mmio->address -= gicd_base; return gic_handle_dist_access(mmio); } @@ -151,8 +162,17 @@ int mmio_dabt_decode(cpu_regs_t *regs, unsigned long esr) if (mmio_result == MMIO_ERROR) return TRAP_FORBIDDEN; - if (mmio_result == MMIO_UNHANDLED) - goto error_unhandled; + /* Stage-2 faults outside the GIC distributor range come from guest + * accesses to IPAs we don't model (e.g. QEMU virt PFlash, PCIe high + * MMIO beyond the 39-bit IPA limit). Treat them as accesses to + * non-existent memory: reads return 0, writes are dropped, PC + * advances. Linux's CFI flash probe gets back zeros and gives up + * cleanly instead of taking a synchronous external abort. Without + * this any unmapped guest IPA would panic the hypervisor. */ + if (mmio_result == MMIO_UNHANDLED) { + if (!is_write) + mmio.value = 0; + } /* Put the read value into the dest register */ if (!is_write && (srt != 31)) { @@ -170,4 +190,4 @@ int mmio_dabt_decode(cpu_regs_t *regs, unsigned long esr) panic("Unhandled data %s at 0x%lx(%d)\n", (is_write ? "write" : "read"), mmio.address, size); return TRAP_UNHANDLED; -} \ No newline at end of file +} diff --git a/so3/arch/arm64/mmu.c b/so3/arch/arm64/mmu.c index 6301018e9b..d3799e7297 100644 --- a/so3/arch/arm64/mmu.c +++ b/so3/arch/arm64/mmu.c @@ -61,7 +61,6 @@ static void alloc_init_l3(u64 *l0pgtable, addr_t addr, addr_t end, addr_t phys, u64 *l1pte, *l2pte, *l3pte; u64 *l3pgtable; -#ifdef CONFIG_VA_BITS_48 u64 *l0pte; /* These PTEs must exist. */ @@ -70,12 +69,6 @@ static void alloc_init_l3(u64 *l0pgtable, addr_t addr, addr_t end, addr_t phys, l1pte = l1pte_offset(l0pte, addr); BUG_ON(!*l1pte); -#elif CONFIG_VA_BITS_39 - l1pte = l0pgtable + l1pte_index(addr); - BUG_ON(!*l1pte); -#else -#error "Wrong VA_BITS configuration." -#endif do { l2pte = l2pte_offset(l1pte, addr); @@ -137,22 +130,14 @@ static void alloc_init_l2(u64 *l0pgtable, addr_t addr, addr_t end, addr_t phys, u64 *l2pgtable; addr_t next; -#ifdef CONFIG_VA_BITS_48 u64 *l0pte; /* We are sure this pte exists */ l0pte = l0pte_offset(l0pgtable, addr); BUG_ON(!*l0pte); -#endif do { -#ifdef CONFIG_VA_BITS_48 l1pte = l1pte_offset(l0pte, addr); -#elif CONFIG_VA_BITS_39 - l1pte = l0pgtable + l1pte_index(addr); -#else -#error "Wrong VA_BITS configuration." -#endif /* L1 page table already exist? */ if (!*l1pte) { /* A L2 table must be created */ @@ -334,68 +319,6 @@ void __create_mapping(void *pgtable, addr_t virt_base, addr_t phys_base, size_t mmu_page_table_flush((addr_t) pgtable, (addr_t) (pgtable + TTB_L0_SIZE)); } -#elif CONFIG_VA_BITS_39 - -/** - * In the 39-bit VA version, the first page table is actually the l1pgtable from the - * point of view of these functions. - * - * @param l1pgtable The first level page table (l0pgtable actually) - * @param virt_base - * @param phys_base - * @param size - * @param nocache true for I/O access typically - */ -static void __create_mapping(void *l1pgtable, addr_t virt_base, addr_t phys_base, size_t size, bool nocache, mmu_stage_t stage) -{ - addr_t addr, end, length, next, phys; - u64 *l1pte; - - /* If l1pgtable is NULL, we consider the system page table */ - if (l1pgtable == NULL) - l1pgtable = __sys_root_pgtable; - - BUG_ON(!size); - - addr = virt_base & PAGE_MASK; - length = ALIGN_UP(size + (virt_base & ~PAGE_MASK), PAGE_SIZE); - - end = addr + length; - phys = phys_base; - - do { - next = l1_addr_end(addr, end); - - if (((addr | next | phys) & ~BLOCK_1G_MASK) == 0) { - l1pte = l1pte_offset(l1pgtable, addr); - *l1pte = phys & TTB_L1_BLOCK_ADDR_MASK; - - set_pte_block(l1pte, (nocache ? DCACHE_OFF : DCACHE_WRITEALLOC)); - - /* Set AP[1] bit 6 to 1 to make R/W/Executable the pages in user space */ - if ((addr != phys) && user_space_vaddr(addr)) - *l1pte |= PTE_BLOCK_AP1; - - LOG_DEBUG("Allocating a 1 GB block at l1pte: %p content: %lx\n", l1pte, *l1pte); - - flush_pte_entry(addr, l1pte); - - phys += SZ_1G; - addr += SZ_1G; - - } else { - alloc_init_l2(l1pgtable, addr, next, phys, nocache, stage); - phys += next - addr; - addr = next; - } - - } while (addr != end); - - mmu_page_table_flush((addr_t) l1pgtable, (addr_t) (l1pgtable + TTB_L1_ENTRIES)); -} - -#else -#error "Wrong VA_BITS configuration." #endif void create_mapping(void *pgtable, addr_t virt_base, addr_t phys_base, size_t size, bool nocache) @@ -419,9 +342,7 @@ static bool empty_table(void *pgtable) void release_mapping(void *pgtable, addr_t vaddr, size_t size) { -#ifdef CONFIG_VA_BITS_48 uint64_t *l0pte; -#endif uint64_t *l1pte, *l2pte, *l3pte; size_t free_size = 0; @@ -433,21 +354,12 @@ void release_mapping(void *pgtable, addr_t vaddr, size_t size) size = ALIGN_UP(size + (vaddr & ~PAGE_MASK), PAGE_SIZE); while (free_size < size) { -#ifdef CONFIG_VA_BITS_48 l0pte = l0pte_offset(pgtable, vaddr); if (!*l0pte) /* Already free */ return; l1pte = l1pte_offset(l0pte, vaddr); -#elif CONFIG_VA_BITS_39 - l1pte = l1pte_offset(pgtable, vaddr); - if (!*l1pte) - /* Already free */ - return; -#else -#error "Wrong VA_BITS configuration." -#endif BUG_ON(!*l1pte); if (pte_type(l1pte) == PTE_TYPE_BLOCK) { @@ -457,10 +369,8 @@ void release_mapping(void *pgtable, addr_t vaddr, size_t size) free_size += SZ_1G; vaddr += SZ_1G; -#ifdef CONFIG_VA_BITS_48 if (empty_table(l0pte)) free(l0pte); -#endif } else { BUG_ON(pte_type(l1pte) != PTE_TYPE_TABLE); @@ -506,13 +416,7 @@ void *new_root_pgtable(void) void *pgtable; uint32_t ttb_size; -#ifdef CONFIG_VA_BITS_48 ttb_size = TTB_L0_SIZE; -#elif CONFIG_VA_BITS_39 - ttb_size = TTB_L1_SIZE; -#else -#error "Wrong VA_BITS configuration." -#endif pgtable = memalign(ttb_size, PAGE_SIZE); if (!pgtable) { @@ -591,13 +495,7 @@ static void __reset_pgtable(u64 *pgtable, int level) */ void reset_root_pgtable(void *pgtable, bool remove) { -#ifdef CONFIG_VA_BITS_48 __reset_pgtable((u64 *) pgtable, 0); -#elif CONFIG_VA_BITS_39 - __reset_pgtable((u64 *) pgtable, 1); -#else -#error "Wrong VA_BITS configuration." -#endif if (remove) free(pgtable); @@ -628,21 +526,12 @@ void mmu_configure(addr_t fdt_addr) memset((void *) __sys_linearmap_l2pgtable, 0, TTB_L2_SIZE); /* Create an identity mapping of 1 GB on running kernel so that the kernel code can go ahead right after the MMU on */ -#ifdef CONFIG_VA_BITS_48 __sys_root_pgtable[l0pte_index(mem_info.phys_base)] = (u64) __sys_idmap_l1pgtable & TTB_L0_TABLE_ADDR_MASK; set_pte_table(&__sys_root_pgtable[l0pte_index(mem_info.phys_base)], DCACHE_WRITEALLOC); __sys_idmap_l1pgtable[l1pte_index(mem_info.phys_base)] = mem_info.phys_base & TTB_L1_BLOCK_ADDR_MASK; set_pte_block(&__sys_idmap_l1pgtable[l1pte_index(mem_info.phys_base)], DCACHE_WRITEALLOC); -#elif CONFIG_VA_BITS_39 - __sys_root_pgtable[l1pte_index(mem_info.phys_base)] = mem_info.phys_base & TTB_L1_BLOCK_ADDR_MASK; - set_pte_block(&__sys_root_pgtable[l1pte_index(mem_info.phys_base)], DCACHE_WRITEALLOC); -#else -#error "Wrong VA_BITS configuration." -#endif - -#ifdef CONFIG_VA_BITS_48 /* Create the initial linear mapping of the kernel in its target virtual address space */ __sys_root_pgtable[l0pte_index(CONFIG_KERNEL_VADDR)] = (u64) __sys_linearmap_l1pgtable & TTB_L0_TABLE_ADDR_MASK; @@ -662,33 +551,10 @@ void mmu_configure(addr_t fdt_addr) set_pte_block(&__sys_linearmap_l2pgtable[l2pte_index(CONFIG_KERNEL_VADDR + i * SZ_2M)], DCACHE_WRITEALLOC); } -#elif CONFIG_VA_BITS_39 - __sys_root_pgtable[l1pte_index(CONFIG_KERNEL_VADDR)] = (u64) __sys_linearmap_l2pgtable & TTB_L1_TABLE_ADDR_MASK; - set_pte_table(&__sys_root_pgtable[l1pte_index(CONFIG_KERNEL_VADDR)], DCACHE_WRITEALLOC); - - /* Set up a 128 MB linear mapping to progress with the bootstrap code - * until the memory manager re-configure the memory mapping with - * a better granularity. - */ - for (i = 0; i < 64; i++) { - __sys_linearmap_l2pgtable[l2pte_index(CONFIG_KERNEL_VADDR + i * SZ_2M)] = (mem_info.phys_base + i * SZ_2M) & - TTB_L2_BLOCK_ADDR_MASK; - set_pte_block(&__sys_linearmap_l2pgtable[l2pte_index(CONFIG_KERNEL_VADDR + i * SZ_2M)], DCACHE_WRITEALLOC); - } -#else -#error "Wrong VA_BITS configuration." -#endif /* Early mapping I/O for UART. Here, the UART is supposed to be in a different L1 entry than the RAM. */ -#ifdef CONFIG_VA_BITS_48 __sys_idmap_l1pgtable[l1pte_index(CONFIG_UART_LL_PADDR)] = CONFIG_UART_LL_PADDR & TTB_L1_BLOCK_ADDR_MASK; set_pte_block(&__sys_idmap_l1pgtable[l1pte_index(CONFIG_UART_LL_PADDR)], DCACHE_OFF); -#elif CONFIG_VA_BITS_39 - __sys_root_pgtable[l1pte_index(CONFIG_UART_LL_PADDR)] = CONFIG_UART_LL_PADDR & TTB_L1_BLOCK_ADDR_MASK; - set_pte_block(&__sys_root_pgtable[l1pte_index(CONFIG_UART_LL_PADDR)], DCACHE_OFF); -#else -#error "Wrong VA_BITS configuration." -#endif #ifdef CONFIG_AVZ } @@ -917,13 +783,7 @@ void duplicate_pgtable_entry(u64 *from, u64 *to, int level, u64 vaddr, pcb_t *pc void duplicate_user_space(pcb_t *from, pcb_t *to) { /* Walk through the L0 pgtable and copy the entries */ -#ifdef CONFIG_VA_BITS_48 duplicate_pgtable_entry((u64 *) from->pgtable, (u64 *) to->pgtable, 0, 0, to); -#elif CONFIG_VA_BITS_39 - duplicate_pgtable_entry((u64 *) from->pgtable, (u64 *) to->pgtable, 1, 0, to); -#else -#error "Wrong VA_BITS configuration." -#endif } #ifdef CONFIG_RAMDEV @@ -950,15 +810,12 @@ void ramdev_create_mapping(void *root_pgtable, addr_t ramdev_start, addr_t ramde */ addr_t virt_to_phys_pt(addr_t vaddr) { -#ifdef CONFIG_VA_BITS_48 addr_t *l0pte; -#endif addr_t *l1pte, *l2pte, *l3pte; uint32_t offset; offset = vaddr & ~PAGE_MASK; -#ifdef CONFIG_VA_BITS_48 if (user_space_vaddr(vaddr)) l0pte = l0pte_offset(current_pgtable(), vaddr); else @@ -969,16 +826,6 @@ addr_t virt_to_phys_pt(addr_t vaddr) l1pte = l1pte_offset(l0pte, vaddr); BUG_ON(!*l1pte); -#elif CONFIG_VA_BITS_39 - if (user_space_vaddr(vaddr)) - l1pte = l1pte_offset((u64 *) current_pgtable(), vaddr); - else - l1pte = l1pte_offset(__sys_root_pgtable, vaddr); - BUG_ON(!*l1pte); -#else -#error "Wrong VA_BITS configuration." -#endif - if (pte_type(l1pte) == PTE_TYPE_BLOCK) return (*l1pte & TTB_L1_BLOCK_ADDR_MASK) | offset; diff --git a/so3/arch/arm64/rpi4_64/include/mach/ipamap.h b/so3/arch/arm64/rpi4_64/include/mach/ipamap.h index 4dca8f0f71..909b26896f 100644 --- a/so3/arch/arm64/rpi4_64/include/mach/ipamap.h +++ b/so3/arch/arm64/rpi4_64/include/mach/ipamap.h @@ -89,7 +89,7 @@ ipamap_t agency_ipamap[] = { * which will be trapped and handled by the hypervisor. */ -ipamap_t capsule_ipamap[] = { +ipamap_t S3C_ipamap[] = { { /* Only mapping the CPU interface to the vGIC CPU interface (GICV). diff --git a/so3/arch/arm64/so3.lds b/so3/arch/arm64/so3.lds index 44adc01626..1260fc0b63 100644 --- a/so3/arch/arm64/so3.lds +++ b/so3/arch/arm64/so3.lds @@ -15,7 +15,7 @@ SECTIONS /* where some locations at low addresses are reserved */ /* for spin tables used for starting cores. */ - . = ENTRY_ADDR + 0x80000; + . = ENTRY_ADDR + LOAD_OFFSET; __vectors_start = .; @@ -64,27 +64,27 @@ SECTIONS *(.sbss*) } - .bss (NOLOAD) : - { - *(.bss*) - - . = ALIGN(4096); + .bss (NOLOAD) : + { + *(.bss .bss.*) + + . = ALIGN(4096); __per_cpu_start = .; - - *(.bss.percpu) - + + *(.percpu_data) + . = ALIGN(128); - *(.bss.percpu.read_mostly) - + *(.percpu_data.read_mostly) + . = ALIGN(128); - + __per_cpu_data_end = .; - + . = ALIGN(128); - + __per_cpu_end = .; - + } :data __bss_end = .; diff --git a/so3/arch/arm64/traps.c b/so3/arch/arm64/traps.c index 3ed7518832..81b5a49095 100644 --- a/so3/arch/arm64/traps.c +++ b/so3/arch/arm64/traps.c @@ -20,6 +20,7 @@ #include #include #include +#include #ifdef CONFIG_AVZ @@ -27,6 +28,7 @@ #include #include +#include #include #ifdef CONFIG_SOO @@ -78,26 +80,113 @@ void trap_handle_error(addr_t lr) { #ifdef CONFIG_AVZ unsigned long esr = read_sysreg(esr_el2); - unsigned long far = read_sysreg(far_el2); unsigned long elr = read_sysreg(elr_el2); + unsigned long far = read_sysreg(far_el2); unsigned long hpfar = read_sysreg(hpfar_el2); + printk("CPU%d: ELR: %lx, FAR: %lx, HPFAR: %lx (IPA=0x%lx), LR(x30): %lx\n", smp_processor_id(), elr, far, hpfar, + hpfar << 8, lr); #else unsigned long esr = read_sysreg(esr_el1); + unsigned long elr = read_sysreg(elr_el1); unsigned long far = read_sysreg(far_el1); - unsigned long elr = 0; - unsigned long hpfar = 0; + printk("CPU%d: ELR: %lx, FAR: %lx, LR(x30): %lx\n", smp_processor_id(), elr, far, lr); #endif - printk(" FAR: %lx\n", far); - printk(" ELR: %lx\n", elr); - printk(" HPFAR: %lx (IPA: %lx)\n", hpfar, (hpfar >> 4) << 12); show_invalid_entry_message(ESR_ELx_EC(esr), esr, lr); } #ifdef CONFIG_SMP -extern addr_t cpu_entrypoint; +extern addr_t cpu_entrypoints[4]; + +/* Called from pre_ret_to_el1 assembly before entering the WFI poll loop */ +void pre_ret_debug_wfi(u32 cpu_id) +{ + (void) cpu_id; +} + +/* Called from pre_ret_to_el1 assembly when entrypoint becomes non-zero */ +void pre_ret_debug_woke(u32 cpu_id, u64 ep) +{ + (void) cpu_id; + (void) ep; +} #endif +#include +#include +#include + +#define CNTHP_PPI_INTID 26 + +/* Set to 0 to bring up Linux with only CPU0 — useful for isolating vGIC + * issues before enabling full SMP. Set to 1 for normal 4-CPU operation. + * Used by the PSCI CPU_ON handler below (which is shared between GICv2 + * and GICv3), so it must be defined regardless of CONFIG_GIC_V3. */ +#define AVZ_SMP_BOOT 1 + +#ifdef CONFIG_GIC_V3 +/* EL2 IRQ handler for GICv3 with HCR_EL2.IMO=1. + * All physical IRQs (Group 1 NS) are taken at EL2. CNTHP (PPI 26) and the + * vGIC maintenance IRQ (PPI 25) are handled locally; every other IRQ is + * forwarded to Linux via a hardware-backed virtual LR. + * + * ICH_HCR_EL2.EN is enabled lazily inside gic_inject_irq() the first time a + * virtual LR is written (mirrors Bao's vgic_add_lr pattern). This guarantees + * EN=0 while Linux's gic_cpu_sys_reg_init() runs so it accesses the physical + * ICC registers directly, avoiding undefined-instruction faults on ICV_AP0Rx. */ +void avz_el2_irq_handle(cpu_regs_t *regs) +{ + u64 intid = read_sysreg_s(SYS_ICC_IAR1_EL1); + u32 id = (u32) (intid & 0x3ff); + + /* Spurious — no EOI required */ + if (id >= 1020) + return; + + /* Linux's gic_cpu_init writes GICR_ICENABLER0 = 0xffffffff which + * disables ALL PPIs at the redistributor on each CPU, including + * AVZ's MAINT (PPI 25) and CNTHP (PPI 26). Re-assert them on + * every IRQ entry — the write is idempotent and per-CPU. */ + { + int cpu_id = smp_processor_id(); + u8 *gicr_sgi = (u8 *) gic->gicc + cpu_id * 0x20000 + 0x10000; + iowrite32(gicr_sgi + 0x100, (1u << IRQ_ARCH_ARM_MAINT) | (1u << CNTHP_PPI_INTID)); + } + + if (id == CNTHP_PPI_INTID) { + /* CNTHP (id=26): AVZ's own hypervisor timer — handle locally. */ + avz_el2_timer_tick(); + /* EOImode=1: priority drop, then explicit deactivate. */ + write_sysreg_s(intid, SYS_ICC_EOIR1_EL1); + write_sysreg_s(intid, SYS_ICC_DIR_EL1); + isb(); + } else if (id == IRQ_ARCH_ARM_MAINT) { + /* vGIC maintenance (id=25): drain overflow queue into free LRs. */ + gic_inject_pending(); + write_sysreg_s(intid, SYS_ICC_EOIR1_EL1); + write_sysreg_s(intid, SYS_ICC_DIR_EL1); + isb(); + } else if (is_sgi(id)) { + /* SGIs (0–15): software-backed. Deactivate physically, then inject + * as virtual SGI so Linux's IPI handler fires. */ + write_sysreg_s(intid, SYS_ICC_EOIR1_EL1); + write_sysreg_s(intid, SYS_ICC_DIR_EL1); + isb(); + gic_set_pending((u16) id); + } else { + /* PPIs (16–31, except 25/26) and SPIs (32+) destined for Linux. + * Drop priority at EL2 so further physical IRQs can be taken; + * the HW=1 LR triggers the physical deactivate when Linux later + * writes ICV_EOIR1_EL1 — DO NOT call DIR here, otherwise + * level-triggered IRQs storm before Linux clears the device's + * level source. */ + gic_set_pending((u16) id); + write_sysreg_s(intid, SYS_ICC_EOIR1_EL1); + isb(); + } +} +#endif /* CONFIG_GIC_V3 */ + /** * @brief Handling the dabt condition * @@ -191,18 +280,57 @@ void trap_handle(cpu_regs_t *regs) regs->x0 = PSCI_VERSION(1, 1); break; - case PSCI_0_2_FN64_CPU_ON: - printk("Power on CPU #%ld starting at %lx...\n", regs->x1 & 3, regs->x2); + case PSCI_0_2_FN64_CPU_ON: { + int target_cpu = regs->x1 & 3; - cpu_entrypoint = regs->x2; - smp_trigger_event(regs->x1 & 3); +#if AVZ_SMP_BOOT == 0 + regs->x0 = PSCI_RET_NOT_SUPPORTED; + break; +#endif + cpu_entrypoints[target_cpu] = regs->x2; + dsb(ish); + +#ifdef CONFIG_GIC_V3 + /* GICv3: targeted SGI via ICC_SGI1R_EL1 system register. + * ICC_SGI1R_EL1: [3:0]=SGI_ID, [23:16]=Aff1 target list (bit per Aff0). */ + { + u64 sgi1r = ((u64) (1U << target_cpu) << 16) | IPI_EVENT_CHECK; + write_sysreg_s(sgi1r, SYS_ICC_SGI1R_EL1); + isb(); + } +#else + /* GICv2: targeted SGI via GICD_SGIR MMIO. + * Format: [25:24]=00b (use TargetList), [23:16]=CPUTargetList + * (one bit per CPU), [3:0]=SGIINTID. + * Earlier code used (1u << 24) which is mode 01b + * (all-but-self) and ignores the TargetList — broadcasting + * IPI_EVENT_CHECK to every other CPU during PSCI_CPU_ON. */ + iowrite32(&gic->gicd->sgir, ((1u << target_cpu) << 16) | IPI_EVENT_CHECK); + dsb(ish); +#endif regs->x0 = PSCI_RET_SUCCESS; break; + } case PSCI_0_2_FN_MIGRATE_INFO_TYPE: + /* No Trusted OS requiring migration — multiprocessor system */ + regs->x0 = PSCI_0_2_TOS_MP; + break; + case PSCI_1_0_FN_PSCI_FEATURES: - regs->x0 = PSCI_RET_SUCCESS; + /* No PSCI 1.x features implemented beyond the base set */ + regs->x0 = PSCI_RET_NOT_SUPPORTED; + break; + + case PSCI_0_2_FN_CPU_SUSPEND: + case PSCI_0_2_FN64_CPU_SUSPEND: + /* Not implemented: Linux cpuidle falls back to WFI */ + regs->x0 = PSCI_RET_NOT_SUPPORTED; + break; + + case PSCI_0_2_FN_CPU_OFF: + regs->x0 = PSCI_RET_NOT_SUPPORTED; break; #endif /* CONFIG_SMP */ @@ -214,6 +342,10 @@ void trap_handle(cpu_regs_t *regs) __sigreturn(); break; #endif /* CONFIG_SOO */ + default: + lprintk("[AVZ] HVC caught from EL1: x0=0x%lx ELR_EL2=0x%lx\n", hvc_code, read_sysreg(elr_el2)); + regs->x0 = PSCI_RET_NOT_SUPPORTED; + break; } break; #endif /* CONFIG_AVZ */ @@ -258,6 +390,35 @@ void trap_handle(cpu_regs_t *regs) break; #endif + case ESR_ELx_EC_SYS64: { + /* Architectural traps that survive HSTR_EL2=0 — primarily + * ICC_SGI1R_EL1 writes, which the GICv3 spec mandates trap to EL2 + * when HCR_EL2.IMO=1 so the hypervisor can route the IPI. */ + u64 elr = read_sysreg(elr_el2); + u32 iss = (u32) (esr & 0x1ffffff); + u32 dir = iss & 1; + u32 crm = (iss >> 1) & 0xf; + u32 rt = (iss >> 5) & 0x1f; + u32 crn = (iss >> 10) & 0xf; + u32 op1 = (iss >> 14) & 0x7; + u32 op2 = (iss >> 17) & 0x7; + u32 op0 = (iss >> 20) & 0x3; + + if (!dir && op0 == 3 && op1 == 0 && crn == 12 && crm == 11 && op2 == 5) { + u64 sgi_val = (rt != 31) ? ((u64 *) regs)[rt] : 0; + write_sysreg_s(sgi_val, SYS_ICC_SGI1R_EL1); + isb(); + } else if (dir && rt != 31) { + ((u64 *) regs)[rt] = 0; + } + + regs->pc = elr + 4; + break; + } + + case ESR_ELx_EC_IABT_LOW: + goto __err; + default: __err: lprintk("### On CPU %d: ESR_Elx_EC(esr): 0x%lx\n", smp_processor_id(), ESR_ELx_EC(esr)); diff --git a/so3/arch/arm64/verdin-imx8mp/Makefile b/so3/arch/arm64/verdin-imx8mp/Makefile new file mode 100644 index 0000000000..580dd0a20a --- /dev/null +++ b/so3/arch/arm64/verdin-imx8mp/Makefile @@ -0,0 +1,5 @@ +# +# Makefile +# + +obj-$(CONFIG_SMP) += platsmp.o diff --git a/so3/arch/arm64/verdin-imx8mp/include/mach/io.h b/so3/arch/arm64/verdin-imx8mp/include/mach/io.h new file mode 100644 index 0000000000..8027c5f1dd --- /dev/null +++ b/so3/arch/arm64/verdin-imx8mp/include/mach/io.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2026 EDGEMTech SA + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef MACH_IO_H +#define MACH_IO_H + +/* + * Freescale i.MX8MP UART3 register offsets (fsl,imx6q-uart compatible). + * Base address: 0x30880000 (UART3, used as debug console). + */ + +#define IMX_UART_URXD 0x00 /* Receiver register */ +#define IMX_UART_UTXD 0x40 /* Transmitter register */ +#define IMX_UART_UCR1 0x80 /* Control register 1 */ +#define IMX_UART_UCR2 0x84 /* Control register 2 */ +#define IMX_UART_UCR3 0x88 /* Control register 3 */ +#define IMX_UART_UCR4 0x8c /* Control register 4 */ +#define IMX_UART_UFCR 0x90 /* FIFO control register */ +#define IMX_UART_USR1 0x94 /* Status register 1 */ +#define IMX_UART_USR2 0x98 /* Status register 2 */ + +/* USR2 bits */ +#define IMX_UART_USR2_TXFE (1 << 14) /* TX FIFO empty */ +#define IMX_UART_USR2_TXDC (1 << 3) /* Transmitter complete */ + +/* UCR1 bits */ +#define IMX_UART_UCR1_UARTEN (1 << 0) /* UART enable */ + +/* UCR2 bits */ +#define IMX_UART_UCR2_TXEN (1 << 2) /* Transmitter enable */ +#define IMX_UART_UCR2_RXEN (1 << 1) /* Receiver enable */ +#define IMX_UART_UCR2_SRST (1 << 0) /* SW reset (active low) */ + +#endif /* MACH_IO_H */ diff --git a/so3/arch/arm64/verdin-imx8mp/include/mach/ipamap.h b/so3/arch/arm64/verdin-imx8mp/include/mach/ipamap.h new file mode 100644 index 0000000000..a2b06ebcd3 --- /dev/null +++ b/so3/arch/arm64/verdin-imx8mp/include/mach/ipamap.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2026 EDGEMTech SA + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef MACH_IPAMAP_H +#define MACH_IPAMAP_H + +#include + +/* + * IPA (Intermediate Physical Address) map for the AVZ agency on iMX8MP. + * + * The peripheral space (0x30000000–0x3FFFFFFF) is split into two regions so + * that the GICv3 distributor (GICD @ 0x38800000, size 64 KiB) is NOT mapped + * in Stage-2. Linux accesses to GICD therefore cause Stage-2 data aborts + * that are intercepted by AVZ's mmio_dabt_decode() handler, which forwards + * them to the physical GICD or emulates them as needed. + * + * GICR (redistributor @ 0x38880000) and all other peripherals remain + * directly accessible by Linux. + */ +ipamap_t agency_ipamap[] = { + { + /* AIPS1/2/3 peripherals below the GIC distributor */ + .ipa_addr = 0x30000000, + .phys_addr = 0x30000000, + .size = 0x08800000, /* 0x30000000 – 0x387FFFFF */ + }, + /* GICD @ 0x38800000 size 0x10000 intentionally omitted */ + { + /* GICR, CPU interface compat, and all peripherals above GICD */ + .ipa_addr = 0x38810000, + .phys_addr = 0x38810000, + .size = 0x077F0000, /* 0x38810000 – 0x3FFFFFFF */ + }, +}; + +/* + * Capsule IPA map — minimal stub (no SOO capsules on this platform). + * The vGIC CPU interface entry is kept for structural compatibility with + * the hypervisor's domain setup code; the GICv3 compat GICC is mapped + * at its physical address. + */ +ipamap_t S3C_ipamap[] = { + { + /* GICv3 CPU interface compatibility register frame */ + .ipa_addr = 0x38C20000, + .phys_addr = 0x38C20000, + .size = 0x10000, + }, +}; + +#endif /* MACH_IPAMAP_H */ diff --git a/so3/arch/arm64/verdin-imx8mp/platsmp.c b/so3/arch/arm64/verdin-imx8mp/platsmp.c new file mode 100644 index 0000000000..4f3f4d4103 --- /dev/null +++ b/so3/arch/arm64/verdin-imx8mp/platsmp.c @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2016,2017 Daniel Rossier + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +/* + * Secondary CPU bring-up for Toradex Verdin iMX8MP. + * ATF provides PSCI; cpu_on() issues an SMC to bring secondaries to + * secondary_startup via PSCI_CPU_ON. + */ + +#include +#include +#include + +#include +#include + +#include + +extern void secondary_startup(void); + +static DEFINE_SPINLOCK(cpu_lock); + +void smp_boot_secondary(unsigned int cpu) +{ + spin_lock(&cpu_lock); + cpu_on(cpu, (u32) __pa(secondary_startup)); + spin_unlock(&cpu_lock); +} diff --git a/so3/arch/arm64/virt64/include/mach/ipamap.h b/so3/arch/arm64/virt64/include/mach/ipamap.h index 6bdc9ae073..a838c3bcdb 100644 --- a/so3/arch/arm64/virt64/include/mach/ipamap.h +++ b/so3/arch/arm64/virt64/include/mach/ipamap.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2023 Daniel Rossier + * Copyright (C) 2020-2026 Daniel Rossier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -21,29 +21,75 @@ #include +/* + * IPA (Intermediate Physical Address) map for the AVZ agency on virt64. + * + * - GICD (0x08000000, size 0x10000) intentionally omitted from + * Stage-2: every Linux access faults to mmio_dabt_decode → + * gic_handle_dist_access, which forwards to physical GICD with + * GICD_SGIR translated into smp_cross_call. Mirrors verdin's + * GICv3 model. + * - GICC (guest IPA 0x08010000) → physical GICV (0x08040000) so the + * guest's CPU-interface accesses hit the virtual interface and + * AVZ's HW=1 LR injections actually reach Linux. + * - v2m (0x08020000) is pass-through (Linux exposes it but does not + * use MSIs without a configured PCIe device). + * - GICH (0x08030000) and GICV (0x08040000) are EL2-only — no guest + * mapping at those IPAs. + * - Everything else (UART, RTC, fw-cfg, virtio MMIO, …) above the GIC + * block stays pass-through. + */ ipamap_t agency_ipamap[] = { { + /* GICD pass-through. Trap+forward (mirroring verdin GICv3) + * was attempted with two different models — direct physical + * GICD_SGIR replay, and Jailhouse-style queue+wake — both + * regress SMP bring-up under QEMU virt GICv2 + secure=on: + * cross-CPU SGIs queued from EL2 don't reliably wake the + * target Linux CPU under that combination. Pass-through + * keeps Linux's GICD writes hitting hardware directly, which + * works correctly with the GICv2 vGIC priority/source-CPU/EOIR + * fixes that landed alongside this in gic.c. */ .ipa_addr = 0x08000000, .phys_addr = 0x08000000, - .size = 0x3000000, + .size = 0x10000, }, { - /* PCIe configuration ranges */ - .ipa_addr = 0x4010000000, - .phys_addr = 0x4010000000, - .size = 0x10000000, + /* GICC view → physical GICV (vGIC CPU interface). */ + .ipa_addr = 0x08010000, + .phys_addr = 0x08040000, + .size = 0x10000, + }, + { + /* v2m MSI frame */ + .ipa_addr = 0x08020000, + .phys_addr = 0x08020000, + .size = 0x10000, }, { - /* PCIe devices IO and 32 bits memory ranges */ + /* Peripherals above the GIC block (UART, RTC, virtio, …) */ + .ipa_addr = 0x08050000, + .phys_addr = 0x08050000, + .size = 0x2FB0000, + }, + { + /* PCIe MMIO low window (QEMU virt: 0x10000000-0x3effffff). + * Linux PCIe enumeration walks this range looking for BARs. + * Pass-through: no devices mean reads return 0xff and writes + * are harmless, but the Stage-2 mapping must exist. */ .ipa_addr = 0x10000000, .phys_addr = 0x10000000, - .size = 0x2f000000, + .size = 0x30000000, }, { - /* PCIe devices 64 bits memory ranges */ - .ipa_addr = 0x8000000000, - .phys_addr = 0x8000000000, - .size = 0x8000000000, + /* PCIe ECAM extended config space (QEMU virt: + * 0x4010000000-0x401fffffff, 256MB for buses 00-ff). Linux's + * pci-host-generic accesses this region while enumerating — + * unmapped, the access faults at Stage-2 and crashes the + * faulting CPU (CPU1 in our boot log). */ + .ipa_addr = 0x4010000000UL, + .phys_addr = 0x4010000000UL, + .size = 0x10000000, }, }; @@ -52,7 +98,7 @@ ipamap_t agency_ipamap[] = { * which will be trapped and handled by the hypervisor. */ -ipamap_t capsule_ipamap[] = { +ipamap_t S3C_ipamap[] = { { /* Only mapping the CPU interface to the vGIC CPU interface. diff --git a/so3/avz/include/avz/dcm.h b/so3/avz/include/avz/dcm.h index c8426035ea..11c47be591 100644 --- a/so3/avz/include/avz/dcm.h +++ b/so3/avz/include/avz/dcm.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 Daniel Rossier + * Copyright (C) 2016-2026 Daniel Rossier * Copyright (C) 2017,2018 Baptiste Delporte * * This program is free software; you can redistribute it and/or modify @@ -35,8 +35,8 @@ #define DCM_N_RECV_BUFFERS 10 -/* Max ME size in bytes */ -#define DATACOMM_ME_MAX_SIZE (32 * 1024 * 1024) +/* Max capsule size in bytes */ +#define DATACOMM_S3C_MAX_SIZE (32 * 1024 * 1024) #endif /* __KERNEL__ */ @@ -49,15 +49,15 @@ #define DCM_IOCTL_SET_AGENCY_UID _IOWR(0x5000DC30, 5, char) typedef struct { - void *ME_data; /* Reference to the uncompressed ME */ - size_t size; /* Size of this ME ready to be compressed */ - uint32_t prio; /* Priority of this ME (unused at the moment) */ + void *S3C_data; /* Reference to the uncompressed capsule */ + size_t size; /* Size of this capsule ready to be compressed */ + uint32_t prio; /* Priority of this capsule (unused at the moment) */ } dcm_ioctl_send_args_t; typedef struct { - void *ME_data; + void *S3C_data; size_t buffer_size; - size_t ME_size; + size_t S3C_size; } dcm_ioctl_recv_args_t; /* @@ -69,7 +69,7 @@ typedef enum { DCM_BUFFER_SEND = 0, DCM_BUFFER_RECV } dcm_buffer_direction_t; /* * - DCM_BUFFER_FREE means the buffer is ready for send/receive operations. - * - DCM_BUFFER_BUSY means the buffer is reserved for a ME. + * - DCM_BUFFER_BUSY means the buffer is reserved for a capsule. * - DCM_BUFFER_SENDING means the buffer is currently along the path for being sent out. * The buffer can still be altered depending on the steps of sending (See SOOlink/Coder). */ @@ -86,9 +86,9 @@ typedef struct { dcm_buffer_status_t status; /* - * Reference to the ME buffer. + * Reference to the capsule buffer. */ - void *ME_data; + void *S3C_data; size_t size; uint32_t prio; @@ -98,7 +98,7 @@ typedef struct { #endif /* __KERNEL__ */ #ifdef __KERNEL__ -int dcm_ME_rx(void *ME_buffer, size_t size); +int dcm_S3C_rx(void *S3C_buffer, size_t size); #endif /* __KERNEL__ */ #endif /* DCM_H */ diff --git a/so3/avz/include/avz/domain.h b/so3/avz/include/avz/domain.h index d7d333ecc0..ecd02f96d3 100644 --- a/so3/avz/include/avz/domain.h +++ b/so3/avz/include/avz/domain.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016,2017 Daniel Rossier + * Copyright (C) 2016-2026 Daniel Rossier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -151,8 +151,8 @@ extern struct domain *domains[MAX_DOMAINS]; extern int construct_agency(struct domain *d); #ifdef CONFIG_SOO -extern int construct_ME(struct domain *d); -ME_state_t get_ME_state(unsigned int ME_slotID); +extern int construct_S3C(struct domain *d); +S3C_state_t get_S3C_state(unsigned int S3C_slotID); #endif /* CONFIG_SOO */ void do_domctl(domctl_t *args); diff --git a/so3/avz/include/avz/injector.h b/so3/avz/include/avz/injector.h index ea5a4a5e00..c71918c212 100644 --- a/so3/avz/include/avz/injector.h +++ b/so3/avz/include/avz/injector.h @@ -71,7 +71,7 @@ void inject_capsule(avz_hyp_t *args); /* Start the execution of a capsule */ void start_capsule(avz_hyp_t *args); -void read_ME_snapshot(avz_hyp_t *args); -void write_ME_snapshot(avz_hyp_t *args); +void read_S3C_snapshot(avz_hyp_t *args); +void write_S3C_snapshot(avz_hyp_t *args); #endif /* INJECTOR_H */ \ No newline at end of file diff --git a/so3/avz/include/avz/memslot.h b/so3/avz/include/avz/memslot.h index b09e48e66d..0e2da02fdd 100644 --- a/so3/avz/include/avz/memslot.h +++ b/so3/avz/include/avz/memslot.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016,2017 Daniel Rossier + * Copyright (C) 2016-2026 Daniel Rossier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -20,7 +20,7 @@ /* Number of possible MEs in the local SOO */ #define MEMSLOT_BASE 2 -#define MEMSLOT_NR (MEMSLOT_BASE + MAX_ME_DOMAINS) +#define MEMSLOT_NR (MEMSLOT_BASE + MAX_S3C_DOMAINS) /* Basic memslots. */ #define MEMSLOT_AVZ 0 @@ -56,11 +56,11 @@ extern memslot_entry_t memslot[]; * that the domain_build will be able to elf-parse and load to their final destination. */ void loadAgency(void); -void loadME(unsigned int slotID, void *itb); +void load_S3C(unsigned int slotID, void *itb); void memslot_init(void); -int get_ME_free_slot(unsigned int size, int slotID); -void put_ME_slot(unsigned int ME_slotID); +int get_S3C_free_slot(unsigned int size, int slotID); +void put_S3C_slot(unsigned int S3C_slotID); #endif /* MEMSLOT_H */ diff --git a/so3/avz/include/avz/sched.h b/so3/avz/include/avz/sched.h index 96964c7513..9a1496a221 100644 --- a/so3/avz/include/avz/sched.h +++ b/so3/avz/include/avz/sched.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 Daniel Rossier + * Copyright (C) 2016-2026 Daniel Rossier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -81,7 +81,7 @@ void evtchn_destroy_final(struct domain *d); /* from complete_domain_destroy */ #define DOMAIN_DESTROYED (1 << 31) /* assumes atomic_t is >= 32 bits */ /* - * Creation of new domain context associated to the agency or a Mobile Entity. + * Creation of new domain context associated to the agency or a SO3 capsule. * @domid is the domain number * @partial tells if the domain creation remains partial, without the creation of the vcpu structure which may intervene in a second step */ diff --git a/so3/avz/include/avz/soo.h b/so3/avz/include/avz/soo.h index 6276562849..128e18ac16 100644 --- a/so3/avz/include/avz/soo.h +++ b/so3/avz/include/avz/soo.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2025 Daniel Rossier + * Copyright (C) 2014-2026 Daniel Rossier * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. @@ -21,12 +21,12 @@ #include /* Device tree features */ -#define ME_FEAT_ROOT "/me_features" +#define S3C_FEAT_ROOT "/s3c_features" void soo_activity_init(void); -void shutdown_ME(unsigned int ME_slotID); +void shutdown_S3C(unsigned int S3C_slotID); -ME_state_t get_ME_state(uint32_t ME_slotID); -void set_ME_state(uint32_t slotID, ME_state_t state); +S3C_state_t get_S3C_state(uint32_t S3C_slotID); +void set_S3C_state(uint32_t slotID, S3C_state_t state); #endif /* SOO_H */ diff --git a/so3/avz/include/avz/uapi/avz.h b/so3/avz/include/avz/uapi/avz.h index 4eed6041e9..b685215adf 100644 --- a/so3/avz/include/avz/uapi/avz.h +++ b/so3/avz/include/avz/uapi/avz.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Daniel Rossier + * Copyright (C) 2016-2026 Daniel Rossier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -50,10 +50,10 @@ /* DOMID_SELF is used in certain contexts to refer to oneself. */ #define DOMID_SELF (0x7FF0U) -#define MAX_ME_DOMAINS 5 +#define MAX_S3C_DOMAINS 5 /* We include the (non-RT & RT) agency domain */ -#define MAX_DOMAINS (2 + MAX_ME_DOMAINS) +#define MAX_DOMAINS (2 + MAX_S3C_DOMAINS) /* Agency */ #define DOMID_AGENCY 0 @@ -83,6 +83,7 @@ #ifndef DOMID_T #define DOMID_T + typedef uint16_t domid_t; typedef unsigned long addr_t; #endif @@ -115,8 +116,8 @@ typedef struct { console_t console; } avz_console_io_t; -#define DOMCTL_pauseME 1 -#define DOMCTL_unpauseME 2 +#define DOMCTL_pause_S3C 1 +#define DOMCTL_unpause_S3C 2 #define DOMCTL_get_AVZ_shared 3 struct domctl { @@ -138,6 +139,7 @@ typedef struct domctl domctl_t; #define EVTCHNSTAT_unbound 1 /* Channel is waiting interdom connection.*/ #define EVTCHNSTAT_interdomain 2 /* Channel is connected to remote domain. */ #define EVTCHNSTAT_virq 3 /* Channel is bound to a virtual IRQ line */ + /* * EVTCHNOP_alloc_unbound: Allocate a evtchn in domain and mark as * accepting interdomain bindings from domain . A fresh evtchn diff --git a/so3/avz/kernel/Makefile b/so3/avz/kernel/Makefile index 164cdf8017..f6039745a4 100644 --- a/so3/avz/kernel/Makefile +++ b/so3/avz/kernel/Makefile @@ -5,7 +5,7 @@ obj-y += console.o obj-y += agency_build.o -obj-${CONFIG_SOO} += ME_build.o +obj-${CONFIG_SOO} += capsule_build.o obj-y += domain.o obj-y += setup.o diff --git a/so3/avz/kernel/agency_build.c b/so3/avz/kernel/agency_build.c index b2093b7099..cf9953db1c 100644 --- a/so3/avz/kernel/agency_build.c +++ b/so3/avz/kernel/agency_build.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2024 Daniel Rossier + * Copyright (C) 2016-2026 Daniel Rossier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -69,8 +69,19 @@ int construct_agency(struct domain *d) printk("Agency FDT device tree: 0x%lx (phys)\n", d->avz_shared->fdt_paddr); printk("Shared AVZ page is located at: %p\n", d->avz_shared); + printk("Agency entry IPA: 0x%lx FDT IPA: 0x%lx\n", pa_to_ipa(MEMSLOT_AGENCY, memslot[MEMSLOT_AGENCY].entry_addr), + pa_to_ipa(MEMSLOT_AGENCY, d->avz_shared->fdt_paddr)); - initialize_hyp_dom_stack(d, pa_to_ipa(MEMSLOT_AGENCY, d->avz_shared->fdt_paddr), memslot[MEMSLOT_AGENCY].entry_addr); + /* Dump the first 4 instructions at the guest entry point to confirm the kernel is loaded */ + { + u32 *insns = (u32 *) __xva(MEMSLOT_AGENCY, memslot[MEMSLOT_AGENCY].entry_addr); + + printk("Guest entry (PA 0x%lx, VA 0x%p): [0]=0x%08x [1]=0x%08x [2]=0x%08x [3]=0x%08x\n", + memslot[MEMSLOT_AGENCY].entry_addr, insns, insns[0], insns[1], insns[2], insns[3]); + } + + initialize_hyp_dom_stack(d, pa_to_ipa(MEMSLOT_AGENCY, d->avz_shared->fdt_paddr), + pa_to_ipa(MEMSLOT_AGENCY, memslot[MEMSLOT_AGENCY].entry_addr)); return 0; } diff --git a/so3/avz/kernel/ME_build.c b/so3/avz/kernel/capsule_build.c similarity index 85% rename from so3/avz/kernel/ME_build.c rename to so3/avz/kernel/capsule_build.c index 1d22b885dd..95f35a8b59 100644 --- a/so3/avz/kernel/ME_build.c +++ b/so3/avz/kernel/capsule_build.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2024 Daniel Rossier + * Copyright (C) 2016-2026 Daniel Rossier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -31,10 +31,10 @@ #include /* - * construct_ME sets up a new Mobile Entity. + * construct_S3C sets up a new SO3 capsule. */ -int construct_ME(struct domain *d) +int construct_S3C(struct domain *d) { unsigned int slotID; unsigned long alloc_spfn; @@ -44,12 +44,12 @@ int construct_ME(struct domain *d) printk("***************************** Loading SO3 Guest Container *****************************\n"); if (memslot[slotID].size == 0) - panic("No ME image supplied\n"); + panic("No capsule image supplied\n"); /* We are already on the swapper_pg_dir page table to have full access to RAM */ /* Put the signature for consistency checking */ - strcpy(d->avz_shared->signature, SOO_ME_SIGNATURE); + strcpy(d->avz_shared->signature, SOO_S3C_SIGNATURE); d->max_pages = ~0U; @@ -68,7 +68,7 @@ int construct_ME(struct domain *d) d->avz_shared->fdt_paddr = pa_to_ipa(slotID, memslot[slotID].fdt_paddr); - printk("ME FDT device tree: 0x%lx (phys)\n", d->avz_shared->fdt_paddr); + printk("capsule FDT device tree: 0x%lx (phys)\n", d->avz_shared->fdt_paddr); initialize_hyp_dom_stack(d, d->avz_shared->fdt_paddr, memslot[slotID].ipa_addr + L_TEXT_OFFSET); diff --git a/so3/avz/kernel/domain.c b/so3/avz/kernel/domain.c index a70c3a6e72..cbe0e9c125 100644 --- a/so3/avz/kernel/domain.c +++ b/so3/avz/kernel/domain.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2025 Daniel Rossier + * Copyright (C) 2014-2026 Daniel Rossier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -27,6 +27,8 @@ #include #include +#include +#include #include #include @@ -44,14 +46,14 @@ static DEFINE_SPINLOCK(domctl_lock); /* * We don't care of the IDLE domain here... * In the domain table, the index 0 and 1 are dedicated to the non-RT and RT agency domains. - * The indexes 1..MAX_DOMAINS are for the MEs. ME_slotID should correspond to domain ID. + * The indexes 1..MAX_DOMAINS are for the MEs. S3C_slotID should correspond to domain ID. */ struct domain *domains[MAX_DOMAINS]; struct domain *agency; /* - * Creation of new domain context associated to the agency or a Mobile Entity. + * Creation of new domain context associated to the agency or a SO3 capsule. * * @domid is the domain number * @cpu_id the CPU on which this domain is allowed to run @@ -111,7 +113,7 @@ struct domain *domain_create(domid_t domid, int cpu_id) if (is_idle_domain(d) && (cpu_id == AGENCY_CPU)) d->sched = &sched_agency; else { - if (cpu_id == ME_CPU) { + if (cpu_id == S3C_CPU) { d->sched = &sched_flip; d->need_periodic_timer = true; @@ -287,14 +289,14 @@ void do_domctl(domctl_t *args) d = domains[args->domain]; switch (args->cmd) { - case DOMCTL_pauseME: + case DOMCTL_pause_S3C: domain_pause_by_systemcontroller(d); break; - case DOMCTL_unpauseME: + case DOMCTL_unpause_S3C: - LOG_DEBUG("%s: unpausing ME\n", __func__); + LOG_DEBUG("%s: unpausing capsule\n", __func__); domain_unpause_by_systemcontroller(d); break; diff --git a/so3/avz/kernel/domain_utils.c b/so3/avz/kernel/domain_utils.c index 64b747f580..555ff8b5d1 100644 --- a/so3/avz/kernel/domain_utils.c +++ b/so3/avz/kernel/domain_utils.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2017 Daniel Rossier + * Copyright (C) 2016-2026 Daniel Rossier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -259,24 +259,25 @@ void loadAgency(void) /** * The ITB image will be parsed and the components placed in their target memory location. + * This works in AVZ context. * * @param slotID * @param itb ITB image */ -void loadME(unsigned int slotID, void *itb) +void load_S3C(unsigned int slotID, void *itb) { - void *ME_vaddr; + void *S3C_vaddr; uint32_t dom_addr, entry_addr, fdt_paddr; - size_t ME_size, fdt_size, initrd_size; + size_t S3C_size, fdt_size, initrd_size; void *fdt_vaddr, *initrd_vaddr; - void *dest_ME_vaddr; + void *dest_S3C_vaddr; uint32_t initrd_start, initrd_end; int nodeoffset, next_node, depth = 0; int ret; const char *propstring; mem_info_t guest_mem_info; - /* Look for a node of ME type in the fit image */ + /* Look for a node of capsule type in the fit image */ nodeoffset = 0; depth = 0; while (nodeoffset >= 0) { @@ -299,9 +300,9 @@ void loadME(unsigned int slotID, void *itb) lprintk("ITB: Domain entry addr = 0x%x\n", entry_addr); /* Get the pointer to the OS binary image from the ITB we got from the user space. */ - ret = fit_image_get_data_and_size(itb, nodeoffset, (const void **) &ME_vaddr, &ME_size); + ret = fit_image_get_data_and_size(itb, nodeoffset, (const void **) &S3C_vaddr, &S3C_size); if (ret) { - lprintk("!! The properties in the ME node does not look good !!\n"); + lprintk("!! The properties in the capsule node does not look good !!\n"); BUG(); } else break; @@ -311,7 +312,7 @@ void loadME(unsigned int slotID, void *itb) } if (nodeoffset < 0) { - lprintk("!! Unable to find a node with type ME in the FIT image... !!\n"); + lprintk("!! Unable to find a node with type capsule in the FIT image... !!\n"); BUG(); }; @@ -377,12 +378,12 @@ void loadME(unsigned int slotID, void *itb) nodeoffset = next_node; } - dest_ME_vaddr = (void *) memslot[slotID].base_vaddr; + dest_S3C_vaddr = (void *) memslot[slotID].base_vaddr; - dest_ME_vaddr += L_TEXT_OFFSET; + dest_S3C_vaddr += L_TEXT_OFFSET; /* Move the kernel binary within the domain slotID. */ - memcpy(dest_ME_vaddr, ME_vaddr, ME_size); + memcpy(dest_S3C_vaddr, S3C_vaddr, S3C_size); memslot[slotID].fdt_paddr = ipa_to_pa(slotID, fdt_paddr); diff --git a/so3/avz/kernel/gnttab.c b/so3/avz/kernel/gnttab.c index 333694cbf3..9660a6dc73 100644 --- a/so3/avz/kernel/gnttab.c +++ b/so3/avz/kernel/gnttab.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024-2025 Daniel Rossier + * Copyright (C) 2024-2026 Daniel Rossier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -143,9 +143,9 @@ addr_t allocate_grant_pfn(struct domain *d) } /** - * @brief Map the grant page associated to vbstore in the IPA domain of the ME + * @brief Map the grant page associated to vbstore in the IPA domain of the capsule * - * @param target_domid the ME which needs the vbstore pfn + * @param target_domid the capsule which needs the vbstore pfn * @param pfn * @return */ @@ -165,14 +165,7 @@ addr_t map_vbstore_pfn(int target_domid, int pfn) else grant_paddr = pfn_to_phys(pfn); - /* - * The vbstore ring buffer is shared Normal memory between agency - * and ME. Map it Normal cacheable (nocache=false) so that both - * sides see coherent writes via the inner-shareable cache domain. - * Using Device attributes here would break ring-buffer coherency - * with the agency which maps the same page as Normal cacheable. - */ - __create_mapping((addr_t *) d->pagetable_vaddr, grant_paddr, pfn_to_phys(cur->pfn), PAGE_SIZE, false, + __create_mapping((addr_t *) d->pagetable_vaddr, grant_paddr, pfn_to_phys(cur->pfn), PAGE_SIZE, true, S2); return phys_to_pfn(grant_paddr); diff --git a/so3/avz/kernel/hypercalls.c b/so3/avz/kernel/hypercalls.c index 30af35cf84..28578f48b9 100644 --- a/so3/avz/kernel/hypercalls.c +++ b/so3/avz/kernel/hypercalls.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2018 Daniel Rossier + * Copyright (C) 2014-2026 Daniel Rossier * Copyright (C) 2018 Baptiste Delporte * * This program is free software; you can redistribute it and/or modify @@ -44,41 +44,41 @@ #include /** - * Return the state of the ME corresponding to the ME_slotID. - * If the ME does not exist anymore (for example, following a KILL_ME), - * the state is set to ME_state_dead. + * Return the state of the capsule corresponding to the S3C_slotID. + * If the capsule does not exist anymore (for example, following a KILL_S3C), + * the state is set to S3C_state_dead. */ -ME_state_t get_ME_state(unsigned int ME_slotID) +S3C_state_t get_S3C_state(unsigned int S3C_slotID) { - if (domains[ME_slotID] == NULL) - return ME_state_dead; + if (domains[S3C_slotID] == NULL) + return S3C_state_dead; else - return domains[ME_slotID]->avz_shared->dom_desc.u.ME.state; + return domains[S3C_slotID]->avz_shared->dom_desc.u.S3C.state; } -void set_ME_state(unsigned int ME_slotID, ME_state_t state) +void set_S3C_state(unsigned int S3C_slotID, S3C_state_t state) { - domains[ME_slotID]->avz_shared->dom_desc.u.ME.state = state; + domains[S3C_slotID]->avz_shared->dom_desc.u.S3C.state = state; } -void shutdown_ME(unsigned int ME_slotID) +void shutdown_S3C(unsigned int S3C_slotID) { struct domain *dom; - dom = domains[ME_slotID]; + dom = domains[S3C_slotID]; - /* Perform a removal of ME */ + /* Perform a removal of capsule */ dom->is_dying = DOMDYING_dead; - DBG("Shutdowning slotID: %d - Domain pause nosync ...\n", ME_slotID); + DBG("Shutdowning slotID: %d - Domain pause nosync ...\n", S3C_slotID); vcpu_pause(dom); - DBG("Destroy evtchn if necessary - state: %d\n", get_ME_state(ME_slotID)); + DBG("Destroy evtchn if necessary - state: %d\n", get_S3C_state(S3C_slotID)); evtchn_destroy(dom); DBG("Wiping domain area...\n"); - memset((void *) memslot[ME_slotID].base_vaddr, 0, memslot[ME_slotID].size); + memset((void *) memslot[S3C_slotID].base_vaddr, 0, memslot[S3C_slotID].size); DBG("Destroying domain structure ...\n"); @@ -86,28 +86,28 @@ void shutdown_ME(unsigned int ME_slotID) DBG("Now resetting domains to NULL.\n"); - /* bye bye dear ME ! */ - domains[ME_slotID] = NULL; + /* bye bye dear capsule ! */ + domains[S3C_slotID] = NULL; /* Reset the slot availability */ - put_ME_slot(ME_slotID); + put_S3C_slot(S3C_slotID); } /** - * Return the descriptor of a domain (agency or ME). - * A size of 0 means there is no ME in the slot. + * Return the descriptor of a domain (agency or capsule). + * A size of 0 means there is no capsule in the slot. */ void get_dom_desc(uint32_t slotID, dom_desc_t *dom_desc) { /* Check for authorization... (to be done) */ /* - * If no ME is present in the slot specified by slotID, we assign a size of 0 in the ME descriptor. + * If no capsule is present in the slot specified by slotID, we assign a size of 0 in the capsule descriptor. * We presume that the slotID of agency is never free... */ if ((slotID > 1) && !memslot[slotID].busy) - dom_desc->u.ME.size = 0; + dom_desc->u.S3C.size = 0; else /* Copy the content to the target desc */ memcpy(dom_desc, &domains[slotID]->avz_shared->dom_desc, sizeof(dom_desc_t)); @@ -152,12 +152,12 @@ void do_avz_hypercall(void *__args) get_dom_desc(args->u.avz_dom_desc_args.slotID, &args->u.avz_dom_desc_args.dom_desc); break; - case AVZ_ME_READ_SNAPSHOT: - read_ME_snapshot(args); + case AVZ_S3C_READ_SNAPSHOT: + read_S3C_snapshot(args); break; - case AVZ_ME_WRITE_SNAPSHOT: - write_ME_snapshot(args); + case AVZ_S3C_WRITE_SNAPSHOT: + write_S3C_snapshot(args); break; case AVZ_INJECT_CAPSULE: @@ -188,17 +188,17 @@ void do_avz_hypercall(void *__args) args->u.avz_dc_event_args.state = ESUCCESS; break; - case AVZ_KILL_ME: + case AVZ_KILL_S3C: - shutdown_ME(args->u.avz_kill_me_args.slotID); + shutdown_S3C(args->u.avz_kill_s3c_args.slotID); break; - case AVZ_GET_ME_STATE: - args->u.avz_me_state_args.state = get_ME_state(args->u.avz_me_state_args.slotID); + case AVZ_GET_S3C_STATE: + args->u.avz_s3c_state_args.state = get_S3C_state(args->u.avz_s3c_state_args.slotID); break; - case AVZ_SET_ME_STATE: { - set_ME_state(args->u.avz_me_state_args.slotID, args->u.avz_me_state_args.state); + case AVZ_SET_S3C_STATE: { + set_S3C_state(args->u.avz_s3c_state_args.slotID, args->u.avz_s3c_state_args.state); break; } @@ -210,7 +210,7 @@ void do_avz_hypercall(void *__args) fbdev_change_focus(args->u.avz_fbdev_focus_args.new_slotID); break; - case AVZ_FBDEV_GET_ME_ADDR: + case AVZ_FBDEV_GET_S3C_ADDR: args->u.avz_fbdev_addr_args.paddr = fbdev_get_domain_ipa(); break; @@ -222,8 +222,5 @@ void do_avz_hypercall(void *__args) break; } - /* inner shareable */ - dsb(ish); - - isb(); + flush_dcache_all(); } diff --git a/so3/avz/kernel/injector.c b/so3/avz/kernel/injector.c index 93c12b6fa0..5e6333d707 100644 --- a/so3/avz/kernel/injector.c +++ b/so3/avz/kernel/injector.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2022 Daniel Rossier + * Copyright (C) 2014-2026 Daniel Rossier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -55,11 +55,11 @@ void inject_capsule(avz_hyp_t *args) int slotID; size_t fdt_size; void *fdt_vaddr; - struct domain *domME, *__current; + struct domain *dom_S3C, *__current; void *itb_vaddr; mem_info_t guest_mem_info; - LOG_DEBUG("%s: Preparing ME injection, source image vaddr = %lx\n", __func__, + LOG_DEBUG("%s: Preparing capsule injection, source image vaddr = %lx\n", __func__, ipa_to_va(MEMSLOT_AGENCY, args->u.avz_inject_capsule_args.itb_paddr)); BUG_ON(local_irq_is_enabled()); @@ -68,7 +68,7 @@ void inject_capsule(avz_hyp_t *args) LOG_DEBUG("%s: ITB vaddr: %lx\n", __func__, itb_vaddr); - /* Retrieve the domain size of this ME through its device tree. */ + /* Retrieve the domain size of this capsule through its device tree. */ fit_image_get_data_and_size(itb_vaddr, fit_image_get_node(itb_vaddr, "fdt"), (const void **) &fdt_vaddr, &fdt_size); if (!fdt_vaddr) { printk("### %s: wrong device tree.\n", __func__); @@ -78,38 +78,38 @@ void inject_capsule(avz_hyp_t *args) get_mem_info(fdt_vaddr, &guest_mem_info); /* Find a slotID to store this capsule */ - slotID = get_ME_free_slot(guest_mem_info.size, args->u.avz_inject_capsule_args.slotID); + slotID = get_S3C_free_slot(guest_mem_info.size, args->u.avz_inject_capsule_args.slotID); if (slotID == -1) goto out; - domME = domains[slotID]; + dom_S3C = domains[slotID]; /* At the beginning, the capsule is stopped */ - domME->avz_shared->dom_desc.u.ME.state = ME_state_stopped; + dom_S3C->avz_shared->dom_desc.u.S3C.state = S3C_state_stopped; /* Store slotID & capsuleID */ - domME->avz_shared->dom_desc.u.ME.slotID = slotID; - domME->avz_shared->dom_desc.u.ME.capsuleID = args->u.avz_inject_capsule_args.capsuleID; + dom_S3C->avz_shared->dom_desc.u.S3C.slotID = slotID; + dom_S3C->avz_shared->dom_desc.u.S3C.capsuleID = args->u.avz_inject_capsule_args.capsuleID; - /* Set the size of this ME in its own descriptor with the dom_context size */ - domME->avz_shared->dom_desc.u.ME.size = memslot[slotID].size; + /* Set the size of this capsule in its own descriptor with the dom_context size */ + dom_S3C->avz_shared->dom_desc.u.S3C.size = memslot[slotID].size; __current = current_domain; /* Clear the RAM allocated to this capsule */ memset((void *) __xva(slotID, memslot[slotID].base_paddr), 0, memslot[slotID].size); - loadME(slotID, itb_vaddr); + load_S3C(slotID, itb_vaddr); - if (construct_ME(domains[slotID]) != 0) - panic("Could not set up ME guest OS\n"); + if (construct_S3C(domains[slotID]) != 0) + panic("Could not set up capsule guest OS\n"); out: /* Prepare to return the slotID to the caller. */ args->u.avz_inject_capsule_args.slotID = slotID; - domME->avz_shared->dom_desc.u.ME.vbstore_pfn = map_vbstore_pfn(slotID, 0); - domME->avz_shared->dom_desc.u.ME.vbstore_revtchn = agency->avz_shared->dom_desc.u.agency.vbstore_evtchn[slotID]; + dom_S3C->avz_shared->dom_desc.u.S3C.vbstore_pfn = map_vbstore_pfn(slotID, 0); + dom_S3C->avz_shared->dom_desc.u.S3C.vbstore_revtchn = agency->avz_shared->dom_desc.u.agency.vbstore_evtchn[slotID]; } /** @@ -135,7 +135,7 @@ build_domain_migration_info build_vcpu_migration_info Build the structures holding the key info to be migrated over ------------------------------------------------------------------------------*/ -static void build_domain_context(unsigned int ME_slotID, struct domain *me, struct dom_context *domctxt) +static void build_domain_context(unsigned int S3C_slotID, struct domain *me, struct dom_context *domctxt) { /* Event channel info */ memcpy(domctxt->evtchn, me->evtchn, sizeof(me->evtchn)); @@ -145,17 +145,17 @@ static void build_domain_context(unsigned int ME_slotID, struct domain *me, stru /* Get the start_info structure */ domctxt->avz_shared = *(me->avz_shared); - strcpy(domctxt->avz_shared.signature, SOO_ME_SIGNATURE); + strcpy(domctxt->avz_shared.signature, SOO_S3C_SIGNATURE); - /* The snapshot will contain a capsule with the state ME_state_suspended only - * if the capsule was living, otherwise it has to be in ME_state_stopped, right + /* The snapshot will contain a capsule with the state S3C_state_suspended only + * if the capsule was living, otherwise it has to be in S3C_state_stopped, right * before its execution. */ - if (me->avz_shared->dom_desc.u.ME.state == ME_state_suspended) - domctxt->avz_shared.dom_desc.u.ME.state = ME_state_hibernate; + if (me->avz_shared->dom_desc.u.S3C.state == S3C_state_suspended) + domctxt->avz_shared.dom_desc.u.S3C.state = S3C_state_hibernate; - BUG_ON((me->avz_shared->dom_desc.u.ME.state != ME_state_stopped) && - (me->avz_shared->dom_desc.u.ME.state != ME_state_suspended)); + BUG_ON((me->avz_shared->dom_desc.u.S3C.state != S3C_state_stopped) && + (me->avz_shared->dom_desc.u.S3C.state != S3C_state_suspended)); domctxt->pause_count = me->pause_count; @@ -173,7 +173,7 @@ static void build_domain_context(unsigned int ME_slotID, struct domain *me, stru memcpy(domctxt->virq_to_evtchn, me->virq_to_evtchn, sizeof(me->virq_to_evtchn)); /* Store the IPA physical address base */ - domctxt->ipa_addr = memslot[ME_slotID].ipa_addr; + domctxt->ipa_addr = memslot[S3C_slotID].ipa_addr; /* * CPU regs context along the exception path in the hypervisor @@ -193,10 +193,10 @@ static void build_domain_context(unsigned int ME_slotID, struct domain *me, stru * * @param args provided from the Linux kernel */ -void read_ME_snapshot(avz_hyp_t *args) +void read_S3C_snapshot(avz_hyp_t *args) { unsigned int slotID = args->u.avz_snapshot_args.slotID; - struct domain *domME = domains[slotID]; + struct domain *dom_S3C = domains[slotID]; void *snapshot_buffer = (void *) ipa_to_va(MEMSLOT_AGENCY, args->u.avz_snapshot_args.snapshot_paddr); /* If the size is 0, we return the snapshot size. */ @@ -205,17 +205,17 @@ void read_ME_snapshot(avz_hyp_t *args) return; } - /* If the capsule is living, it will be put in ME_state_suspended state by Linux + /* If the capsule is living, it will be put in S3C_state_suspended state by Linux * before being entering this function. */ - if (domME->avz_shared->dom_desc.u.ME.state == ME_state_suspended) { + if (dom_S3C->avz_shared->dom_desc.u.S3C.state == S3C_state_suspended) { /* Pause the capsule */ - domain_pause_by_systemcontroller(domME); + domain_pause_by_systemcontroller(dom_S3C); } /* Gather all the info we need into structures */ /* This will put the capsule snapshot in HIBERNATE state */ - build_domain_context(slotID, domME, &domain_context); + build_domain_context(slotID, dom_S3C, &domain_context); /* Copy the size of the payload which is made of the dom_info structure and the capsule */ args->u.avz_snapshot_args.size = memslot[slotID].size + sizeof(domain_context); @@ -226,26 +226,26 @@ void read_ME_snapshot(avz_hyp_t *args) /* Copy the dom_info structure */ memcpy(snapshot_buffer + sizeof(uint32_t), &domain_context, sizeof(domain_context)); - /* Finally copy the ME */ + /* Finally copy the capsule */ memcpy(snapshot_buffer + sizeof(uint32_t) + sizeof(domain_context), (void *) __xva(slotID, memslot[slotID].base_paddr), memslot[slotID].size); - if (domME->avz_shared->dom_desc.u.ME.state == ME_state_suspended) { - /* Now, this ME is suspended and must be resumed by the agency */ - domME->avz_shared->dom_desc.u.ME.state = ME_state_resuming; + if (dom_S3C->avz_shared->dom_desc.u.S3C.state == S3C_state_suspended) { + /* Now, this capsule is suspended and must be resumed by the agency */ + dom_S3C->avz_shared->dom_desc.u.S3C.state = S3C_state_resuming; - domain_unpause_by_systemcontroller(domME); + domain_unpause_by_systemcontroller(dom_S3C); } } /** * @brief Recover the dom_context structure from a pre-saved capsule * - * @param ME_slotID + * @param S3C_slotID * @param me * @param domctxt */ -void restore_domain_context(unsigned int ME_slotID, struct domain *me, struct dom_context *domctxt) +void restore_domain_context(unsigned int S3C_slotID, struct domain *me, struct dom_context *domctxt) { int i; @@ -254,11 +254,11 @@ void restore_domain_context(unsigned int ME_slotID, struct domain *me, struct do *(me->avz_shared) = domctxt->avz_shared; /* Check that our signature is valid so that the image transfer should be good. */ - if (strcmp(me->avz_shared->signature, SOO_ME_SIGNATURE)) - panic("%s: Cannot find the correct signature in the shared page (" SOO_ME_SIGNATURE ")...\n", __func__); + if (strcmp(me->avz_shared->signature, SOO_S3C_SIGNATURE)) + panic("%s: Cannot find the correct signature in the shared page (" SOO_S3C_SIGNATURE ")...\n", __func__); /* Update the domID of course */ - me->avz_shared->domID = ME_slotID; + me->avz_shared->domID = S3C_slotID; memcpy(me->evtchn, domctxt->evtchn, sizeof(me->evtchn)); @@ -268,7 +268,7 @@ void restore_domain_context(unsigned int ME_slotID, struct domain *me, struct do * want that the local event channel gets changed. * * Re-binding is performed during the resuming via vbus (backend side) OR - * if the ME gets killed, the event channel will be closed without any effect to a remote domain. + * if the capsule gets killed, the event channel will be closed without any effect to a remote domain. */ for (i = 0; i < NR_EVTCHN; i++) @@ -290,7 +290,7 @@ void restore_domain_context(unsigned int ME_slotID, struct domain *me, struct do memcpy(me->virq_to_evtchn, domctxt->virq_to_evtchn, sizeof((me->virq_to_evtchn))); /* IPA physical address base */ - memslot[ME_slotID].ipa_addr = domctxt->ipa_addr; + memslot[S3C_slotID].ipa_addr = domctxt->ipa_addr; /* Fields related to CPU */ me->vcpu = domctxt->vcpu; @@ -300,12 +300,12 @@ void restore_domain_context(unsigned int ME_slotID, struct domain *me, struct do * * @param args If args->u.avz_snapshot_args.size == 0, the function will try to find an empty slot. */ -void write_ME_snapshot(avz_hyp_t *args) +void write_S3C_snapshot(avz_hyp_t *args) { uint32_t snapshot_size; void *snapshot_buffer; uint32_t slotID; - struct domain *domME; + struct domain *dom_S3C; struct dom_context *domctxt; void *dom_stack; struct cpu_regs *frame; @@ -319,7 +319,7 @@ void write_ME_snapshot(avz_hyp_t *args) LOG_DEBUG("Looking for an available slot for a capsule of %d bytes...\n", snapshot_size - sizeof(uint32_t) - sizeof(struct dom_context)); - slotID = get_ME_free_slot(snapshot_size - sizeof(uint32_t) - sizeof(struct dom_context), slotID); + slotID = get_S3C_free_slot(snapshot_size - sizeof(uint32_t) - sizeof(struct dom_context), slotID); if (slotID > 0) args->u.avz_snapshot_args.slotID = slotID; else @@ -330,16 +330,16 @@ void write_ME_snapshot(avz_hyp_t *args) LOG_DEBUG("Writing the snapshot into memory...\n"); snapshot_buffer = (void *) ipa_to_va(MEMSLOT_AGENCY, args->u.avz_snapshot_args.snapshot_paddr); - domME = domains[slotID]; + dom_S3C = domains[slotID]; domctxt = (struct dom_context *) (snapshot_buffer + sizeof(uint32_t)); LOG_DEBUG("Restoring the domain context...\n"); - restore_domain_context(slotID, domME, domctxt); + restore_domain_context(slotID, dom_S3C, domctxt); LOG_DEBUG("Set up the page tables...\n"); - __setup_dom_pgtable(domME, memslot[slotID].base_paddr, memslot[slotID].size); + __setup_dom_pgtable(dom_S3C, memslot[slotID].base_paddr, memslot[slotID].size); - /* Copy the ME content */ + /* Copy the capsule content */ memcpy((void *) __xva(slotID, memslot[slotID].base_paddr), snapshot_buffer + sizeof(uint32_t) + sizeof(struct dom_context), memslot[slotID].size); @@ -349,7 +349,7 @@ void write_ME_snapshot(avz_hyp_t *args) BUG_ON(!dom_stack); /* Keep the reference for future removal */ - domME->domain_stack = dom_stack; + dom_S3C->domain_stack = dom_stack; /* Reserve the frame which will be restored later */ frame = dom_stack + DOMAIN_STACK_SIZE - sizeof(cpu_regs_t); @@ -358,38 +358,39 @@ void write_ME_snapshot(avz_hyp_t *args) memcpy(frame, &domctxt->stack_frame, sizeof(struct cpu_regs)); /* We need to re-map the vbstore page corresponding to this slotID */ - map_vbstore_pfn(domME->avz_shared->domID, domME->avz_shared->dom_desc.u.ME.vbstore_pfn); - LOG_DEBUG("State of the saved capsule: %d\n", domME->avz_shared->dom_desc.u.ME.state); + map_vbstore_pfn(dom_S3C->avz_shared->domID, dom_S3C->avz_shared->dom_desc.u.S3C.vbstore_pfn); + LOG_DEBUG("State of the saved capsule: %d\n", dom_S3C->avz_shared->dom_desc.u.S3C.state); - if (domME->avz_shared->dom_desc.u.ME.state != ME_state_stopped) { - BUG_ON(domME->avz_shared->dom_desc.u.ME.state != ME_state_hibernate); + if (dom_S3C->avz_shared->dom_desc.u.S3C.state != S3C_state_stopped) { + BUG_ON(dom_S3C->avz_shared->dom_desc.u.S3C.state != S3C_state_hibernate); /* As we will be resumed from the schedule function, we need to update the * CPU registers from the VCPU regs. */ - domME->vcpu.regs.sp = (unsigned long) frame; - domME->vcpu.regs.x21 = (unsigned long) domME->avz_shared->dom_desc.u.ME.resume_fn; + dom_S3C->vcpu.regs.sp = (unsigned long) frame; + dom_S3C->vcpu.regs.x21 = (unsigned long) dom_S3C->avz_shared->dom_desc.u.S3C.resume_fn; - domME->vcpu.regs.lr = (unsigned long) resume_to_guest; + dom_S3C->vcpu.regs.lr = (unsigned long) resume_to_guest; /* Now restoring event channel configuration */ - evtchn_bind_existing_interdomain(domME, agency, domME->avz_shared->dom_desc.u.ME.vbstore_levtchn, + evtchn_bind_existing_interdomain(dom_S3C, agency, dom_S3C->avz_shared->dom_desc.u.S3C.vbstore_levtchn, agency->avz_shared->dom_desc.u.agency.vbstore_evtchn[slotID]); - LOG_DEBUG("%s: Rebinding directcomm event channels: %d (agency) <-> %d (ME)\n", __func__, - agency->avz_shared->dom_desc.u.agency.dc_evtchn[slotID], domME->avz_shared->dom_desc.u.ME.dc_evtchn); + LOG_DEBUG("%s: Rebinding directcomm event channels: %d (agency) <-> %d (capsule)\n", __func__, + agency->avz_shared->dom_desc.u.agency.dc_evtchn[slotID], + dom_S3C->avz_shared->dom_desc.u.S3C.dc_evtchn); - evtchn_bind_existing_interdomain(domME, agency, domME->avz_shared->dom_desc.u.ME.dc_evtchn, + evtchn_bind_existing_interdomain(dom_S3C, agency, dom_S3C->avz_shared->dom_desc.u.S3C.dc_evtchn, agency->avz_shared->dom_desc.u.agency.dc_evtchn[slotID]); } - LOG_DEBUG("%s: Now, resuming ME slotID %d...\n", __func__, slotID); + LOG_DEBUG("%s: Now, resuming capsule slotID %d...\n", __func__, slotID); - domain_unpause_by_systemcontroller(domME); + domain_unpause_by_systemcontroller(dom_S3C); } void __sigreturn(void) { - current_domain->avz_shared->dom_desc.u.ME.state = ME_state_awakened; + current_domain->avz_shared->dom_desc.u.S3C.state = S3C_state_awakened; send_timer_event(current_domain); } diff --git a/so3/avz/kernel/sched_flip.c b/so3/avz/kernel/sched_flip.c index 1a27a66ae8..54b6f4e46c 100644 --- a/so3/avz/kernel/sched_flip.c +++ b/so3/avz/kernel/sched_flip.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2018 Daniel Rossier + * Copyright (C) 2014-2026 Daniel Rossier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -144,7 +144,7 @@ void sched_flip_init(void) #ifdef CONFIG_SOO /* Initiate a timer to trigger the schedule function */ - init_timer(&sched_flip.sched_data.s_timer, s_timer_fn, NULL, ME_CPU); + init_timer(&sched_flip.sched_data.s_timer, s_timer_fn, NULL, S3C_CPU); set_timer(&sched_flip.sched_data.s_timer, NOW() + MILLISECS(CONFIG_SCHED_FLIP_SCHEDFREQ)); #endif /* CONFIG_SOO */ diff --git a/so3/avz/kernel/schedule.c b/so3/avz/kernel/schedule.c index bbaa3c504d..872321e4c2 100644 --- a/so3/avz/kernel/schedule.c +++ b/so3/avz/kernel/schedule.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2018 Daniel Rossier + * Copyright (C) 2014-2026 Daniel Rossier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -40,7 +40,7 @@ inline void domain_runstate_change(struct domain *d, int new_state) { /* * We might already be in RUNSTATE_blocked before setting to this state; for example, - * if a ME has been paused and migrates, and is killed during the cooperation phase, + * if a capsule has been paused and migrates, and is killed during the cooperation phase, * the call to shutdown() will lead to be here with such a state already. */ ASSERT((d->runstate == RUNSTATE_blocked) || (d->runstate != new_state)); @@ -199,9 +199,13 @@ static void domain_schedule(void) struct task_slice next_slice; ASSERT(local_irq_is_disabled()); - ASSERT(prev->runstate == RUNSTATE_running); + if (!prev->sched) { + while (1) + cpu_do_idle(); + } + /* To avoid that another CPU manipulates scheduler data structures */ /* Maybe the lock is already acquired from do_sleep() for example */ if (!spin_is_locked(¤t_domain->sched->sched_data.schedule_lock)) @@ -209,7 +213,8 @@ static void domain_schedule(void) sd = ¤t_domain->sched->sched_data; - stop_timer(&sd->s_timer); + if (active_timer(&sd->s_timer)) + stop_timer(&sd->s_timer); /* get policy-specific decision on scheduling... */ next_slice = prev->sched->do_schedule(); diff --git a/so3/avz/kernel/setup.c b/so3/avz/kernel/setup.c index bacac4ef37..2f1d98400d 100644 --- a/so3/avz/kernel/setup.c +++ b/so3/avz/kernel/setup.c @@ -75,7 +75,7 @@ void avz_start(void) loadAgency(); lprintk("\n\n********** Smart Object Oriented technology - AVZ Hypervisor **********\n"); - lprintk("Copyright (c) 2014-2025 REDS Institute, HEIG-VD, Yverdon-les-Bains\n"); + lprintk("Copyright (c) 2014-2026 REDS Institute, HEIG-VD, Yverdon-les-Bains\n"); lprintk("Version %s\n", SO3_KERNEL_VERSION); LOG_INFO("\n\nNow bootstraping the hypervisor kernel ...\n"); diff --git a/so3/avz/kernel/smp.c b/so3/avz/kernel/smp.c index 1a9d4eb4c9..9268cb468b 100644 --- a/so3/avz/kernel/smp.c +++ b/so3/avz/kernel/smp.c @@ -103,23 +103,13 @@ void secondary_start_kernel(void) gicc_init(); - printk("CPU%u: Booted secondary processor\n", cpu); - -#ifdef CONFIG_SOO - if (cpu != ME_CPU) -#endif /* CONFIG_SOO */ - __mmu_switch_kernel((void *) domains[DOMID_AGENCY]->pagetable_paddr, true); +#ifndef CONFIG_SOO + __mmu_switch_kernel((void *) domains[DOMID_AGENCY]->pagetable_paddr, true); +#endif /* !CONFIG_SOO */ booted[cpu] = 1; #ifdef CONFIG_CPU_SPIN_TABLE -#ifdef CONFIG_SOO - /* Agency secondary CPUs are handed directly to Linux via spin-table. - * Set current_domain to agency so HVC calls from EL1 are correctly dispatched. - */ - if (cpu != ME_CPU) - set_current_domain(agency); -#endif /* CONFIG_SOO */ switch (cpu) { case 1: pre_ret_to_el1_spin(CPU1_RELEASE_ADDR); @@ -127,13 +117,9 @@ void secondary_start_kernel(void) case 2: pre_ret_to_el1_spin(CPU2_RELEASE_ADDR); break; - /* CPU #3 is dedicated to capsules in case of CONFIG_SOO */ -#ifndef CONFIG_SOO case 3: pre_ret_to_el1_spin(CPU3_RELEASE_ADDR); break; -#endif /* !CONFIG_SOO*/ - default: printk("%s: trying to start CPU %d that is not supported.\n", __func__, cpu); } @@ -141,9 +127,11 @@ void secondary_start_kernel(void) #ifdef CONFIG_CPU_PSCI #ifdef CONFIG_SOO - if (cpu != ME_CPU) + if (cpu != S3C_CPU) #endif /* CONFIG_SOO */ + { pre_ret_to_el1(); + } #endif /* CONFIG_CPU_PCSI */ secondary_timer_init(); @@ -199,8 +187,6 @@ void cpu_up(unsigned int cpu) while (!booted[cpu]) ; - printk("%s CPU %d finished waiting...\n", __func__, smp_processor_id()); - secondary_data.stack = NULL; secondary_data.pgdir = 0; } @@ -225,25 +211,16 @@ void smp_init(void) #ifdef CONFIG_SOO - /* Boot agency secondary CPUs so they enter spin-table wait for Linux */ - for (i = 1; i < ME_CPU; i++) - cpu_up(i); - - printk("Starting ME CPU...\n"); + printk("Starting capsule CPU...\n"); - cpu_up(ME_CPU); + cpu_up(S3C_CPU); - printk("Brought secondary CPU %d for running SO3 capsules...\n", ME_CPU); + printk("Brought secondary CPU %d for running SO3 capsules...\n", S3C_CPU); #else /* CONFIG_SOO */ -#ifdef CONFIG_AVZ - /* With VT support, we prepare the CPU to be started through a HVC call - * from the domain. - */ for (i = 1; i < CONFIG_NR_CPUS; i++) cpu_up(i); -#endif #endif /* !CONFIG_SOO */ } diff --git a/so3/avz/kernel/timer.c b/so3/avz/kernel/timer.c index cb908b6a13..dd88648b0c 100644 --- a/so3/avz/kernel/timer.c +++ b/so3/avz/kernel/timer.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2023 Daniel Rossier + * Copyright (C) 2014-2026 Daniel Rossier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -23,6 +23,36 @@ #include #include +/* + * Called on every EL2 timer tick (CNTHP, PPI 26) on agency CPUs (non-capsule). + * Per-CPU tick counter so each CPU emits its own 5s heartbeat — without + * this, CPU0's heartbeat would mask whether secondary CPUs are ticking. + */ +static DEFINE_PER_CPU(unsigned int, agency_tick_count); + +/* EDGEMTech instrumentation: per-CPU counters to discriminate stall cause. + * Updated from gic_handle/gic_inject_irq (gic.c). */ +DEFINE_PER_CPU(unsigned long, gic_iar_count); +DEFINE_PER_CPU(unsigned long, gic_inj_27_count); +DEFINE_PER_CPU(unsigned long, gic_inj_total_count); +DEFINE_PER_CPU(unsigned long, gic_busy_count); +DEFINE_PER_CPU(unsigned long, gic_eexist_count); +DEFINE_PER_CPU(unsigned long, gic_sgi0_recv); +DEFINE_PER_CPU(unsigned long, gic_sgi0_eexist); + +void agency_timer_interrupt(void) +{ + unsigned int *count = &this_cpu(agency_tick_count); + + if (++(*count) >= 5 * CONFIG_HZ) { + *count = 0; + +#if 0 /* Debug purpose */ + printk("[AVZ] alive on CPU%d\n", smp_processor_id()); +#endif /* 0 */ + } +} + void timer_interrupt(bool periodic) { int i; @@ -43,7 +73,10 @@ void timer_interrupt(bool periodic) } } } + } else { + agency_timer_interrupt(); } + raise_softirq(TIMER_SOFTIRQ); } diff --git a/so3/avz/mm/memory.c b/so3/avz/mm/memory.c index 32c5af8f3d..2306e81685 100644 --- a/so3/avz/mm/memory.c +++ b/so3/avz/mm/memory.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2021 Daniel Rossier + * Copyright (C) 2014-2026 Daniel Rossier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -32,20 +32,20 @@ #include -#define ME_MEMCHUNK_SIZE 2 * 1024 * 1024 -#define ME_MEMCHUNK_NR 256 /* 256 chunks of 2 MB */ +#define S3C_MEMCHUNK_SIZE 2 * 1024 * 1024 +#define S3C_MEMCHUNK_NR 256 /* 256 chunks of 2 MB */ /* * Set of memslots in the RAM memory (do not confuse with memchunk !) * In the memslot table, the index 0 is for AVZ, the index 1 is for the two agency domains (domain 0 (non-RT) and domain 1 (RT)) - * and the indexes 2..MEMSLOT_NR are for the MEs. If the ME_slotID is provided, the index is given by ME_slotID. - * Hence, the ME_slotID matches with the ME domID. + * and the indexes 2..MEMSLOT_NR are for the MEs. If the S3C_slotID is provided, the index is given by S3C_slotID. + * Hence, the S3C_slotID matches with the capsule domID. */ memslot_entry_t memslot[MEMSLOT_NR]; /* Memory chunks bitmap for allocating MEs */ /* 8 bits per int int */ -unsigned int memchunk_bitmap[ME_MEMCHUNK_NR / 32]; +unsigned int memchunk_bitmap[S3C_MEMCHUNK_NR / 32]; /* * Returns the power of 2 (order) which matches the size @@ -74,11 +74,11 @@ static unsigned int allocate_memslot(unsigned int order) { int pos; - pos = bitmap_find_free_region((unsigned long *) &memchunk_bitmap, ME_MEMCHUNK_NR, order); + pos = bitmap_find_free_region((unsigned long *) &memchunk_bitmap, S3C_MEMCHUNK_NR, order); if (pos < 0) return 0; - return memslot[1].base_paddr + memslot[1].size + pos * ME_MEMCHUNK_SIZE; + return memslot[1].base_paddr + memslot[1].size + pos * S3C_MEMCHUNK_SIZE; } static void release_memslot(unsigned int addr, unsigned int order) @@ -86,7 +86,7 @@ static void release_memslot(unsigned int addr, unsigned int order) int pos; pos = addr - memslot[1].base_paddr - memslot[1].size; - pos /= ME_MEMCHUNK_SIZE; + pos /= S3C_MEMCHUNK_SIZE; bitmap_release_region((unsigned long *) &memchunk_bitmap, pos, order); } @@ -113,15 +113,15 @@ void switch_mm_domain(struct domain *d) } /** - * Get the next available memory slot for ME hosting. + * Get the next available memory slot for capsule hosting. * * @param size Requested size - * @param ME_state Initial state of the ME + * @param S3C_state Initial state of the capsule * @param slotID if different than -1, try to allocate to this specific slot * @return int -1 if no slot is available or if a slot is available * */ -int get_ME_free_slot(unsigned int size, int slotID) +int get_S3C_free_slot(unsigned int size, int slotID) { unsigned int order, addr; unsigned int bits_NR; @@ -141,7 +141,7 @@ int get_ME_free_slot(unsigned int size, int slotID) /* memslot[slotID] is available */ - bits_NR = DIV_ROUND_UP(size, ME_MEMCHUNK_SIZE); + bits_NR = DIV_ROUND_UP(size, S3C_MEMCHUNK_SIZE); order = get_power_from_size(bits_NR); @@ -153,16 +153,16 @@ int get_ME_free_slot(unsigned int size, int slotID) /* Determine the phys/virt start addresses of the guest */ memslot[slotID].base_paddr = addr; - memslot[slotID].base_vaddr = ME_BASE + ((addr_t) (slotID - 1) << ME_ID_SHIFT); + memslot[slotID].base_vaddr = S3C_BASE + ((addr_t) (slotID - 1) << S3C_ID_SHIFT); - memslot[slotID].size = (1 << order) * ME_MEMCHUNK_SIZE; /* Readjust size */ + memslot[slotID].size = (1 << order) * S3C_MEMCHUNK_SIZE; /* Readjust size */ memslot[slotID].busy = true; - /* Map the L2 virtual address space of ME #(slotID-1) to the physical RAM */ + /* Map the L2 virtual address space of capsule #(slotID-1) to the physical RAM */ create_mapping(NULL, memslot[slotID].base_vaddr, memslot[slotID].base_paddr, memslot[slotID].size, false); - /* Create a domain context including the ME descriptor before the ME gets injected. */ - domains[slotID] = domain_create(slotID, ME_CPU); + /* Create a domain context including the capsule descriptor before the capsule gets injected. */ + domains[slotID] = domain_create(slotID, S3C_CPU); return slotID; } @@ -170,12 +170,12 @@ int get_ME_free_slot(unsigned int size, int slotID) /* * Release a slot */ -void put_ME_slot(unsigned int slotID) +void put_S3C_slot(unsigned int slotID) { release_mapping(NULL, memslot[slotID].base_vaddr, memslot[slotID].size); /* Release the allocated memchunks */ - release_memslot(memslot[slotID].base_paddr, get_power_from_size(DIV_ROUND_UP(memslot[slotID].size, ME_MEMCHUNK_SIZE))); + release_memslot(memslot[slotID].base_paddr, get_power_from_size(DIV_ROUND_UP(memslot[slotID].size, S3C_MEMCHUNK_SIZE))); memslot[slotID].busy = false; } diff --git a/so3/configs/rpi4_64_avz_defconfig b/so3/configs/rpi4_64_avz_defconfig index a4fed0644b..1eb3483570 100644 --- a/so3/configs/rpi4_64_avz_defconfig +++ b/so3/configs/rpi4_64_avz_defconfig @@ -53,6 +53,7 @@ CONFIG_UART_LL_PADDR=0xfe215040 # CONFIG_RAMDEV is not set CONFIG_ARM_TIMER=y CONFIG_GIC=y +CONFIG_GIC_V2=y # # SO3 Applications diff --git a/so3/configs/rpi4_64_defconfig b/so3/configs/rpi4_64_defconfig index 4af173db30..d07c57c7a5 100644 --- a/so3/configs/rpi4_64_defconfig +++ b/so3/configs/rpi4_64_defconfig @@ -51,6 +51,7 @@ CONFIG_UART_LL_PADDR=0xfe215040 CONFIG_RAMDEV=y CONFIG_ARM_TIMER=y CONFIG_GIC=y +CONFIG_GIC_V2=y # # SO3 Applications diff --git a/so3/configs/rpi4_defconfig b/so3/configs/rpi4_defconfig index 04c15d7c33..e436b68198 100644 --- a/so3/configs/rpi4_defconfig +++ b/so3/configs/rpi4_defconfig @@ -49,6 +49,7 @@ CONFIG_UART_LL_PADDR=0xfe215040 CONFIG_RAMDEV=y CONFIG_ARM_TIMER=y CONFIG_GIC=y +CONFIG_GIC_V2=y CONFIG_I2C_BSC=y CONFIG_RPI_SENSE=y diff --git a/so3/configs/verdin-imx8mp_avz_defconfig b/so3/configs/verdin-imx8mp_avz_defconfig new file mode 100644 index 0000000000..3106d20614 --- /dev/null +++ b/so3/configs/verdin-imx8mp_avz_defconfig @@ -0,0 +1,101 @@ +# +# Automatically generated make config: don't edit +# SO3 Polymorphic OS Configuration +# +# CONFIG_ARCH_ARM32 is not set + +CONFIG_ARCH_ARM64=y +CONFIG_ARCH="arm64" +CONFIG_CROSS_COMPILE="aarch64-none-elf-" +# CONFIG_ARM_TRUSTZONE is not set +CONFIG_KERNEL_VADDR=0x0000100000000000 + +# +# Platform +# +# CONFIG_VIRT64 is not set +# CONFIG_RPI4_64 is not set + +CONFIG_VERDIN_IMX8MP=y +# CONFIG_VA_BITS_39 is not set +CONFIG_VA_BITS_48=y +CONFIG_THREAD_ENV=y + +# +# Kernel & CPU features +# + +CONFIG_MAX_THREADS=32 +CONFIG_SMP=y +CONFIG_NR_CPUS=4 +CONFIG_CPU_PSCI=y +CONFIG_HZ=100 +CONFIG_SCHED_FLIP_SCHEDFREQ=30 + +# +# SO3 Scheduling configuration +# + +CONFIG_SCHED_RR=y +# CONFIG_SCHED_PRIO is not set +CONFIG_SCHED_FREQ_PREEMPTION=y + +# +# Drivers +# + +CONFIG_UART=y +CONFIG_IO_MAPPING_BASE=0x0000200000000000 +# CONFIG_I2C is not set +# CONFIG_NET is not set +# CONFIG_FB is not set +# CONFIG_INPUT is not set +# CONFIG_NS16550 is not set + +CONFIG_IMX_UART=y +CONFIG_UART_LL_PADDR=0x30880000 +# CONFIG_MMC is not set +# CONFIG_RAMDEV is not set + +CONFIG_ARM_TIMER=y +CONFIG_GIC=y +CONFIG_GIC_V3=y + +# +# SO3 Applications +# +# CONFIG_APP_SAMPLE is not set +# CONFIG_APP_REFSO3 is not set + +# +# Filesystems +# + +CONFIG_ROOTFS_NONE=y +# CONFIG_ROOTFS_MMC is not set +# CONFIG_ROOTFS_RAMDEV is not set + +# +# IPC +# +# CONFIG_IPC_SIGNAL is not set +# CONFIG_IPC_PIPE is not set + +CONFIG_HEAP_SIZE_MB=8 +CONFIG_SYS_STACK_SIZE_KB=64 +CONFIG_THREAD_STACK_SIZE_KB=64 +# CONFIG_RTOS is not set +CONFIG_AVZ=y +CONFIG_USER_INIT_PROGRAM="init.elf" +# CONFIG_LOG_LEVEL_CRITICAL is not set +# CONFIG_LOG_LEVEL_ERROR is not set +# CONFIG_LOG_LEVEL_WARNING is not set + +CONFIG_LOG_LEVEL_INFO=y +# CONFIG_LOG_LEVEL_DEBUG is not set +# CONFIG_LOG_LEVEL_TRACE is not set +# CONFIG_LOG_LEVEL_NONE is not set + +CONFIG_LOG_LEVEL=4 +# CONFIG_SOO is not set +CONFIG_MMU=y diff --git a/so3/configs/virt32_defconfig b/so3/configs/virt32_defconfig index 77220ec2fb..2e6060e2ba 100644 --- a/so3/configs/virt32_defconfig +++ b/so3/configs/virt32_defconfig @@ -50,6 +50,7 @@ CONFIG_RAMDEV=y # CONFIG_SP804 is not set CONFIG_ARM_TIMER=y CONFIG_GIC=y +CONFIG_GIC_V2=y # CONFIG_PL050_KMI is not set # CONFIG_VIRT_INPUT is not set diff --git a/so3/configs/virt32_fb_defconfig b/so3/configs/virt32_fb_defconfig index cd3d23ca46..8025abef20 100644 --- a/so3/configs/virt32_fb_defconfig +++ b/so3/configs/virt32_fb_defconfig @@ -50,6 +50,7 @@ CONFIG_RAMDEV=y # CONFIG_SP804 is not set CONFIG_ARM_TIMER=y CONFIG_GIC=y +CONFIG_GIC_V2=y CONFIG_PL111_CLCD=y # CONFIG_QEMU_RAMFB is not set # CONFIG_VIRTFB is not set diff --git a/so3/configs/virt32_lvperf_defconfig b/so3/configs/virt32_lvperf_defconfig index 8029114f38..e27a7a0837 100644 --- a/so3/configs/virt32_lvperf_defconfig +++ b/so3/configs/virt32_lvperf_defconfig @@ -50,6 +50,7 @@ CONFIG_RAMDEV=y # CONFIG_SP804 is not set CONFIG_ARM_TIMER=y CONFIG_GIC=y +CONFIG_GIC_V2=y # CONFIG_PL111_CLCD is not set # CONFIG_QEMU_RAMFB is not set CONFIG_VIRTFB=y diff --git a/so3/configs/virt64_avz_defconfig b/so3/configs/virt64_avz_defconfig index 159e008d82..ba73c31e48 100644 --- a/so3/configs/virt64_avz_defconfig +++ b/so3/configs/virt64_avz_defconfig @@ -52,6 +52,7 @@ CONFIG_UART_LL_PADDR=0x09000000 # CONFIG_RAMDEV is not set CONFIG_ARM_TIMER=y CONFIG_GIC=y +CONFIG_GIC_V2=y # CONFIG_PL050_KMI is not set # CONFIG_VIRT_INPUT is not set diff --git a/so3/configs/virt64_avz_soo_defconfig b/so3/configs/virt64_avz_soo_defconfig index 04457c507d..67555e6bc9 100644 --- a/so3/configs/virt64_avz_soo_defconfig +++ b/so3/configs/virt64_avz_soo_defconfig @@ -52,6 +52,7 @@ CONFIG_UART_LL_PADDR=0x09000000 # CONFIG_RAMDEV is not set CONFIG_ARM_TIMER=y CONFIG_GIC=y +CONFIG_GIC_V2=y # CONFIG_PL050_KMI is not set # CONFIG_VIRT_INPUT is not set diff --git a/so3/configs/virt64_capsule_defconfig b/so3/configs/virt64_capsule_defconfig index 0eb6b71fd0..5b095dbdea 100644 --- a/so3/configs/virt64_capsule_defconfig +++ b/so3/configs/virt64_capsule_defconfig @@ -51,6 +51,7 @@ CONFIG_SOO_SERIAL=y CONFIG_RAMDEV=y CONFIG_SOO_TIMER=y CONFIG_GIC=y +CONFIG_GIC_V2=y # CONFIG_PL050_KMI is not set CONFIG_SOO_INPUT=y # CONFIG_VIRT_INPUT is not set @@ -92,11 +93,11 @@ CONFIG_SOO=y CONFIG_MMU=y # -# SOO Mobile Entity - Smart Object Oriented subsystem +# SOO SO3 capsule - Smart Object Oriented subsystem # # -# SOO Mobile Entity frontend drivers +# SOO SO3 capsule frontend drivers # CONFIG_VDUMMY_FRONTEND=y CONFIG_VUART_FRONTEND=y diff --git a/so3/configs/virt64_defconfig b/so3/configs/virt64_defconfig index a04a7ab05f..47bc26280d 100644 --- a/so3/configs/virt64_defconfig +++ b/so3/configs/virt64_defconfig @@ -51,6 +51,7 @@ CONFIG_UART_LL_PADDR=0x09000000 CONFIG_RAMDEV=y CONFIG_ARM_TIMER=y CONFIG_GIC=y +CONFIG_GIC_V2=y # CONFIG_PL111_CLCD is not set # CONFIG_QEMU_RAMFB is not set # CONFIG_PL050_KMI is not set diff --git a/so3/configs/virt64_fb_defconfig b/so3/configs/virt64_fb_defconfig index 786247c74e..00fde9945f 100644 --- a/so3/configs/virt64_fb_defconfig +++ b/so3/configs/virt64_fb_defconfig @@ -51,6 +51,7 @@ CONFIG_UART_LL_PADDR=0x09000000 CONFIG_RAMDEV=y CONFIG_ARM_TIMER=y CONFIG_GIC=y +CONFIG_GIC_V2=y CONFIG_PL111_CLCD=y # CONFIG_QEMU_RAMFB is not set # CONFIG_VIRTFB is not set diff --git a/so3/configs/virt64_lvperf_defconfig b/so3/configs/virt64_lvperf_defconfig index ad35246104..9cb21e008a 100644 --- a/so3/configs/virt64_lvperf_defconfig +++ b/so3/configs/virt64_lvperf_defconfig @@ -51,6 +51,7 @@ CONFIG_UART_LL_PADDR=0x09000000 CONFIG_RAMDEV=y CONFIG_ARM_TIMER=y CONFIG_GIC=y +CONFIG_GIC_V2=y # CONFIG_PL111_CLCD is not set # CONFIG_QEMU_RAMFB is not set CONFIG_VIRTFB=y diff --git a/so3/devices/device.c b/so3/devices/device.c index a92deee377..787636d6c5 100644 --- a/so3/devices/device.c +++ b/so3/devices/device.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017-2018 Daniel Rossier + * Copyright (C) 2017-2026 Daniel Rossier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as diff --git a/so3/devices/irq.c b/so3/devices/irq.c index d79fb413fa..56b5125339 100644 --- a/so3/devices/irq.c +++ b/so3/devices/irq.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2018 Daniel Rossier + * Copyright (C) 2016-2026 Daniel Rossier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -194,7 +194,7 @@ void irq_handle(cpu_regs_t *regs) /* * We do not check if IRQs were disabled before the handler in case of an hypercall - * path which may happen in AVZ and in the ME because hypercalls are executed during + * path which may happen in AVZ and in the capsule because hypercalls are executed during * their initialization along which a timer IRQ can be raised up and lead to their processing * here. */ diff --git a/so3/devices/irq/Kconfig b/so3/devices/irq/Kconfig index 9ca4a07f52..8d8f7e5cc5 100644 --- a/so3/devices/irq/Kconfig +++ b/so3/devices/irq/Kconfig @@ -2,5 +2,11 @@ config GIC bool "Generic IRQ Controller" - - \ No newline at end of file + +config GIC_V2 + bool + depends on GIC + +config GIC_V3 + bool + depends on GIC diff --git a/so3/devices/irq/gic.c b/so3/devices/irq/gic.c index 2b5654e810..f7dc72f3cf 100644 --- a/so3/devices/irq/gic.c +++ b/so3/devices/irq/gic.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2023 Daniel Rossier + * Copyright (C) 2014-2026 Daniel Rossier * Copyright (C) 2014 Romain Bornet * Copyright (C) 2016-2017 Alexandre Malki * @@ -71,6 +71,14 @@ gic_t *gic; DEFINE_PER_CPU(spinlock_t, intc_lock); +DECLARE_PER_CPU(unsigned long, gic_iar_count); +DECLARE_PER_CPU(unsigned long, gic_inj_27_count); +DECLARE_PER_CPU(unsigned long, gic_inj_total_count); +DECLARE_PER_CPU(unsigned long, gic_busy_count); +DECLARE_PER_CPU(unsigned long, gic_eexist_count); +DECLARE_PER_CPU(unsigned long, gic_sgi0_recv); +DECLARE_PER_CPU(unsigned long, gic_sgi0_eexist); + #ifdef CONFIG_AVZ #define MAX_PENDING_IRQS 256 @@ -88,8 +96,106 @@ struct pending_irqs { volatile unsigned int tail; }; -struct pending_irqs pending_irqs; +DEFINE_PER_CPU(struct pending_irqs, pending_irqs); + +#ifdef CONFIG_GIC_V3 +/* GICv3: list registers are ICH_LR*_EL2 system registers (64-bit) */ +static u64 gic_read_lr(unsigned int n) +{ + switch (n) { + case 0: + return read_sysreg_s(SYS_ICH_LR0_EL2); + case 1: + return read_sysreg_s(SYS_ICH_LR1_EL2); + case 2: + return read_sysreg_s(SYS_ICH_LR2_EL2); + case 3: + return read_sysreg_s(SYS_ICH_LR3_EL2); + case 4: + return read_sysreg_s(SYS_ICH_LR4_EL2); + case 5: + return read_sysreg_s(SYS_ICH_LR5_EL2); + case 6: + return read_sysreg_s(SYS_ICH_LR6_EL2); + case 7: + return read_sysreg_s(SYS_ICH_LR7_EL2); + case 8: + return read_sysreg_s(SYS_ICH_LR8_EL2); + case 9: + return read_sysreg_s(SYS_ICH_LR9_EL2); + case 10: + return read_sysreg_s(SYS_ICH_LR10_EL2); + case 11: + return read_sysreg_s(SYS_ICH_LR11_EL2); + case 12: + return read_sysreg_s(SYS_ICH_LR12_EL2); + case 13: + return read_sysreg_s(SYS_ICH_LR13_EL2); + case 14: + return read_sysreg_s(SYS_ICH_LR14_EL2); + case 15: + return read_sysreg_s(SYS_ICH_LR15_EL2); + default: + return 0; + } +} +void gic_write_lr(unsigned int n, u64 value) +{ + switch (n) { + case 0: + write_sysreg_s(value, SYS_ICH_LR0_EL2); + break; + case 1: + write_sysreg_s(value, SYS_ICH_LR1_EL2); + break; + case 2: + write_sysreg_s(value, SYS_ICH_LR2_EL2); + break; + case 3: + write_sysreg_s(value, SYS_ICH_LR3_EL2); + break; + case 4: + write_sysreg_s(value, SYS_ICH_LR4_EL2); + break; + case 5: + write_sysreg_s(value, SYS_ICH_LR5_EL2); + break; + case 6: + write_sysreg_s(value, SYS_ICH_LR6_EL2); + break; + case 7: + write_sysreg_s(value, SYS_ICH_LR7_EL2); + break; + case 8: + write_sysreg_s(value, SYS_ICH_LR8_EL2); + break; + case 9: + write_sysreg_s(value, SYS_ICH_LR9_EL2); + break; + case 10: + write_sysreg_s(value, SYS_ICH_LR10_EL2); + break; + case 11: + write_sysreg_s(value, SYS_ICH_LR11_EL2); + break; + case 12: + write_sysreg_s(value, SYS_ICH_LR12_EL2); + break; + case 13: + write_sysreg_s(value, SYS_ICH_LR13_EL2); + break; + case 14: + write_sysreg_s(value, SYS_ICH_LR14_EL2); + break; + case 15: + write_sysreg_s(value, SYS_ICH_LR15_EL2); + break; + default: + break; + } +} +#else /* GICv2: list registers are GICH MMIO */ static u32 gic_read_lr(unsigned int n) { return ioread32(&gic->gich->lr[n]); @@ -99,9 +205,16 @@ void gic_write_lr(unsigned int n, u32 value) { iowrite32(&gic->gich->lr[n], value); } +#endif /* CONFIG_GIC_V3 */ void display_lr(unsigned int n) { +#ifdef CONFIG_GIC_V3 + u64 lr = gic_read_lr(n); + + printk("LR%u: virq=%lx state=%lx hw=%lx grp=%lx prio=%lx pirq=%lx\n", n, lr & 0xfffff, (lr >> 61) & 0x3, + (lr >> 60) & 0x1, (lr >> 59) & 0x1, (lr >> 48) & 0xff, (lr >> 32) & 0xffff); +#else u32 lr = gic_read_lr(n); printk("LR state: \n"); @@ -110,6 +223,7 @@ void display_lr(unsigned int n) printk(" - pending: %x\n", lr & GICH_LR_PENDING_BIT); printk(" - active: %x\n", lr & GICH_LR_ACTIVE_BIT); printk(" - hw: %x\n", lr & GICH_LR_HW_BIT); +#endif } #endif /* CONFIG_AVZ */ @@ -222,6 +336,16 @@ int irq_set_affinity(unsigned int irq, int cpu) static void gic_enable_maint_irq(bool enable) { +#ifdef CONFIG_GIC_V3 + u64 hcr = read_sysreg_s(SYS_ICH_HCR_EL2); + + if (enable) + hcr |= GICH_HCR_UIE; + else + hcr &= ~GICH_HCR_UIE; + + write_sysreg_s(hcr, SYS_ICH_HCR_EL2); +#else u32 hcr; hcr = ioread32(&gic->gich->hcr); @@ -231,12 +355,66 @@ static void gic_enable_maint_irq(bool enable) hcr &= ~GICH_HCR_UIE; iowrite32(&gic->gich->hcr, hcr); +#endif } -static int gic_inject_irq(u16 irq_id) +/* Encode the SGI source CPU in bits[15:13] of the u16 irq_id passed to + * gic_set_pending() / gic_inject_irq(). INTIDs only need bits[9:0], so the + * upper bits are free. Decoded only for SGIs (irq_id < 16); ignored for + * PPIs/SPIs. This avoids touching every internal call site of + * gic_set_pending() while still letting the GICv2 vGIC LR carry the real + * source CPU instead of smp_processor_id() of the receiving CPU. */ +#define GIC_SGI_SRC_CPU_SHIFT 13 +#define GIC_SGI_SRC_CPU_MASK (0x7 << GIC_SGI_SRC_CPU_SHIFT) +#define GIC_SGI_PACK(intid, src_cpu) ((u16) ((intid) | (((src_cpu) & 0x7) << GIC_SGI_SRC_CPU_SHIFT))) + +static int gic_inject_irq(u16 irq_id_packed) { unsigned int n; int first_free = -1; + u16 irq_id = irq_id_packed & 0x3ff; + u8 src_cpu = (irq_id_packed >> GIC_SGI_SRC_CPU_SHIFT) & 0x7; + +#ifdef CONFIG_GIC_V3 + unsigned long elsr = read_sysreg_s(SYS_ICH_ELRSR_EL2); + u64 lr64; + + for (n = 0; n < gic->gic_num_lr; n++) { + if (elsr & (1UL << n)) { + if (first_free == -1) + first_free = n; + continue; + } + + /* Check for duplicate vINTID (lower 13 bits of LR) */ + if ((gic_read_lr(n) & 0x1fff) == irq_id) + return -EEXIST; + } + + if (first_free == -1) + return -EBUSY; + + if (is_sgi(irq_id)) { + /* SGIs are software-backed: EL2 already wrote EOIR before injection */ + lr64 = GICH_LR_STATE_PENDING64 | GICH_LR_GRP1_BIT64 | + ((u64) GICH_LR_DEFAULT_PRIORITY << GICH_LR_PRIORITY_SHIFT64) | (u64) irq_id; + } else { + /* PPIs and SPIs: hardware-backed (HW=1). EL2 does NOT write EOIR. + * The physical INTID stays Active, preventing level-triggered re-delivery + * storms. The vGIC hardware deactivates the physical INTID automatically + * when Linux writes ICV_EOIR1_EL1. pINTID = vINTID (1:1 pass-through). */ + lr64 = GICH_LR_STATE_PENDING64 | GICH_LR_GRP1_BIT64 | GICH_LR_HW_BIT64 | + ((u64) GICH_LR_DEFAULT_PRIORITY << GICH_LR_PRIORITY_SHIFT64) | + ((u64) irq_id << GICH_LR_PHYS_ID_SHIFT64) | (u64) irq_id; + } + + u64 hcr = read_sysreg_s(SYS_ICH_HCR_EL2); + if (!(hcr & GICH_HCR_EN)) + write_sysreg_s(hcr | GICH_HCR_EN, SYS_ICH_HCR_EL2); + + gic_write_lr(first_free, lr64); + +#else /* CONFIG_GIC_V2 */ u32 lr; unsigned long elsr[2]; @@ -253,96 +431,88 @@ static int gic_inject_irq(u16 irq_id) /* Check that there is no overlapping */ lr = gic_read_lr(n); - if ((lr & GICH_LR_VIRT_ID_MASK) == irq_id) + if ((lr & GICH_LR_VIRT_ID_MASK) == irq_id) { + this_cpu(gic_eexist_count)++; + if (irq_id == 0) + this_cpu(gic_sgi0_eexist)++; return -EEXIST; + } } - if (first_free == -1) + if (first_free == -1) { + this_cpu(gic_busy_count)++; return -EBUSY; + } /* Inject group 0 interrupt (seen as IRQ by the guest) */ lr = irq_id; lr |= GICH_LR_PENDING_BIT; + /* Set priority to 0xa0 (Linux's default for SGIs/PPIs/SPIs). Without + * this the LR carries priority 0 — highest — which masks every other + * vIRQ at the virtual CPU interface until vEOI. In particular a + * pending vSGI 1 at vpriority 0 prevents vCNTV (PPI 27, vpriority + * 0xa0) from firing, freezing Linux's scheduler tick on the CPU. */ + lr |= ((0xa0 >> 3) & GICH_LR_PRIORITY_MASK) << GICH_LR_PRIORITY_SHIFT; if (is_sgi(irq_id)) { - lr |= (smp_processor_id() & 0x7) << GICH_LR_CPUID_SHIFT; + lr |= ((u32) src_cpu & 0x7) << GICH_LR_CPUID_SHIFT; } else { lr |= GICH_LR_HW_BIT; lr |= (u32) irq_id << GICH_LR_PHYS_ID_SHIFT; } gic_write_lr(first_free, lr); +#endif /* CONFIG_GIC_V3 */ + +#ifdef CONFIG_AVZ + this_cpu(gic_inj_total_count)++; + if (irq_id == 27) + this_cpu(gic_inj_27_count)++; +#endif return 0; } void gic_inject_pending(void) { + struct pending_irqs *pirqs = &this_cpu(pending_irqs); u16 irq_id; - while (pending_irqs.head != pending_irqs.tail) { - irq_id = pending_irqs.irqs[pending_irqs.head]; + while (pirqs->head != pirqs->tail) { + irq_id = pirqs->irqs[pirqs->head]; if (gic_inject_irq(irq_id) == -EBUSY) { - /* - * The list registers are full, trigger maintenance - * interrupt and leave. - */ gic_enable_maint_irq(true); return; } - /* - * Ensure that the entry was read before updating the head - * index. - */ dmb(ish); - pending_irqs.head = (pending_irqs.head + 1) % MAX_PENDING_IRQS; + pirqs->head = (pirqs->head + 1) % MAX_PENDING_IRQS; } - /* - * The software interrupt queue is empty - turn off the maintenance - * interrupt. - */ gic_enable_maint_irq(false); } void gic_set_pending(u16 irq_id) { + struct pending_irqs *pirqs = &this_cpu(pending_irqs); unsigned int new_tail; if (gic_inject_irq(irq_id) != -EBUSY) return; - spin_lock(&pending_irqs.lock); - - new_tail = (pending_irqs.tail + 1) % MAX_PENDING_IRQS; + spin_lock(&pirqs->lock); - /* Queue space available? */ - if (new_tail != pending_irqs.head) { - pending_irqs.irqs[pending_irqs.tail] = irq_id; + new_tail = (pirqs->tail + 1) % MAX_PENDING_IRQS; - /* - * Make the entry content is visible before updating the tail - * index. - */ + if (new_tail != pirqs->head) { + pirqs->irqs[pirqs->tail] = irq_id; dmb(ish); - - pending_irqs.tail = new_tail; + pirqs->tail = new_tail; } - /* - * The unlock has memory barrier semantic on ARM v7 and v8. Therefore - * the change to tail will be visible when sending SGI_INJECT later on. - */ - spin_unlock(&pending_irqs.lock); - - /* - * The list registers are full, trigger maintenance interrupt if we are - * on the target CPU. In the other case, send SGI_INJECT to the target - * CPU. - */ + spin_unlock(&pirqs->lock); gic_enable_maint_irq(true); } @@ -356,11 +526,123 @@ void gic_clear_pending_irqs(void) gic_write_lr(n, 0); /* Clear active priority bits. */ +#ifdef CONFIG_GIC_V3 + write_sysreg_s(0UL, SYS_ICH_AP0R0_EL2); + write_sysreg_s(0UL, SYS_ICH_AP1R0_EL2); +#else iowrite32(&gic->gich->apr, 0); +#endif +} + +#endif /* CONFIG_AVZ */ + +#ifdef CONFIG_AVZ + +#ifdef CONFIG_GIC_V3 +/* GICv3 hypervisor control via ICH_* system registers. */ +void gich_init(void) +{ + u32 gicc_ctlr, gicc_pmr; + u32 vtr, vmcr; + int n; + + /* Read physical CPU interface state via GICv3 system registers. */ + gicc_ctlr = (u32) read_sysreg(icc_ctlr_el1); + gicc_pmr = (u32) read_sysreg(icc_pmr_el1); + + /* Reset virtual machine control register. */ + write_sysreg_s(0UL, SYS_ICH_VMCR_EL2); + + /* Clear ICH_HCR_EL2 first; EN=1 will be set after ICH_VMCR_EL2 is + * programmed so Linux's ICC_* accesses redirect to ICV_* virtual + * registers (which are valid) rather than physical registers (which are + * UNDEFINED at NS EL1 on this platform). */ + write_sysreg_s(0UL, SYS_ICH_HCR_EL2); + + /* Determine number of implemented list registers. */ + vtr = (u32) read_sysreg_s(SYS_ICH_VTR_EL2); + gic->gic_num_lr = (vtr & 0x3f) + 1; + + /* ICH_VMCR_EL2 (GICv3): VPMR is 8-bit at [31:24], same encoding as ICC_PMR_EL1. */ + vmcr = (gicc_pmr & 0xff) << 24; + + /* VENG0=1 and VENG1=1: Linux's gic_has_group0() (PMR write-read test) + * always returns true on this platform, so Linux writes ICC_AP0R0_EL1. + * With EN=1 that becomes ICV_AP0R0_EL1; VENG0=1 makes that register + * defined. AVZ injects all IRQs as Group 1, so virtual Group 0 is + * never actually delivered. */ + vmcr |= GICH_VMCR_ENABLE_GRP0_MASK | GICH_VMCR_ENABLE_GRP1_MASK; + /* VEOIM=0: Linux's ICV_EOIR1_EL1 does priority drop + virtual deactivate. + * For HW=1 LRs this also triggers the physical deactivate at the + * redistributor, which is exactly what we want. We do NOT mirror the + * physical EOImode here (EL2 set it to 1 for itself, but Linux runs in + * its default mode). */ + + write_sysreg_s((u64) vmcr, SYS_ICH_VMCR_EL2); + + /* Enable virtual CPU interface now so Linux's ICC_* accesses during + * gic_cpu_sys_reg_init() are redirected to ICV_* virtual registers. */ + write_sysreg_s(GICH_HCR_EN, SYS_ICH_HCR_EL2); + + { + struct pending_irqs *pirqs = &this_cpu(pending_irqs); + spin_lock_init(&pirqs->lock); + pirqs->head = 0; + pirqs->tail = 0; + } + + gic_clear_pending_irqs(); + + for (n = 0; n < 16; n++) { + if (ioread8(((u8 *) &gic->gicd->cpendsgirn) + n)) { + iowrite8(((u8 *) &gic->gicd->cpendsgirn) + n, 0xff); + gic_set_pending(n); + } + } +} + +/* Per-CPU virtual interface init for secondary CPUs (CPU0 calls gich_init()). + * Sets ICH_VMCR_EL2 and enables ICH_HCR_EL2.EN so Linux's ICC_* accesses + * redirect to virtual ICV_* registers and virtual IRQs can be delivered. */ +void gich_secondary_init(void) +{ + u32 gicc_ctlr = (u32) read_sysreg(icc_ctlr_el1); + u64 vmcr; + struct pending_irqs *pirqs = &this_cpu(pending_irqs); + + spin_lock_init(&pirqs->lock); + pirqs->head = 0; + pirqs->tail = 0; + + vmcr = (u64) 0xf0 << 24; + vmcr |= GICH_VMCR_ENABLE_GRP0_MASK | GICH_VMCR_ENABLE_GRP1_MASK; + /* VEOIM=0: see comment in gich_init(). */ + (void) gicc_ctlr; + + write_sysreg_s(vmcr, SYS_ICH_VMCR_EL2); + isb(); + write_sysreg_s((u64) GICH_HCR_EN, SYS_ICH_HCR_EL2); + isb(); } +/* Explicitly clear the GIC redistributor's Pending bit for a PPI (id 16-31) + * on the current CPU, BEFORE writing EOIR. Without this, the GIC may hold + * Active+Pending state for a level-triggered PPI (e.g. CNTP) and EOIR would + * transition Active+Pending → Pending instead of → Inactive, causing a storm. */ +void gic_clear_ppi_pending(u16 id) +{ + int cpu_id = smp_processor_id(); + u8 *gicr_sgi = (u8 *) gic->gicc + cpu_id * 0x20000 + 0x10000; + + iowrite32(gicr_sgi + 0x280, 1u << id); /* GICR_ICPENDR0 */ + dsb(sy); +} + +#else /* CONFIG_GIC_V2: hypervisor control via GICH MMIO */ + void gich_init(void) { + struct pending_irqs *pirqs = &this_cpu(pending_irqs); u32 gicc_ctlr, gicc_pmr; u32 vtr, vmcr; int n; @@ -399,8 +681,9 @@ void gich_init(void) iowrite32(&gic->gich->vmcr, vmcr); - pending_irqs.head = 0; - pending_irqs.tail = 0; + spin_lock_init(&pirqs->lock); + pirqs->head = 0; + pirqs->tail = 0; /* * Clear pending virtual IRQs in case anything is left from previous @@ -424,48 +707,154 @@ void gich_init(void) } } } + +/* Per-CPU virtual interface init for secondary CPUs. + * + * GICH MMIO at &gic->gich and the SGI/PPI half of GICD_ISENABLER0 are banked + * per-CPU on GICv2: each CPU sees its own VMCR/HCR/LRs and its own SGI/PPI + * enable bits through the same address. When a secondary CPU comes up via + * PSCI_CPU_ON it must: + * - enable SGIs and PPIs at the banked GICD_ISENABLER0 (so the maintenance + * PPI 25 and any AVZ-internal PPIs reach this CPU), + * - set VMCR (Group 0 enable, priority mask mirrored from physical GICC_PMR), + * - enable the vGIC (HCR.EN=1), + * - initialise its per-CPU pending_irqs ring. + * Without this, vIRQ delivery is off on the secondary CPU, Linux's scheduler + * tick (PPI 27) silently disappears, and once the LR pool fills there is no + * MAINT IRQ to drive the drain — both lead to RCU stalls. */ +void gich_secondary_init(void) +{ + struct pending_irqs *pirqs = &this_cpu(pending_irqs); + u32 gicc_ctlr = ioread32(&gic->gicc->ctlr); + u32 gicc_pmr = ioread32(&gic->gicc->pmr); + u32 vmcr; + + /* Enable banked SGIs/PPIs on this CPU (mirrors gic_init's CPU0 setup). */ + iowrite32(&gic->gicd->isenabler, 0xffffffff); + + spin_lock_init(&pirqs->lock); + pirqs->head = 0; + pirqs->tail = 0; + + vmcr = (gicc_pmr >> GICV_PMR_PRIORITY_SHIFT) << GICH_VMCR_PRIMASK_SHIFT; + if (gicc_ctlr & GICC_CTLR_GRPEN1) + vmcr |= GICH_VMCR_ENABLE_GRP0_MASK; + if (gicc_ctlr & GICC_CTLR_EOImode) + vmcr |= GICH_VMCR_EOI_MODE_MASK; + + iowrite32(&gic->gich->vmcr, vmcr); + iowrite32(&gic->gich->hcr, GICH_HCR_EN); +} + +#endif /* CONFIG_GIC_V3 */ #endif /* CONFIG_AVZ */ void gicc_init(void) { - u32 bypass = 0; - int i; - +#ifdef CONFIG_GIC_V3 /* - * Deal with the banked PPI and SGI interrupts - disable all - * PPI interrupts, ensure all SGI interrupts are enabled. - */ - iowrite32(&gic->gicd->icenabler, GICD_INT_EN_CLR_PPI); - iowrite32(&gic->gicd->isenabler, GICD_INT_EN_SET_SGI); + * GICv3: in ARE=1 mode, GICD_ISENABLER0/ICENABLER0 for SGIs/PPIs are + * RAZ/WI. Per-CPU SGI/PPI configuration lives in each CPU's + * redistributor SGI frame (Frame 1), at GICR_base + cpu*0x20000 + 0x10000. + * + * gic->gicc maps the GICR base (reg[1] in the AVZ DTB). + */ + { + int i; + int cpu_id = smp_processor_id(); + u8 *gicr_rd = (u8 *) gic->gicc + cpu_id * 0x20000; + u8 *gicr_sgi = gicr_rd + 0x10000; + u32 waker; + + /* GICR_WAKER (RD_base + 0x14): clear ProcessorSleep so this + * redistributor forwards SGIs/PPIs to the PE. */ + waker = ioread32(gicr_rd + 0x14); + if (waker & (1u << 1)) { + iowrite32(gicr_rd + 0x14, waker & ~(1u << 1)); + /* Poll until ChildrenAsleep (bit 2) clears */ + do { + waker = ioread32(gicr_rd + 0x14); + } while (waker & (1u << 2)); + } - /* Priority for all SGI and PPI interrupts is the highest (value 0) */ - for (i = 0; i < 32; i += 4) - iowrite32(&gic->gicd->ipriorityr[i / 4], 0); + /* GICR_IGROUPR0 (0x80): Group 1 NS for all SGIs/PPIs */ + iowrite32(gicr_sgi + 0x80, 0xffffffff); + /* GICR_ICENABLER0 (0x180): disable all PPIs */ + iowrite32(gicr_sgi + 0x180, GICD_INT_EN_CLR_PPI); + /* GICR_ICPENDR0 (0x280): clear any stale Pending state on PPIs + * left over from the bootloader / OPTEE / TF-A so a phantom + * fire (e.g. CNTV PPI 27) cannot reach Linux before its handler + * is registered, which would leave the LR Active forever. */ + iowrite32(gicr_sgi + 0x280, GICD_INT_EN_CLR_PPI); + /* GICR_ISENABLER0 (0x100): enable SGIs + maintenance IRQ (PPI 25) + CNTHP (PPI 26) */ + iowrite32(gicr_sgi + 0x100, GICD_INT_EN_SET_SGI | (1u << IRQ_ARCH_ARM_MAINT) | (1u << 26u)); + /* GICR_IPRIORITYR[0..7] (0x400): priority 0 for all SGIs/PPIs */ + for (i = 0; i < 32; i += 4) + iowrite32(gicr_sgi + 0x400 + i, 0); + } - /* Allow all priorities */ - iowrite32(&gic->gicc->pmr, GICC_INT_PRI_THRESHOLD); + /* GICv3: CPU interface via system registers (ICC_*). */ + write_sysreg(GICC_INT_PRI_THRESHOLD, icc_pmr_el1); + + /* EOImode=1 on the physical CPU interface (ICC_CTLR_EL1 from EL2 hits + * the physical interface): split priority drop (EOIR1) from deactivate + * (DIR). Required so EL2 can drop priority after placing a forwarded + * IRQ in an LR (HW=1) without prematurely deactivating it — the HW=1 + * LR triggers the physical deactivate when Linux later writes + * ICV_EOIR1_EL1. AVZ-only: standalone SO3 runs at EL1 with EOImode=0 + * (a single EOIR drops priority and deactivates), otherwise every IRQ + * would stay Active and block all further interrupts. */ +#ifdef CONFIG_AVZ + write_sysreg(read_sysreg(icc_ctlr_el1) | (1u << 1), icc_ctlr_el1); + isb(); +#endif - /* - * Preserve bypass disable bits to be written back later - */ - bypass = ioread32(&gic->gicc->ctlr); - bypass &= GICC_DIS_BYPASS_MASK; + write_sysreg_s(1UL, SYS_ICC_IGRPEN1_EL1); +#else + { + u32 bypass; - iowrite32(&gic->gicc->ctlr, bypass | GICC_ENABLE | GIC_CPU_EOI); + /* GICv2: CPU interface via GICC MMIO. */ + iowrite32(&gic->gicc->pmr, GICC_INT_PRI_THRESHOLD); + + /* + * Preserve bypass disable bits to be written back later. + */ + bypass = ioread32(&gic->gicc->ctlr); + bypass &= GICC_DIS_BYPASS_MASK; + + /* EOImode=1 on the physical CPU interface so EL2 can drop priority + * (EOIR) without deactivating, leaving HW=1 LR injections to chain + * the physical deactivate when the guest writes its own EOIR. + * AVZ-only: standalone SO3 runs at EL1 and uses EOImode=0 so a + * single EOIR write both drops priority and deactivates. Setting + * EOImode=1 here would leave every IRQ Active forever (the EL1 + * handler never writes DIR), blocking all subsequent interrupts. */ + iowrite32(&gic->gicc->ctlr, bypass | GICC_ENABLE | GIC_CPU_EOI +#ifdef CONFIG_AVZ + | GICC_CTLR_EOImode +#endif + ); + } +#endif /* CONFIG_GIC_V3 */ #ifdef CONFIG_AVZ gich_init(); #endif } -static void gic_eoi_irq(u32 irq_id, bool deactivate) +static void gic_eoi_irq(u32 iar_value, bool deactivate) { /* - * The GIC doesn't seem to care about the CPUID value written to EOIR, - * which is rather convenient... - */ - iowrite32(&gic->gicc->eoir, irq_id); + * For GICv2 SGIs, EOIR/DIR writes MUST include the source CPU bits + * [12:10] from the IAR — otherwise the hardware leaves the SGI + * Active for the original source CPU, the running priority stays + * elevated, and no further IRQ at the same priority can fire on + * this CPU. Caller passes the full IAR (or just INTID for non-SGI + * paths where the CPUID bits are zero anyway). + */ + iowrite32(&gic->gicc->eoir, iar_value); if (deactivate) - iowrite32(&gic->gicc->dir, irq_id); + iowrite32(&gic->gicc->dir, iar_value); } static void gic_enable(unsigned int irq) @@ -502,6 +891,19 @@ static void gic_handle(void *data) int irq_nr; int irqstat; +#ifdef CONFIG_AVZ + this_cpu(gic_iar_count)++; + /* Defend MAINT (PPI 25) and CNTHP (PPI 26) on this CPU. + * Linux's gic_cpu_init writes GICD_ICENABLER0 = 0xffff0000 on every + * CPU during gic init, blanket-disabling all PPIs on that CPU's + * banked GICD register. Without this re-assert, Linux's secondary + * bring-up clobbers our hypervisor PPIs and the MAINT-driven drain + * of pending vIRQs (queued on -EEXIST/-EBUSY) never fires — + * causing RCU stalls when back-to-back SGIs arrive. Idempotent. + * GICD_ISENABLER0 is banked per-CPU on GICv2 for INTIDs 0-31. */ + iowrite32(&gic->gicd->isenabler, (1u << IRQ_ARCH_ARM_MAINT) | (1u << 26)); +#endif + do { irqstat = ioread32(&gic->gicc->iar); irq_nr = irqstat & GICC_IAR_INT_ID_MASK; @@ -509,28 +911,63 @@ static void gic_handle(void *data) if (irq_nr > 1021) break; - if (irq_nr < 16) { #ifdef CONFIG_AVZ - if ((smp_processor_id() == ME_CPU) && current_domain->avz_shared->evtchn_upcall_pending) - gic_set_pending(irq_nr); + /* MAINT (id 25): drain overflow queue, fully deactivate. */ + if (irq_nr == IRQ_ARCH_ARM_MAINT) { + gic_inject_pending(); + gic_eoi_irq(irqstat, true); + continue; + } + /* CNTHP (id 26): AVZ's hypervisor timer. Hardcode dispatch to + * avz_el2_timer_tick instead of going through irq_desc->action. + * If CNTHP fires before periodic_timer_init binds timer_isr (a + * spurious IRQ inherited from u-boot/early init), the action-NULL + * fallback below would inject the IRQ to the guest and never + * re-arm — turning AVZ's periodic timer into a one-shot and + * killing the EL2 scheduler tick. Mirror the GICv3 path. */ + if (irq_nr == 26) { + avz_el2_timer_tick(); + gic_eoi_irq(irqstat, true); + continue; + } + + /* SGIs (0-15): software-backed virtual IPIs. EL2 owns the physical + * deactivate; the LR carries a vSGI without HW=1. Pass the full + * IAR (including source CPU bits[12:10]) to gic_eoi_irq so the + * GIC properly deactivates the SGI from its actual source. + * Also forward the source CPU into the LR so Linux's vIAR sees + * the right sender for IPI accounting. */ + if (irq_nr < 16) { + u8 src_cpu = (irqstat >> 10) & 0x7; + if (irq_nr == 0) + this_cpu(gic_sgi0_recv)++; + gic_eoi_irq(irqstat, true); + gic_set_pending(GIC_SGI_PACK(irq_nr, src_cpu)); + continue; + } + + /* PPIs and SPIs: dispatch by registered AVZ action; everything + * else (guest virt timer PPI 27, guest SPIs) is forwarded via + * HW=1 LR injection. In the HW=1 case EL2 does priority drop + * only — the physical deactivate is chained when the guest + * writes its own EOIR. */ + if (irq_to_desc(irq_nr)->action != NULL) { + irq_to_desc(irq_nr)->irq_ops->handle_high(irq_nr); + gic_eoi_irq(irqstat, true); + } else { + gic_set_pending(irq_nr); + gic_eoi_irq(irqstat, false); + } #else - /* Forward the IRQ processing to another logical IRQ chip */ + if (irq_nr < 16) { irq_to_desc(irq_nr)->irq_ops->handle_high(irq_nr); - -#endif gic_eoi_irq(irq_nr, false); } else { -#ifdef CONFIG_AVZ - if (irq_nr == IRQ_ARCH_ARM_MAINT) { - gic_inject_pending(); - gic_eoi_irq(irq_nr, true); - continue; - } -#endif irq_to_desc(irq_nr)->irq_ops->handle_high(irq_nr); gic_eoi_irq(irq_nr, false); } +#endif } while (true); } @@ -542,14 +979,19 @@ void smp_cross_call(long cpu_mask, unsigned int irq) flags = spin_lock_irqsave(&per_cpu(intc_lock, cpu)); - /* - * Ensure that stores to Normal memory are visible to the - * other CPUs before they observe us issuing the IPI. - */ smp_mb(); - /* This always happens on GIC0 */ +#ifdef CONFIG_GIC_V3 + { + /* GICv3: GICD_SGIR is RAZ/WI when ARE=1; use ICC_SGI1R_EL1 instead. + * Format: [3:0]=INTID, [23:16]=Aff1 target list (one bit per CPU in cluster). */ + u64 sgi1r = ((u64) (cpu_mask & 0xffff) << 16) | (irq & 0xf); + write_sysreg_s(sgi1r, SYS_ICC_SGI1R_EL1); + isb(); + } +#else iowrite32(&gic->gicd->sgir, (cpu_mask << 16) | irq); +#endif spin_unlock_irqrestore(&per_cpu(intc_lock, cpu), flags); } @@ -695,18 +1137,23 @@ static int gic_init(dev_t *dev, int fdt_offset) gic->gich = (struct gich_regs *) io_map(fdt64_to_cpu(((const fdt64_t *) prop->data)[4]), fdt64_to_cpu(((const fdt64_t *) prop->data)[5])); - spin_lock_init(&pending_irqs.lock); - - /* Disable PPIs, except for the maintenance interrupt. */ - iowrite32(&gic->gicd->isenabler, 0xffff0000 & ~(1 << IRQ_ARCH_ARM_MAINT)); - - /* Ensure all IPIs and the maintenance PPI are enabled */ - iowrite32(&gic->gicd->isenabler, 0x0000ffff & ~(1 << IRQ_ARCH_ARM_MAINT)); + /* Enable SGIs (0-15) and PPIs (16-31), including the maintenance + * interrupt at PPI 25. GICH_HCR.UIE/NPIE controls whether MAINT + * actually fires; the GICD enable just allows the physical PPI to + * reach the CPU when vGIC hardware asserts it. Without this, when + * all LRs fill (4 in-flight vIRQs) gic_inject_irq returns -EBUSY + * and the queued IRQs in pending_irqs stay there forever — the + * drain via MAINT can never fire. */ + iowrite32(&gic->gicd->isenabler, 0xffffffff); #endif gic_hw_reset(); + /* Initialize the CPU0 per-CPU interface and virtual CPU interface. + * Secondary CPUs do this in secondary_start_kernel() via gicc_init(). */ + gicc_init(); + irq_ops.enable = gic_enable; irq_ops.disable = gic_disable; irq_ops.mask = gic_mask; diff --git a/so3/devices/irq/vgic.c b/so3/devices/irq/vgic.c index bb99fe690a..1aef79d4f4 100644 --- a/so3/devices/irq/vgic.c +++ b/so3/devices/irq/vgic.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024-2025 Daniel Rossier + * Copyright (C) 2024-2026 Daniel Rossier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -27,33 +27,59 @@ DEFINE_SPINLOCK(dist_lock); -static enum mmio_result gicv2_handle_dist_access(struct mmio_access *mmio) -{ - unsigned long val = mmio->value; - struct sgi; +#ifdef CONFIG_GIC_V3 +/* + * GICv3 distributor MMIO handler. + * + * The physical GICD is not mapped in the guest Stage-2 tables, so every + * Linux GICD access causes a Stage-2 data abort that lands here. For a + * single NS Linux guest with HCR_EL2.IMO=1 the simplest correct policy is + * to pass all reads and writes directly to the physical GICD. Physical SPIs + * enabled by Linux via GICD_ISENABLER will fire, be intercepted at EL2, and + * forwarded to Linux as virtual IRQs via ICH_LR*. + * + * GICD_SGIR is the GICv2-compatibility SGI register. GICv3 Linux uses + * ICC_SGI1R_EL1 instead, but handle it anyway for robustness. + */ +static enum mmio_result gicv3_handle_dist_access(struct mmio_access *mmio) +{ switch (mmio->address) { case GICD_SGIR: - if (!mmio->is_write) - return MMIO_HANDLED; - - smp_cross_call((val >> 16) & 0xff, val & 0xf); + if (mmio->is_write) + smp_cross_call((mmio->value >> 16) & 0xff, mmio->value & 0xf); + return MMIO_HANDLED; + default: + mmio_perform_access(gic->gicd, mmio); return MMIO_HANDLED; + } +} - case GICD_CTLR: - case GICD_TYPER: - case GICD_IIDR: - case REG_RANGE(GICDv2_PIDR0, 4, 4): - case REG_RANGE(GICDv2_PIDR4, 4, 4): - case REG_RANGE(GICDv2_CIDR0, 4, 4): - /* Allow read access, ignore write */ - if (!mmio->is_write) - mmio_perform_access(gic->gicd, mmio); +#else /* CONFIG_GIC_V2 */ + +/* + * Single-guest GICv2 distributor handler — mirrors the GICv3 trap+forward + * model. The physical GICD is unmapped from the agency Stage-2 tables, so + * every Linux GICD access faults here. For a single non-secure Linux + * guest with HCR_EL2.IMO=1, the simplest correct policy is to forward all + * reads and writes directly to the physical GICD; SPIs Linux enables via + * GICD_ISENABLER will fire, be intercepted at EL2, and forwarded to Linux + * as virtual IRQs via the GICH list registers. Special cases: + * - GICD_SGIR is translated into smp_cross_call so SGIs are routed via + * AVZ's targeted-SGI helper (and visible to the maintenance/dispatch + * paths) rather than going straight to the physical distributor. + */ +static enum mmio_result gicv2_handle_dist_access(struct mmio_access *mmio) +{ + switch (mmio->address) { + case GICD_SGIR: + if (mmio->is_write) + smp_cross_call((mmio->value >> 16) & 0xff, mmio->value & 0xf); + return MMIO_HANDLED; - /* fall through */ default: - /* Ignore access. */ + mmio_perform_access(gic->gicd, mmio); return MMIO_HANDLED; } } @@ -123,8 +149,13 @@ static enum mmio_result gicv2_handle_irq_target(struct mmio_access *mmio, unsign return MMIO_HANDLED; } +#endif /* CONFIG_GIC_V3 */ + enum mmio_result gic_handle_dist_access(struct mmio_access *mmio) { +#ifdef CONFIG_GIC_V3 + return gicv3_handle_dist_access(mmio); +#else unsigned long reg = mmio->address; enum mmio_result ret; @@ -143,8 +174,6 @@ enum mmio_result gic_handle_dist_access(struct mmio_access *mmio) case REG_RANGE(GICD_ISPENDR, 32, 4): case REG_RANGE(GICD_ICACTIVER, 32, 4): case REG_RANGE(GICD_ISACTIVER, 32, 4): - - /* Currently, the guest has no way to handle a physical IRQ*/ ret = gicv2_handle_dist_access(mmio); break; @@ -153,4 +182,5 @@ enum mmio_result gic_handle_dist_access(struct mmio_access *mmio) } return ret; -} \ No newline at end of file +#endif /* CONFIG_GIC_V3 */ +} diff --git a/so3/devices/serial.c b/so3/devices/serial.c index a4fe97097f..41c549a1d2 100644 --- a/so3/devices/serial.c +++ b/so3/devices/serial.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2019 Daniel Rossier + * Copyright (C) 2014-2026 Daniel Rossier * Copyright (C) 2017 Alexandre Malki * * This program is free software; you can redistribute it and/or modify @@ -66,7 +66,7 @@ char serial_getc(void) /* * Emergency printk() or very early printk() before the real uart driver is ready. - * It may be used in the context of the virtualized ME SO3 as well. + * It may be used in the context of the virtualized capsule SO3 as well. */ int ll_serial_write(char *str, int len) { diff --git a/so3/devices/serial/Kconfig b/so3/devices/serial/Kconfig index 4ba64b6d6f..9143984665 100644 --- a/so3/devices/serial/Kconfig +++ b/so3/devices/serial/Kconfig @@ -10,7 +10,11 @@ config PL011_UART config BCM283x_MU_UART bool "BCM283x mini-UART" depends on RPI4 || RPI4_64 - + +config IMX_UART + bool "Freescale i.MX UART (fsl,imx6q-uart)" + depends on VERDIN_IMX8MP + config UART_LL_PADDR hex "Low-level UART base physical address" diff --git a/so3/devices/serial/Makefile b/so3/devices/serial/Makefile index 7b27f62dad..50d805cf54 100644 --- a/so3/devices/serial/Makefile +++ b/so3/devices/serial/Makefile @@ -2,6 +2,7 @@ obj-$(CONFIG_PL011_UART) += pl011.o obj-$(CONFIG_NS16550) += ns16550.o obj-$(CONFIG_BCM283x_MU_UART) += bcm283x_mu.o +obj-$(CONFIG_IMX_UART) += imx_uart.o obj-$(CONFIG_SOO_SERIAL) += soo_serial.o diff --git a/so3/devices/serial/imx_uart.c b/so3/devices/serial/imx_uart.c new file mode 100644 index 0000000000..22b022c512 --- /dev/null +++ b/so3/devices/serial/imx_uart.c @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2026 EDGEMTech SA + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +/* + * Minimal driver for the Freescale i.MX UART (fsl,imx6q-uart compatible). + * Targets UART3 on the Toradex Verdin iMX8MP @ 0x30880000. + * + * Only TX is implemented — sufficient for AVZ's early console output. + * UART initialisation is performed by ATF/U-Boot before AVZ starts, + * so this driver assumes the UART is already configured and enabled. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include + +#include + +#define SERIAL_BUFFER_SIZE 80 + +volatile void *__uart_vaddr = (void *) CONFIG_UART_LL_PADDR; + +static volatile char serial_buffer[SERIAL_BUFFER_SIZE]; +static volatile uint32_t prod = 0, cons = 0; + +extern mutex_t read_lock; + +typedef struct { + addr_t base; + irq_def_t irq_def; +} imx_uart_t; + +static imx_uart_t imx_uart = { + .base = CONFIG_UART_LL_PADDR, +}; + +static int imx_uart_put_byte(char c) +{ + /* Wait until TX FIFO is empty before writing */ + while (!(ioread32(imx_uart.base + IMX_UART_USR2) & IMX_UART_USR2_TXFE)) + ; + + iowrite32(imx_uart.base + IMX_UART_UTXD, (u32) c); + + return 1; +} + +static char imx_uart_get_byte(bool polling) +{ + char tmp; + + if (polling) { + /* Poll until a byte arrives in the RX register */ + while (!(ioread32(imx_uart.base + IMX_UART_USR2) & (1 << 0))) + ; + + return (char) (ioread32(imx_uart.base + IMX_UART_URXD) & 0xFF); + } else { + while (prod == cons) { + schedule(); + + smp_mb(); + wfi(); + } + + tmp = serial_buffer[cons]; + cons = (cons + 1) % SERIAL_BUFFER_SIZE; + + return tmp; + } + + return 0; +} + +static irq_return_t imx_uart_int(int irq, void *dummy) +{ + u32 val; + + val = ioread32(imx_uart.base + IMX_UART_URXD); + if (!(val & (1 << 15))) /* CHARRDY bit */ + return IRQ_COMPLETED; + + serial_buffer[prod] = (char) (val & 0xFF); + + if (serial_buffer[prod] == 3) { + imx_uart_put_byte('^'); + imx_uart_put_byte('C'); + imx_uart_put_byte('\n'); + prod--; + +#ifdef CONFIG_IPC_SIGNAL + if (current()->pcb != NULL) + sys_do_kill(current()->pcb->pid, SIGINT); +#endif + } + + prod = (prod + 1) % SERIAL_BUFFER_SIZE; + + return IRQ_COMPLETED; +} + +static void imx_uart_enable_irq(void) +{ + irq_ops.enable(imx_uart.irq_def.irqnr); +} + +static void imx_uart_disable_irq(void) +{ + irq_ops.disable(imx_uart.irq_def.irqnr); +} + +static int imx_uart_init(dev_t *dev, int fdt_offset) +{ + const struct fdt_property *prop; + int prop_len; + addr_t new_base_vaddr; + + serial_ops.put_byte = imx_uart_put_byte; + serial_ops.get_byte = imx_uart_get_byte; + + serial_ops.enable_irq = imx_uart_enable_irq; + serial_ops.disable_irq = imx_uart_disable_irq; + + prop = fdt_get_property(__fdt_addr, fdt_offset, "reg", &prop_len); + BUG_ON(!prop); + + BUG_ON(prop_len != 2 * sizeof(unsigned long)); + + new_base_vaddr = io_map(fdt64_to_cpu(((const fdt64_t *) prop->data)[0]), PAGE_SIZE); + BUG_ON(!new_base_vaddr); + + imx_uart.base = new_base_vaddr; + + fdt_interrupt_node(fdt_offset, &imx_uart.irq_def); + + irq_bind(imx_uart.irq_def.irqnr, imx_uart_int, NULL, NULL); + + serial_ops.enable_irq(); + + return 0; +} + +void __ll_put_byte(char c) +{ + if (c == '\n') + imx_uart_put_byte('\r'); + imx_uart_put_byte(c); +} + +void printch(char c) +{ + __ll_put_byte(c); +} + +REGISTER_DRIVER_POSTCORE("serial,imx", imx_uart_init); diff --git a/so3/devices/serial/pl011.c b/so3/devices/serial/pl011.c index 601be88f20..a9ab3b4399 100644 --- a/so3/devices/serial/pl011.c +++ b/so3/devices/serial/pl011.c @@ -183,10 +183,20 @@ static int pl011_init(dev_t *dev, int fdt_offset) fdt_interrupt_node(fdt_offset, &pl011.irq_def); - /* Bind ISR into interrupt controller */ +#ifndef CONFIG_AVZ + /* When AVZ runs as hypervisor, the PL011 RX interrupt belongs to + * the agency guest — its console driver attaches its own handler. + * If we bind one here, gic_handle's irq_desc->action != NULL + * branch fires AVZ's pl011_int and EOIs the IRQ before it ever + * reaches the guest, freezing input at the guest login prompt. + * AVZ uses pl011_put_byte for output only and does not need RX. */ irq_bind(pl011.irq_def.irqnr, pl011_int, NULL, NULL); +#endif - /* Enable interrupt (IRQ controller) */ + /* Enable interrupt (IRQ controller) — done unconditionally so the + * hardware-level state of IMSC and the GIC SPI enable is identical + * in CONFIG_AVZ and non-AVZ builds; only the AVZ-side action is + * skipped above. */ iowrite16(pl011.base + UART011_IMSC, UART011_RXIM | UART011_RTIM); serial_ops.enable_irq(); diff --git a/so3/devices/timer/arm_timer.c b/so3/devices/timer/arm_timer.c index 4486ad9d26..6b23d81bee 100644 --- a/so3/devices/timer/arm_timer.c +++ b/so3/devices/timer/arm_timer.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2022 Daniel Rossier + * Copyright (C) 2014-2026 Daniel Rossier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -84,7 +84,7 @@ static irq_return_t timer_isr(int irq, void *dev) next_event(arm_timer->reload); #ifdef CONFIG_AVZ - timer_interrupt((smp_processor_id() == ME_CPU) ? true : false); + timer_interrupt((smp_processor_id() == S3C_CPU) ? true : false); #else jiffies++; @@ -103,6 +103,36 @@ void periodic_timer_start(void) next_event(arm_timer->reload); } +#ifdef CONFIG_AVZ + +/* Called from the EL2 IRQ handler when CNTHP (PPI 26) fires while Linux + * runs at EL1. Used by both GIC versions: the dispatch in gic.c + * special-cases INTID 26 and invokes this directly, bypassing the + * irq_desc action table. This guarantees CNTHP gets re-armed even if a + * spurious early CNTHP IRQ arrives before periodic_timer_init binds the + * action — which would otherwise route the IRQ to the guest and leave + * the timer one-shot. Safe to call before periodic_timer.dev is set: + * dev_get_drvdata returns NULL and we skip. + */ +void avz_el2_timer_tick(void) +{ + arm_timer_t *arm_timer; + + if (!periodic_timer.dev) + return; + + arm_timer = (arm_timer_t *) dev_get_drvdata(periodic_timer.dev); + if (!arm_timer) + return; + + /* Re-arm the timer for the next period. */ + next_event(arm_timer->reload); + + /* Agency tick (non-capsule path). */ + timer_interrupt(false); +} +#endif /* CONFIG_AVZ */ + /* * Read the clocksource timer value taking into account a time reference. * @@ -125,7 +155,6 @@ void secondary_timer_init(void) #ifdef CONFIG_AVZ arch_timer_reg_write_el2(ARCH_TIMER_REG_CTRL, 0); #else - ctrl = arch_timer_reg_read_cp15(ARCH_TIMER_VIRT_ACCESS, ARCH_TIMER_REG_CTRL); ctrl &= ~ARCH_TIMER_CTRL_ENABLE; arch_timer_reg_write_cp15(ARCH_TIMER_VIRT_ACCESS, ARCH_TIMER_REG_CTRL, ctrl); @@ -170,7 +199,6 @@ static int periodic_timer_init(dev_t *dev, int fdt_offset) #ifdef CONFIG_AVZ arch_timer_reg_write_el2(ARCH_TIMER_REG_CTRL, 0); #else - ctrl = arch_timer_reg_read_cp15(ARCH_TIMER_VIRT_ACCESS, ARCH_TIMER_REG_CTRL); ctrl &= ~ARCH_TIMER_CTRL_ENABLE; arch_timer_reg_write_cp15(ARCH_TIMER_VIRT_ACCESS, ARCH_TIMER_REG_CTRL, ctrl); diff --git a/so3/dts/Makefile b/so3/dts/Makefile index 370c085860..626cf5a705 100644 --- a/so3/dts/Makefile +++ b/so3/dts/Makefile @@ -1,15 +1,13 @@ dtb-$(CONFIG_RPI4) += rpi4.dtb dtb-$(CONFIG_RPI4_64) += rpi4_64.dtb rpi4_64_avz.dtb -dtb-$(CONFIG_VIRT64) += virt64.dtb virt64_avz.dtb virt64_lvperf.dtb +dtb-$(CONFIG_VIRT64) += virt64.dtb virt64_avz.dtb virt64_capsule.dtb virt64_lvperf.dtb dtb-$(CONFIG_VIRT32) += virt32.dtb virt32_lvperf.dtb +dtb-$(CONFIG_VERDIN_IMX8MP) += verdin-imx8mp_avz.dtb ifeq ($(CONFIG_SOO),y) - ifeq ($(CONFIG_AVZ),) -dtb-$(CONFIG_VIRT64) += virt64_capsule.dtb rpi4_64_capsule.dtb - ifeq ($(CONFIG_SOO_REFSO3),y) ifeq ($(CONFIG_RAMDEV),y) dtb-y += refso3_ramfs.dtb diff --git a/so3/dts/rpi4_64_avz.dts b/so3/dts/rpi4_64_avz.dts index 3c6ce229d9..ede720f3f5 100644 --- a/so3/dts/rpi4_64_avz.dts +++ b/so3/dts/rpi4_64_avz.dts @@ -47,9 +47,10 @@ #interrupt-cells = <3>; /* GIC dist, cpu, hyp */ - reg = <0x0 0xff841000 0x0 0x1000>, - <0x0 0xff842000 0x0 0x2000>, - <0x0 0xff844000 0x0 0x2000>; + reg = <0x0 0x08000000 0x0 0x10000 + 0x0 0x08010000 0x0 0x10000 + 0x0 0x08030000 0x0 0x10000>; + status = "ok"; }; diff --git a/so3/dts/verdin-imx8mp_avz.dts b/so3/dts/verdin-imx8mp_avz.dts new file mode 100644 index 0000000000..5378e403b4 --- /dev/null +++ b/so3/dts/verdin-imx8mp_avz.dts @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2026 EDGEMTech SA + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +/dts-v1/; + +/ { + model = "AVZ Toradex Verdin iMX8MP"; + compatible = "arm,verdin-imx8mp"; + + #address-cells = <2>; + #size-cells = <2>; + + cpus { + device_type = "cpu"; + compatible = "arm,cortex-a53"; + }; + + /* + * Full DRAM managed by AVZ: 0x40000000–0xBDFFFFFF (minus OP-TEE at top). + * AVZ itself runs at 0x40200000 (placed by U-Boot: DRAM_base + text_offset). + * Linux guest occupies 0x41000000 upward via the memslot mechanism. + */ + memory@40000000 { + device_type = "memory"; + reg = <0x0 0x40000000 0x0 0x7e000000>; + }; + + mem { + compatible = "mem"; + status = "ok"; + }; + + /* + * GICv3 — ARM Generic Interrupt Controller v3. + * GICD (distributor) @ 0x38800000 size 0x10000 + * GICR (redistributor) @ 0x38880000 size 0xc0000 + * GICC (compat CPU if) @ 0x38C20000 size 0x10000 + * + * AVZ uses the GICv2 compatibility interface (GICC) exposed by + * GICv3 when ARE_NS = 0 in GICD_CTLR. + */ + gic: interrupt-controller@38800000 { + compatible = "intc,gic"; + interrupt-controller; + #interrupt-cells = <3>; + /* GICD, GICR, GICC */ + reg = <0x0 0x38800000 0x0 0x10000 + 0x0 0x38880000 0x0 0xc0000 + 0x0 0x38C20000 0x0 0x10000>; + status = "ok"; + }; + + /* UART3 — debug console, already initialised by ATF/U-Boot */ + serial@30880000 { + compatible = "serial,imx"; + reg = <0x0 0x30880000 0x0 0x10000>; + interrupt-parent = <&gic>; + interrupts = <0 28 4>; + status = "ok"; + }; + + /* Periodic timer — ARM CP15 virtual timer PPI #10 */ + periodic-timer { + compatible = "arm,periodic-timer"; + interrupt-parent = <&gic>; + interrupts = <1 10 4>; + status = "ok"; + }; + + /* Clocksource — ARM CP15 physical counter */ + clocksource-timer { + compatible = "arm,clocksource-timer"; + status = "ok"; + }; +}; diff --git a/so3/include/banner.h b/so3/include/banner.h index 048875bc0f..7cb09bb1b6 100644 --- a/so3/include/banner.h +++ b/so3/include/banner.h @@ -5,7 +5,7 @@ #define SO3_BANNER \ "\n\n********** Smart Object Oriented SO3 Operating System **********\n\ -Copyright (c) 2014-2023 REDS Institute, HEIG-VD, Yverdon\n\ +Copyright (c) 2014-2026 REDS Institute, HEIG-VD, Yverdon\n\ Version " SO3_KERNEL_VERSION "\n\n" #endif /* BENNER_H */ diff --git a/so3/include/common.h b/so3/include/common.h index 8d33190eaf..b919952611 100644 --- a/so3/include/common.h +++ b/so3/include/common.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2019 Daniel Rossier + * Copyright (C) 2014-2026 Daniel Rossier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -36,13 +36,13 @@ * CPU #0 is the primary (non-RT) Agency CPU. * CPU #1 is the hard RT Agency CPU. * CPU #2 is the second (SMP) Agency CPU. - * CPU #3 is the ME CPU. + * CPU #3 is the capsule CPU. */ #define AGENCY_CPU 0 #define AGENCY_RT_CPU 1 -#define ME_CPU 3 +#define S3C_CPU 3 #endif /* CONFIG_AVZ */ diff --git a/so3/include/device/arch/gic.h b/so3/include/device/arch/gic.h index 65f870f95e..ca646b68c5 100644 --- a/so3/include/device/arch/gic.h +++ b/so3/include/device/arch/gic.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2023 Daniel Rossier + * Copyright (C) 2014-2026 Daniel Rossier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -262,6 +262,32 @@ struct __attribute__((packed)) gich_regs { #define GICH_LR_PHYS_ID_SHIFT 10 #define GICH_LR_VIRT_ID_MASK 0x3ff +/* GICv3 ICH_LR*_EL2 64-bit field definitions (ARM GICv3 spec IHI0069): + * [63] RES0 + * [62:61] State: 00=Invalid, 01=Pending, 10=Active, 11=Active+Pending + * [60] HW: hardware-backed interrupt + * [59] Group: 0=Group0 (FIQ), 1=Group1 (IRQ) + * [55:48] Priority + * [47:32] pINTID (physical INTID, valid when HW=1) + * [19:0] vINTID (virtual INTID) + */ +/* ICH_LR_EL2 layout (per ARM ARM, matches Linux's arch_gicv3.h): + * [63] State[1] = Active + * [62] State[0] = Pending + * [61] HW + * [60] Group (1 = Group 1) + * [55:48] Priority + * [41:32] pINTID (when HW=1) + * [31:0] vINTID + */ +#define GICH_LR_STATE_PENDING64 (1ULL << 62) /* State[63:62]=01 */ +#define GICH_LR_STATE_ACTIVE64 (1ULL << 63) /* State[63:62]=10 */ +#define GICH_LR_HW_BIT64 (1ULL << 61) +#define GICH_LR_GRP1_BIT64 (1ULL << 60) +#define GICH_LR_PRIORITY_SHIFT64 48 +#define GICH_LR_PHYS_ID_SHIFT64 32 +#define GICH_LR_DEFAULT_PRIORITY 0xa0 + #define GIC_SGI_UNKNOWN 0 #define GIC_SGI_EVENT 1 @@ -299,8 +325,14 @@ typedef struct __attribute__((packed)) { #ifdef CONFIG_AVZ void gic_set_pending(u16 irq_id); +void gic_inject_pending(void); void gic_clear_pending_irqs(void); +#ifdef CONFIG_GIC_V3 +void gich_secondary_init(void); +void gic_clear_ppi_pending(u16 id); +#endif + #endif /* CONFIG_AVZ */ extern gic_t *gic; diff --git a/so3/include/device/fdt.h b/so3/include/device/fdt.h index 9332860fee..15c444bba1 100644 --- a/so3/include/device/fdt.h +++ b/so3/include/device/fdt.h @@ -1,5 +1,6 @@ #ifndef FDT_H #define FDT_H + /* * libfdt - Flat Device Tree manipulation * Copyright (C) 2006 David Gibson, IBM Corporation. diff --git a/so3/include/device/timer.h b/so3/include/device/timer.h index 3c6e1c91c1..43019f4838 100644 --- a/so3/include/device/timer.h +++ b/so3/include/device/timer.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2023 Daniel Rossier + * Copyright (C) 2014-2026 Daniel Rossier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -133,6 +133,8 @@ void secondary_timer_init(void); #ifdef CONFIG_AVZ void timer_interrupt(bool periodic); +void agency_timer_interrupt(void); +void avz_el2_timer_tick(void); #endif /* CONFIG_AVZ */ #endif /* DEVICE_TIMER_H */ diff --git a/so3/include/log.h b/so3/include/log.h index e6003c45de..82d89d25f1 100644 --- a/so3/include/log.h +++ b/so3/include/log.h @@ -45,13 +45,14 @@ #elif defined(CONFIG_VLOGS_FRONTEND) -#define LOG(level, fmt, ...) \ - do { \ - if (vlogs_ready()) \ - vlogs_write("[ME:%d][" #level "] <%s:%d> " fmt, get_ME_desc()->slotID, __func__, __LINE__, \ - ##__VA_ARGS__); \ - else \ - lprintk("[ME:%d][" #level "] <%s:%d> " fmt, get_ME_desc()->slotID, __func__, __LINE__, ##__VA_ARGS__); \ +#define LOG(level, fmt, ...) \ + do { \ + if (vlogs_ready()) \ + vlogs_write("[S3C:%d][" #level "] <%s:%d> " fmt, get_S3C_desc()->slotID, __func__, __LINE__, \ + ##__VA_ARGS__); \ + else \ + lprintk("[S3C:%d][" #level "] <%s:%d> " fmt, get_S3C_desc()->slotID, __func__, __LINE__, \ + ##__VA_ARGS__); \ } while (0) #else diff --git a/so3/include/percpu.h b/so3/include/percpu.h index 3c1a8fc15f..1b4bce3d75 100644 --- a/so3/include/percpu.h +++ b/so3/include/percpu.h @@ -31,8 +31,21 @@ extern unsigned long __per_cpu_offset[CONFIG_NR_CPUS]; #define __get_cpu_var(var) per_cpu(var, smp_processor_id()) #define __raw_get_cpu_var(var) per_cpu(var, raw_smp_processor_id()) -/* Separate out the type, so (int[3], foo) works. */ -#define __DEFINE_PER_CPU(type, name, suffix) __attribute__((__section__(".bss.percpu"))) __typeof__(type) per_cpu_##name +/* Separate out the type, so (int[3], foo) works. + * + * Section renamed from ".bss.percpu" → ".percpu_data" because the linker + * script's `*(.bss*)` glob was matching `.bss.percpu` BEFORE the + * __per_cpu_start label, leaving __per_cpu_data_end == __per_cpu_start + * (size 0). The result: init_percpu_area's memset(p, 0, 0) was a no-op, + * per_cpu_offset[cpu] = p - 0xc3000 pointed BEFORE the allocated page, + * and `this_cpu(var)` accessed random memory adjacent to each CPU's + * allocation. Functional per-CPU state (intc_lock, pending_irqs, timers) + * was completely broken, only working by accident. + * + * Also fixed: the `suffix` arg now actually affects the section name, + * so DEFINE_PER_CPU_READ_MOSTLY properly lands in .percpu_data.read_mostly. */ +#define __DEFINE_PER_CPU(type, name, suffix) \ + __attribute__((__section__(".percpu_data" #suffix))) __typeof__(type) per_cpu_##name #define DECLARE_PER_CPU(type, name) extern __typeof__(type) per_cpu__##name diff --git a/so3/include/version.h b/so3/include/version.h index 747b822075..272ecd95ec 100644 --- a/so3/include/version.h +++ b/so3/include/version.h @@ -27,6 +27,6 @@ * - Upgrade U-boot to 2022.04 */ -#define SO3_KERNEL_VERSION "6.1.0" +#define SO3_KERNEL_VERSION "6.2.0" #endif /* VERSION_H */ diff --git a/so3/kernel/timer.c b/so3/kernel/timer.c index 8ae7c83f17..36e4c30df3 100644 --- a/so3/kernel/timer.c +++ b/so3/kernel/timer.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2023 Daniel Rossier + * Copyright (C) 2014-2026 Daniel Rossier * Copyright (C) 2017 Baptiste Delporte * * This program is free software; you can redistribute it and/or modify @@ -359,8 +359,8 @@ static void dump_timerq(unsigned char key) printk("Dumping timer queues:\n"); - ts = &per_cpu(timers, ME_CPU); - printk("CPU #%d:\n", ME_CPU); + ts = &per_cpu(timers, S3C_CPU); + printk("CPU #%d:\n", S3C_CPU); spin_lock(&ts->lock); diff --git a/so3/mm/Kconfig b/so3/mm/Kconfig index a252c68497..da385d4121 100644 --- a/so3/mm/Kconfig +++ b/so3/mm/Kconfig @@ -2,8 +2,8 @@ config KERNEL_VADDR hex "Virtual base address of the SO3 kernel or hypervisor" default 0xff000000 if ARM32 - default 0xffff700000000000 if ARM64 default 0x100000000000 if AVZ + default 0xffff700000000000 if ARM64 config HEAP_SIZE_MB int "Size of the heap (MB)" diff --git a/so3/soo/Kconfig b/so3/soo/Kconfig index bb28ce1652..8bca182e43 100644 --- a/so3/soo/Kconfig +++ b/so3/soo/Kconfig @@ -1,9 +1,9 @@ # -# SOO Mobile Entity configuration +# SOO SO3 capsule configuration # if SOO && !AVZ -menu "SOO Mobile Entity - Smart Object Oriented subsystem" +menu "SOO SO3 capsule - Smart Object Oriented subsystem" source "soo/drivers/Kconfig" diff --git a/so3/soo/drivers/Kconfig b/so3/soo/drivers/Kconfig index d28e2bd408..d6de4c483e 100644 --- a/so3/soo/drivers/Kconfig +++ b/so3/soo/drivers/Kconfig @@ -1,8 +1,8 @@ # -# SOO Mobile Entity configuration +# SOO SO3 capsule configuration # -menu "SOO Mobile Entity frontend drivers" +menu "SOO SO3 capsule frontend drivers" config VDUMMY_FRONTEND bool "vdummy Dummy driver support debugging and testing" diff --git a/so3/soo/drivers/vdevfront.c b/so3/soo/drivers/vdevfront.c index 5998208870..f450d8c884 100644 --- a/so3/soo/drivers/vdevfront.c +++ b/so3/soo/drivers/vdevfront.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 Daniel Rossier + * Copyright (C) 2020-2026 Daniel Rossier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -35,7 +35,7 @@ * * Assumptions: * - If the frontend is suspended during processing_start, it is for a short time, until the FE gets connected. - * - If the frontend is suspended and a shutdown operation is in progress, the ME will disappear! Therefore, + * - If the frontend is suspended and a shutdown operation is in progress, the capsule will disappear! Therefore, * we do not take care about ongoing activities. All will disappear... * */ @@ -129,7 +129,7 @@ static void __otherend_changed(struct vbus_device *vdev, enum vbus_state backend vdrvfront->closed(vdev); /* The processing_lock is kept forever, since it has to keep all processing activities suspended. - * Until the ME disappears... + * Until the capsule disappears... */ break; diff --git a/so3/soo/drivers/vdummyfront/vdummy.c b/so3/soo/drivers/vdummyfront/vdummy.c index fa93fe1b66..d2bfcab517 100644 --- a/so3/soo/drivers/vdummyfront/vdummy.c +++ b/so3/soo/drivers/vdummyfront/vdummy.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018-2019 Daniel Rossier + * Copyright (C) 2018-2026 Daniel Rossier * Copyright (C) 2018-2019 Baptiste Delporte * * This program is free software; you can redistribute it and/or modify @@ -54,7 +54,7 @@ irq_return_t vdummy_interrupt(int irq, void *dev_id) vdummy_priv_t *vdummy_priv = dev_get_drvdata(vdev->dev); vdummy_response_t *ring_rsp; - DBG("%s, %d\n", __func__, ME_domID()); + DBG("%s, %d\n", __func__, S3C_domID()); while ((ring_rsp = vdummy_get_ring_response(&vdummy_priv->vdummy.ring)) != NULL) { DBG("%s, cons=%d\n", __func__, i); diff --git a/so3/soo/drivers/vfbdevfront/vfbdev.c b/so3/soo/drivers/vfbdevfront/vfbdev.c index b50eec242c..33d7befcfd 100644 --- a/so3/soo/drivers/vfbdevfront/vfbdev.c +++ b/so3/soo/drivers/vfbdevfront/vfbdev.c @@ -254,7 +254,7 @@ static void retrieve_data(vfbdev_priv_t *priv) vdevfront_processing_end(vfbdev_dev); /* Retrieve fb address from AVZ */ - hyp_args.cmd = AVZ_FBDEV_GET_ME_ADDR; + hyp_args.cmd = AVZ_FBDEV_GET_S3C_ADDR; avz_hypercall(&hyp_args); priv->fb_paddr = hyp_args.u.avz_fbdev_addr_args.paddr; } diff --git a/so3/soo/drivers/vlogsfront/vlogs.c b/so3/soo/drivers/vlogsfront/vlogs.c index 939be05aec..e69ba35bd3 100644 --- a/so3/soo/drivers/vlogsfront/vlogs.c +++ b/so3/soo/drivers/vlogsfront/vlogs.c @@ -246,7 +246,7 @@ static int vlogs_cwrite(int fd, const void *buffer, int count) { static char msg[1024]; - sprintf(msg, "[ME:%d] %s", get_ME_desc()->capsuleID, (const char *) buffer); + sprintf(msg, "[S3C:%d] %s", get_S3C_desc()->capsuleID, (const char *) buffer); vlogs_write((char *) msg); diff --git a/so3/soo/drivers/vuihandlerfront/vuihandler.c b/so3/soo/drivers/vuihandlerfront/vuihandler.c index 561638808d..e4ff79a42f 100644 --- a/so3/soo/drivers/vuihandlerfront/vuihandler.c +++ b/so3/soo/drivers/vuihandlerfront/vuihandler.c @@ -1,6 +1,6 @@ /* * Copyright (C) 2020-2022 David Truan - * Copyright (C) 2018-2019 Daniel Rossier + * Copyright (C) 2018-2026 Daniel Rossier * Copyright (C) 2018-2019 Baptiste Delporte * * This program is free software; you can redistribute it and/or modify @@ -39,7 +39,7 @@ #include -/* Callbacks to be set by the ME app */ +/* Callbacks to be set by the capsule app */ ui_interrupt_t __ui_interrupt = NULL; ui_send_model_t __ui_send_model = NULL; diff --git a/so3/soo/include/me/agency.h b/so3/soo/include/capsule/agency.h similarity index 100% rename from so3/soo/include/me/agency.h rename to so3/soo/include/capsule/agency.h diff --git a/so3/soo/include/me/common.h b/so3/soo/include/capsule/common.h similarity index 95% rename from so3/soo/include/me/common.h rename to so3/soo/include/capsule/common.h index c7217f799d..356aa126bf 100644 --- a/so3/soo/include/me/common.h +++ b/so3/soo/include/capsule/common.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 Daniel Rossier + * Copyright (C) 2021-2026 Daniel Rossier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -16,8 +16,8 @@ * */ -#ifndef ME_COMMON_H -#define ME_COMMON_H +#ifndef S3C_COMMON_H +#define S3C_COMMON_H #include @@ -61,7 +61,7 @@ typedef struct { */ uint8_t soohosts[0]; -} me_common_t; +} s3c_common_t; int concat_hosts(struct list_head *hosts, uint8_t *hosts_array); @@ -75,7 +75,7 @@ void del_host(struct list_head *hosts, uint64_t agencyUID); /** * Add a new entry in the host list. * - * @param me_common + * @param s3c_common * @param agencyUID */ void new_host(struct list_head *hosts, uint64_t agencyUID, void *priv, int priv_len); @@ -148,4 +148,4 @@ void dump_hosts(struct list_head *hosts); */ void do_local_cooperation(int domID); -#endif /* ME_COMMON_H */ +#endif /* S3C_COMMON_H */ diff --git a/so3/soo/include/me/refso3.h b/so3/soo/include/capsule/refso3.h similarity index 90% rename from so3/soo/include/me/refso3.h rename to so3/soo/include/capsule/refso3.h index 0c0597c439..1cdc8ddc2b 100644 --- a/so3/soo/include/me/refso3.h +++ b/so3/soo/include/capsule/refso3.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 Daniel Rossier + * Copyright (C) 2021-2026 Daniel Rossier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -22,7 +22,7 @@ #include #include -#include +#include /* * Never use lock (completion, spinlock, etc.) in the shared page since @@ -36,7 +36,7 @@ typedef struct { * MUST BE the last field, since it contains a field at the end which is used * as "payload" for a concatened list of hosts. */ - me_common_t me_common; + s3c_common_t s3c_common; } sh_refso3_t; diff --git a/so3/soo/include/soo/avz.h b/so3/soo/include/soo/avz.h index d91bc44d8f..9edeff3867 100644 --- a/so3/soo/include/soo/avz.h +++ b/so3/soo/include/soo/avz.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2022 Daniel Rossier + * Copyright (C) 2016-2026 Daniel Rossier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -18,6 +18,6 @@ #include -#define ME_domID() (avz_shared->domID) +#define S3C_domID() (avz_shared->domID) void avz_setup(void); diff --git a/so3/soo/include/soo/dev/vuihandler.h b/so3/soo/include/soo/dev/vuihandler.h index 24d8bbbc28..89e456c82f 100644 --- a/so3/soo/include/soo/dev/vuihandler.h +++ b/so3/soo/include/soo/dev/vuihandler.h @@ -61,9 +61,9 @@ typedef struct __attribute__((packed)) { #define VUIHANDLER_BEACON 0 #define VUIHANDLER_DATA 1 -#define VUIHANDLER_ASK_LIST 4 /* Ask for the XML ME list */ -#define VUIHANDLER_SEND 5 /* Specify that the packet contains an event data to be forwarded to the ME */ -#define VUIHANDLER_SELECT 6 /* Ask for the ME model */ +#define VUIHANDLER_ASK_LIST 4 /* Ask for the XML capsule list */ +#define VUIHANDLER_SEND 5 /* Specify that the packet contains an event data to be forwarded to the capsule */ +#define VUIHANDLER_SELECT 6 /* Ask for the capsule model */ #define VUIHANDLER_POST 7 #define VUIHANDLER_BT_PKT_HEADER_SIZE sizeof(vuihandler_pkt_t) @@ -153,7 +153,7 @@ typedef void (*ui_send_model_t)(void); /** * @brief Allows to register two callbacks to the vuihandler. * - * @param ui_send_model: callbacks to send the model. It is a function to let the ME app do whatever it + * @param ui_send_model: callbacks to send the model. It is a function to let the capsule app do whatever it * wants and not just pass a model char *. * @param ui_interrupt: callbacks to be called when receiving a VUIHANDLER_POST or VUIHANDLER_DATA packet. * diff --git a/so3/soo/include/soo/soo.h b/so3/soo/include/soo/soo.h index dcef818149..639b4382ec 100644 --- a/so3/soo/include/soo/soo.h +++ b/so3/soo/include/soo/soo.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2018 Daniel Rossier + * Copyright (C) 2016-2026 Daniel Rossier * Copyright (C) 2016-2018 Baptiste Delporte * * This program is free software; you can redistribute it and/or modify @@ -24,21 +24,21 @@ #include -int get_ME_state(void); -void set_ME_state(ME_state_t state); +int get_S3C_state(void); +void set_S3C_state(S3C_state_t state); -bool get_ME_id(uint32_t slotID, ME_id_t *ME_id); +bool get_S3C_id(uint32_t slotID, S3C_id_t *S3C_id); -void get_ME_id_array(ME_id_t *ME_id_array); -char *xml_prepare_id_array(ME_id_t *ME_id_array); +void get_S3C_id_array(S3C_id_t *S3C_id_array); +char *xml_prepare_id_array(S3C_id_t *S3C_id_array); -ME_desc_t *get_ME_desc(void); +S3C_desc_t *get_S3C_desc(void); -/* ME ID management */ -const char *get_me_shortdesc(void); +/* capsule ID management */ +const char *get_s3c_shortdesc(void); const char *get_me_name(void); u64 get_spid(void); -void vbstore_ME_ID_populate(void); +void vbstore_S3C_ID_populate(void); #endif /* SOO_H */ diff --git a/so3/soo/include/soo/uapi/soo.h b/so3/soo/include/soo/uapi/soo.h index 959906a812..7abffe9dc6 100644 --- a/so3/soo/include/soo/uapi/soo.h +++ b/so3/soo/include/soo/uapi/soo.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2025 Daniel Rossier + * Copyright (C) 2014-2026 Daniel Rossier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -23,10 +23,10 @@ #include -/* This signature is used to check the coherency of the ME image, after a migration +/* This signature is used to check the coherency of the capsule image, after a migration * or a restoration for example. */ -#define SOO_ME_SIGNATURE "SooZ" +#define SOO_S3C_SIGNATURE "SooZ" #endif /* __ASSEMBLY__ */ @@ -88,51 +88,51 @@ typedef struct fbdev_pfns { #define AVZ_SCHEDULER_FLIP 0 /* - * ME states: - * - ME_state_stopped: Capsule is stopped (right after start or later) - * - ME_state_living: ME is full-functional and activated (all frontend devices are consistent) - * - ME_state_suspended: ME is suspended before migrating. This state is maintained for the resident ME instance - * - ME_state_hibernate: ME is in a state of hibernate snapshot - * - ME_state_resuming: ME ready to perform resuming (after recovering) - * - ME_state_awakened: ME is just being awakened - * - ME_state_terminated: ME has been terminated (by a shutdown) - * - ME_state_dead: ME does not exist + * capsule states: + * - S3C_state_stopped: Capsule is stopped (right after start or later) + * - S3C_state_living: capsule is full-functional and activated (all frontend devices are consistent) + * - S3C_state_suspended: capsule is suspended before migrating. This state is maintained for the resident capsule instance + * - S3C_state_hibernate: capsule is in a state of hibernate snapshot + * - S3C_state_resuming: capsule ready to perform resuming (after recovering) + * - S3C_state_awakened: capsule is just being awakened + * - S3C_state_terminated: capsule has been terminated (by a shutdown) + * - S3C_state_dead: capsule does not exist */ typedef enum { - ME_state_stopped, - ME_state_living, - ME_state_suspended, - ME_state_hibernate, - ME_state_resuming, - ME_state_awakened, - ME_state_killed, - ME_state_terminated, - ME_state_dead -} ME_state_t; + S3C_state_stopped, + S3C_state_living, + S3C_state_suspended, + S3C_state_hibernate, + S3C_state_resuming, + S3C_state_awakened, + S3C_state_killed, + S3C_state_terminated, + S3C_state_dead +} S3C_state_t; /* Keep information about slot availability - * FREE: the slot is available (no ME) - * BUSY: the slot is allocated a ME + * FREE: the slot is available (no capsule) + * BUSY: the slot is allocated a capsule */ -typedef enum { ME_SLOT_FREE, ME_SLOT_BUSY } ME_slotState_t; +typedef enum { S3C_SLOT_FREE, S3C_SLOT_BUSY } S3C_slotState_t; -/* ME ID related information */ -#define ME_NAME_SIZE 40 -#define ME_SHORTDESC_SIZE 1024 +/* capsule ID related information */ +#define S3C_NAME_SIZE 40 +#define S3C_SHORTDESC_SIZE 1024 /* - * Definition of ME ID information used by functions which need + * Definition of capsule ID information used by functions which need * to get a list of running MEs with their information. */ typedef struct { uint32_t slotID; - ME_state_t state; + S3C_state_t state; uint64_t spid; - char name[ME_NAME_SIZE]; - char shortdesc[ME_SHORTDESC_SIZE]; -} ME_id_t; + char name[S3C_NAME_SIZE]; + char shortdesc[S3C_SHORTDESC_SIZE]; +} S3C_id_t; struct work_struct; struct semaphore; @@ -175,25 +175,25 @@ extern atomic_t dc_incoming_domID[DC_EVENT_MAX]; #define AGENCY_IOCTL_SHUTDOWN _IOW('S', 3, agency_ioctl_args_t) #define AGENCY_IOCTL_INJECT_CAPSULE _IOWR('S', 4, agency_ioctl_args_t) #define AGENCY_IOCTL_START_CAPSULE _IOWR('S', 5, agency_ioctl_args_t) -#define AGENCY_IOCTL_GET_ME_ID _IOWR('S', 6, agency_ioctl_args_t) -#define AGENCY_IOCTL_GET_ME_ID_ARRAY _IOR('S', 7, agency_ioctl_args_t) +#define AGENCY_IOCTL_GET_S3C_ID _IOWR('S', 6, agency_ioctl_args_t) +#define AGENCY_IOCTL_GET_S3C_ID_ARRAY _IOR('S', 7, agency_ioctl_args_t) #define SOO_NAME_SIZE 16 /* - * ME descriptor + * capsule descriptor * * WARNING !! Be careful when modifying this structure. It *MUST* be aligned with - * the same structure used in the ME. + * the same structure used in the capsule. */ typedef struct { unsigned int slotID; unsigned int capsuleID; /* ID handled by emiso engine */ uint64_t spid; - ME_state_t state; + S3C_state_t state; - unsigned int size; /* Size of the ME with the struct dom_context size */ + unsigned int size; /* Size of the capsule with the struct dom_context size */ unsigned int dc_evtchn; unsigned int vbstore_revtchn, vbstore_levtchn; @@ -201,7 +201,7 @@ typedef struct { void (*resume_fn)(void); -} ME_desc_t; +} S3C_desc_t; /* * Agency descriptor @@ -214,7 +214,7 @@ typedef struct { uint64_t agencyUID; /* Agency UID */ - /* Event channels used for directcomm channel between agency and agency-RT or ME */ + /* Event channels used for directcomm channel between agency and agency-RT or capsule */ unsigned int dc_evtchn[MAX_DOMAINS]; /* Event channels used by vbstore */ @@ -226,13 +226,13 @@ typedef struct { } agency_desc_t; /* - * SOO agency & ME descriptor - This structure is used in the shared info page of the agency or ME domain. + * SOO agency & capsule descriptor - This structure is used in the shared info page of the agency or capsule domain. */ typedef struct { union { agency_desc_t agency; - ME_desc_t ME; + S3C_desc_t S3C; } u; } dom_desc_t; @@ -266,7 +266,7 @@ struct avz_shared { */ u64 current_s_time; - /* Agency or ME descriptor */ + /* Agency or capsule descriptor */ dom_desc_t dom_desc; /* Used to store a signature for consistency checking, for example after a migration/restoration */ @@ -304,19 +304,19 @@ typedef struct agency_ioctl_args { /* AVZ hypercalls devoted to SOO */ -#define AVZ_ME_READ_SNAPSHOT 4 -#define AVZ_ME_WRITE_SNAPSHOT 5 +#define AVZ_S3C_READ_SNAPSHOT 4 +#define AVZ_S3C_WRITE_SNAPSHOT 5 #define AVZ_START_CAPSULE 6 #define AVZ_INJECT_CAPSULE 7 -#define AVZ_KILL_ME 8 +#define AVZ_KILL_S3C 8 #define AVZ_DC_EVENT_SET 9 -#define AVZ_GET_ME_STATE 10 -#define AVZ_SET_ME_STATE 11 +#define AVZ_GET_S3C_STATE 10 +#define AVZ_SET_S3C_STATE 11 #define AVZ_GET_DOM_DESC 12 #define AVZ_GRANT_TABLE_OP 13 #define AVZ_FBDEV_SET_PFNS 14 #define AVZ_FBDEV_CHANGE_FOCUS 15 -#define AVZ_FBDEV_GET_ME_ADDR 16 +#define AVZ_FBDEV_GET_S3C_ADDR 16 /* AVZ_INJECT_CAPSULE */ typedef struct { @@ -337,12 +337,12 @@ typedef struct { int state; } avz_dc_event_t; -/* AVZ_GET_ME_STATE */ -/* AVZ_SET_ME_STATE */ +/* AVZ_GET_S3C_STATE */ +/* AVZ_SET_S3C_STATE */ typedef struct { uint32_t slotID; int state; -} avz_me_state_t; +} avz_s3c_state_t; /* AVZ_GET_DOM_DESC */ typedef struct { @@ -350,7 +350,7 @@ typedef struct { dom_desc_t dom_desc; } avz_dom_desc_t; -/* AVZ_GET_ME_FREE_SLOT */ +/* AVZ_GET_S3C_FREE_SLOT */ typedef struct { int slotID; int size; @@ -369,10 +369,10 @@ typedef struct { int size; } avz_snapshot_t; -/* AVZ_KILL_ME */ +/* AVZ_KILL_S3C */ typedef struct { uint32_t slotID; -} avz_kill_me_t; +} avz_kill_s3c_t; /* AVZ_GRANT_TABLE_OP */ typedef struct { @@ -389,7 +389,7 @@ typedef struct { int new_slotID; } avz_fbdev_focus_t; -/* AVZ_FBDEV_GET_ME_ADDR */ +/* AVZ_FBDEV_GET_S3C_ADDR */ typedef struct { addr_t paddr; } avz_fbdev_addr_t; @@ -404,12 +404,12 @@ typedef struct { avz_inject_capsule_t avz_inject_capsule_args; avz_start_capsule_t avz_start_capsule_args; avz_dc_event_t avz_dc_event_args; - avz_me_state_t avz_me_state_args; + avz_s3c_state_t avz_s3c_state_args; avz_dom_desc_t avz_dom_desc_args; avz_free_slot_t avz_free_slot_args; avz_mig_init_t avz_mig_init_args; avz_snapshot_t avz_snapshot_args; - avz_kill_me_t avz_kill_me_args; + avz_kill_s3c_t avz_kill_s3c_args; avz_console_io_t avz_console_io_args; avz_domctl_t avz_domctl_args; avz_gnttab_t avz_gnttab_args; @@ -438,7 +438,7 @@ typedef struct { /* * SOO callback functions. * The following definitions are used as argument in domcalls or in the - * agency_ctl() function as a callback to be propagated to a specific ME. + * agency_ctl() function as a callback to be propagated to a specific capsule. * */ @@ -449,7 +449,7 @@ typedef struct { typedef struct soo_domcall_arg { /* Stores the agency ctl function. - * Possibly, the agency_ctl function can be associated to a callback operation asked by a ME + * Possibly, the agency_ctl function can be associated to a callback operation asked by a capsule */ unsigned int cmd; unsigned int slotID; /* Origin of the domcall */ @@ -459,7 +459,7 @@ typedef struct soo_domcall_arg { pre_resume_args_t pre_resume_args; post_activate_args_t post_activate_args; - ME_state_t set_me_state_args; + S3C_state_t set_s3c_state_args; } u; } soo_domcall_arg_t; @@ -488,7 +488,7 @@ void do_async_dom(int slotID, dc_event_t); void perform_task(dc_event_t dc_event); -void shutdown_ME(unsigned int ME_slotID); +void shutdown_S3C(unsigned int S3C_slotID); void cache_flush_all(void); diff --git a/so3/soo/include/soo/vbstore_me.h b/so3/soo/include/soo/vbstore_capsule.h similarity index 85% rename from so3/soo/include/soo/vbstore_me.h rename to so3/soo/include/soo/vbstore_capsule.h index 1b3aca2b36..cd57c86deb 100644 --- a/so3/soo/include/soo/vbstore_me.h +++ b/so3/soo/include/soo/vbstore_capsule.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2019 Daniel Rossier + * Copyright (C) 2016-2026 Daniel Rossier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -16,12 +16,12 @@ * */ -#ifndef VBSTORE_ME_H -#define VBSTORE_ME_H +#ifndef VBSTORE_S3C_H +#define VBSTORE_S3C_H #include extern struct completion vbstore_populated_sync; extern bool vbstore_populated; -#endif /* VBSTORE_ME_H */ +#endif /* VBSTORE_S3C_H */ diff --git a/so3/soo/include/soo/vbus.h b/so3/soo/include/soo/vbus.h index f9c432212f..284f3a0f3f 100644 --- a/so3/soo/include/soo/vbus.h +++ b/so3/soo/include/soo/vbus.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2019 Daniel Rossier + * Copyright (C) 2014-2026 Daniel Rossier * Copyright (C) 2016, 2018 Baptiste Delporte * * This program is free software; you can redistribute it and/or modify @@ -79,7 +79,7 @@ struct vbus_device { enum vbus_state state; - /* So far, only the ME use this completion struct. On the agency side, + /* So far, only the capsule use this completion struct. On the agency side, * the device can not be shutdown on live. */ struct completion down; @@ -147,7 +147,7 @@ bool vbus_gather(struct vbus_transaction t, const char *dir, ...); void free_otherend_watch(struct vbus_device *dev, bool with_vbus); extern void vbstore_init(void); -extern void vbstore_me_init(void); +extern void vbstore_s3c_init(void); bool is_vbstore_populated(void); diff --git a/so3/soo/kernel/debug/time.c b/so3/soo/kernel/debug/time.c index 2b436169b5..6f90988e35 100644 --- a/so3/soo/kernel/debug/time.c +++ b/so3/soo/kernel/debug/time.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2018 Daniel Rossier + * Copyright (C) 2016-2026 Daniel Rossier * Copyright (C) 2016-2018 Baptiste Delporte * * This program is free software; you can redistribute it and/or modify @@ -20,7 +20,7 @@ /* * Timestamp and delay measurement facility. * - * There are two sets of functions: one for a non RT domain (non RT agency or ME), one for the + * There are two sets of functions: one for a non RT domain (non RT agency or capsule), one for the * RT agency. The RT version is prefixed with rtdm_. * * Typical usage: diff --git a/so3/soo/kernel/evtchn.c b/so3/soo/kernel/evtchn.c index e919a52913..43386a4823 100644 --- a/so3/soo/kernel/evtchn.c +++ b/so3/soo/kernel/evtchn.c @@ -1,6 +1,6 @@ /* - * Copyright (C) 2014-2016 Daniel Rossier + * Copyright (C) 2014-2026 Daniel Rossier * Copyright (C) 2016 Baptiste Delporte * * This program is free software; you can redistribute it and/or modify @@ -66,7 +66,7 @@ void dump_evtchn_pending(void) { int i; - printk(" Evtchn info in Agency/ME domain %d\n\n", ME_domID()); + printk(" Evtchn info in Agency/capsule domain %d\n\n", S3C_domID()); for (i = 0; i < NR_EVTCHN; i++) printk("e:%d m:%d p:%d ", i, evtchn_info.evtchn_mask[i], avz_shared->evtchn_pending[i]); @@ -109,7 +109,7 @@ void virq_handle(unsigned irq_nr) if (loopmax > 500) /* Probably something wrong ;-) */ printk("%s: Warning trying to process evtchn: %d IRQ: %d for quite a long time (dom ID: %d) on CPU %d / masked: %d...\n", - __func__, evtchn, evtchn_info.evtchn_to_irq[evtchn], ME_domID(), smp_processor_id(), + __func__, evtchn, evtchn_info.evtchn_to_irq[evtchn], S3C_domID(), smp_processor_id(), evtchn_is_masked(evtchn)); virq = evtchn_info.evtchn_to_irq[evtchn]; diff --git a/so3/soo/kernel/setup.c b/so3/soo/kernel/setup.c index ce2c61132d..fb775b6661 100644 --- a/so3/soo/kernel/setup.c +++ b/so3/soo/kernel/setup.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2023 Daniel Rossier + * Copyright (C) 2014-2026 Daniel Rossier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -77,7 +77,7 @@ void avz_setup(void) { avz_get_shared(); - avz_shared->dom_desc.u.ME.resume_fn = resume_fn; + avz_shared->dom_desc.u.S3C.resume_fn = resume_fn; LOG_INFO("SOO Virtualizer (avz) shared page:\n\n"); @@ -92,9 +92,9 @@ void avz_setup(void) void post_init_setup(void) { - LOG_INFO("Mapping VBstore shared page pfn %lx\n", avz_shared->dom_desc.u.ME.vbstore_pfn); + LOG_INFO("Mapping VBstore shared page pfn %lx\n", avz_shared->dom_desc.u.S3C.vbstore_pfn); - __intf = (void *) io_map(pfn_to_phys(avz_shared->dom_desc.u.ME.vbstore_pfn), PAGE_SIZE); + __intf = (void *) io_map(pfn_to_phys(avz_shared->dom_desc.u.S3C.vbstore_pfn), PAGE_SIZE); BUG_ON(!__intf); /* @@ -105,9 +105,9 @@ void post_init_setup(void) * (inner-shareable) for the ring buffer protocol. * Override the Stage-1 PTE with Normal cacheable attributes. */ - create_mapping(NULL, (addr_t) __intf, pfn_to_phys(avz_shared->dom_desc.u.ME.vbstore_pfn), PAGE_SIZE, false); + create_mapping(NULL, (addr_t) __intf, pfn_to_phys(avz_shared->dom_desc.u.S3C.vbstore_pfn), PAGE_SIZE, false); - LOG_INFO("SOO Mobile Entity booting ...\n"); + LOG_INFO("SOO SO3 capsule booting ...\n"); soo_guest_activity_init(); @@ -116,15 +116,15 @@ void post_init_setup(void) /* Initialize the Vbus subsystem */ vbus_init(); - /* Write the entries related to the ME ID in vbstore */ - vbstore_ME_ID_populate(); + /* Write the entries related to the capsule ID in vbstore */ + vbstore_S3C_ID_populate(); /* How create all vbstore entries required by the frontend drivers */ vbstore_init_dev_populate(); LOG_INFO("%s", SO3_BANNER); - LOG_DEBUG("ME running as domain %d\n", ME_domID()); + LOG_DEBUG("capsule running as domain %d\n", S3C_domID()); } REGISTER_POSTINIT(post_init_setup) diff --git a/so3/soo/kernel/soo_guest_activity.c b/so3/soo/kernel/soo_guest_activity.c index 9f86bde3a4..e588db77da 100644 --- a/so3/soo/kernel/soo_guest_activity.c +++ b/so3/soo/kernel/soo_guest_activity.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2019 Daniel Rossier + * Copyright (C) 2016-2026 Daniel Rossier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -67,7 +67,7 @@ void dc_stable(int dc_event) * To perform a ping from the RT domain, please use rtdm_do_sync_agency() in rtdm_vbus.c * * As for the domain table, the index 0 and 1 are for the agency and the indexes 2..MAX_DOMAINS - * are for the MEs. If a ME_slotID is provided, the proper index is given by ME_slotID. + * are for the MEs. If a S3C_slotID is provided, the proper index is given by S3C_slotID. * * @domID: the target domain * @dc_event: type of event used in the synchronization @@ -83,8 +83,8 @@ void do_sync_dom(int domID, dc_event_t dc_event) set_dc_event(domID, dc_event); - DBG("%s: notifying via evtchn %d...\n", __func__, avz_shared->dom_desc.u.ME.dc_evtchn); - notify_remote_via_evtchn(avz_shared->dom_desc.u.ME.dc_evtchn); + DBG("%s: notifying via evtchn %d...\n", __func__, avz_shared->dom_desc.u.S3C.dc_evtchn); + notify_remote_via_evtchn(avz_shared->dom_desc.u.S3C.dc_evtchn); /* Wait for the response from the outgoing domain */ DBG("%s: waiting for completion on dc_event %d...\n", __func__, dc_event); @@ -117,12 +117,12 @@ void tell_dc_stable(int dc_event) atomic_set(&dc_incoming_domID[dc_event], -1); - notify_remote_via_evtchn(avz_shared->dom_desc.u.ME.dc_evtchn); + notify_remote_via_evtchn(avz_shared->dom_desc.u.S3C.dc_evtchn); } /* - * Prepare a remote ME to react to a ping event. - * @domID: the target ME + * Prepare a remote capsule to react to a ping event. + * @domID: the target capsule */ void set_dc_event(domid_t domID, dc_event_t dc_event) { @@ -144,22 +144,22 @@ void set_dc_event(domid_t domID, dc_event_t dc_event) } /* - * Get the state of a ME. + * Get the state of a capsule. */ -int get_ME_state(void) +int get_S3C_state(void) { - return avz_shared->dom_desc.u.ME.state; + return avz_shared->dom_desc.u.S3C.state; } -void set_ME_state(ME_state_t state) +void set_S3C_state(S3C_state_t state) { - /* Be careful if the ME is in living state and suddently is set to killed. + /* Be careful if the capsule is in living state and suddently is set to killed. * Backends will be in a weird state. */ - if ((state == ME_state_killed) && (avz_shared->dom_desc.u.ME.state == ME_state_living)) - lprintk("## WARNING ! ME %d is set to killed while living!\n", ME_domID()); + if ((state == S3C_state_killed) && (avz_shared->dom_desc.u.S3C.state == S3C_state_living)) + lprintk("## WARNING ! capsule %d is set to killed while living!\n", S3C_domID()); - avz_shared->dom_desc.u.ME.state = state; + avz_shared->dom_desc.u.S3C.state = state; } void postmig_setup(void); @@ -169,15 +169,15 @@ void perform_task(dc_event_t dc_event) switch (dc_event) { case DC_SHUTDOWN: - /* The ME will initiate the shutdown processing on its own. */ + /* The capsule will initiate the shutdown processing on its own. */ - DBG("perform a CB_SHUTDOWN on capsule %d\n", ME_domID()); + DBG("perform a CB_SHUTDOWN on capsule %d\n", S3C_domID()); - BUG_ON(get_ME_state() == ME_state_stopped); + BUG_ON(get_S3C_state() == S3C_state_stopped); cb_shutdown(); - if (get_ME_state() == ME_state_terminated) { + if (get_S3C_state() == S3C_state_terminated) { /* Prepare vbus to stop everything with the frontend */ /* (interactions with vbus) */ DBG("Device shutdown...\n"); @@ -187,7 +187,7 @@ void perform_task(dc_event_t dc_event) DBG("Removing devices ...\n"); remove_devices(); - /* Remove vbstore entries related to this ME */ + /* Remove vbstore entries related to this capsule */ DBG("Removing vbstore entries ...\n"); remove_vbstore_entries(); } @@ -197,7 +197,7 @@ void perform_task(dc_event_t dc_event) case DC_RESUME: DBG("resuming vbstore...\n"); - BUG_ON((get_ME_state() != ME_state_resuming) && (get_ME_state() != ME_state_awakened)); + BUG_ON((get_S3C_state() != S3C_state_resuming) && (get_S3C_state() != S3C_state_awakened)); /* Giving a chance to perform actions before resuming devices */ args.cmd = CB_PRE_RESUME; @@ -207,7 +207,7 @@ void perform_task(dc_event_t dc_event) vbs_resume(); /* During a resuming after an awakened snapshot, re-init watch for device/ */ - if (get_ME_state() == ME_state_awakened) + if (get_S3C_state() == S3C_state_awakened) postmig_setup(); DBG("vbstore resumed.\n"); @@ -252,29 +252,29 @@ void do_soo_activity(void *arg) switch (args->cmd) { case CB_PRE_SUSPEND: /* Called by perform_pre_suspend */ - DBG("Pre-suspend callback for ME %d\n", ME_domID()); + DBG("Pre-suspend callback for capsule %d\n", S3C_domID()); cb_pre_suspend(arg); break; case CB_PRE_RESUME: /* Called from vbus/vbs.c */ - DBG("Pre-resume callback for ME %d\n", ME_domID()); + DBG("Pre-resume callback for capsule %d\n", S3C_domID()); cb_pre_resume(arg); break; case CB_POST_ACTIVATE: /* Called by perform_post_activate() */ - DBG("Post_activate callback for ME %d\n", ME_domID()); + DBG("Post_activate callback for capsule %d\n", S3C_domID()); cb_post_activate(args); break; } } -ME_desc_t *get_ME_desc(void) +S3C_desc_t *get_S3C_desc(void) { - return (ME_desc_t *) &avz_shared->dom_desc.u.ME; + return (S3C_desc_t *) &avz_shared->dom_desc.u.S3C; } agency_desc_t *get_agency_desc(void) diff --git a/so3/soo/kernel/soo_id.c b/so3/soo/kernel/soo_id.c index 43d97a6403..32cc24f733 100644 --- a/so3/soo/kernel/soo_id.c +++ b/so3/soo/kernel/soo_id.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2021 Daniel Rossier + * Copyright (C) 2016-2026 Daniel Rossier * Copyright (C) 2016-2018 Baptiste Delporte * * This program is free software; you can redistribute it and/or modify @@ -27,30 +27,30 @@ #include #include -/* ME ID related information management */ +/* capsule ID related information management */ /** - * Get the short description related to this ME. + * Get the short description related to this capsule. * (not mandatory) * * @return a pointer to the string in the DT if it exists, NULL otherwise. */ -const char *get_me_shortdesc(void) +const char *get_s3c_shortdesc(void) { const char *str = NULL; int node; /* Get the short description */ - node = fdt_find_node_by_name(__fdt_addr, 0, "ME"); + node = fdt_find_node_by_name(__fdt_addr, 0, "capsule"); ASSERT(node >= 0); - fdt_property_read_string(__fdt_addr, node, "me_shortdesc", &str); + fdt_property_read_string(__fdt_addr, node, "s3c_shortdesc", &str); return str; } /** - * Get the name of this ME. + * Get the name of this capsule. * (not mandatory) * * @return a pointer to the string in the DT if it exists, NULL otherwise. @@ -61,7 +61,7 @@ const char *get_me_name(void) int node; /* Get the short description */ - node = fdt_find_node_by_name(__fdt_addr, 0, "ME"); + node = fdt_find_node_by_name(__fdt_addr, 0, "capsule"); ASSERT(node >= 0); fdt_property_read_string(__fdt_addr, node, "me_name", &str); @@ -70,7 +70,7 @@ const char *get_me_name(void) } /** - * Get the SPID related to this ME. + * Get the SPID related to this capsule. * (mandatory) * * @param what Either "spid" @@ -82,9 +82,9 @@ u64 get_spid(void) int node; /* Get the short description */ - node = fdt_find_node_by_name(__fdt_addr, 0, "ME"); + node = fdt_find_node_by_name(__fdt_addr, 0, "capsule"); if (node < 0) { - printk("%s: node \"ME\" not found\n", __func__); + printk("%s: node \"capsule\" not found\n", __func__); BUG(); } @@ -98,33 +98,33 @@ u64 get_spid(void) } /** - * Write the entries related to the ME ID in vbstore + * Write the entries related to the capsule ID in vbstore */ -void vbstore_ME_ID_populate(void) +void vbstore_S3C_ID_populate(void) { const char *name, *shortdesc; u64 spid; char rootname[VBS_KEY_LENGTH], entry[VBS_KEY_LENGTH]; - /* Set all ME ID related information */ + /* Set all capsule ID related information */ - /* Set the SPID of this ME */ + /* Set the SPID of this capsule */ spid = get_spid(); - avz_shared->dom_desc.u.ME.spid = spid; + avz_shared->dom_desc.u.S3C.spid = spid; /* Set the name */ name = get_me_name(); /* And set a short description which can be used on the user GUI */ - shortdesc = get_me_shortdesc(); + shortdesc = get_s3c_shortdesc(); strcpy(rootname, "soo/me"); - sprintf(entry, "%d", ME_domID()); + sprintf(entry, "%d", S3C_domID()); vbus_mkdir(VBT_NIL, rootname, entry); - sprintf(rootname, "soo/me/%d", ME_domID()); + sprintf(rootname, "soo/me/%d", S3C_domID()); sprintf(entry, "%lx", spid); vbus_write(VBT_NIL, rootname, "spid", entry); diff --git a/so3/soo/kernel/vbstore/Makefile b/so3/soo/kernel/vbstore/Makefile index d890c46387..389046dfb1 100644 --- a/so3/soo/kernel/vbstore/Makefile +++ b/so3/soo/kernel/vbstore/Makefile @@ -1,3 +1,3 @@ -obj-y += vbstore_me.o +obj-y += vbstore_capsule.o diff --git a/so3/soo/kernel/vbstore/vbstore_me.c b/so3/soo/kernel/vbstore/vbstore_capsule.c similarity index 78% rename from so3/soo/kernel/vbstore/vbstore_me.c rename to so3/soo/kernel/vbstore/vbstore_capsule.c index 9462c9734b..8af418b376 100644 --- a/so3/soo/kernel/vbstore/vbstore_me.c +++ b/so3/soo/kernel/vbstore/vbstore_capsule.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2018 Daniel Rossier + * Copyright (C) 2016-2026 Daniel Rossier * Copyright (C) 2016-2018 Baptiste Delporte * * This program is free software; you can redistribute it and/or modify @@ -28,21 +28,21 @@ #include #include #include -#include +#include #include -static void vbs_me_rmdir(const char *dir, const char *node) +static void vbs_s3c_rmdir(const char *dir, const char *node) { vbus_rm(VBT_NIL, dir, node); } -static void vbs_me_mkdir(const char *dir, const char *node) +static void vbs_s3c_mkdir(const char *dir, const char *node) { vbus_mkdir(VBT_NIL, dir, node); } -static void vbs_me_write(const char *dir, const char *node, const char *string) +static void vbs_s3c_write(const char *dir, const char *node, const char *string) { vbus_write(VBT_NIL, dir, node, string); } @@ -76,41 +76,41 @@ static void vbstore_dev_init(unsigned int domID, const char *devname, bool realt /* Virtualized interface of dev config */ sprintf(propname, "%d", domID); - vbs_me_mkdir("device", propname); + vbs_s3c_mkdir("device", propname); strcpy(devrootname, "device/%d"); sprintf(rootname, devrootname, domID); - vbs_me_mkdir(rootname, devname); + vbs_s3c_mkdir(rootname, devname); strcat(devrootname, "/"); strcat(devrootname, devname); sprintf(rootname, devrootname, domID); /* "/device/%d/vuart" */ - vbs_me_mkdir(rootname, "0"); + vbs_s3c_mkdir(rootname, "0"); strcat(devrootname, "/0"); /* "/device/%d/vuart/0" */ sprintf(rootname, devrootname, domID); - vbs_me_write(rootname, "state", "1"); /* = VBusStateInitialising */ + vbs_s3c_write(rootname, "state", "1"); /* = VBusStateInitialising */ switch (realtime) { case true: - vbs_me_write(rootname, "realtime", "1"); + vbs_s3c_write(rootname, "realtime", "1"); break; case false: - vbs_me_write(rootname, "realtime", "0"); + vbs_s3c_write(rootname, "realtime", "0"); } - vbs_me_write(rootname, "backend-id", "0"); + vbs_s3c_write(rootname, "backend-id", "0"); strcpy(devrootname, "backend/"); strcat(devrootname, devname); strcat(devrootname, "/%d/0"); /* "backend/vuart/%d/0" */ sprintf(propname, devrootname, domID); - vbs_me_write(rootname, "backend", propname); + vbs_s3c_write(rootname, "backend", propname); /* Create the vbstore entries for the backend side */ @@ -118,30 +118,30 @@ static void vbstore_dev_init(unsigned int domID, const char *devname, bool realt strcpy(devrootname, "backend/"); strcat(devrootname, devname); /* "/backend/vuart" */ - vbs_me_mkdir(devrootname, propname); + vbs_s3c_mkdir(devrootname, propname); strcat(devrootname, "/%d"); /* "/backend/vuart/%d" */ sprintf(rootname, devrootname, domID); - vbs_me_mkdir(rootname, "0"); + vbs_s3c_mkdir(rootname, "0"); strcat(devrootname, "/0"); /* "/backend/vuart/%d/state/1" */ sprintf(rootname, devrootname, domID); - vbs_me_write(rootname, "state", "1"); + vbs_s3c_write(rootname, "state", "1"); switch (realtime) { case true: - vbs_me_write(rootname, "realtime", "1"); + vbs_s3c_write(rootname, "realtime", "1"); /* The two next entries are used to synchronize RT and non-RT vbus/vbstore. */ - vbs_me_write(rootname, "sync_backfront", "0"); + vbs_s3c_write(rootname, "sync_backfront", "0"); - vbs_me_write(rootname, "sync_backfront_rt", "0"); + vbs_s3c_write(rootname, "sync_backfront_rt", "0"); break; case false: - vbs_me_write(rootname, "realtime", "0"); + vbs_s3c_write(rootname, "realtime", "0"); } strcpy(devrootname, "device/%d/"); @@ -149,10 +149,10 @@ static void vbstore_dev_init(unsigned int domID, const char *devname, bool realt strcat(devrootname, "/0"); /* "device/%d/vuart/0" */ sprintf(propname, devrootname, domID); - vbs_me_write(rootname, "frontend", propname); + vbs_s3c_write(rootname, "frontend", propname); sprintf(propname, "%d", domID); - vbs_me_write(rootname, "frontend-id", propname); + vbs_s3c_write(rootname, "frontend-id", propname); /* Now, we create the corresponding device on the frontend side */ strcpy(devrootname, "device/%d/"); @@ -191,7 +191,7 @@ static void vbstore_dev_remove(unsigned int domID, const char *devname) strcat(devrootname, propname); strcat(devrootname, devname); /* "/device/vuart/" */ - vbs_me_rmdir(devrootname, "0"); + vbs_s3c_rmdir(devrootname, "0"); /* Remove the vbstore entries for the backend side */ @@ -201,63 +201,63 @@ static void vbstore_dev_remove(unsigned int domID, const char *devname) strcat(devrootname, devname); /* "/backend/vuart/" */ strcat(devrootname, propname); /* "/backend/vuart/2" */ - vbs_me_rmdir(devrootname, "0"); + vbs_s3c_rmdir(devrootname, "0"); } /* - * Remove all vbstore entries related to this ME. + * Remove all vbstore entries related to this capsule. */ void remove_vbstore_entries(void) { int fdt_node; char rootname[VBS_KEY_LENGTH], entry[VBS_KEY_LENGTH]; - /* Remove the vbstore entries related to the ME */ + /* Remove the vbstore entries related to the capsule */ strcpy(rootname, "soo/me"); - sprintf(entry, "%d", ME_domID()); + sprintf(entry, "%d", S3C_domID()); vbus_rm(VBT_NIL, rootname, entry); fdt_node = fdt_find_compatible_node(__fdt_addr, "vuihandler,frontend"); if (fdt_device_is_available(__fdt_addr, fdt_node)) { DBG("%s: Removing vUIHandler from vbstore...\n", __func__); - vbstore_dev_remove(ME_domID(), "vuihandler"); + vbstore_dev_remove(S3C_domID(), "vuihandler"); } fdt_node = fdt_find_compatible_node(__fdt_addr, "vuart,frontend"); if (fdt_device_is_available(__fdt_addr, fdt_node)) { DBG("%s: removing vuart from vbstore...\n", __func__); - vbstore_dev_remove(ME_domID(), "vuart"); + vbstore_dev_remove(S3C_domID(), "vuart"); } fdt_node = fdt_find_compatible_node(__fdt_addr, "vdummy,frontend"); if (fdt_device_is_available(__fdt_addr, fdt_node)) { DBG("%s: removing vdummy from vbstore...\n", __func__); - vbstore_dev_remove(ME_domID(), "vdummy"); + vbstore_dev_remove(S3C_domID(), "vdummy"); } fdt_node = fdt_find_compatible_node(__fdt_addr, "vsenseled,frontend"); if (fdt_device_is_available(__fdt_addr, fdt_node)) { DBG("%s: removing vsenseled from vbstore...\n", __func__); - vbstore_dev_remove(ME_domID(), "vsenseled"); + vbstore_dev_remove(S3C_domID(), "vsenseled"); } fdt_node = fdt_find_compatible_node(__fdt_addr, "vsensej,frontend"); if (fdt_device_is_available(__fdt_addr, fdt_node)) { DBG("%s: removing vsensej from vbstore...\n", __func__); - vbstore_dev_remove(ME_domID(), "vsensej"); + vbstore_dev_remove(S3C_domID(), "vsensej"); } fdt_node = fdt_find_compatible_node(__fdt_addr, "vfbdev,frontend"); if (fdt_device_is_available(__fdt_addr, fdt_node)) { DBG("%s: removing vfbdev from vbstore...\n", __func__); - vbstore_dev_remove(ME_domID(), "vfbdev"); + vbstore_dev_remove(S3C_domID(), "vfbdev"); } fdt_node = fdt_find_compatible_node(__fdt_addr, "vinput,frontend"); if (fdt_device_is_available(__fdt_addr, fdt_node)) { DBG("%s: removing vinput from vbstore...\n", __func__); - vbstore_dev_remove(ME_domID(), "vinput"); + vbstore_dev_remove(S3C_domID(), "vinput"); } } @@ -273,49 +273,49 @@ void vbstore_devices_populate(void) fdt_node = fdt_find_compatible_node(__fdt_addr, "vdummy,frontend"); if (fdt_device_is_available(__fdt_addr, fdt_node)) { DBG("%s: init vdummy...\n", __func__); - vbstore_dev_init(ME_domID(), "vdummy", false, "vdummy,frontend"); + vbstore_dev_init(S3C_domID(), "vdummy", false, "vdummy,frontend"); } fdt_node = fdt_find_compatible_node(__fdt_addr, "vuihandler,frontend"); if (fdt_device_is_available(__fdt_addr, fdt_node)) { DBG("%s: Init vUIHandler...\n", __func__); - vbstore_dev_init(ME_domID(), "vuihandler", false, "vuihandler,frontend"); + vbstore_dev_init(S3C_domID(), "vuihandler", false, "vuihandler,frontend"); } fdt_node = fdt_find_compatible_node(__fdt_addr, "vuart,frontend"); if (fdt_device_is_available(__fdt_addr, fdt_node)) { DBG("%s: init vuart...\n", __func__); - vbstore_dev_init(ME_domID(), "vuart", false, "vuart,frontend"); + vbstore_dev_init(S3C_domID(), "vuart", false, "vuart,frontend"); } fdt_node = fdt_find_compatible_node(__fdt_addr, "vsenseled,frontend"); if (fdt_device_is_available(__fdt_addr, fdt_node)) { DBG("%s: init vsenseled...\n", __func__); - vbstore_dev_init(ME_domID(), "vsenseled", false, "vsenseled,frontend"); + vbstore_dev_init(S3C_domID(), "vsenseled", false, "vsenseled,frontend"); } fdt_node = fdt_find_compatible_node(__fdt_addr, "vsensej,frontend"); if (fdt_device_is_available(__fdt_addr, fdt_node)) { DBG("%s: init vsensej...\n", __func__); - vbstore_dev_init(ME_domID(), "vsensej", false, "vsensej,frontend"); + vbstore_dev_init(S3C_domID(), "vsensej", false, "vsensej,frontend"); } fdt_node = fdt_find_compatible_node(__fdt_addr, "vlogs,frontend"); if (fdt_device_is_available(__fdt_addr, fdt_node)) { DBG("%s: init vlogs...\n", __func__); - vbstore_dev_init(ME_domID(), "vlogs", false, "vlogs,frontend"); + vbstore_dev_init(S3C_domID(), "vlogs", false, "vlogs,frontend"); } fdt_node = fdt_find_compatible_node(__fdt_addr, "vfbdev,frontend"); if (fdt_device_is_available(__fdt_addr, fdt_node)) { DBG("%s: init vfbdev...\n", __func__); - vbstore_dev_init(ME_domID(), "vfbdev", false, "vfbdev,frontend"); + vbstore_dev_init(S3C_domID(), "vfbdev", false, "vfbdev,frontend"); } fdt_node = fdt_find_compatible_node(__fdt_addr, "vinput,frontend"); if (fdt_device_is_available(__fdt_addr, fdt_node)) { DBG("%s: init vinput...\n", __func__); - vbstore_dev_init(ME_domID(), "vinput", false, "vinput,frontend"); + vbstore_dev_init(S3C_domID(), "vinput", false, "vinput,frontend"); } } @@ -328,9 +328,9 @@ void vbstore_trigger_dev_probe(void) } /* - * Prepare the vbstore entries used by this ME. + * Prepare the vbstore entries used by this capsule. */ -void vbstore_me_init(void) +void vbstore_s3c_init(void) { vbus_probe_frontend_init(); @@ -346,9 +346,9 @@ void vbstore_init_dev_populate(void) /* Now, we are ready to register vbstore entries */ vbstore_devices_populate(); - if (get_ME_state() == ME_state_stopped) { + if (get_S3C_state() == S3C_state_stopped) { /* - * If the ME is in ME_state_booting state, this means that the ME has been locally injected. + * If the capsule is in S3C_state_booting state, this means that the capsule has been locally injected. * There is no need to adjust the grant tables. The TRIGGER_DEV_PROBE event can be sent. */ vbstore_trigger_dev_probe(); diff --git a/so3/soo/kernel/vbus/vbus.c b/so3/soo/kernel/vbus/vbus.c index d173b6acc1..08f3f90776 100644 --- a/so3/soo/kernel/vbus/vbus.c +++ b/so3/soo/kernel/vbus/vbus.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2018 Daniel Rossier + * Copyright (C) 2016-2026 Daniel Rossier * Copyright (C) 2016-2018 Baptiste Delporte * * This program is free software; you can redistribute it and/or modify @@ -44,7 +44,7 @@ #define VBUS_TIMEOUT 120 -/* Event channels used for directcomm channel between agency and ME */ +/* Event channels used for directcomm channel between agency and capsule */ spinlock_t dc_lock; /* List of device drivers */ @@ -174,7 +174,7 @@ void vbus_otherend_changed(struct vbus_watch *watch) state = vbus_read_driver_state(vdev->otherend); - DBG("On domID: %d, otherend changed / device: %s state: %d, CPU %d\n", ME_domID(), vdev->nodename, state, + DBG("On domID: %d, otherend changed / device: %s state: %d, CPU %d\n", S3C_domID(), vdev->nodename, state, smp_processor_id()); /* @@ -257,7 +257,7 @@ int vbus_dev_probe(struct vbus_device *vdev) init_completion(&vdev->down); init_completion(&vdev->sync_backfront); - DBG("ME #%d talk_to_otherend: %s\n", ME_domID(), vdev->nodename); + DBG("capsule #%d talk_to_otherend: %s\n", S3C_domID(), vdev->nodename); talk_to_otherend(vdev); @@ -271,7 +271,7 @@ int vbus_dev_remove(struct vbus_device *vdev) unsigned int dir_exists; /* - * If the ME is running on a Smart Object which does not offer all the backends matching the ME's frontends, + * If the capsule is running on a Smart Object which does not offer all the backends matching the capsule's frontends, * some frontend related entries may not have been created. We must check here if the entry matching the dev * to remove exists. */ @@ -444,7 +444,7 @@ void vbus_dev_changed(const char *node, char *type, struct vbus_type *bus, const add_new_dev(vdev); } else { - BUG_ON(ME_domID() == DOMID_AGENCY); + BUG_ON(S3C_domID() == DOMID_AGENCY); /* Update the our state in vbstore. */ /* We force the update, this will not trigger a watch since the watch is set right afterwards */ @@ -483,14 +483,14 @@ static irq_return_t directcomm_isr(int irq, void *data) dc_event = atomic_read(&avz_shared->dc_event); - DBG("(ME domid %d): Received directcomm interrupt for event: %d\n", ME_domID(), avz_shared->dc_event); + DBG("(capsule domid %d): Received directcomm interrupt for event: %d\n", S3C_domID(), avz_shared->dc_event); /* We should not receive twice a same dc_event, before it has been fully processed. */ BUG_ON(atomic_read(&dc_incoming_domID[dc_event]) != -1); atomic_set(&dc_incoming_domID[dc_event], DOMID_AGENCY); /* At the moment, only from the agency */ - /* Work to be done in ME */ + /* Work to be done in capsule */ switch (dc_event) { case DC_RESUME: @@ -511,7 +511,7 @@ static irq_return_t directcomm_isr(int irq, void *data) return IRQ_BOTTOM; default: - printk("(ME) %s: something weird happened, directcomm interrupt was triggered, but no DC event was configured !\n", + printk("(capsule) %s: something weird happened, directcomm interrupt was triggered, but no DC event was configured !\n", __func__); break; } @@ -533,7 +533,7 @@ void vbus_init(void) spin_lock_init(&dc_lock); - vbstore_me_init(); + vbstore_s3c_init(); /* Set up the direct communication channel for post-migration activities * previously established by dom0. @@ -541,9 +541,9 @@ void vbus_init(void) vbus_transaction_start(&vbt); - sprintf(buf, "soo/directcomm/%d", ME_domID()); + sprintf(buf, "soo/directcomm/%d", S3C_domID()); - res = vbus_scanf(vbt, buf, "event-channel", "%d", &avz_shared->dom_desc.u.ME.dc_evtchn); + res = vbus_scanf(vbt, buf, "event-channel", "%d", &avz_shared->dom_desc.u.S3C.dc_evtchn); if (res != 1) { printk("%s: reading soo/directcomm failed. Error code: %d\n", __func__, res); @@ -553,8 +553,8 @@ void vbus_init(void) vbus_transaction_end(vbt); /* Binding the irqhandler to the eventchannel */ - DBG("%s: setting up the direct comm event channel (%d) ...\n", __func__, avz_shared->dom_desc.u.ME.dc_evtchn); - res = bind_interdomain_evtchn_to_irqhandler(DOMID_AGENCY, avz_shared->dom_desc.u.ME.dc_evtchn, directcomm_isr, + DBG("%s: setting up the direct comm event channel (%d) ...\n", __func__, avz_shared->dom_desc.u.S3C.dc_evtchn); + res = bind_interdomain_evtchn_to_irqhandler(DOMID_AGENCY, avz_shared->dom_desc.u.S3C.dc_evtchn, directcomm_isr, directcomm_isr_thread, NULL); if (res <= 0) { @@ -562,9 +562,9 @@ void vbus_init(void) BUG(); } - avz_shared->dom_desc.u.ME.dc_evtchn = evtchn_from_irq(res); + avz_shared->dom_desc.u.S3C.dc_evtchn = evtchn_from_irq(res); DBG("%s: local event channel bound to directcomm towards non-RT Agency : %d\n", __func__, - avz_shared->dom_desc.u.ME.dc_evtchn); + avz_shared->dom_desc.u.S3C.dc_evtchn); DBG("vbus_init OK!\n"); } diff --git a/so3/soo/kernel/vbus/vbus_frontend.c b/so3/soo/kernel/vbus/vbus_frontend.c index 8696d7f93e..c1507b8586 100644 --- a/so3/soo/kernel/vbus/vbus_frontend.c +++ b/so3/soo/kernel/vbus/vbus_frontend.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2018 Daniel Rossier + * Copyright (C) 2016-2026 Daniel Rossier * Copyright (C) 2016-2018 Baptiste Delporte * * This program is free software; you can redistribute it and/or modify @@ -28,7 +28,7 @@ #include #include #include -#include +#include #include #include @@ -164,7 +164,7 @@ static int remove_dev_watches(struct vbus_device *vdev, void *data) /* Process entry "device//.. */ sscanf(vdev->nodename, "device/%d/%s", &domID, item); - sprintf(vdev->nodename, "device/%d/%s", ME_domID(), item); + sprintf(vdev->nodename, "device/%d/%s", S3C_domID(), item); /* * It may happen that the related watches have been already removed during a visit on a smart @@ -179,7 +179,7 @@ static int remove_dev_watches(struct vbus_device *vdev, void *data) /* * Rename the device name stored in the bus so that we avoid to re-probe a same device (possibly - * in a different ME slot. + * in a different capsule slot. */ /* Process entry "backend///.. */ @@ -192,7 +192,7 @@ static int remove_dev_watches(struct vbus_device *vdev, void *data) ptr_item++; sscanf(ptr_item, "%d/%s", &domID, item); - sprintf(ptr_item, "%d/%s", ME_domID(), item); + sprintf(ptr_item, "%d/%s", S3C_domID(), item); DBG("%s: removing watches for %s\n", __func__, vdev->nodename); @@ -214,16 +214,16 @@ void postmig_setup(void) DBG0("Waiting for vbstore dev to be populated\n"); - sprintf(root_name, "%s/%d", initial_rootname, ME_domID()); - DBG("vbus_frontend: %s ... for domID: %d\n", root_name, ME_domID()); + sprintf(root_name, "%s/%d", initial_rootname, S3C_domID()); + DBG("vbus_frontend: %s ... for domID: %d\n", root_name, S3C_domID()); DBG0("Re-adjusting watches....\n"); /* Walk through all devices and readjust watches */ frontend_for_each(NULL, remove_dev_watches); - /* Write the entries related to the ME ID in vbstore */ - vbstore_ME_ID_populate(); + /* Write the entries related to the capsule ID in vbstore */ + vbstore_S3C_ID_populate(); /* Re-create the vbstore entries for devices */ @@ -269,7 +269,7 @@ void vbus_probe_frontend_init(void) /* Re-adjust the root node name with the dom ID */ - sprintf(root_name, "%s/%d", vbus_frontend.root, ME_domID()); + sprintf(root_name, "%s/%d", vbus_frontend.root, S3C_domID()); vbus_frontend.root = root_name; diff --git a/so3/soo/kernel/vbus/vbus_vbstore.c b/so3/soo/kernel/vbus/vbus_vbstore.c index d205487401..3ceb81f24a 100644 --- a/so3/soo/kernel/vbus/vbus_vbstore.c +++ b/so3/soo/kernel/vbus/vbus_vbstore.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2018 Daniel Rossier + * Copyright (C) 2016-2026 Daniel Rossier * Copyright (C) 2016-2018 Baptiste Delporte * * This program is free software; you can redistribute it and/or modify @@ -66,7 +66,7 @@ struct vbs_handle { struct completion watch_wait; - /* To manage ongoing transactions when the ME must be suspended */ + /* To manage ongoing transactions when the capsule must be suspended */ struct mutex transaction_group_mutex; /* Protect watch (de)register against save/restore. */ @@ -236,7 +236,7 @@ static void *vbs_talkv(struct vbus_transaction t, vbus_msg_type_t type, const ms smp_mb(); - notify_remote_via_evtchn(avz_shared->dom_desc.u.ME.vbstore_levtchn); + notify_remote_via_evtchn(avz_shared->dom_desc.u.S3C.vbstore_levtchn); /* Now we are waiting for the answer from vbstore */ DBG("Now, we wait for the reply / msg ID: %d (0x%lx)\n", msg.id, &msg.list); @@ -882,20 +882,20 @@ void vbus_vbstore_init(void) int vbus_irq; /* - * Bind evtchn for interdomain communication: must be executed from the agency or from a ME. + * Bind evtchn for interdomain communication: must be executed from the agency or from a capsule. */ /* dev temporary used to set up event channel used by vbstore. */ dev.otherend_id = 0; DBG("%s: binding a local event channel to the remote evtchn %d in Agency (intf: %lx) ...\n", __func__, - avz_shared->dom_desc.u.ME.vbstore_revtchn, __intf); + avz_shared->dom_desc.u.S3C.vbstore_revtchn, __intf); - vbus_bind_evtchn(&dev, avz_shared->dom_desc.u.ME.vbstore_revtchn, - (uint32_t *) &avz_shared->dom_desc.u.ME.vbstore_levtchn); + vbus_bind_evtchn(&dev, avz_shared->dom_desc.u.S3C.vbstore_revtchn, + (uint32_t *) &avz_shared->dom_desc.u.S3C.vbstore_levtchn); - DBG("Local vbstore_evtchn is %d (remote is %d)\n", avz_shared->dom_desc.u.ME.vbstore_levtchn, - avz_shared->dom_desc.u.ME.vbstore_revtchn); + DBG("Local vbstore_evtchn is %d (remote is %d)\n", avz_shared->dom_desc.u.S3C.vbstore_levtchn, + avz_shared->dom_desc.u.S3C.vbstore_revtchn); INIT_LIST_HEAD(&vbs_state.reply_list); @@ -911,7 +911,7 @@ void vbus_vbstore_init(void) init_completion(&vbs_state.watch_wait); /* Initialize the shared memory rings to talk to vbstore */ - vbus_irq = bind_evtchn_to_irq_handler(avz_shared->dom_desc.u.ME.vbstore_levtchn, vbus_vbstore_isr, NULL, NULL); + vbus_irq = bind_evtchn_to_irq_handler(avz_shared->dom_desc.u.S3C.vbstore_levtchn, vbus_vbstore_isr, NULL, NULL); if (vbus_irq < 0) { printk("VBus request irq failed %i\n", vbus_irq); BUG(); diff --git a/usr/lib/logs/capsule_log.c b/usr/lib/logs/capsule_log.c index 6463214d22..fa209a628f 100644 --- a/usr/lib/logs/capsule_log.c +++ b/usr/lib/logs/capsule_log.c @@ -25,7 +25,7 @@ /* * Generates logs messages. It does: - * 1. Add '[ME:]' prefix to the message + * 1. Add '[S3C:]' prefix to the message * 2. Send message though vLOGS * 3. The message is added in a file (`/var/log/soo/me_.log) on the * agency (SOO environment) diff --git a/usr/lib/logs/include/capsule_log.h b/usr/lib/logs/include/capsule_log.h index 5cf9583bfb..5bd81f3611 100644 --- a/usr/lib/logs/include/capsule_log.h +++ b/usr/lib/logs/include/capsule_log.h @@ -21,7 +21,7 @@ /* * Generates logs messages. It does: - * 1. Add '[ME:]' prefix to the message + * 1. Add '[S3C:]' prefix to the message * 2. Send message though vLOGS * 3. The message is added in a file (`/var/log/soo/me_.log) on the * agency (SOO environment)