IVSHMEM Doorbell Sample Application
###################################
Overview
********
This sample shows how two processes on different operating systems can
communicate using ivshmem. This is a subset of the functionality provided by
OpenAMP.
Prerequisites
*************
QEMU needs to available.
ivshmem-server needs to be available and running. The server is available in
Zephyr SDK or pre-built in some distributions. Otherwise, it is available in
QEMU source tree.
ivshmem-client needs to be available as it is employed in this sample as an
external application. The same conditions of ivshmem-server applies to the
ivshmem-server, as it is also available via QEMU.
Building and Running
********************
Building ivshmem-doorbell is as follows:
qemu_cortex_a53
===============
.. zephyr-app-commands::
:zephyr-app: samples/drivers/virtualization/ivshmem/doorbell
:host-os: unix
:board: qemu_cortex_a53
:goals: run
:compact:
qemu_kvm_arm64
==============
.. zephyr-app-commands::
:zephyr-app: samples/drivers/virtualization/ivshmem/doorbell
:host-os: unix
:board: qemu_kvm_arm64
:goals: run
:compact:
qemu_x86_64
===========
.. zephyr-app-commands::
:zephyr-app: samples/drivers/virtualization/ivshmem/doorbell
:host-os: unix
:board: qemu_x86_64
:goals: run
:compact:
How to
******
.. note::
The ivshmem shared memory can be manipulated to crash QEMU and bring down
Zephyr. Check `ivshmem_doorbell_sample_security`_ for details.
.. note::
Due to limited RAM memory available in qemu_x86_64 dts, it is not possible
to use the default shared memory size of ivshmem (4MB) for this platform.
Steps to reproduce this sample:
#. Run ivshmem-server. For the ivshmem-server, both number of vectors and
shared memory size are decided at run-time (when the server is executed).
For Zephyr, the number of vectors and shared memory size of ivshmem are
decided at compile-time and run-time, respectively.
- (Arm64) Use vectors == 2 for the project configuration in this sample.
Here is an example:
.. code-block:: console
# n = number of vectors
$ sudo ivshmem-server -n 2
$ *** Example code, do not use in production ***
- (x86_64) The default shared memory size is bigger than the memory
available for x86_64. For the provided sample configuration:
.. code-block:: console
# n = number of vectors, l = shared memory size
$ sudo ivshmem-server -n 2 -l 4096
$ *** Example code, do not use in production ***
- (Optional) If vectors != 2, you need to change ivshmem driver
:kconfig:option:`CONFIG_IVSHMEM_MSI_X_VECTORS`.
#. Appropriately set ownership of :file:`/dev/shm/ivshmem` and
``/tmp/ivshmem_socket`` for your deployment scenario. For instance:
.. code-block:: console
# assumption: "ivshmem" group should be the only allowed to access ivshmem
$ sudo chgrp ivshmem /dev/shm/ivshmem
$ sudo chmod 060 /dev/shm/ivshmem
$ sudo chgrp ivshmem /tmp/ivshmem_socket
$ sudo chmod 060 /tmp/ivshmem_socket
$
#. Run Zephyr.
.. code-block:: console
$ west build -t run
-- west build: running target run
[0/1] To exit from QEMU enter: 'CTRL+a, x'[QEMU] CPU: cortex-a53
*** Booting Zephyr OS build zephyr-v3.3.0-1649-g612f49da5dee ***
Use write_shared_memory.sh and ivshmem-client to send a message
#. Write a message in the shared memory. The shared memory size *must* be kept
the same as specified for ivshmem-server. This is the purpose of the
``write_shared_memory`` script; failing to respect the shared memory size
may lead to a QEMU crash. For instance:
- (Arm64) a simple "hello world" message (the script assumes the default
size of ivshmem-server):
.. code-block:: console
# ./write_shared_memory.sh -m "your message"
$ ./write_shared_memory.sh -m "hello world"
$
- (x86_64) a simple "hello world" message:
.. code-block:: console
# ./write_shared_memory.sh -m "your message" -s <size of shared memory>
# assumption: the user created ivshmem-server with size 4096
$ ./write_shared_memory.sh -m "hello world" -s 4096
$
5. Send an interrupt to the guest. Using ivshmem-client, for instance:
.. code-block:: console
# find out client id. In this execution, it is 0 (peer_id)
$ ivshmem-client
dump: dump peers (including us)
int <peer> <vector>: notify one vector on a peer
int <peer> all: notify all vectors of a peer
int all: notify all vectors of all peers (excepting us)
listen on server socket 3
cmd> dump
our_id = 1
vector 0 is enabled (fd=7)
vector 1 is enabled (fd=8)
peer_id = 0
vector 0 is enabled (fd=5)
vector 1 is enabled (fd=6)
cmd> int 0 0
#. The sample will print the text in the shared memory whenever an interrupt is
received (in any of the ivshmem-vectors). Example of output for arm64:
.. code-block:: console
$ west build -t run
-- west build: running target run
[0/1] To exit from QEMU enter: 'CTRL+a, x'[QEMU] CPU: cortex-a53
*** Booting Zephyr OS build zephyr-v3.3.0-1649-g612f49da5dee ***
Use write_shared_memory.sh and ivshmem-client to send a message
received IRQ and full message: hello world
Known Issues
************
The guest application should be started before the host one, even though the
latter starts the communication. This is because it takes a while for the guest
to actually register the IRQ (needs to enable PCI, map PCI BARs, enable IRQ,
map callback). If the host is initialized first, the guest may lose the first
IRQ and the protocol will not work.
.. _ivshmem_doorbell_sample_security:
Security
********
This sample assumes that the shared memory region size is constant; therefore,
once the memory is set during PCI configuration, it should not be tampered
with. This is straight-forward if you are writing an application and uses
:c:func:`mmap`; however, using shell tools (like :command:`echo`) will treat
the shared memory as a file, and overwrite the shared memory size to the input
length.
One way to ensure proper consistency is: (i) restrict access to the shared
memory to trusted users; a rogue user with improper access can easily truncate
the memory size to zero, for example by using :command:`truncate`, and make QEMU
crash, as the application will attempt to read the initial, bigger, size; and
(ii) make sure writes always respect the shared memory region size.