Raspberry Pi 3 (aarch64) in Assembler programmieren



  • @Finnegan sagte in Raspberry Pi 3 (aarch64) in Assembler programmieren:

    Ja, die 3D-Funktionen des Chips wären bei der lausigen Dokumentation auch eine echte Herausforderung. Da nimmt man vermutlich doch besser Linux und den GLES-Treiber den es da denke ich mal für den Raspi gibt.

    Wie gesagt, gross was mit 3D würde ich gar nicht benötigen. Aber wie du ja selbst sagst, die Dokumentation ist mehr dürftig. Ich habe auf meinem Pi3-Server (Octoprint, Git, Postgres ...) Arch laufen. So an sich ist es deutlich angenehmer, meiner Meinung nach, als das Pi OS. Gerade auch wegen der aktuelleren Pakete und ich finde pacman auch besser als apt. Aber eben, so ist da in meinen Augen alles besser und er scheint auch schneller zu laufen, aber versuch da mal irgendwas mit 3D zu machen. Da ich das aber auf einem Server nicht brauche, kann ich drauf verzichten und deshalb hat die GPU auch nur das Minimum an Speicher.

    Ein paar Beschleuniger-Funktionalitäten wie Zugriff auf einen Blitter oder für Alpha-Blending wären allerdings schon ganz nützlich. Diese Dinge stehen einem aber auch auf dem PC nicht unbedingt zur Verfügung, da diese Funktionen etwa zeitgleich mit dem ersten DirectX als VBE-Erweiterungen in den Vesa-Standard übernommen wurden und sich daher in dieser Form nie sonderlich breit durchgsetzt haben. Letztlich ist VBE auch nur eine simple Grafik-API mit der man sich eben spart, jede einzelne Grafikkarte direkt programmieren zu müssen. Das ist schon eine Abstraktionsebene über der im Link besprochenen Lowlevel-Grafik für den Raspi.

    Nützlich wäre das mit Sicherheit!

    Ich würde auch sagen, dass alles in Assembler zu machen, für ein größeres Projekt wie das wo du da hin willst, auch nicht unbedingt die richtige Wahl ist. Wenn ich sowas umsetzen wollte, dann würde ich mich auf diejenigen Dinge beschränken, die benötigt werden um ein in einer höheren Programmiersprache geschriebenes Programm ausführen zu können. Immer noch "baremetal" wohlgemerkt.

    Ich beginne immer mehr dir da Recht zu geben. Wenn ich alleine bedenke, wie lange ich für ein beklopptes

    char test[11];

    strcpy(test, "Ja, geht!\n");

    jetzt brauche, da fängt die Lust langsam an zu bröckeln. Auf der anderen Seite macht es mir unglaublich viel Spass die Kontrolle über Register und den Speicher zu haben.

    Hier meine ich aber glaube ich, auch in C direkt auf Speicheradressen zugreifen zu können. Ich meine damals auf dem AmigaOS auf die Weise an die Bilder in meiner damaligen Digitalkamera gekommen zu sein. Muss ich mich mal wieder schlau machen, ist verdammt lange her und ich mache schon sehr lange nur C++.

    Das dürfte vor allem erstmal die initialisierung einer Ausführungsumgebung sein. Ich mache in meiner Freizeit gerade etwas ähnliches für PC+DOS, und auch wenn das da sehr anders laufen dürfte, sollte es doch einige Parallelen geben. Das beinhaltet z.B. solche Dinge wie das initialisieren des .bss-Section mit Nullen oder die Speicherdetektion (RAM). Wie viel Speicher gibt es und welche (physischen) Speicherbereiche können von dem Programm überhaupt genutzt werden? Es gibt da ja schliesslich diverse Bereiche des Addressraums, auf die Hardware gemappt ist. Die sollte man tunlichst nicht als regulären Arbeitsspeicher nutzen. Z.B. ein C-Array für reguläre Programmdaten, das durch einen dummen Zufall über ein paar Hardware-Register läuft, wird das Program wahrscheinlich lustige Dinge tun lassen und kann im schlimmsten Fall sogar die Hardware beschädigen. Vielleicht ist das auf dem Raspi ja eindeutig spezifiziert, welche Speicherbereiche das Programm nutzen darf, und welche nicht - da kann man das dann einfach hartcodieren. Auf em PC muss man dafür erstmal das BIOS fragen und bekommt je nach installierter Hardware und BIOS teilweise sehr unterschiedliche Antworten.

    Das ist ja mit ein Grund, warum ich das auf dem Pi machen will. Da hat man eine Hardware. Okay, es gibt Unterschiede zwischen den einzelnen Versionen, aber wenn ich etwas für den Pi3 machen will, dann ist da eben diese eine spezifische Hardware verbaut. Da wird die Hardware immer an die gleiche Stelle gemapt usw. Das ist auch, soweit ich es in den Datenblätter bislang sehen konnte, auch gut dokumentiert.

    Was ich eben immer noch verdammt spannend finde, wie ich eingangs ja den Youtuber genannt habe, in C lädt man diesen Header, öffnet diese Library und dann Magic, Text auf dem Bildschirm. Während man eben in Assembler direkt sagen kann

    mov adresse, irgendwas

    und wenn die Adresse die obere linke Ecke vom Bildschirm ist, hat man dort einen Punkt, ein Zeichen, oder wie auch immer. Richtig die Hardware bei der Arbeit beobachten. Für mich derzeit richtig grosses Kino! Jetzt nicht so wie der das gemacht hat, sondern allgemein gesprochen. Ich will das an Adresse X der Wert Y steht und peng, ist so. Ist ja bei C nicht so. wenn ich da sage:

    int x;

    x = 10;

    dann weiss ich zwar das die Variable X den Wert 10 hat, aber wo das jetzt im Speicher steht , keine Ahnung. Klar, ich könnte einen Zeiger drauf setzen und so dann schauen, wo es sitzt, aber irgendwie. Na ja. Wahrscheinlich ist meine Begeisterung auch gerade völlig irrational, aber sie ist eben da ;).

    Dann muss die CPU auch in einen Modus versetzt werden, die an für die Ausführung des Programms haben will ...

    Ja, da ist auch so was beim Pi. Der muss in El1 versetzt werden, wenn ich mich nicht irre. Das wäre Firmware. Normal ist er in El3, wenn ich da nicht ganz irre.

    Die Idee ist, erstmal eine simple C-Standarbibliothek ans laufen zu bekommen. ...

    Tatsächlich wird das wahrscheinlich in Kürze mein nächster Weg sein. Wenn ich noch weiter so hier dran hänge, ohne wirkliche Fortschritte zu machen, wird der Kurs gewechselt. Es ist unglaublich frustrierend, wenn man einfach keine Fortschritte macht.

    ... eben Gefahr laufe, dass mir ein malloc dann irgendwann einen Speicherbereich zurückgibt, auf den irgendeine Hardware gemappt ist (nicht gut, s.o.).

    Ich habe das im Moment eigentlich so verstanden, dass malloc() nichts anderes macht als das, was ich da in meinem Beispiel gezeigt habe. Also

    char *test;

    test = (char 😉 malloc(sizeof(char), 16);


    test: .byte 16

    Oder irre ich mich da jetzt? Denn in beiden Fällen wird ja Speicher reserviert, ich weiss aber nicht wo im Speicher.

    Wenn eine libc läuft, dann könntest du schonmal immerhin sinnvoll in C weiterprogrammieren. Das dürfte für so ein Projekt auf jeden Fall produktiver sein, als durchgehend alles in Assembler zu machen.

    Ich finde immer weniger Argumente, um dir zu widersprechen 😉

    "Textdatei öffnen" ist halt so ne Sache, wenn man noch kein Dateisystem zur Verfügung hat. Wie gesagt, die Daten wie z.B. die Textdatei in die Binary einbauen, so dass du dann vom Linker ein Symbol für die Text-Daten zur Verfügung gestellt bekommst. Das ist dann in deinem Programm letztendlich eine Speicheradresse, an der die Daten zu finden sind. Der Text wird dann zusammen mit deinem Programmcode geladen. Hier werden ein paar Methoden gezeigt, wie man sowas machen kann.

    Fängt an mir zu gefallen ;).

    Nun, den Speicher hast du ja bereits mit test: .zero .byte 16 reserviert. Allerdings wird dieser direkt in deine Binary eingebaut, als eine Folge von 16 0-Bytes. Wenn du nur den Speicher benötigst, ohne irgenwelche festen Daten, dann ist das eher ein Fall für die .bss-Section und nicht die .data-Section. In ersterer wird nur Speicher reserviert und diese Sektion wird für gewöhnlich auch nicht in die Binary geschreiben. Lediglich Referenzen auf diesen werden vom Linker aufgelöst.

    Ach ja .bss, da war ja was :(. Ganz übersehen.



  • @Finnegan sagte in Raspberry Pi 3 (aarch64) in Assembler programmieren:

    Ich würde damit meine Lebenszeit nicht verschwenden. Entweder nimmt man einen Microcontroller z.B. Raspberry Pi Pico (oder einen Arduino) und steuert dann via SPI ein Display an (dafür gibt es fertige Display Lösungen), oder man bootet den großen Pi direkt in Linux und programmiert das Teil ganz normale als Linux System.

    Das ist gerade ein wenig, als sagtest du einem Modelleisenbahnbauer dass er mit solchen unproduktiven Dingen seine Lebenszeit nicht verschwenden soll. Wenns Spass macht, warum nicht? Ausserdem lernt man dabei einiges über so ein System das vielleicht nochmal nützlich werden kann.

    Wenn man so etwas machen will, dann sollte man sich zumindest eine Hardware aussuchen die auch entsprechend dokumentiert ist. Wir reden hier nicht über einen Nachbau eines C64 sondern über einen 64Bit ARM Prozessor mit OpenGL ES fähigem 3D Chip.



  • @john-0 sagte in Raspberry Pi 3 (aarch64) in Assembler programmieren:

    @Finnegan sagte in Raspberry Pi 3 (aarch64) in Assembler programmieren:

    Ich würde damit meine Lebenszeit nicht verschwenden. Entweder nimmt man einen Microcontroller z.B. Raspberry Pi Pico (oder einen Arduino) und steuert dann via SPI ein Display an (dafür gibt es fertige Display Lösungen), oder man bootet den großen Pi direkt in Linux und programmiert das Teil ganz normale als Linux System.

    Da müsste man dann mal definieren, was eine Verschwendung überhaupt ist. Ich sehe in dem erlernen neuer Fähigkeiten, im der Verfolgen eines für sich erstrebenswerten Ziels keine Zeitverschwendung, auch wenn es am Ende vielleicht nicht produktiv ist. Ich habe schon viel mit Arduinos rum gespielt und tue das auch immer noch. So einen will ich auch mal in Assembler programmieren, einfach weil ich es mal getan haben will.

    Und wenn man es spannend findet, eben nicht direkt auf einem Linux zu programmieren? Weil man das dauernd macht? Wenn es Spass macht, sich einer neuen Herausforderung zu stellen?

    Schlussendlich ist deine Ansicht zu dem Thema genauso richtig und falsch wie meine. Ausserdem nützt sie mir beim gewinnen von Erfahrung eigentlich eher gar nichts und ist hier dann entsprechend auch etwas überflüssig.

    Wenn man so etwas machen will, dann sollte man sich zumindest eine Hardware aussuchen die auch entsprechend dokumentiert ist. Wir reden hier nicht über einen Nachbau eines C64 sondern über einen 64Bit ARM Prozessor mit OpenGL ES fähigem 3D Chip.

    Ähm, bist du zufällig auch in Foren unterwegs, wo Projekte für den Pi vorgestellt werden? Wo man eine 64Bit ARM Architektur mit OpenGL ES fähigem 3D Chip als Wetterstation oder Internetradio verwendet?

    Oder anders gefragt, soll ich mir jetzt irgendwo einen C64 kaufen, oder was in der Art, weil die Hardware da einfacher ist? Mich aber unter Umständen super viel Geld kostet? Anstatt einen Pi zu nehmen, den ich eh schon habe? Oder gleich alles in Chip8 umsetzen?

    Es gibt einen Spruch eine US-Präsidenten, den ich gut finde. Wir haben uns nicht dazu entschlossen es zu tun, weil es leicht ist, sondern wir haben uns dazu entschlossen, weil es schwer ist (kein Zitiat, aber dürfte klar sein was ich meine).

    Man wächst ja mit der Herausforderung, oder?



  • @john-0 sagte in [Raspberry Pi 3 (aarch64) in

    Wenn man so etwas machen will, dann sollte man sich zumindest eine Hardware aussuchen die auch entsprechend dokumentiert ist. Wir reden hier nicht über einen Nachbau eines C64 sondern über einen 64Bit ARM Prozessor mit OpenGL ES fähigem 3D Chip.

    Die Sachen um einen Videomodus zu setzen und einen Frambuffer zu bekommen sahen mir jetzt eher nicht ganz so wild aus. Das nutzt zwar nicht sämliche Fähigkeiten des Grafikchips, reicht aber schon aus um eine Menge Spass damit zu haben. Wenn du die Framebuffer-Speicheradresse hast, dann kannst du deine Pixel direkt in den Speicher schreiben. Und das kann durchaus nur eine Editor-Seite Assembler-Code sein, bis man an diesem Punkt angelangt ist. Zumindest auf dem PC bewegt sich die Codemenge in dieser Größenordnung.

    Die Felder virtual_width, virtual_height, x_offset und y_offset der Raspi-Framebuffer-Datenstruktur sehen mir auch so aus, als ob man damit eventuell sogar Double-Buffering und Soft-Scrolling hinbekommen könnte. Mir persönlich würd das zum "spielen" schonmal reichen. Damit geht schon einiges, selbst wenn man die CPU die meisten Kopier- und Blending-Operationen machen lässt, die eigentlich auch die GPU könnte. Das war schon damals auf nem ranzigen i386 flott genug, das sollte ein Raspi doch um einiges besser hinbekomen 😉

    Ausserdem will man sehr wahrscheinlich selbst bei erstklassiger Dokumentation des Grafikchips nicht wirklich jedes Feature der Hardware "zu Fuß" nutzen. Das wäre zumindest mir persönlich dann doch zu viel Zeug und zu komplex. Für anspruchsvollere 3D-Sachen würde ich dann auch in ein Linux booten und den GLES-Treiber nutzen.

    Edit: @john-0 wenn du allerdings eine besser dokumentierte Hardware als einen Raspi vorzuschlagen hast, die vergleichbare GPU-Fähigkeiten hat, wäre ich nicht abgeneigt. Es wäre schon ziemlich cool, zumindest die Möglichkeit zu haben, auch die komplexeren GPU-Funktionen direkt programmieren zu können. Vielleicht sind ja ein paar Dinge nicht so kompliziert. Der erwähnte Blitter und Alpha Blending in Hardware wären zumindest etwas, in das ich persönlich noch zusätzliche Arbeit reinstecken würde, das nutzbar zu machen.

    Und wenn als Alternative eh nur simplere Hardware gibt, mit der man auch nur maximal einen Framebuffer bekommt, dann kann man (zumindest zum experimentieren) IMHO auch gleich beim Raspi bleiben. Erst recht wenn man den eh schon rumliegen hat 😉



  • @Hirnfrei sagte in Raspberry Pi 3 (aarch64) in Assembler programmieren:

    Ich habe das im Moment eigentlich so verstanden, dass malloc() nichts anderes macht als das, was ich da in meinem Beispiel gezeigt habe. Also

    
    test = (char *) malloc(sizeof(char), 16);
    
    ---
    
    test: .byte 16
    

    Oder irre ich mich da jetzt? Denn in beiden Fällen wird ja Speicher reserviert, ich weiss aber nicht wo im Speicher.

    Nein, malloc funktioniert anders. Wenn du in Assembler wie oben die 16 bytes reservierst, dann werden die 16 Bytes an der Stelle, wo das Label steht, direkt in dein Binary eingebettet (wenn es die .data oder .text Section ist), oder in den Speicherbereich, in den dein Binary geladen wird. Wo genau ist letztendlich davon abhängig, wo der Linker, bzw. dein Linker-Skript die Sektion, in der sich der Speicherbereich befindet platziert. Der Speicherbereich befindet sich aber für gewöhnlich dann innerhalb der Sektion exakt an der Stelle, an den du ihn in deinem Assembler-Code hingeschrieben hast. Vereinfacht gesagt befinden sich die 16 Byte dann fest eincodiert "mitten zwischen deinem Code". Da lässt sich nachträglich auch nichts mehr feigeben oder die Größe ändern.

    malloc hingegen ist ein dynamischer Speichermanager. Es gibt ja schliesslich auch noch das free-Pendant. Dieser kann während der Laufzeit angeforderten Speicher in belieberiger Größe (im Gegensatz zur zur Link-Zeit fest vorgegebener Größe) reservieren, wieder freigeben und dann auch für spätere Anforderungen erneut nutzen.

    Dazu fordert die Malloc-Implementierung Speicher vom System an, den sogenannten Heap. Schau dir mal diese Grafik hier an, das ist in etwa wie der Programmspeicher bei simplen Systemen meistens organisiert ist. Der Stack wächst von oben (hohe Adressen) nach unten und der Heap von unten nach oben. Zwischen Heap und Stack befindet sich feier Speicher. Die sbrk()-Funktion, mit der mehr Heap-Speicher angefordert wird, macht im Prinzip nichts anderes, als den Pointer auf das Ende des Heaps zu erhöhen und ihn näher in Richtung des Stacks zu bewegen (allerings ist sbrk heutzutage schon eine etwas archaische Methode, unter Linux wird da eher mmap verwendet. Für so simple Baremetal-Programme tuts aber durchaus auch ein sbrk-Ansatz).

    Diesen Heap-Speicher verwaltet nun die Malloc-Implementierung und baut darin u.a. auch eigene Datenstrukturen auf, mit denen der Heap-Speicher möglichst effizient dynamisch verwaltet werden kann. Das können Listen mit freien Heap-Speicherbereichen sein, Baumstrukturen und ähnliches. Ziel ist es dabei, erstens die malloc-Anfragen möglichst schnell bedienen zu können und zweitens auch bei unterschiedlichsten malloc/free Anfragemustern, welche die Anwendung produziert, die Fragmentierung des Heap-Speichers möglichst niedrig zu halten. Du kannst dir sicher vorstellen, dass bei einer naiven Implementierung nach etlichen malloc/free unterschiedlichster Größe der Heap irgendwann aussähe wie ein Schweizer Käse, so dass Anforderungen von großen, zusammenhängenden Speicherbereichen nicht mehr bedient werden können. Malloc-Implementierungen versuchen das so gut wie möglich zu vermeiden. Dafür braucht es schon einiges an Logik und Datenstrukturen mit Metadaten. Das ist im Prinzip, was ein malloc ausmacht.



  • @Hirnfrei sagte in Raspberry Pi 3 (aarch64) in Assembler programmieren:

    Da müsste man dann mal definieren, was eine Verschwendung überhaupt ist. Ich sehe in dem erlernen neuer Fähigkeiten, im der Verfolgen eines für sich erstrebenswerten Ziels keine Zeitverschwendung, auch wenn es am Ende vielleicht nicht produktiv ist. Ich habe schon viel mit Arduinos rum gespielt und tue das auch immer noch. So einen will ich auch mal in Assembler programmieren, einfach weil ich es mal getan haben will.

    Die GPU der Raspberry Pis sind nicht öffentlich dokumentiert. D.h. wenn man damit was machen will läuft man Gefahr, dass man Reverse Engineering machen muss. Das ergibt wenig Sinn. Daher auch mein Hinweis doch lieber einen Pi Pico zu nehmen und ein Display über SPI anzusteuern, denn dafür gibt es öffentliche Dokumentationen.



  • Ich stricke das jetzt mal um. Wie ich gesehen habe, kann man mit asm(); ja aus C heraus mit den Registern und so spielen. Das funktioniert auch ganz gut wie ich sehe. Allerdings habe ich das jetzt noch nicht als Bare Metal gemacht, da ich überhaupt mal sehen wollte, ob es funktioniert.

    Tut es, nur ist mir etwas aufgefallen.

    void los()
    {
        char begin[] = "Hallo Welt mit Syscall!\n";
    
        asm("mov x8, #64");
        asm("mov x0, #1");
        asm("mov x2, #24");
        asm("svc 0");
    }
    
    

    Die Funktion soll ganz banal einen Syscall aufrufen.

    In x8 steht, welcher Syscall das sein soll. 64 ist write.
    In x0 steht der fd
    in x2 die Länge der Ausgabe
    Und svc 0 feuert das Ganze ab.

    So. Funktioniert hervorragend. Auch mit GDB sehe ich wunderschön, wie jede Zeile ausgeführt wird und genau macht was sie soll. Mit einer Ausnahme!

    char begin[] = "Hallo Welt mit Syscall!\n";

    wird direkt in x1 geschrieben. Dort soll es auch hin, aber woher weiss der Compiler das? Ich sage dem nirgendwo, was ich mit der Variable machen will. Das verwirrt mich mal wieder :(. Ich finde auch keine Erklärung dazu.



  • @Hirnfrei sagte in Raspberry Pi 3 (aarch64) in Assembler programmieren:

    wird direkt in x1 geschrieben. Dort soll es auch hin, aber woher weiss der Compiler das? Ich sage dem nirgendwo, was ich mit der Variable machen will. Das verwirrt mich mal wieder :(. Ich finde auch keine Erklärung dazu.

    Das könnte sehr gut Zufall sein. Eigentlich gibt es ja keinen Grund, dass der Compiler mit begin überhaupt irgendwas macht. Das ist nur eine lokale Variable, die nicht weiter verwendet wird, die kann eigentlich komplett ignoriert werden. Könnte es vielleicht sein, dass du keine Optimierungen aktiviert hattest? Lokale Variablen werden normalerweise auf den Stack gespeichert oder in Registern. Vielleicht ist x1 einfach nur der Ort, an dem der Compiler das begin in diesem Fall ablegt, wenn es nicht rausoptimiert wurde.

    Der Code ist IMHO auf jeden Fall nicht korrekt, auch wenn er funktioniert. Du solltest in einem asm-Block auf jeden Fall explizit x1 setzen.



  • @Finnegan sagte in Raspberry Pi 3 (aarch64) in Assembler programmieren:

    Das könnte sehr gut Zufall sein. Eigentlich gibt es ja keinen Grund, dass der Compiler mit begin überhaupt irgendwas macht. Das ist nur eine lokale Variable, die nicht weiter verwendet wird, die kann eigentlich komplett ignoriert werden. Könnte es vielleicht sein, dass du keine Optimierungen aktiviert hattest? Lokale Variablen werden normalerweise auf den Stack gespeichert oder in Registern. Vielleicht ist x1 einfach nur der Ort, an dem der Compiler das begin in diesem Fall ablegt, wenn es nicht rausoptimiert wurde.

    Richtig. Ich optimiere da gar nichts. Zumindest nicht bewusst. Es ist eben wirklich interessant, dass es ausgerechnet bei X1 gespeichert wird. X0 würde nichts bringen, genauso wenig wie X8 und X2. Ausserdem wird es direkt in der entsprechenden Zeile in X1 gesetzt. Da könnte man also nicht einmal annehmen, es wäre weil der Compiler merkt was ich da vorhabe. Mysteriös.

    Der Code ist IMHO auf jeden Fall nicht korrekt, auch wenn er funktioniert. Du solltest in einem asm-Block auf jeden Fall explizit x1 setzen.

    Wenn ich jetzt zum Beispiel:

    asm("ldr x1, =r" : (begin));
    

    einbaue, jammert der Compiler etwas wegen "string literal". Also wie ich jetzt die Variable da rein bekomme, da bin ich noch nicht ganz firm.



  • Falls der Compiler für asm AT&T-Syntax (wie z.B. der gcc) verwendet, dann schau mal in die Antwort von manipulating c variable via inline assembly.



  • @Th69 sagte in Raspberry Pi 3 (aarch64) in Assembler programmieren:

    Falls der Compiler für asm AT&T-Syntax (wie z.B. der gcc) verwendet, dann schau mal in die Antwort von manipulating c variable via inline assembly.

    Super. Das hat mir auf jeden Fall schon etwas geholfen. Funktioniert aber leider nur mit Integer.



  • @Hirnfrei sagte in Raspberry Pi 3 (aarch64) in Assembler programmieren:

    @Th69 sagte in Raspberry Pi 3 (aarch64) in Assembler programmieren:

    Falls der Compiler für asm AT&T-Syntax (wie z.B. der gcc) verwendet, dann schau mal in die Antwort von manipulating c variable via inline assembly.

    Super. Das hat mir auf jeden Fall schon etwas geholfen. Funktioniert aber leider nur mit Integer.

    Auch ein Pointer ist aus Assembler-Perspektive ein Integer. Oder geht es hier noch um irgendwas andereres, wie einen Float? Man möge mich korrigieren, aber was verschiedene Datentypen in Registern angeht, sind mir in Assembler eigentlich nur (signed/unsigned) Integer und Floats unterschiedlicher Breite untergekommen. Und auch wenn ich das noch nicht gebraucht habe, sollten sich letztere denke ich mal auch in einem inline-Assembly-Block initialisieren lassen. Im Zweifel via (reinterpret-) Cast auf einen Integer er gleichen Breite. Bei Registern sind m.E. nur die Bits ausschlaggebend, die a drin stehen.



  • @Finnegan sagte in Raspberry Pi 3 (aarch64) in Assembler programmieren:

    Auch ein Pointer ist aus Assembler-Perspektive ein Integer. Oder geht es hier noch um irgendwas andereres, wie einen Float? Man möge mich korrigieren, aber was verschiedene Datentypen in Registern angeht, sind mir in Assembler eigentlich nur (signed/unsigned) Integer und Floats unterschiedlicher Breite untergekommen. Und auch wenn ich das noch nicht gebraucht habe, sollten sich letztere denke ich mal auch in einem inline-Assembly-Block initialisieren lassen. Im Zweifel via (reinterpret-) Cast auf einen Integer er gleichen Breite. Bei Registern sind m.E. nur die Bits ausschlaggebend, die a drin stehen.

    Aua. Klar, Zeiger = Speicheradresse. Der tat weh ;). Habe meinen Fehler eingesehen. Jetzt heisst es mit rumspielen :). Den Threat lasse ich aber mal auf, da kommen sicher noch Fragen.



  • @Hirnfrei sagte in Raspberry Pi 3 (aarch64) in Assembler programmieren:

    Aua. Klar, Zeiger = Speicheradresse. Der tat weh ;). Habe meinen Fehler eingesehen. Jetzt heisst es mit rumspielen :). Den Threat lasse ich aber mal auf, da kommen sicher noch Fragen.

    Hehe, Jo. Assembler ist so low-level, da sind selbst so grundlegende Abstraktionen wie Typen etwas, dass man sich manchmal bewusst abgewöhnen muss 😉



  • @Finnegan sagte in Raspberry Pi 3 (aarch64) in Assembler programmieren:

    @Hirnfrei sagte in Raspberry Pi 3 (aarch64) in Assembler programmieren:

    Aua. Klar, Zeiger = Speicheradresse. Der tat weh ;). Habe meinen Fehler eingesehen. Jetzt heisst es mit rumspielen :). Den Threat lasse ich aber mal auf, da kommen sicher noch Fragen.

    Hehe, Jo. Assembler ist so low-level, da sind selbst so grundlegende Abstraktionen wie Typen etwas, dass man sich manchmal bewusst abgewöhnen muss 😉

    So ist es. Ich hab mich ja schon schwer getan, bis ich das mit den Registern verstanden hatte. Da wird irgendein Wert i x8 gepackt dann einer in x0. Wtf, was das für ein Quatsch was bringt das.

    Dann der Aha Moment.

    Ah, der Wert vom Syscall kommt nach x1. Der Filedescripter nach x0, die Ausgabe nach x1, die Ausgabelänge nach x2 und dann mit svc 0 schiesst man das erst ab.

    Aber schau an, da lernt man dann wie so ein Prozessor funkitoniert! Zumindest im Prinzip. Finde ich super Spannend!

    Ich hab jetzt aber auch einen Fahrplan. Da ich jetzt einen HDMI Capture hab und somit auch auf meinem Computer sehe, was der Pi da macht, geht es jetzt richtig los mit dem Bare-Metal und auch nicht mehr mit qemu, sondern richtig auf dem Pi.

    1. Hallo Welt (wer hätte es gedacht
    2. Datei laden (kombination aus C mit asm())
    3. Framebuffer
    4. Hoffen das ich soweit komme 😉

    Ich muss aber mal doof fragen. Ich weiss genau, ich habe das schon einmal irgendwo gemacht oder gelesen. Man kann einen Zeiger doch auf eine bestimmte Adresse vom Speicher zeigen lassen, oder irre ich da?



  • @Hirnfrei sagte in [Raspberry Pi 3 (aarch64) in

    Ich hab jetzt aber auch einen Fahrplan. Da ich jetzt einen HDMI Capture hab und somit auch auf meinem Computer sehe, was der Pi da macht, geht es jetzt richtig los mit dem Bare-Metal und auch nicht mehr mit qemu, sondern richtig auf dem Pi.

    Das absolute Sahnehäubchen wäre natürlich noch, einen gdbserver-Stub in deinem Code ans laufen zu bekommen. Damit könnte man dann sogar Remote-Debuggen. Also in deinem Editor Breakpoints setzen, durch das Programm steppen und gleichzeitig via HDMI eine Ausgabe zu haben. Da kann ich aber keine Tips geben, wie man das genau angeht, das habe ich in der Form noch nie gemacht. Dafür müsste man dem gbserver beibringen, den seriellen Port des Raspi zu nutzen. Gut möglich aber, das es da schon fertige Lösungen gibt. Denke da lohnt sich eine Recherche, wenn du dein Projekt wirklich so durchziehen willst.

    1. Hallo Welt (wer hätte es gedacht
    2. Datei laden (kombination aus C mit asm())
    3. Framebuffer
    4. Hoffen das ich soweit komme 😉

    Das klingt ja schonmal nach nem Plan. Allerdings bin ich mir nicht so sicher, was du mit "Datei laden" meinst. Das Thema haben wir ja schonmal angesprochen. Du hast kein Dateisystem und auch noch keine libc. D.h. auch keine lowlevel-Funktionen, mit denen man irgendwie Dateien öffnen könnte (open/close/read/write). Ja, noch nichtmal ein malloc, d.h. es funktionieren derzeit nur statische Allokationen wie der funktionslokale "Hallo Welt"-String, oder irgendwelche Arrays fester Größe.

    Ich muss aber mal doof fragen. Ich weiss genau, ich habe das schon einmal irgendwo gemacht oder gelesen. Man kann einen Zeiger doch auf eine bestimmte Adresse vom Speicher zeigen lassen, oder irre ich da?

    Ich benötige sowas nur wirklich selten, da ich kaum so hardwarenahe Sachen mache, aber einen Zeiger auf eine feste Adresse zeigen zu lassen, müsste in etwa so gehen:

    unsigned char* memory = (unsigned char*) (0xabcd);
    

    Einfach ein Integer-Literal mit der gewünschten Adresse in einen Pointer casten. In C++ dann mit reinterpret_cast. Mit char-Pointern sollte das unproblematisch sein, beim wildem hin- un her-casten sollte man aber vorsichtig sein, da können eventuell Regeln zu Strict Aliasing und in C++ Regeln zur Lebenszeit und zu uninitialisierten Objekten reinspielen. Da kommt man schonmal schnell in UB-Territorium.

    Speicher, dessen Inhalt auch unabhägig von deinem Programm von der Hardware geändert werden kann, sollte zudem auch noch über einen volatile-Pointer angesprochen werden. Sonst könnte Compiler anfangen, Leseoperationen rauszupotimieren, weil der Speicher innerhalb des C-Programms ja gar nicht modifiziert wurde. Das wäre dann für Pointer, über die du mit der Hardware kommunizierst und diese dir auch antwortet, indem sie in den Speicher schreibt. Für einen Framebuffer selbst sollte man das aber noch nicht benötigen. Wohl aber für Pointer auf irgendwelche Hardware-Register.



  • @Finnegan sagte in Raspberry Pi 3 (aarch64) in Assembler programmieren:

    @Hirnfrei sagte in [Raspberry Pi 3 (aarch64) in

    Das absolute Sahnehäubchen wäre natürlich noch, einen gdbserver-Stub in deinem Code ans laufen zu bekommen. Damit könnte man dann sogar Remote-Debuggen. Also in deinem Editor Breakpoints setzen, durch das Programm steppen und gleichzeitig via HDMI eine Ausgabe zu haben. Da kann ich aber keine Tips geben, wie man das genau angeht, das habe ich in der Form noch nie gemacht. Dafür müsste man dem gbserver beibringen, den seriellen Port des Raspi zu nutzen. Gut möglich aber, das es da schon fertige Lösungen gibt. Denke da lohnt sich eine Recherche, wenn du dein Projekt wirklich so durchziehen willst.

    Da ist qemu ja wirklich von Vorteil. Da kann ich das starten. Mache ich auch schon die ganze Zeit so. Hat schon Recht, dass wäre echt der Knaller!

    Das klingt ja schonmal nach nem Plan. Allerdings bin ich mir nicht so sicher, was du mit "Datei laden" meinst. Das Thema haben wir ja schonmal angesprochen. Du hast kein Dateisystem und auch noch keine libc. D.h. auch keine lowlevel-Funktionen, mit denen man irgendwie Dateien öffnen könnte (open/close/read/write). Ja, noch nichtmal ein malloc, d.h. es funktionieren derzeit nur statische Allokationen wie der funktionslokale "Hallo Welt"-String, oder irgendwelche Arrays fester Größe.

    Nimm "Datei laden" nicht zu wörtlich. Eben den Inhalt einer Datei da rein bekommen. Ob jetzt fest eingelötet, wie du mir den Link geschickt hast, oder über Dateisystem, oder Bytes von dem Datenträger lesen, oder eigens Filesystem bauen, spielt da jetzt keine wirklich Rolle. Ich meine damit einfach nur, den Inhalt einer Datei in das (wie nennt man das jetzt eigentlich? OS? Bare-Metal-OS?) Programm zu bekommen um damit zu arbeiten. Auf welchem Weg lasse ich einfach mal offen.

    Genau genommen weiss ich auch noch gar nicht, wofür ich es benutzen werden. Ideen habe ich da viele. Vielleicht so eine Art GrundOS bauen, was sich dann aus einer spezifischen Datei das eigenliche Programm holt? Oder, was ich mir gerade eher vorstellen könnte, wäre Einstellungen für den ODB2 damit zu setzen, ohne gleich das Programm neu bauen zu müssen. Nicht jedes Steuergerät gibt die gleichen Daten raus. So könnte ich das selbe Programm auch einfach für mein zweites Auto verwernden. Als Beispiel jetzt mal. Ich will eben einfach die Möglichkeit haben, da auch Daten laden zu können. Wobei speichern auch cool wäre. GPS Daten als Fahrtenbuch oder so.

    Oh je, bei mir ist gerade eine Idee eingeschlagen ... Ich hab schon ein Video gesehen, wo sehr gut die Nutzung vom Mini-UART demonstriert wird. Ich könnte meinem Pi ja ein Floppy bauen :). Als ein SD.-Card Lesegerät. Mit einem Arduino ist das auslesen und speichern auf eine SD-Karte ja easy. Hab ich schon oft gemacht auch formatieren, Daten auslesen und das alles. Die Daten könnte ich dann ja zum Pi über UART schaufeln.

    Boah. Entdecke die Möglichkeiten!

    Ich benötige sowas nur wirklich selten, da ich kaum so hardwarenahe Sachen mache, aber einen Zeiger auf eine feste Adresse zeigen zu lassen, müsste in etwa so gehen:

    unsigned char* memory = (unsigned char*) (0xabcd);
    

    Einfach ein Integer-Literal mit der gewünschten Adresse in einen Pointer casten. In C++ dann mit reinterpret_cast. Mit char-Pointern sollte das unproblematisch sein, beim wildem hin- un her-casten sollte man aber vorsichtig sein, da können eventuell Regeln zu Strict Aliasing und in C++ Regeln zur Lebenszeit und zu uninitialisierten Objekten reinspielen. Da kommt man schonmal schnell in UB-Territorium.

    Das teste ich mal.. Klarr, vorsichtig muss man da sein. Würde ich auch nur dann nutzen, wenn es notwendig ist. Siehe unten.

    Speicher, dessen Inhalt auch unabhägig von deinem Programm von der Hardware geändert werden kann, sollte zudem auch noch über einen volatile-Pointer angesprochen werden. Sonst könnte Compiler anfangen, Leseoperationen rauszupotimieren, weil der Speicher innerhalb des C-Programms ja gar nicht modifiziert wurde. Das wäre dann für Pointer, über die du mit der Hardware kommunizierst und diese dir auch antwortet, indem sie in den Speicher schreibt. Für einen Framebuffer selbst sollte man das aber noch nicht benötigen. Wohl aber für Pointer auf irgendwelche Hardware-Register.

    Prinzipiell geht es mir auch nur darum. Wenn ich bei Assembler eins gelernt habe, dann das Hardware immer irgendwo in den Speicher gemampt ist und den muss man ja dann auch ansprechen können.


Anmelden zum Antworten