Experience Embedded

Professionelle Schulungen, Beratung und Projektunterstützung

OpenAMP - endlich!

Autor: Frank Storm, Avnet Silica

Beitrag - Embedded Software Engineering Kongress 2017

 

Viele und unterschiedliche Prozessor-Cores sind in heutigen SoCs Standard. Wie man diese asynchron, also mit unterschiedlichen Betriebssystemen, betreibt und sie dann auch miteinander kommunizieren lässt, leider nicht. Dies führt dazu, dass viele Anwender eigene Lösungen implementieren, die zeit- und wartungsintensiv sind. Dieser Artikel beschreibt OpenAMP, einen offenen Standard, der es sich zur Aufgabe gesetzt hat, dieses Problem zu lösen.

Fast alle großen SoCs kommen heute mit mehr als einem Prozessor daher. Im oberen Segment sind Arm-Prozessoren als Quad-Core inzwischen Standard, ob als A53, A57 oder als big.LITTLE Kombination aus A15 und A7 (i.MX8 [1], Marvell Armada [2], Xilinx Zynq UltraScale+ [3]). Im mittleren Segment hat man dann eher Dual-Core Systeme (i.MX8, Xilinx Zynq 7000 [4]). Da die typischen Applikationsprozessoren (Arm A) nicht für alle Applikationen ideal sind, stellen die Hersteller den Applikationsprozessoren auch noch andere Cores zur Seite. Das sind dann entweder spezielle Real-Time Prozessoren, wie der Cortex-R5 beim Zynq UltraScale+, oder typische Mikrocontroller-Cores, wie der Cortex-M4 beim i.MX8. Sogar im unteren Segment wird einem Cortex-M4 noch ein Cortex-M0+ zur Seite gestellt, wie in der NXP Controllerfamilie LPC54000 [5].

Bereits in der Vergangenheit hat es diese Kombination aus Universalprozessor und Spezialprozessor gegeben, wie die im Mobilfunkbereich lange Zeit verbreitete OMAP-Familie. Der Spezialprozessor war in diesem Fall ein DSP, der für die Abarbeitung des GSM Stacks zuständig war. Schaut man sich die Verwendung der Zusatzprozessoren an, so werden diese sehr oft gezielt für Echtzeitaufgaben eingesetzt, sei es zur Abarbeitung eines Funkprotokoll-Stacks oder zur Antriebsregelung im Industriebereich. Dem Applikationsprozessor fallen dann die Aufgaben zu, sich um die Schnittstellen nach außen zu kümmern, das Human Machine Interface (Display oder Web-Server) zu realisieren und nebenbei noch "House-Keeping" zu betreiben.

Wir haben in vielen Kundenanwendungen gesehen, dass diese Aufteilung auch bei Systemen mit zwei Applikationsprozessoren gemacht wird, wie dem Zynq-7000, der über zwei Cortex-A9 Cores verfügt. Hier ist es nahezu Standard, auf dem ersten Core ein Linux laufen zu lassen, das sich um Schnittstellen wie Ethernet und USB kümmert und eine komfortable Umgebung für ausgewachsene Web-Server bietet. Auf dem zweiten Core läuft dann ein schlankes Echtzeit-Betriebssystem, wie z.B. FreeRTOS oder µC-OS, oder eben auch gar kein Betriebssystem (Bare Metal).

In der Praxis bringen diese heterogenen Systeme eine Reihe von Herausforderungen mit sich. Dabei kann es auch eher hinderlich sein, wenn die Prozessoren zu eng miteinander verzahnt sind. Typische Problemstellungen sind die Aufteilung des Speichers, das Festlegen eines Shared-Memory, das von beiden Cores zum Austausch von Daten genutzt wird, das Verteilen der System-Ressourcen (wer darf sich z.B. um den CAN-Bus kümmern?) und auch die Frage nach der Interrupt-Verteilung (wer unterbricht wen, läuft dies über einen gemeinsamen Interrupt-Controller oder vielleicht über eine spezielle Mailbox-Hardware?). Da wird auch die Frage wichtig, wie MMU oder MPU konfiguriert werden müssen oder welche Bereiche nicht gecacht werden dürfen. Je mehr Flexibilität der SoC-Hersteller in den Chip gelegt hat, umso mehr muss der Anwender bei der Umsetzung beachten.

