Hello, Guest the thread was called3.6k times and contains 98 replays

last post from Snoopy at the

Verwendung von SYS in Bank 0 führt zu Absturz

  • sparhawk

    Vielleicht richtest du die Frage auf Discord in ‚closed-rom’ direkt an Bit Shifter.

    Es wäre schon interessant zu wissen wie der SYS Befehl sauber anzuwenden ist.

    Ich habe das dort jetzt mal gepostet. Allerdings finde ich Discord (oder generell einen Chat) für sowas nicht sonderlich nützlich,weil man da auch alle möglichen anderen Sachen diskutiert. Da ist ein Thread schon fokussierter.


    Quote

    Wie grubi schrieb lässt sich der IRQ durchaus vom BASIC aus abschalten.

    Ja, das geht vielleicht, aber ich finde das schlechtes Design, wenn man dem User aufbürdet dass er so ein Monstrum an Parametern übergeben muss nur um einen Fehler im OS auszubügeln. Das kann man als Workaround machen, aber sollte keine Dauerlösung sein.

  • sparhawk

    Wenn du auf Discord in ‚closed-roms‘ etwas zurückblätterst, dann kannst du Bit Shifters Sicht der Dinge nachlesen.

    Es ist wohl so gedacht, das der User bei Verwendung des SYS Befehls komplett selbst für alles verantwortlich ist.

    Grundsätzlich ist das auch ok. Nur wäre ein deutlicherer Hinweis mit ein paar erklärenden Worten dazu im Handbuch wünschenswert.


    Aber vielleicht schreibt er ja noch was erhellendes im Discord dazu.

  • Ich antworte hier mal als MEGA65 User, der ich ja war, als ich mit dem Mandelbrot Benchmark angefangen habe:


    Ja, das ganze Banking zu verstehen und ein Programm zum laufen zu bringen ist kompliziert. Dafür muss man erstmal verstehen wie das beim MEGA65 läuft und was man alles zu beachten hat. Aber wenn man es verstanden hat, dann kommt man damit auch klar. Keiner sagt das das am Anfang einfach ist... aber es gibt Ressourcen die einem helfen können. Z.B. Beispiele vielen vielen Programmierern die man verwenden kann um zu versthen wie die ein Problem gelöst haben.


    Auch das Handbuch mag noch nicht perfekt sein (aber es ist viel besser als vor einem halben Jahr). Ebenso gibt es bestimmt noch hunderte (wahrscheinlich tausende) Dinge die Bit Shifter am ROM verbessern möchte. Und auch die Hardware ist ein vielen Stellen noch zu verbessern. Aber dass ist ja das schöne an so einem Projekt: alles lässt sich noch verbessern oder erweitern. Denn man muss nur ein neues Handbuch oder ROM oder Bitstream runterladen.


    Zu beachten ist auch: es gab gar keinen fertigen C65! Das ist sicher allen hier klar, aber es schadet nicht das zu wiederholen. Und natürlich ist der MEGA65 jetzt kein C65 mehr. Kann er auch gar nicht sein, denn es gab ja gar keinen fertigen C65!


    Wer jetzt sagt das Veränderung böse ist, dem muss ich antworten: Die Platform ist dazu gedacht sich verbessern zu können. Warum also sollte man das nicht machen? Also ich würde mich ärgern wenn sich da nix ändert. Vor ein paar Tagen habe ich mir meinen alten C128 aus dem Keller geholt. Wenn man sich den ansieht dann ist klar das das auch früher schon so war! Ein Parallelkabel zur 1571, Schalter für diverese Features auf dem Board ein oder aus zu schaltenn extra ROMs mit Macro Assembler, veränderte ROMS, CHARSETS, usw. Wo bitte ist also jetzt jeder alte C64/C128 gleich? Da wurde schon immer drann rumgedoktort. Und wenn dadurch was nicht mehr geklappt hat, na dann kam der nächste Hardware Workaround um das zu fixen. Mit dem MEGA65 wird das viel einfacher.... also rege ich mich bestimmt nicht darüber auf das ich eine flexible und anpassbare Retro Plattform bekoken werde, die in der Zukunft noch besser werden kann!

  • Es ist wohl so gedacht, das der User bei Verwendung des SYS Befehls komplett selbst für alles verantwortlich ist.

    Das sehe ich nicht zwingend so. Wenn man ein ML Programm schreibt, dann will man ja nicht gleich sein eigenes OS machen. Abgesehen davon ist der SYS BEfehl in der Form quasi nutzlos, weil man ja praktisch immer gezwungen ist, die ganze Befehlsleiste anzugeben. Aus Usersicht ist das nutzlos, denn ein User weiss nicht unbedingt ob er das weglassen kann oder nicht. Also kann man den SYS Befehl dann auch gleich so machen dass die restlichen Parameter als Default angenommen werden, weil man sie sowieso eigentlich immer mit angeben muss.

    Ich halte halt nicht viel von Programmdesign wo der User gezwungen wird sich sinnlose Sachen zu merken, wenn man das ganz leicht auch direkt mit implementieren kann.

  • So, ich hab's jetzt geschafft ein Programm in Bank 0 laufen zu lassen und die System-Interrupts trotzdem weiter laufen zu lassen, ohne dass gleich alles abstürzt. So kann ich z.B. auch eine Tastaturabfrage machen, da der System-IRQ die aktuell gedrückte Taste in $D4 abspeichert.


    Hier das Beispielprogramm, im Speicher bei Adresse $4300 in Bank 0:


    Es ist eine simple Warteschleife, die - damit man was zu sehen hat - die drei ersten Zeichen auf dem Bildschim durchzählt. Extra ineffizient programmiert, so dass der ganze Vorgang ca. 20 Sekunden dauert. Ausserdem wird die Tastatur per $D4 abgefragt, und sobald die Leertaste gedrückt wird, wird das Programm vorzeitig beendet.


    Der Aufruf mit:


    BANK 0

    SYS $4300


    führt natürlich wie gehabt zum Absturz, da in Bank 0 der KERNAL fehlt, und der nächste Interrupt ins Leere läuft. Aber man kann das Programm mit


    BANK 0

    SYS $4300,0,0,0,0,4


    laufen lassen, da so die Interrupts abgeschaltet sind und nichts schlimmes mehr passieren kann - hatten wir im Thread ja schon ausführlich besprochen.


    ACHTUNG: Wenn man das mit ...,0,0,0,0,4 einmal macht, werden die so mit SYS gesetzten Register A,X,Y,Z,P in der Zeropage nach Beenden des Programms bei $06,$07,$08,$09,$05 abgespeichert. Und wenn man jetzt mit SYS $4300 ohne weitere Parameter aufruft, werden sie von da wieder geholt. D.h. ab jetzt sind die Interrupts immer aus, bis man wieder mal SYS $4300,0,0,0,0,0 macht - ab dann sind sie wieder an. Zumindest, solange das Maschinenprogramm den Status nicht verändert.


    Wenn man das Programm ohne Interrupts laufen lässt, stürzt es zwar nicht ab, aber die Tastaturabfrage funktioniert nicht, da der KERNAL-Interrupthandler nicht läuft. Den will ich eigentlich haben, damit ich alles machen kann, wozu man eben so einen KERNAL hat. Also, die Unterbrechung mit der Leertaste funktioniert nicht, man muss 20 Sekunden warten bis die Schleife durch ist.


    Da das Thema jetzt etwas komplexer wird, mache ich mal einen Mehrteiler draus. Wie kann ich den KERNAL jetzt trotzdem laufen lassen? Es geht im nächsten Beitrag weiter.

  • Teil 2 - Die Grundidee


    Natürlich könnte ich in BANK 128 bleiben und mein Programm einfach laufen lassen. Dann habe ich aber nur Speicher von $0000-$1FFF, und der Rest ist mit ROM belegt. Und in diesen ersten 8KB ist nicht mehr wirklich viel Speicher frei. Also gehe ich nach BANK 0, und habe dort 64 KB RAM für mich ganz alleine. Jetzt ist das ROM mit dem KERNAL bei $E000-$FFFF nicht mehr sichtbar. Und eigentlich hätte ich auch gerne Zugriff auf den I/O-Bereich mit VIC, SID, CIA etc. und das Farbram bei $D000-$DFFF. Was tun?


    Ich mappe dieses ganze Zeug einfach in den Adressraum rein. 16 KB kann ich verschmerzen (Mapping geht immer in 8KB-Blöcken). Dann habe ich immer noch 48 KB für mich.


    Die MAP wird mit den Register A,X,Y,Z gesetzt:


    LDA #$00

    LDX #$E0

    LDY #$00

    LDZ #$83

    MAP


    Damit habe ich jetzt:

    $0000-$1FFF ungemappt, also die ersten 8 KB mit den Systemvariablen, dem Stack etc.

    $2000-$7FFF auf RAM in Bank 0 (könnte man nach demselben Prinzip auch mit Bank 1 machen)

    $8000-$BFFF ungemappt, also RAM aus Bank 0

    $C000-$DFFF ungemappt. Hier gibt es eine Besonderheit (siehe nächster Abschnitt).

    $E000-$FFFF auf Bank 3 gemappt, hier ist jetzt also das KERNAL-ROM zu sehen.


    Normalerweise ist es so, dass ungemappte Bereiche halt auf das entsprechende RAM in Bank 0 gehen. Bei $C000-$CFFF und $D000-$DFFF gibt es hier allerdings eine Ausnahme. Der 45GS02 Prozessor signalisiert über die MAP-Leitung nach außen, ob ein Speicherzugriff gemappt oder ungemappt erfolgt. Das gibt dem Adressdecoder die Möglichkeit für weitere Mappings, die über die MAP-Anweisung hinausgehen.


    Wenn man $C000-$DFFF auf RAM oder ROM-Bereiche mappt, so sind diese Bereiche sicht- und nutzbar. Wird der Bereich allerdings nicht gemappt (wie im obigen Beispiel), so entscheidet das Register $D030 im VIC darüber, was passiert. Hier gibt es einige Flags, um bestimmte Speicherbereiche in den 64KB-Adressraum der CPU einzublenden. Interessant ist hier das Bit 5 (ROMC). Ist es gesetzt, so wird bei $C000-$CFFF das Interface-ROM eingeblendet, das eigentlich in Bank 2 liegt. Achtung beim Rumspielen mit $D030: Die Bits 1 und 2 haben eine andere Funktion (betreffen die Videoausgabe) und sollten nicht verändert werden. Ich erwähne das, da diese Bits in der alten C65 Spezifikation nicht erwähnt werden und daher leicht übersehen werden können.


    Außerdem wird, wenn $C000-$DFFF nicht gemappt ist, das Standard-Mapping des C64 in diesem Bereich aktiv. D.h. über $01 kann man kontrollieren, was hier erscheint. Wenn man daran nicht rumfummelt, ist hier der I/O-Bereich im Mega65-Modus zu sehen, also alle Register die man sich nur wünschen kann. Das Farbram liegt bei $D800-$DBFF, also nur das erste 1KB des Farbram. Die vollen 2KB die man im Textmodus braucht können mit dem Setzen von Bit 0 in $D030 eingeblendet werden (CRAM2K-Flag), dann werden allerdings die CIAs und andere Register überdeckt und sind nicht mehr zugänglich.


    Also, was haben wir bis jetzt? Unser Programm im RAM zwischen $0000-$BFFF, das Interface bei $C000-$CFFF, den I/O-Bereich mit 1KB Farbram bei $D000-$DFFF, und den KERNAL bei $E000-$FFFF. Ich lege jetzt mal alles in den Bereich $4000-$43FF, einfach weil mir das so gefällt und der Speicher dort frei ist. Das kleine Warteprogramm liegt bei $4300.


    Super, der KERNAL ist da, dann können wir jetzt unser kleines Programm mit Interrupts laufen lassen, oder?


    Nicht so schnell :P


    Was passiert jetzt, wenn ein Interrupt kommt?


    Zunächst holt sich die CPU die Adresse des Interrupt-Handlers aus $FFFE/$FFFF. Das hat bis jetzt gefehlt, aber da wir an dieser Stelle jetzt das ROM eingeblendet haben, funktioniert das schon mal. So weit, so gut.


    Der Interrupt-Handler liegt im KERNAL bei $F9A2. Haben wir eingeblendet, perfekt. Der Handler läuft los.


    Erst mal checkt er, ob das ein BRK oder ein Hardware-Interrupt war. Bei BRK geht es weiter über den Vektor bei $0316, wieder in den KERNAL, wo der Maschinensprache-Monitor gestartet wird.


    In userem Fall wird es aber eher ein Software-Interrupt sein. Der VIC generiert bei jedem Frame einen Interrupt, also 50 oder 60 mal pro Sekunde (je nach Frequenz der Videoausgabe). Das Hochzählen der Basic-Variable TI funktioniert beim Mega65 aber anders, beim C64 war das noch Aufgabe des Interrupt Handlers (der damals von Timer A der CIA#1 ausgelöst wurde, 60 mal pro Sekunde, unabhängig von der Bildfrequenz). Dieser Interrupt hat uns bisher Probleme bereitet, denn ein aufgetretener Interrupt muss vom Handler bestätigt (acknowledged) werden, damit die Interrupt-Leitung wieder inaktiv wird und alles bereit für den nächsten Interrupt ist. Andernfalls landen wir in einer Endlos-Interruptschleife, wie früher im Thread bereits diskutiert.


    Im Fall des VIC-Interrupts geht das über das Interrupt Control Register bei $D019. Wenn man es liest, zeigen die Bits an, warum der Interrupt ausglöst wurde (z.B. wegen Sprite-Kollision oder Raster-Interrupt). Ein ausgelöster Interrupt wird bestätigt, indem eine 1 an die Stelle des entsprechenden Flags zurückgeschrieben wird.


    Also mit:

    LDA $D019

    STA $D019

    können alle VIC-Interrupts bestätigt werden. Das macht normalerweise der Interrupt-Handler des KERNAL (wenn auch nicht exakt mit dieser Befehlssequenz).


    Bevor das passiert, springt die Interrupt-Routine aber über den Vektor bei $0314/$0135. Dieser führt zurück in den KERNAL (aktuell nach $F96B, aber das kann sich alles jederzeit mit neuen ROMs ändern - ich habe hier noch V920266).


    Dort passiert dann das Interrupt-Acknowleding und eine Menge anderes Zeug, und schließlich wird der Interrupt mit RTI beendet, das Hauptprogramm läuft weiter.


    Aber dieses "eine Menge anderes Zeug" ist leider ein Problem. Das passt nämlich nicht alles in den KERNAL rein, und deshalb verzweigt der Interrupt-Handler irgendwann in das BASIC-ROM ab $2000. Das ist aber gar nicht eingeblendet, schließlich wollten wir ja selbst etwas RAM für unser Programm haben. Also wieder mal: Absturz!

  • Aber dieses "eine Menge anderes Zeug" ist leider ein Problem. Das passt nämlich nicht alles in den KERNAL rein, und deshalb verzweigt der Interrupt-Handler irgendwann in das BASIC-ROM ab $2000. Das ist aber gar nicht eingeblendet, schließlich wollten wir ja selbst etwas RAM für unser Programm haben. Also wieder mal: Absturz!

    Heisst das also, dass beim M65 das BASIC-ROM nicht nur das BASIC-ROM ist, sondern mehr? Das wäre schon ungünstig.

    Beim C128 nimmt das BASIC-ROM auch zuviel Platz weg, aber das habe ich dann halt so gelöst, dass ich einen Stub verwendet habe der sich über SYS starten lässt (würde also beim M65 irgendwo bei $1800+ liegen) und der dann die entsprechenden Mappings einrichtet so dass ich auf I/O und den Kernel Zugriff habe, aber kein BASIC-ROM mir den Platz weg nimmt. Funktioniert das dann auf dem M65 überhaupt so ähnlich?

    Wenn ich jetzt dein Posting so lese klingt das erstmal, als wenn ich für jedes kleinere ML Programm schon ein halbes OS schreiben muss.

  • Teil 3 - Das Standard-Mapping des Mega65: Bank 128


    Damit der Interrupt ordentlich ausgeführt werden kann, brauchen wir also noch mehr ROM in unserem kleinen 64KB-Bereich. Aber eigentlich nicht dauernd, sondern nur wenn gerade ein Interrupt ausgeführt wird.


    Der Mega65 hat ein Standard Mapping, in dem das KERNAL-ROM, das BASIC-ROM, das Grafik-Befehle-ROM (alles aus Bank 3), das Interface-ROM (aus Bank 2) und der I/O-Bereich eingeblendet sind, und dazu noch die ersten 8KB RAM aus Bank 0. Dieses Mapping wird BANK 128 genannt (obwohl es technisch gesehen gar keine Speicherbank ist, sondern eine Zusammenstellung aus verschiedenen Bänken). Im Basic kann man dieses Mapping mit dem Befehl "BANK 128" herstellen. Genau genommen ist nur Bit 7 ausschlaggebend, also alle Bänke von 128-255 führen letztlich zum Standard-Mapping. Nach dem Einschalten ist der Mega65 auf Bank 128.


    Aber jetzt nicht mehr! Unser Programm liegt in Bank 0, und deshalb haben wir das Mapping mit "BANK 0" geändert, bevor wir das Programm mit SYS aufgerufen haben.


    Klasse wäre jetzt, wenn der Computer für die Dauer des Interrupts auf Bank 128 wechseln würde, und danach wieder in unser eigenes Mapping zurückgehen würde, damit unser Programm weiterlaufen kann.


    Im KERNAL gibt es übrigens eine Routine, die das Standardmapping wieder herstellt. Sie hat einen Vektor, der bei $FFF6 liegt. Also mit


    JSR ($FFF6)

    NOP


    kann das Standardmapping jederzeit hergestellt werden - sehr praktisch. Diese Routine führt allerdings nur den MAP-Befehl, nicht aber das noch notwendige NOP aus (heisst in diesem Kontext auch EOM - End of Mapping, hat aber denselben Opcode wie NOP, nämlich $EA). daher müssen wir das NOP noch selber machen.


    Oft muss man in Zusammenhang mit dem Herstellen eines neuen Mapping noch anderes Zeug erledigen, damit Interrupts wieder ordentlich funktionieren. Deshalb sperrt die MAP-Anweisung alle Interrupts so lange bis ein NOP (=EOM) kommt. Man kann also alles Nötige zwischen dem MAP und dem NOP erledigen, ohne Angst vor Interrupts haben zu müssen. Das ist übrigens ein Mechanismus, der unabhängig vom Interrupt-Disable Flag im Statusregister funktioniert.


    Also, was machen wir? Wir biegen den Interrupt-Vektor bei $0314 auf unsere eigene Interrupt-Routine um. Diese stellt das Standardmapping her, ruft dann den normalen Interrupt-Handler des KERNALs (und BASIC-ROMs) auf, der alles macht was er eben so tut - Interrupts bestätigen, Cursor blinken lassen, Tastatur abfragen etc. - und stellen unser eigenes Mapping wieder her, wenn der Interrupt-Handler des KERNAL fertig ist. Dann ist das ganze überflüssige Zeug in unserem Speicher wieder weg, und unser Programm kann weiter laufen.



    Das Hauptprogramm liegt bei $4000. Es stellt unser eigenes Mapping mit I/O bei $D000 und KERNAL ab $E000 her und lässt ansonsten ganz viel RAM in Bank 0 stehen. Bevor das Mapping mit NOP beendet wird, wird der Interrupt-Vektor bei $0314/$0315 noch auf $1F00 gesetzt - hier soll später unsere eigene Interrupt-Routine liegen, die das Standard Mapping herstellt, den KERNAL Handler ausführt und dann wieder zu unserem eigenen Mapping zurückkehrt. Das Unterprogramm bei $4100 legt genau diesen alternativen Interrupthandler bei $1F00 ab. Dann werden mit NOP und CLI die Interrupts wieder erlaubt, und das Hauptprogramm bei $4300 aufgerufen (unsere Zählschleife mit Unterbrechungsmöglichkeit mittels Leertaste, siehe Teil 1).


    Wenn das Programm durchgelaufen ist, werden erst mal Interrupts wieder gesperrt, und der ehemalige Interrupt-Vektor des Systems bei $314/$315 wieder hergestellt. Achtung: Das hier ist ein Hack. Im realen Leben würde man den alten Vektor erst mal sichern, und dann wieder zurück kopieren. In meinem Fall ist der Systemvektor $F96B. Wer weiss, ob das in anderen ROM-Versionen als V920266 noch funktioniert. Das ganze soll nur ein Proof of Concept sein und muss für andere ROMs evtl. adaptiert, oder noch besser ordentlich programmiert werden.


    Ein CLI am Programmende, vor dem RTS, ist übrigens nicht nötig. Die SYS-Routine von BASIC erledigt das - und das ist auch sicherer so, sonst gäbe es für einige Mikrosekunden wieder das Risiko für einen Absturz wenn Interrupts wieder laufen, aber unser Hack schon wieder weg ist. Aus demselben Grund sollte das Hautprogramm mit


    BANK 0

    SYS $4000,0,0,0,0,4


    aufgerufen werden.


    Die SYS-Routine, die letztlich unser Maschinenprogramm bei $4000 aufruft, liegt übrigens im RAM! Und zwar in den ersten 8KB. Sie wird aus dem ROM hierher kopiert. Andernfalls könnte sie ja nicht unser Programm aufrufen, und danach wieder alles aufräumen, denn nur die ersten 8KB RAM sind in allen Bank-Konfigurationen konstant eingeblendet.


    In meinem Fall liegt die SYS-Routine des Systems bei $03A8. Kurz zusammengefasst macht sie folgendes (interessant, was bei einem SYS so alles passiert):


    1. Zunächst wird die Standard-MAP Konfiguration hergestellt. Diese ist bei $0110-$0113 abgelegt (AXYZ-Register). Diese Register werden außerdem auf den Stack gepusht, so dass die Standardkonfiguration nach dem Ende des mit SYS aufgerufenen Programms leicht wieder hergestellt werden kann.

    2. Nur falls wir nicht nach BANK 128 wollen, also z.B. wenn wir vor dem SYS einen "BANK 0" gemacht haben: Die neue MAP Konfiguration einstellen (die einzustellende Bank gemäß dem BANK-Befehl aus BASIC findet sich in Speicherzelle $02). Außerdem wird die aktuelle MAP-Konfiguration in $011C-$011F gesichert. In diesen Speicherzellen kann man immer, auch von Basic aus, nachsehen welche MAP-Konfiguration gerade aktiv ist.

    3. Die Register A,X,Y,Z,P aus den Speicherzellen $06,$07,$08,$09,$05 laden (dort wurden sie entweder durch die zusätzlichen Parameter des SYS-Befehls, oder nach dem Ende eines vergangenen SYS-Aufrufs gespeichert).

    4. Das gewünschte Maschinenprogramm mit JSR aufrufen (die Adresse dafür findet sich in $03-$04).

    5. Nach Ende des Programms die aktuellen Registerinhalte wieder in $05-$09 speichern, so dass sie mit PEEK oder RREG ausgelesen werden können, oder für den nachsten SYS schon bereit stehen.

    6. Die in Schritt 1 auf den Stack gepushte Standard-Mapkonfiguration wiederherstellen (wird auch wieder in $011C-$011F abgelegt), so dass es jetzt wieder ins BASIC ROM zurückgehen kann.

  • Teil 4 - Der neue Interrupt Handler


    Der verbogene IRQ-Vektor bei $0314/$0315 zeigt jetzt also nach $1F00, wo der neue Interrupthandler liegen soll. Er muss in den ersten 8K liegen (die ja immer eingeblendet bleiben), damit er das Standardmapping einstellen kann und dabei nicht selbst verschwindet. Warum gerade bei $1F00? Ich habe erhrlich gesagt keine Ahnung, wo zuverlässig freier Speicher in den ersten 8KB rumliegt. $1F00 hat für mich einfach "gut ausgesehen" und bislang auch funktioniert. Nach einem Reset ist aber alles, was dort liegt, wieder verschwunden, auch wenn der restliche Speicher erhalten bleibt.


    Wir brauchen also noch die kleine Routine bei $4100, die den neuen Interrupt-Handler nach $1F00 kopiert:



    Ganz simpel. Der Interrupt-Handler liegt bei $4200 und wird nach $1F00 kopiert. Hier ist das letzte Puzzlestück: Der neue Interrupthandler. Er besteht aus zwei Teilen.



    Im Screenshot ist er bei $4200 zu sehen - da habe ich ihn im Monitor eingegeben, bei $1F00 verschwindet er sonst nach jedem Reset wieder. Und ich habe viele Resets gebraucht, bis alles funktioniert hat :D


    Also, der Raster-Interrupt wird ausgelöst, läuft über den Vektor bei $FFFE/$FFFF in die Kernel-Routine bei $F9A2. Die sichert erst mal alle Register auf den Stack und springt dann über den Vektor bei $0314/$0315 in den eigentlichen Interrupt-Handler, der eigentlich bei $F96B liegen würde. Stattdessen haben wir den Vektor verbogen, und so landen wir in obiger Routine bei $1F00 (entspricht $4200 im Screenshot).


    Mit JSR ($FFF6) erst mal das Standardmapping (BANK 128) einstellen, damit der KERNAL Interrupthandler funktionieren kann. Der zugehörige NOP ist weiter unten bei $4210 zu sehen, bevor es dann bei $4211 in den alten KERNAL-Interrupthandler bei $F96B geht. Dazwischen liegt allerlei komisches Zeug. Wozu ist es gut?


    Es reicht leider nicht, einfach zum Interrupthandler des KERNAL bei $F96B zu springen. Dieser würde den Interrupt irgendwann beenden, die Register wiederherstellen, und mit einem RTI zurück in unsere Anwendung springen. Jetzt herrscht aber das Standardmapping (BANK 128), also kann unsere Anwendung gar nicht mehr ausgeführt werden. Sie ist schlicht nicht mehr im Adressbereich der CPU sichtbar.


    Daher müssen wir, nachdem der KERNAL Interrupt-Handler ausgeführt wurde, und bevor es zurück zum Anwendungsprogramm geht, unser eigenes Mapping für Bank 0 wieder herstellen!


    Wir brauchen also einen Weg, dass der RTI des KERNAL-Handlers zu uns zurückspringt. Dafür ist das ganze Zeugs vor dem NOP. Ich tue so, als hätte es einen Interrupt gegeben, der meinen Interrupt Handler unterbrochen hat, und schmeiße daher die Rücksprung-Adresse und das Statusregister auf den Stack, wie es halt bei Interrupts so üblich ist. Diese Adresse ist $1F20. Hier soll es nach dem Interrupt-Handler des KERNALs weitergehen.


    Dann kommen noch alle Register auf den Stack - A,X,Y,Z und Base Page, weil die Interrupt-Routine des KERNAL diese vor dem RTI gerne wiederherstellen möchte.


    Der JMP $F96B führt dann den Handler im KERNAL aus. Dieser pullt dann wieder alle Register und springt mit RTI zurück ins Hauptprogramm.


    Denkt er zumindest. In Wirklichkeit springt er nach $1F20, weil wir diese Rücksprungadresse auf den Stack gepusht haben. Ausgetrickst. :P


    Das kleine Progrämmchen bei $1F20, im Screenshot zu sehen bei $4220, stellt das richtige Mapping für unser Anwendungsprogramm wieder her, und beendet dann den Interrupt endgültig, indem es die Register wieder herstellt und einen RTI ausführt. Ich war zu faul, das alles hinzuschreiben, und springe einfach ans Ende der KERNAL Interrupt-Routine bei $F9CA. Die macht es nämlich genau so.


    Fertig. 8)


    Mit


    BANK 0

    SYS $4000,0,0,0,0,4


    aufrufen. Der Warteschleife auf dem Bildschirm zusehen, und nach Lust und Laune mit der Leertaste vorzeitig beenden. Was zu beweisen war. Puh. :puhh:

  • Verzeihung. Ich habe kein Deutsch.


    I have a gist that can be used to run machine code on the Mega65 in bank 0 with Kernal and I/O mapped in but BASIC mapped out, and with the Kernal interrupt handler enabled.


    It also adds a BASIC stub to run your code.


    To use it, you just need to have this code assembled first and then define a «main» symbol for the start of your program.

  • I have a gist that can be used to run machine code on the Mega65 in bank 0 with Kernal and I/O mapped in but BASIC mapped out, and with the Kernal interrupt handler enabled.

    Fantastic, thank you. I wasn't aware that the basic calling in the kernal interrupt routine can be turned off via $1104. I've seen it in the ROM code, but didn't understand what the checking of this address is for. I don't know why basic is called at all - maybe it has to do with features like the PLAY command? This is a much simpler solution and renders my rather complex solution of providing an own interrupt handler, that maps in basic just for the duration of the interrupt, unnecessary. Thanks for sharing!

  • Ich habe die Lösung von Jimbo13 mit meinem kleinen Warteprogramm als MAIN-Programm ausprobiert, funktioniert einwandfrei, und auch die Tastatureingabe funktioniert. Das ist natürlich eine deutlich einfachere Lösung, wenn man den Interrupthandler des Basic per $1104 einfach abschalten kann, dann muss das Basic beim Interrupt nicht mehr eingeblendet werden. Außerdem ist Jimbo13s Lösung auch noch 14 Bytes kürzer :thumbsup:


    Wen es interessiert: Die entsprechende Stelle im Kernal-Interrupt Handler ist bei $3F98A zu finden (oder nur $F98A, wenn der Kernal gemappt ist). Hier wird $1104 gecheckt, und bei Bedarf der Interrupt-Handler des Basic über den Einsprungspunkt bei $2006 aufgerufen.


    Das ist das schöne an so einem Forum. Es gibt ein Problem, man findet eine Lösung, und weil viele Leute draufgucken werden die Lösungen immer besser. Danke an Euch alle!


    Wenn man Maschinenprogramm-Subroutinen in sein Basic Programm einbinden will, und die Basic-Interrupts weiterlaufen sollen (für Hintergrundtätigkeiten des Basic, wie Spritebewegung oder Musikwiedergabe per PLAY-Befehl), kann man ja immer noch meine Lösung benutzen.


    Kurzer Kommentar zu Zeile 45 im Gist: Bevor Jimbo13 den MAP-Befehl ausführt, ruft er JSR $038D auf. Diese Routine ist Teil des SYS-Handlers im RAM, den ich bei meinen Versuchen entdeckt habe. Sie macht nichts weiter, als die MAP-Konfiguration in AXYZ nach $011C-$011F zu sichern. Es ist Konvention, dass die aktuelle MAP-Konfiguration hier zu finden ist. Das habe ich mir in meinem Hack gespart.


    Noch eine Bemerkung zum SYS-Handler, den ich im RAM gefunden habe. Das Hauptprogramm liegt bei $03A8-$03D4, mit einigen Support-Routinen außen rum (z.B. bei $0380, $038D, $039A). Der eigentliche Call des Maschinenprogramms erfolgt in der Routine bei $03D5-$03F8. Hier sieht man, dass das Maschinenprogramm keineswegs mit JSR aufgerufen wird. Hier wird derselbe Trick benutzt, den ich in meinem Interrupt-Handler verwendet habe: Sprungadresse und Prozessorstatus werden auf den Stack gepusht, und dann das Maschinenprogramm mit RTI aufgerufen. So wird das Statusregister übergeben. Ist die erste Anweisung im Maschinenprogramm dann SEI (wie hier), so wird auch die Lücke, in der Interrupts das System noch zum Absturz bringen können (weil der KERNAL noch fehlt) minimal gehalten, nämlich auf den Zeitpunkt zwischen dem RTI und dem SEI. Damit kann man effektiv auf das SYS...,0,0,0,0,4 verzichten und hat nur ein extrem minimales Risiko für einen Absturz. Absolute Sicherheit bringt aber nur der volle SYS-Call, wenn ich das richtig verstehe.

  • grubi Danke für die ausführliche Beschreibung. Den Thread habe ich mir schon gebookmarked, weil der ist auf jeden Fall SEHR interessant und wichtig für zukünftige Projekte. :)


    Kann man im M65 feststellen welches mapping gerade konfiguriert ist? Für einen generischen IRQ Händler sollte man eigentlich in der Lage sein den aktuellen Zustand zu sichern, dann auf 128 schalten und am Ende wieder zurück, damit man wieder ein das gerade laufende Programm kommt, unabhängig davon wo es gerade läuft.

  • Kann man im M65 feststellen welches mapping gerade konfiguriert ist? Für einen generischen IRQ Händler sollte man eigentlich in der Lage sein den aktuellen Zustand zu sichern, dann auf 128 schalten und am Ende wieder zurück, damit man wieder ein das gerade laufende Programm kommt, unabhängig davon wo es gerade läuft.

    Leider nicht. Die MAP Instruktion stellt das Mapping zwar ein, man kann es aber nicht mehr auslesen. Daher gibt es die Konvention, dass jeder, der das Mapping verändert, das neue Mapping nach $011C-$011F speichert. Da kann es jeder wieder auslesen. Aber wenn sich jemand nicht daran hält, hat man eben Pech.


    In $0110-$0113 liegt übrigens das Bank 128 Mapping. Es lautet $00,$E3,$00,$B3. Man kann es recht einfach mit JSR $0380 NOP herstellen; diese Routine kümmert sich auch gleich um das Sichern in $011C-$011F.

  • Da ist man mal einen Abend anderweitig beschäftigt und kann am nächsten Tag gleich ellenlange Beiträge nachlesen. Da wird dann auch schon mal beim Interrupt das komplette Mapping hin- und hergeschaltet. :thumbsup: Ist zum Glück nicht immer zwingend notwendig. :)


    grubi : Vielen Dank für dein Reinknien in die Sache und das ausführliche Erklären deiner Arbeit! :thumbup:



    Leider nicht. Die MAP Instruktion stellt das Mapping zwar ein, man kann es aber nicht mehr auslesen.

    C65-Entwickler 1: "Super, ich habe zwei Wege gefunden, das Mapping einigermaßen einfach zu machen. Welchen wollen wir einbauen?"


    C65-Entwickler 2: "Die sind beide zu einfach. Wir bauen hier einen Computer, der 'supermodern' sein soll, das muss kompliziert sein. Wir nehmen die dritte Variante, die kapiere nicht mal ich!". ;)

  • grubi : Vielen Dank für dein Reinknien in die Sache und das ausführliche Erklären deiner Arbeit! :thumbup:

    Gerne. Hat mir selbst geholfen, nach den paar Tagen mit meinem neuen Computer, mir das neu gelernte mal von der Seele zu schreiben. Und vielleicht ist es ja für Andere, die vor denselben Anfangsschwierigkeiten stehen, nützlich.