Multiprocessing mit OpenAmP Viele Kerne – zwei Möglichkeiten

Symmetrisches oder asymmetrisches Multiprocessing – die Qual der Wahl.
Symmetrisches oder asymmetrisches Multiprocessing – die Qual der Wahl.

Embedded-Systeme werden zunehmend mit mehreren Prozessorkernen realisiert. Das Multiprocessing kann dabei symmetrisch oder asymmetrisch ablaufen. Aber ist auch eine Kombination beider Methoden möglich?

Mit der stetigen Integration von Komponenten und Funktionen in moderne  Embedded-Systeme gehören System on Chips (SoCs) mit mehreren  Prozessorkernen mittlerweile zum Standard. Meist ist ein SoC mit zwei bis acht Cortex-A-Prozessoren von Arm ausgestattet, auf denen in der Regel Unix läuft. Allerdings sind mit solchen Systemen harte Echtzeitanforderungen oder Anforderungen zur funktionalen Sicherheit nur schwer zu realisieren. Deshalb integrieren Entwickler als Co-Prozessoren entweder spezielle Echtzeit (Realtime – RT)-Prozessoren aus der Cortex-R-Familie oder klassische Mikroprozessoren aus der Cortex-M-Reihe. Auf den Co-Prozessoren werden Sicherheits- oder Echtzeitaufgaben per Bare-metal-Anwendung oder mithilfe von Echtzeit Betriebssystemen (Realtime Operating Systems – RTOS) umgesetzt. »Bare Metal« bedeutet, dass die Anwendung ohne Zwischenschicht wie ein Betriebssystem direkt auf den Prozessor zugreift. Die Anwendung ist die einzige Software, die auf dem Mikroprozessor oder Mikrocontroller ausgeführt wird. Viele Mikrocontroller-Designs können als Bare-Metal-Programmierung betrachtet werden.

Strukturen mit einer Bare-metal-Anwendung und einem vollwertigen Betriebssystem (Operating System – OS, zum Beispiel Linux) betreiben asymmetrisches Multiprocessing (AMP). Symmetrisches Multiprocessing (SMP) liegt dagegen vor, wenn das Betriebssystem auf mehreren gleichartigen Kernen läuft. Auf modernen, komplexen SoCs gibt es ein Nebeneinander beider Systeme. Das ist der Fall wenn Linux auf mehreren Kernen und parallel dazu ein RTOS sowie eine Bare-metal-Anwendung laufen (Bild 1). Hierbei sind jedoch einige Herausforderungen zu lösen. Dazu zählen das Speichermanagement, das Verteilen von Interrupts, die Interprozessorkommunikation (IPC) sowie das Lifecycle-Management des Co-Prozessors.

Eine Möglichkeit die Herausforderungen anzugehen ist, die Open-Source-Software »OpenAMP« [1] zu verwenden. OpenAMP ist standardisiert und wird dadurch von vielen SoC-Herstellern wie Xilinx oder NXP unterstützt. Zusätzlich soll eine standardisierte Programmierschnittstelle (API) den Entwicklungsprozess für die Software vereinheitlichen. OpenAMP kombiniert dazu bestehende Techniken mit neuen Entwicklungen und unterstützt das Verwenden von Linux, RTOS und Bare-metal-Anwendungen auf heterogenen sowie homogenen Systemen.

Management für den untergeordneten Prozessor

Ein Bestandteil von OpenAMP ist das Lifecycle-Management des untergeordneten Prozessors (Bild 2). Es beinhaltet das Laden der Prozessor-Firmware, der Stromversorgung und des Taktsignals sowie die Kontrolle über das Reset-Signal. Die Umsetzung basiert auf »remoteproc«, einem Linux-Framework, das hardwareübergreifenden Zugriff auf entfernte Prozessoren erlaubt. Dem übergeordneten Prozessor ist dazu das Executable-and-Linkung-Format (ELF) der Firmware des untergeordneten Prozessors zur Verfügung zu stellen. Bei einem RTOS oder Bare-metal-Master ist sie in das ELF des Masters zu integrieren, bei vorhandenem Dateisystem darin abzulegen. Anschließend wird die Programmierschnittstelle »get_firmware« eingebunden. Ist der Master ein Linux-System, wird die Firmware unter »/lib/firmware« in der »Root« abgelegt, dem Wurzelverzeichnis (unter Linux »/« unter Windows »C:\\«).  »Get_firmware()« ist bereits implementiert. Im nächsten Schritt wird ein über die Ressourcentabelle definierter Speicherbereich reserviert und darin die Strukturen für die Interprozessorkommunikation sowie die Daten- und Textsektionen der Firmware abgelegt. Zum Schluss wird der Remote-Prozessor (RP) mit Takt versorgt und der Reset ausgelöst. Der Master wartet auf eine Nachricht des RP über IPC und baut dann die Verbindung via »VirtIO/RPMsg« (siehe folgenden Abschnitt) auf. Nach Auslösen des Reset-Signals startet der Remote-Prozessor und ruft im Rahmen seiner Initialisierung eine Funktion auf, die die Speicheradressen aus der Ressourcentabelle liest. Darauf basierend initialisiert er die Kommunikation via VirtIO/RPMsg und meldet seinen Kanal dem Master, der den Kanal bestätigt und die Kommunikation etabliert.