Die Erfahrung hat gezeigt, dass Applikation-Notes zum AMP-Betrieb der Cores [6] dankend angenommen werden, meistens aber nur der erste Schritt sind. Je komplexer ein Betriebssystem ist, desto schwieriger wird es auch, z.B. in das Speicher und Cache-Management einzugreifen. Bei einem Bare Metal Programm stellt man noch mal eben ein, welche Speicherbereiche nicht gecacht werden sollen. Bei einem Linux möchte man sich damit gar nicht erst beschäftigen müssen. Leider hat auch hier die Erfahrung gezeigt, dass man sich spätestens dann damit beschäftigen muss, wenn Interrupts verschluckt werden und die Performance unter bestimmten Bedingungen plötzlich nicht mehr ausreicht.

Auch die Fragen nach dem sogenannten Lifecycle-Management, also dem Laden und Starten von Programmen, sowie dem gezielten Schlafenlegen, Herunterfahren und Neuladen sind nicht unbedingt trivial.

Der Wunsch kommt also ganz schnell auf, hier auf etwas Existierendem, Stabilem und Standardisiertem aufsetzen zu können. Weil das auch Hersteller von SoCs, Betriebssystemen und Tools erkannt haben, haben sich etliche Firmen zur Multicore Association (MCA) zusammengeschlossen [7]. Die Multicore Association erarbeitet in Arbeitsgruppen Standards und Implementierungen, die das Zusammenspiel von Multi-Core-Systemen regeln und vereinfachen sollen.

Einer dieser Standards ist OpenAMP - das Open Assymetric Multiprocessing Framework [8]. OpenAMP stellt dabei ein Open-Source-Framework zur Verfügung, was die standardisierte Kommunikation einer Vielzahl von heterogenen Systemen miteinander ermöglicht. Die entsprechende Arbeitsgruppe bei der MCA kümmert sich dabei um die Standardisierung der Schnittstellen.

Man hat dabei einen pragmatischen Ansatz gewählt. Anstatt einen allgemeinen Ansatz zu wählen, der alle möglichen Szenarien abdecken soll, hat man sich auf die in der Praxis typischen Fälle konzentriert und auf bestehende Software Schnittstellen aufgesetzt.

Konkret bedeutet das Folgendes: OpenAMP unterscheidet zwischen einem Master- oder Host-Prozessor, dem die komplette Kontrolle unterliegt, und einem Remote-Prozessor. Auf dem Master-Prozessor läuft typischerweise ein Linux, auf dem Remote-Prozessor ein RTOS oder ein Bare-Metal Programm. Als Interface-Komponente für das Lifecycle Management (LCM) wird remoteproc [9] verwendet, für die Interprozessor-Kommunikation (IPC) findet RPMsg [10] Verwendung. Beide Komponenten sind Bestandteil des Linux-Kernels ab Version 3.4. remoteproc und RPMsg setzen beide auf virtIO auf, ein Layer, der virtuellen Device-Treibern die direkte Kommunikation mit dem Host-OS oder einem Hypervisor erlaubt.

Das gilt zwar für Linux auf der Host-Seite, aber nicht für ein auf Remote-Prozessor-Seite verwendetes RTOS. Deswegen ist es Aufgabe von OpenAMP, hier eine Implementierung zur Verfügung zu stellen, die sich in ein RTOS einbinden lässt.

Zusätzlich zu LCM und IPC kommt bei OpenAMP noch eine dritte Komponente hinzu. Ein auf dem Remote-Prozessor laufendes Programm soll schließlich auch Ausgaben machen können, die man gerne zusammen mit den Ausgaben des Host-Prozessors in einem Terminal-Fenster sehen möchte. Außerdem wäre es vorteilhaft, wenn der Remote-Prozessor (kontrollierten) Zugang zum File-System des Hosts hätte, sei es, um Konfigurationsdateien zu lesen oder autonom auf Daten zugreifen zu können. Hierfür gibt es eine Proxy-Infrastruktur, die I/O- und File-Zugriffe des Remote-Prozessors über den Host abwickelt.

