[PATCH rtems-docs 1/1] user: Add docu for use of Rust with RTEMS

Frank Kuehndel frank.kuehndel at embedded-brains.de
Fri Feb 16 20:52:53 UTC 2024


From: Frank Kühndel <frank.kuehndel at embedded-brains.de>

---
 user/index.rst           |   3 +-
 user/overview/index.rst  |   2 +
 user/rust/bare-metal.rst | 549 +++++++++++++++++++++++++++++++++++++++
 user/rust/index.rst      |  64 +++++
 4 files changed, 617 insertions(+), 1 deletion(-)
 create mode 100644 user/rust/bare-metal.rst
 create mode 100644 user/rust/index.rst

diff --git a/user/index.rst b/user/index.rst
index 5b7f3ce..0cc6b2c 100644
--- a/user/index.rst
+++ b/user/index.rst
@@ -18,7 +18,7 @@ RTEMS User Manual (|version|).
     | |copy| 2018 Shashvat Jain
     | |copy| 2018 Vidushi Vashishth
     | |copy| 2017 Tanu Hari Dixit
-    | |copy| 2016, 2019 embedded brains GmbH & Co. KG
+    | |copy| 2016, 2024 embedded brains GmbH & Co. KG
     | |copy| 2016, 2019 Sebastian Huber
     | |copy| 2012, 2022 Chris Johns
     | |copy| 2012, 2020 Gedare Bloom
@@ -51,5 +51,6 @@ RTEMS User Manual (|version|).
 
     tools/index
     rsb/index
+    rust/index
 
     glossary/index
diff --git a/user/overview/index.rst b/user/overview/index.rst
index 16389d9..cc292e1 100644
--- a/user/overview/index.rst
+++ b/user/overview/index.rst
@@ -66,6 +66,8 @@ RTEMS provides the following basic feature set:
 
     - Python and MicroPython
 
+    - :ref:`Rust <Rust>`
+
 - Parallel languages
 
     - :ref:term:`EMB²`