Zum geordneten Herunterfahren des Remote-Prozessors wird über die RPMsg-Kommunikation die Nachricht »shutdown« versendet, woraufhin der Remote-Prozessor die in der Ressourcentabelle definierten Speicherbereiche freigibt, die Programmausführung beendet und das Abschalten bestätigt. Der Master setzt danach das Reset-Signal und deinitialisiert die Ressourcen auf seiner Seite.

Kommunikation mitShared Memory

Die Kommunikation zwischen den Prozessoren geschieht über gemeinsam genutzte Speicherbereiche (Shared-Memory-Bereiche) sowie Interprozessor-Interrupts (IPI). Das Implementieren basiert auf »VirtIO«, das zur Para-Virtualisierung Linux-basierter Gastsysteme entwickelt wurde und heute ein Teil des Linux-Kernels ist. Nach außen bietet VirtIO ein abstraktes VirtIO-Gerät, das von RPMsg-Treiber instanziiert und konfiguriert wird. Das Management des Shared Memory geschieht über einen »vring«, der mit weiteren Informationen eine »virtqueue« bildet. Sind neue Daten für den Remote-Prozessor im vring verfügbar, wird ein IPI zur Benachrichtigung ausgelöst. Der RPMsg-Treiber nutzt je eine virtqueue zum Senden und Empfangen von Daten pro untergeordnetem Prozessor, die in einem vordefinierten Speicherbereich abgelegt sind. Ein Master kann mehrere Slaves haben, die jeweils selbst wieder Master für einen oder mehrere untergeordnete Systeme sein können (Bild 3). Da der Interrupt Controller in SoCs typischerweise eine geteilte Ressource ist, ist er kooperativ zu nutzen. Insbesondere darf er beim Systemstart des untergeordneten Systems nicht zurückgesetzt werden. Das Festlegen der Shared-Memory-Bereiche geschieht bei Linux über den Gerätebaum (Device Tree), auf Bare-metal beziehungsweise bei RTOS über eine vom Entwickler angelegte Ressourcentabelle, die mit der Festlegung auf den anderen Systemen übereinstimmen muss. Ein Device Tree ist dabei eine Datenstruktur, die die Hardwarekomponenten eines bestimmten Computers beschreibt, so dass der Kernel des Betriebssystems diese Komponenten verwenden und verwalten kann, einschließlich der CPU oder CPUs, des Speichers, der Busse und der Peripheriegeräte.

Beim Initialisieren des RPMsg-Treibers legt dieser alle virtqueues an und weist den vrings Speicherbereiche im Shared Memory zu. Anschließend überträgt der Master die verbindungsnotwendigen Daten und informiert den Remote-Prozessor über einen Interprozessor-Interrupt. Bei einem Linux-Master ist das RPMsg-Interface ein serielles Gerät, das entweder direkt im Kernel oder über einen »Userspace«-Treiber genutzt wird. Der Begriff »Userspace« (oder User Land) bezieht sich auf alle Programme, die außerhalb des Kernels des Betriebssystems laufen.

BegriffBedeutung
APUAccelerated Processing Unit
ADCAnalog-Digital-Wandler
AMPAsymmetrisches Multiprocessing
ELFExecutable and Linking Format
SoCSystem on Chip
FPGAField Programmable Gate Array
I/OInput/Output
IPIInterprozessor Interrupt
IPCInterprozessorkommunikation
OSOperating System, Betriebssystem
APIProgrammierschnittstelle
RTRealtime, Echtzeit
RPURealtime Processing Unit
RPRemote-Prozessor
RTOSRealtime Operating System, Echtzeit-Betriebssystem
SMPSymmetrische Multiprocessing

 