OpenAMP übernimmt zwar einiges an Arbeit, ohne ein paar Vorarbeiten bei der Installation des Linux-Kernels kommt man aber nicht aus. Glücklicherweise beschränken sich diese hauptsächlich auf das Hinzukonfigurieren von remoteproc und RPMsg (falls diese nicht sowieso schon aktiv sind) und das Hinzufügen von ein paar Einträgen im Device-Tree, die gemeinsam genutzte Speicherbereiche und Interrupts definieren und die Verbindung zu remoteproc herstellen.

Ein Beispiel

Anhand eines kleinen Beispiels soll gezeigt werden, wie sich OpenAMP aus Sicht des Anwenders darstellt. Das Beispiel ist ein Programm, das auf dem Remote-Prozessor eine FFT berechnet und dann die berechneten Daten zurückschickt.

Über die Datei firmware im Sysfs-Filesystem wird das Programm auf den Remoteprozessor geladen:

# echo fft_server > 
/sys/class/remoteproc/remoteproc0/firmware

 

Gestartet wird es dann auf ähnliche Weise durch Schreiben des Wertes "start" auf die Sysfs-Datei state:

# echo start > /sys/class/remoteproc/remoteproc0/state

 

Im User-Programm gestaltet sich die Kommunikation dann recht einfach. Das entsprechende RPMsg-Device wird geöffnet, und die FFT-Eingangsdaten werden hineingeschrieben. Anschließend werden die berechneten Ausgangsdaten ausgelesen.

int fd;

int input_data[512];

int output_data[512];

fd = open("/dev/rpmsg0", O_RDWR);
write(fd, input_data, sizeof(input_data));

read(fd, output_data, sizeof(output_data));

 

Das Programm auf dem Remote-Prozessor gestaltet sich etwas aufwändiger.

int output_data[512];

 

void rpmsg_read_cb(

    struct rpmsg_channel *rp_chnl,

    void *data, int len,
    void *priv, unsigned long src)
{

  calculate_fft(data, output_data);
  rpmsg_send(rp_chnl, output_data, sizeof(output_data));

}

struct hil_proc *hproc;
struct rsc_table_info rsc_info;

hproc = platform_create_proc(proc_id);

rsc_info.rsc_tab = get_resource_table((int)rsc_id,

        &rsc_info.size)

remoteproc_resource_init(&rsc_info, hproc,
        rpmsg_channel_created,
        rpmsg_channel_deleted,

        rpmsg_read_cb, &proc, 0);

 

Über remote_proc_resource_init wird die Callback-Funktion rpmsg_read_cb registriert, die beim Empfang von Daten angesprungen wird und dann den eigentlichen Service (hier Berechnung der FFT) übernimmt.

Abhängig von der Funktion, die das Programm auf dem Remote-Prozessor zu erfüllen hat, ist natürlich noch etwas mehr zu tun. Im User-Programm kann es auch empfehlenswert sein, das Abschicken der Eingangsdaten und das Empfangen der berechneten Daten durch Threads zu entkoppeln.

Was bekommt man nun als Anwender?

Die Sourcen für OpenAMP und Beispielprogramme werden über GitHub zur Verfügung gestellt [11]. Von Xilinx gibt es inzwischen eine Application-Note, die den Umgang mit OpenAMP beschreibt [12]. Außerdem stellt Xilinx in seinem Software Development Kit bereits Templates für OpenAMP Projekte zur Verfügung. Von NXP gibt es eine ressourcensparende RPMsg-Implementierung (RPMsg-Lite), die ebenfalls über GitHub zur Verfügung gestellt wird [13]. Von Mentor Graphics gibt es inzwischen mit dem Mentor Embedded Multicore Framework die erste kommerzielle Implementation des OpenAMP-Standards [14].

Warum nicht OpenCL?