diff --git a/user/rust/bare-metal.rst b/user/rust/bare-metal.rst
new file mode 100644
index 0000000..b893f57
--- /dev/null
+++ b/user/rust/bare-metal.rst
@@ -0,0 +1,549 @@
+.. SPDX-License-Identifier: CC-BY-SA-4.0
+
+.. Copyright (C) 2024 embedded brains GmbH & Co. KG
+
+.. _RustBareMetal:
+
+Bare Metal Rust with RTEMS
+==========================
+
+To develop with Rust and RTEMS together, you must find a Rust bare metal
+target which matches an RTEMS BSP. The instructions in this section
+are for a SPARC and a Risc-V *hello world* program. These examples use
+the combinations shown in the table below:
+
++--------------------+------------------+----------------------------+--------------+
+| RTEMS Architecture | RTEMS BSP        | Rust Target                | Rust CPU     |
++====================+==================+============================+==============+
+| rtems-sparc        | sparc/leon3      | sparc-unknown-none-elf     | leon3        |
++--------------------+------------------+----------------------------+--------------+
+| rtems-riscv        | riscv/rv64imafdc | riscv64gc-unknown-none-elf | generic-rv64 |
++--------------------+------------------+----------------------------+--------------+
+
+The following sources may be helpful to find a matching BSP and target:
+
+- ``./waf bsplist`` -- executed in an RTEMS git clone
+- ``source-builder/sb-set-builder --list-bsets`` -- executed in an
+  RTEMS source builder git clone
+- `RTEMS Supported Architectures <https://devel.rtems.org/wiki/TBR/UserManual/SupportedCPUs>`_
+- `RTEMS Board Support Packages <https://devel.rtems.org/wiki/TBR/Website/Board_Support_Packages>`_
+- ``rustc --print target-list``
+- ``rustc --target=riscv64gc-unknown-none-elf --print target-features``
+- ``rustc --target=riscv64gc-unknown-none-elf  --print target-cpus``
+- `Rust Platform Support <https://doc.rust-lang.org/nightly/rustc/platform-support.html>`_
+
+The sample instructions which follow build two executables using the
+same source code for the RTEMS configuration ``init.c`` and the Rust
+hello-world application ``lib.rs``. Only the configuration as well as
+the compile and link commands differ for SPARC Leon3 and RISC-V
+64 bit. The Rust application uses ``printk()`` from RTEMS to print
+text to the console.
+
+After building the RTEMS BSP and installing Rust, the basic steps are:
+
+(1) Compile the RTEMS configuration in ``init.c`` into an object
+    file using the GNU C compiler from the RTEMS tool chain.
+(2) Compile the Rust code containing ``main()`` into a
+    static library using the Rust compiler.
+(3) Link the static library with the Rust code,
+    the RTEMS configuration and the RTEMS OS libraries
+    together into one single executable.
+(4) Finally run the executable on a simulator.
+
+I build the examples in a container. This is optional. If you prefer
+to follow these instructions directly on your machine simply skip the
+section *Build a Container*. Just make sure that you machine meets all
+prerequisites to build the RTEMS tools and install the Rust tools.
+
+.. _RustBareMetal_Container:
+
+Build a Container
+-----------------
+
+The container must be able to execute the RTEMS source builder and to
+install and run the Rust tools. In an empty directory of your choice
+create the following ``Dockerfile``.
+
+.. code-block:: shell
+
+    cat >Dockerfile <<"EOF"
+    # Dockerfile to build a container image to use Rust on top of RTEMS
+    FROM ubuntu:22.04
+    RUN apt-get update && \
+        apt-get -y upgrade && \
+        apt-get install -y \
+            binutils \
+            bison \
+            bzip2 \
+            curl \
+            flex \
+            gcc \
+            g++ \
+            git \
+            gzip \
+            make \
+            patch \
+            pkg-config \
+            python3 \
+            python3-dev \
+            python-is-python3 \
+            qemu-system-misc \
+            texinfo \
+            unzip \
+            xz-utils && \
+        apt-get clean && \
+        rm -rf /var/lib/apt/lists/*
+    RUN useradd -c "Rust Developer" -g "users" \
+                -d "/home/ferris" --create-home "ferris" && \
+        mkdir -p /opt/rtems && \
+        chown ferris:users /opt/rtems && \
+        runuser -u ferris echo 'export PATH=/opt/rtems/6/bin:${PATH}' \
+                >>/home/ferris/.bashrc
+    USER ferris
+    WORKDIR /home/ferris
+    CMD ["/bin/bash"]
+    EOF
+
+I use Podman. If you prefer Docker simply replace ``podman``
+through ``docker`` in the shell commands below.
+
+Build the container image ``rtems_rust``, create and start a container
+with these commands:
+
+.. code-block:: shell
+
+    podman build -t rtems_rust .
+    podman run -it --name=rusty_rtems rtems_rust bash
+
+To follow the step-by-step instructions of the next sub-sections,
+simply execute them as user ``ferris`` in the container. Note that
+this container will not automatically be deleted on ``exit``.
+The building of the RTEMS tools takes a while and you probably want
+to keep the container for further experiments.
+
+.. _RustBareMetal_RTEMSTools:
+
+Build the RTEMS Tools
+---------------------
+
+In an empty directory of your choice, clone the RTEMS source builder
+git repository:
+
+.. code-block:: shell
+
+    git clone git://git.rtems.org/rtems-source-builder.git rsb
+
+Next build the RTEMS tools. In this example, I need the tools for
+*SPARC* and *RISC-V* architectures. The source builder installs them
+in the prefix directory ``/opt/rtems/6``. The directory ``/opt/rtems``
+must exist and the user must have read and write access.
+
+.. code-block:: shell
+
+    cd rsb/rtems
+    ../source-builder/sb-set-builder --prefix /opt/rtems/6 \
+        6/rtems-sparc \
+        6/rtems-riscv
+    cd ../..
+
+The tools will end up in ``/opt/rtems/6/bin`` and that directory
+should be part of the ``$PATH`` environment variable of the user. For
+example:
+
+.. code-block:: shell
+
+    export PATH=/opt/rtems/6/bin:${PATH}
+
+The following commands should work:
+
+.. code-block:: shell
+
+    sparc-rtems6-gcc --version
+    riscv-rtems6-gcc --version
+
+.. _RustBareMetal_RTEMSBSP:
+
+Build and Install the RTEMS BSPs
+--------------------------------
+
+Clone the RTEMS git repository:
+
+.. code-block:: shell
+
+    git clone git://git.rtems.org/rtems.git
+
+Create a ``config.ini`` file for the two BSPs for which your are going
+to build RTEMS:
+
+.. code-block:: shell
+
+    cd rtems
+
+    cat >config.ini <<"EOF"
+    [sparc/leon3]
+    RTEMS_SMP = True
+    [riscv/rv64imafdc]
+    EOF
+
+Build and install RTEMS:
+
+.. code-block:: shell
+
+    ./waf configure --prefix=/opt/rtems/6
+    ./waf
+    ./waf install
+
+Run some RTEMS tests to make sure the installation and the emulators
+are working:
+
+.. code-block:: shell
+
+    sparc-rtems6-sis -leon3 -nouartrx -r m 4 build/sparc/leon3/testsuites/samples/hello.exe
+    sparc-rtems6-sis -leon3 -nouartrx -r m 4 build/sparc/leon3/testsuites/samples/ticker.exe
+    qemu-system-riscv64 -M virt -nographic -bios build/riscv/rv64imafdc/testsuites/samples/hello.exe
+    qemu-system-riscv64 -M virt -nographic -bios build/riscv/rv64imafdc/testsuites/samples/ticker.exe
+
+Finally, leave the git working tree:
+
+.. code-block:: shell
+
+    cd ..
+
+.. _RustBareMetal_InstallRust:
+
+Install and Setup Rust Tools
+----------------------------
+
+Install Rust from the web-page with this command:
+
+.. code-block:: shell
+
+    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
+
+At this point you must setup the environment variables:
+
+.. code-block:: shell
+
+    source "$HOME/.cargo/env"
+
+Check that rust is correctly setup:
+
+.. code-block:: shell
+
+    rustup update
+    cargo --version
+
+.. _RustBareMetal_Sources:
+
+Setup a Rust Project and Create Sources
+---------------------------------------
+
+Write a simple RTEMS ``init.c`` to configure RTEMS in a new directory:
+
+.. code-block:: shell
+
+    mkdir example-rust
+    cd example-rust
+
+    cat >init.c <<"EOF"
+    /*
+     * Simple RTEMS configuration
+     */
+
+    #define CONFIGURE_APPLICATION_NEEDS_CLOCK_DRIVER
+    #define CONFIGURE_APPLICATION_NEEDS_CONSOLE_DRIVER
+
+    #define CONFIGURE_UNLIMITED_OBJECTS
+    #define CONFIGURE_UNIFIED_WORK_AREAS
+
+    #define CONFIGURE_RTEMS_INIT_TASKS_TABLE
+
+    #define CONFIGURE_INIT
+
+    #include <rtems/confdefs.h>
+    EOF
+
+Create a new Rust project which produces a static linked library:
+
+.. code-block:: shell
+
+    cargo new --lib --vcs=none hello-rtems
+    sed -i '/^#/ a \\n[lib]\ncrate-type = ["staticlib"]' hello-rtems/Cargo.toml
+
+Store the Rust application code:
+
+.. code-block:: rust
+
+    cat >hello-rtems/src/lib.rs <<"EOF"
+    #![no_std]
+    #![no_main]
+
+    use core::fmt::Write;
+    use core::ffi::c_char;
+
+    extern "C" {
+        fn printk(fmt: *const core::ffi::c_char, ...) -> core::ffi::c_int;
+        fn rtems_panic(fmt: *const core::ffi::c_char, ...) -> !;
+        fn rtems_shutdown_executive(fatal_code: u32);
+    }
+
+    /// Write text to the console using RTEMS `printk()` function
+    struct Console;
+
+    impl core::fmt::Write for Console {
+        fn write_str(&mut self, message: &str) -> core::fmt::Result {
+            const FORMAT_STR: &core::ffi::CStr = {
+                let Ok(s) = core::ffi::CStr::from_bytes_with_nul(b"%.*s\0") else {
+                    panic!()
+                };
+                s
+            };
+            if message.len() != 0 {
+                unsafe {
+                    printk(FORMAT_STR.as_ptr(), message.len() as core::ffi::c_int, message.as_ptr());
+                }
+            }
+            Ok(())
+        }
+    }
+
+    /// Our `Init()` calls `rust_main()` and handles errors
+    #[no_mangle]
+    pub extern "C" fn Init() {
+        if let Err(e) = rust_main() {
+            panic!("Main returned {:?}", e);
+        }
+        unsafe {
+            rtems_shutdown_executive( 0 );
+        }
+    }
+
+    /// This is the main function of this program
+    fn rust_main() -> Result<(), core::fmt::Error> {
+        let mut console = Console;
+        writeln!(console, "Hello from Rust")?;
+        Ok(())
+    }
+
+    /// Handle panic by forwarding it to the `rtems_panic()` handler
+    #[panic_handler]
+    fn panic(panic: &core::panic::PanicInfo) -> ! {
+        // The panic message can only be reached from libcore in unstable
+        // (i.e. nightly builds). Print at least the location raising the panic.
+        // See https://www.ralfj.de/blog/2019/11/25/how-to-panic-in-rust.html
+        if let Some(location) = panic.location() {
+            const FORMAT_STR: *const c_char = {
+                const BYTES: &[u8] = b"Panic occurred at %.*s:%d:%d\n\0";
+                BYTES.as_ptr().cast()
+            };
+            if location.file().len() != 0 {
+                unsafe {
+                    rtems_panic(FORMAT_STR,
+                        location.file().len() as core::ffi::c_int,
+                        location.file().as_ptr(),
+                        location.line() as core::ffi::c_int,
+                        location.column() as core::ffi::c_int,
+                    );
+                }
+            }
+        }
+
+        // If there is no location, fall back to the basic.
+        let message = "Panic occured!";
+        const FORMAT_PTR: *const c_char = {
+            const BYTES: &[u8] = b"%.*s\n\0";
+            BYTES.as_ptr().cast()
+        };
+        unsafe {
+           rtems_panic(FORMAT_PTR,
+               message.len() as core::ffi::c_int,
+               message.as_ptr());
+        }
+    }
+    EOF
+
+Create a configuration file for Cargo:
+
+.. code-block:: shell
+
+    mkdir hello-rtems/.cargo
+
+    cat >hello-rtems/.cargo/config.toml <<"EOF"
+    [target.riscv64gc-unknown-none-elf]
+    # Either kind should work as a linker
+    linker = "riscv-rtems6-gcc"
+    # linker = "riscv-rtems6-clang"
+    rustflags = [
+        # See `rustc --target=riscv64gc-unknown-none-elf  --print target-cpus`
+        "-Ctarget-cpu=generic-rv64",
+        # The linker is a gcc compatible C Compiler
+        "-Clinker-flavor=gcc",
+        # Pass these options to the linker
+        "-Clink-arg=-march=rv64imafdc",
+        "-Clink-arg=-mabi=lp64d",
+        "-Clink-arg=-mcmodel=medany",
+        # Rust needs libatomic.a to satisfy Rust's compiler-builtin library
+        "-Clink-arg=-latomic",
+    ]
+    runner = "qemu-system-riscv64 -M virt -nographic -bios"
+
+    # Target available in rust nightly from 2023-07-18
+    [target.sparc-unknown-none-elf]
+    # Either kind should work as a linker
+    linker = "sparc-rtems6-gcc"
+    # linker = "sparc-rtems6-clang"
+    rustflags = [
+        # The target is LEON3
+        "-Ctarget-cpu=leon3",
+        # The linker is a gcc compatible C Compiler
+        "-Clinker-flavor=gcc",
+        # Pass these options to the linker
+        "-Clink-arg=-mcpu=leon3",
+        # Rust needs libatomic.a to satisfy Rust's compiler-builtin library
+        "-Clink-arg=-latomic",
+    ]
+    runner = "sparc-rtems6-sis -leon3 -nouartrx -r m 4"
+
+    [build]
+    target = ["riscv64gc-unknown-none-elf", "sparc-unknown-none-elf"]
+
+    [unstable]
+    build-std = ["core"]
+    EOF
+
+.. _RustBareMetal_BuildRiscV:
+
+Build and Run on RISC-V
+-----------------------
+
+First, download some additional files needed for this target:
+
+.. code-block:: shell
+
+    rustup target add riscv64gc-unknown-none-elf
+
+Compile the Rust source file into a static library:
+
+.. code-block:: shell
+
+    cd hello-rtems
+    cargo build --target=riscv64gc-unknown-none-elf
+    cd ..
+
+This should create
+``hello-rtems/target/riscv64gc-unknown-none-elf/debug/libhello_rtems.
+a``. Note that the project directory (``hello-rtems``) is written with
+a minus "``-``" while the library (``libhello_rtems.a``) is written
+with an underscore "``_``".
+
+Compile the RTEMS ``init.c`` file and link everything
+together into a single executable:
+
+.. code-block:: shell
+
+    export PKG_CONFIG_RISCV=/opt/rtems/6/lib/pkgconfig/riscv-rtems6-rv64imafdc.pc
+
+    riscv-rtems6-gcc -Wall -Wextra -O2 -g -fdata-sections -ffunction-sections \
+        $(pkg-config --cflags ${PKG_CONFIG_RISCV}) init.c -c -o init_riscv.o
+
+    riscv-rtems6-gcc init_riscv.o \
+      -Lhello-rtems/target/riscv64gc-unknown-none-elf/debug \
+      -lhello_rtems \
+      -ohello_rtems_riscv.exe \
+      $(pkg-config --variable=ABI_FLAGS ${PKG_CONFIG_RISCV}) \
+      $(pkg-config --libs ${PKG_CONFIG_RISCV})
+
+This should produce the executable file ``hello_rtems_riscv.exe``. Finally,
+run the executable on an emulator (``qemu``):
+
+.. code-block:: shell
+
+    rtems-run --rtems-bsp=rv64imafdc hello_rtems_riscv.exe
+
+The emulator run should produce the following output:
+
+.. code-block:: none
+
+    RTEMS Testing - Run, 6.0.not_released
+     Command Line: /opt/rtems/6/bin/rtems-run --rtems-bsp=rv64imafdc hello_rtems_riscv.exe
+     Host: Linux 7319d7ad96ee 5.14.21-150500.228.g3903735-default #1 SMP PREEMPT_DYNAMIC Fri Jan 19 17:58:02 UTC 2024 (3903735) x86_64
+     Python: 3.10.12 (main, Nov 20 2023, 15:14:05) [GCC 11.4.0]
+    Host: Linux-5.14.21-150500.228.g3903735-default-x86_64-with-glibc2.35 (Linux 7319d7ad96ee 5.14.21-150500.228.g3903735-default #1 SMP PREEMPT_DYNAMIC Fri Jan 19 17:58:02 UTC 2024 (3903735) x86_64 x86_64)
+    Hello from Rust
+
+    [ RTEMS shutdown ]
+    RTEMS version: 6.0.0.b1fdf753387189afe720d3fa1ac13af5fb9943c2
+    RTEMS tools: 13.2.0 20230727 (RTEMS 6, RSB 43d029e85817bd78dc564ffa265c18fccc428dc4, Newlib 3cacedb)
+    executing thread ID: 0x0a010001
+    executing thread name: UI1
+    Run time     : 0:00:00.255214
+
+.. _RustBareMetal_BuildSparc:
+
+Build and Run on SPARC
+----------------------
+
+You need to use the Rust nightly build because the support for
+Gaisler LEON3/4/5 was added in July 2023 and is not yet available
+in stable Rust:
+
+.. code-block:: shell
+
+    rustup toolchain add nightly
+    rustup component add rust-src --toolchain=nightly
+
+Compile the Rust source file into a static library:
+
+.. code-block:: shell
+
+    cd hello-rtems
+    cargo +nightly build --target=sparc-unknown-none-elf
+    cd ..
+
+It should create
+``hello-rtems/target/sparc-unknown-none-elf/debug/libhello_rtems.a``.
+
+Compile the RTEMS ``init.c`` file and link everything
+together into an executable:
+
+.. code-block:: shell
+
+    export PKG_CONFIG_SPARC=/opt/rtems/6/lib/pkgconfig/sparc-rtems6-leon3.pc
+
+    sparc-rtems6-gcc -Wall -Wextra -O2 -g -fdata-sections -ffunction-sections \
+        $(pkg-config --cflags ${PKG_CONFIG_SPARC}) init.c -c -o init_sparc.o
+
+    sparc-rtems6-gcc init_sparc.o \
+        -qnolinkcmds -T linkcmds.leon3 \
+        -Lhello-rtems/target/sparc-unknown-none-elf/debug \
+        -lhello_rtems \
+        -ohello_rtems_sparc.exe \
+        $(pkg-config --libs ${PKG_CONFIG_SPARC})
+
+This should produce the executable file ``hello_rtems_sparc.exe``. Finally,
+run the executable on an emulator (``sis``):
+
+.. code-block:: shell
+
+    rtems-run --rtems-bsp=leon3-sis hello_rtems_sparc.exe
+
+The emulator run should produce the following output:
+
+.. code-block:: none
+
+    RTEMS Testing - Run, 6.0.not_released
+     Command Line: /opt/rtems/6/bin/rtems-run --rtems-bsp=leon3-sis hello_rtems_sparc.exe
+     Host: Linux 7319d7ad96ee 5.14.21-150500.228.g3903735-default #1 SMP PREEMPT_DYNAMIC Fri Jan 19 17:58:02 UTC 2024 (3903735) x86_64
+     Python: 3.10.12 (main, Nov 20 2023, 15:14:05) [GCC 11.4.0]
+    Host: Linux-5.14.21-150500.228.g3903735-default-x86_64-with-glibc2.35 (Linux 7319d7ad96ee 5.14.21-150500.228.g3903735-default #1 SMP PREEMPT_DYNAMIC Fri Jan 19 17:58:02 UTC 2024 (3903735) x86_64 x86_64)
+
+     SIS - SPARC/RISCV instruction simulator 2.30,  copyright Jiri Gaisler 2020
+     Bug-reports to jiri at gaisler.se
+
+     LEON3 emulation enabled, 4 cpus online, delta 50 clocks
+
+     Loaded hello_rtems_sparc.exe, entry 0x40000000
+    Hello from Rust
+    cpu 0 in error mode (tt = 0x80)
+       218400  40019fa0:  91d02000   ta  0x0
+    Run time     : 0:00:00.255628
diff --git a/user/rust/index.rst b/user/rust/index.rst
new file mode 100644
index 0000000..f3d4a28
--- /dev/null
+++ b/user/rust/index.rst
@@ -0,0 +1,64 @@
+.. SPDX-License-Identifier: CC-BY-SA-4.0
+
+.. Copyright (C) 2024 embedded brains GmbH & Co. KG
+
+.. index:: Rust
+
+.. _Rust:
+
+Rust
+****
+
+The number of users of the modern programming language Rust grows
+steadily. Fans can opt for RTEMS as OS when writing Rust
+applications on embedded devices. The sections of this chapter
+provide step by step instructions to get started.
+
+There are two basic approaches to use Rust together with RTEMS:
+
+Bare metal Rust
+  The Rust compiler translates the application code for a target
+  without operating system -- for example ``sparc-unknown-none-elf``.
+  The disadvantage of this approach is that no standard Rust library
+  is available (``#![no_std]`` in Rust code). The advantage is
+  that all targets supported by both Rust and RTEMS can
+  immediately be used.
+
+Rust with std lib
+  The Rust compiler translates the application code for an RTEMS
+  specific target -- for example ``armv7-unknown-rtems-eabi``.
+  The advantage is that all functions from the standard Rust library
+  are available. The disadvantage is that such targets are rare.
+
+  At the time of writing no such target exists. A first target for ARM
+  is planed to be published soon. The reason for the lack of targets is
+  that one must be implemented for each architecture, published to the
+  Rust compiler sources and maintained by someone.
+
+Common to all approaches is the general way how Rust is used with RTEMS:
+
+(1) The RTEMS tools for the architecture are needed. See
+    :ref:`Install the Tool Suite <QuickStartTools>`.
+
+(2) The RTEMS kernel for the BSP is compiled to libraries. See
+    :ref:`Build a Board Support Package (BSP) <QuickStartBSPBuild>`.
+
+(3) A Rust project for the application code is created and configured.
+
+(4) The Rust code of the application is compiled into a library
+    for the target.
+
+(5) The Rust application library and the RTEMS kernel libraries are
+    linked together into a single executable file.
+
+(6) The executable file is either run in an emulator or loaded onto
+    the hardware and executed there.
+
+At the time of writing, there is no common Rust interface for the
+pubic RTEMS functions available. Currently, developers must declare RTEMS
+functions they want to call. This is especially relevant when the
+*Bare metal Rust* approach is used.
+
+.. toctree::
+
+    bare-metal
-- 
2.35.3



More information about the devel mailing list