Praxisbeispiel »MBatt«

MBatt ist ein Projekt zum Entwickeln eines Multilevel-Umrichters für Batteriespeichersysteme [2], an dem die Firma Mixed Mode und der Autor beteiligt sind. Im Projekt wird OpenAMP auf einem Zynq-Ultrascale+-SoC von Xilinx eingesetzt, um die komplexe Steuerung und Regelung eines netzgebundenen Multilevel-Umrichters für Batteriespeichersysteme umzusetzen (Bild 4, Listing 1).

Das Koppeln von Linux mit einem RTOS war eine strategische Design-Entscheidung, um den Entwicklungsaufwand zu reduzieren. Viele Aufgaben wie die Kommunikation mit dem Netzbetreiber, die Überwachung der Batteriemanagement-Komponenten, das Logging und Überwachen sowie weitere nicht echtzeitkritische Aufgaben werden unter Linux umgesetzt. Dazu dient beispielsweise die High-Level-Sprache Python. Aufgaben mit Echtzeitanforderungen werden auf eine Realtime Processing Unit (RPU) und ein Field Programmable Array (FPGA) ausgelagert. Dabei besteht die RPU aus einem Dualcore-Cortex-R5-Prozessor, der im Lock-Step-Verfahren betrieben wird. Als Accelerated Processing Unit (APU) dient ein Dualcore-A53-Prozessor mit SMP Linux. Sehr flexibel ist die auf der RPU befindliche Regelung, wobei ganze Sub-Module im laufenden Betrieb des Umrichters deaktiviert, gewartet und reaktivierbar sind. Das FPGA dient einerseits zur Entlastung der RPU – über das selbständige Auslesen diverser Analog-Digital-Wandler (ADC) – andererseits als spezielle Input/Outuput (I/O)-Erweiterung.

Listing 1: Device-Tree-Beispiel für einen Zynq-Ultrascale+-Prozessor von Xilinx. In dem Beispiel lädt die Accelerating Processing Unit die Raid Processing Unit und  »remoteproc«, »VirtIO« und »RPMsg« laufen im Kernel.

Listing
/ {
2 reserved-memory{
3 #address-cells=<2>;
4 #size-cells=<2>;
5 ranges;
6 rproc_0_dma_reserved:rproc@3ed40000{
7 no-map;
8 compatible=“shared-dma-pool“;
9r eg=<0x00x3ed400000x00x100000>;
10 };
11 rproc_0_fw_reserved:rproc@3ed00000{
12 no-map;
13 reg=<0x00x3ed000000x00x40000>;
14 };
15 };
16 zynqmp-rpu{
17 compatible=“xlnx,zynqmp-r5-remoteproc-1.0“;
18 #address-cells=<2>;
19 #size-cells=<2>;
20 ranges;
21 core_conf=“split“;
22
23 r5_0:r5@0{
24 #address-cells=<2>;
25 #size-cells=<2>;
26 ranges;
27 memory-region=<&rproc_0_fw_reserved>,<&rproc_0_dma_reserved>;
28 pnode-id=<0x7>;
29 mboxes=<&ipi_mailbox_rpu00>,<&ipi_mailbox_rpu01>;
30 mbox-names=“tx“,“rx“;
31
32 tcm_0_a:tcm_0@0{
33 reg=<0x00xFFE000000x00x10000>;
34 pnode-id=<0xf>;
35 };
36 tcm_0_b:tcm_0@1{
37 reg=<0x00xFFE200000x00x10000>;
38 pnode-id=<0x10>;
39 };
40 };
41 };
42 zynqmp_ipi1{
43 compatible=“xlnx,zynqmp-ipi-mailbox“;
44 interrupt-parent=<&gic>;
45 interrupts=<0294>;
46 xlnx,ipi-id=<7>;
47 #address-cells=<1>;
48 #size-cells=<1>;
49 ranges;
50
51 /_APU<->RPU0IPImailboxcontroller_/
52 ipi_mailbox_rpu0:mailbox@ff90600{
53 reg=<0xff9906000x20>,
54 <0xff9906200x20>,
55 <0xff9900c00x20>,
56 <0xff9900e00x20>;
57 reg-names=“local_request_region“,
58 „local_response_region“,
59 „remote_request_region“,
60 „remote_response_region“;
61 #mbox-cells=<1>;
62 xlnx,ipi-id=<1>;
63 };
64 };
65 };