Wo man mit richtig vielen Cores (> 1000) zu tun hat, also im Bereich der GPUs, existieren bereits Frameworks zur Verwaltung von Multicore-Architekturen. Diese sind entweder proprietär (wie CUDA von NVIDIA) oder offen wie OpenCL der Khronos Group [15]. Man kann sich jetzt natürlich die Frage stellen, ob sich ein Framework wie OpenCL nicht auch für die oben beschriebenen AMP-Szenarien verwenden ließe und warum man mit OpenAMP ein neues Framework geschaffen hat.

OpenCL ist dafür konzipiert worden, mit einer generischen Anzahl von Cores umgehen zu können. Mit OpenCL entwickelte Programme sollen auf unterschiedlichen Systemen laufen können, die unterschiedlich viele Prozessoren zur Verfügung stellen. Abzufragen, wie viele Prozessoren das System hat, um daraufhin einen Algorithmus generisch zu verteilen, ist eine der ersten Aufgaben einer OpenCL-Initialisierung. Das führt zu einem mächtigen, aber auch komplexen API. OpenAMP adressiert hingegen Systeme, die typischerweise einen oder vielleicht zwei Remote-Prozessoren zur Verfügung stellen. Hier ist die Aufgabe des Remote-Prozessors klar umrissen und das Hardware-Setup von vornherein bekannt. Dadurch kann aber auch der Overhead wesentlich geringer ausfallen als in einer Umgebung, bei der beliebig viele Cores verwaltet werden müssen. OpenAMP agiert dadurch wesentlich schlanker, was nicht zuletzt auch den Lernaufwand gegenüber OpenCL drastisch reduziert.

Wie geht es weiter?

Momentan hält OpenAMP bei den ersten Entwicklern Einzug. Die ersten Fragen nach Erweiterungen kommen auch bereits ("Wir hätten gerne noch einen C++ Layer"). Manch einer, der bereits eine Lösung hat, wird sich auch mit der Adaption Zeit lassen. Mit OpenAMP steht jetzt aber erstmals ein Standard zur Verfügung, auf dem man bei der Implementierung von AMP-Systemen aufsetzen kann, ohne selber wieder das Rad neu erfinden zu müssen. 

Quellenverzeichnis

[1] https://www.nxp.com/products/microcontrollers-and-processors/arm-based-processors-and-mcus/i.mx-applications-processors/i.mx-8-processors:IMX8-SERIES

[2] https://www.marvell.com/embedded-processors/

[3] https://www.xilinx.com/products/silicon-devices/soc/zynq-ultrascale-mpsoc.html

[4] https://www.xilinx.com/products/silicon-devices/soc/zynq-7000.html

[5] https://www.nxp.com/docs/en/fact-sheet/LPC541XXFAMFS.pdf

[6] https://www.xilinx.com/support/documentation/application_notes/xapp1078-amp-linux-bare-metal.pdf

[7] http://www.multicore-association.org/

[8] http://www.multicore-association.org/workgroup/oamp.php

[9] https://www.kernel.org/doc/Documentation/remoteproc.txt

[10] https://www.kernel.org/doc/Documentation/rpmsg.txt

[11] https://github.com/OpenAMP

[12] https://www.xilinx.com/support/documentation/sw_manuals/xilinx2017_2/ug1186-zynq-openamp-gsg.pdf

[13] https://github.com/NXPmicro/rpmsg-lite

[14] https://www.mentor.com/embedded-software/multicore

[15] https://www.khronos.org/opencl/

 

Beitrag als PDF downloaden


Multicore - unsere Trainings & Coachings

Wollen Sie sich auf den aktuellen Stand der Technik bringen?

Dann informieren Sie sich hier zu Schulungen/ Seminaren/ Trainings/ Workshops und individuellen Coachings von MircoConsult zum Thema Multicore /Mikrocontroller.

 

Training & Coaching zu den weiteren Themen unseren Portfolios finden Sie hier.


Multicore - Fachwissen

Wertvolles Fachwissen zum Thema Multicore /Mikrocontroller steht hier für Sie zum kostenfreien Download bereit.

Zu den Fachinformationen

 
Fachwissen zu weiteren Themen unseren Portfolios finden Sie hier.