Portowanie CP/M

Jul 08, 2024 Last reply: 2 years ago 18 Replies

Nadal walczę z projektem przeportowania CP/M 2.2 na mój komputerek oparty o polski mikroprocesor MCY7880.



W ciągu ostatnich kilku tygodni udało mi się osiągnąć kilka mniejszych i większych sukcesów:



- Dodałem bufor na liniach D0..D7 karty CF, co najwyraźniej zniwelowało (a przynajmniej ograniczyło) problemy z komunikacja pomiędzy procesorem i kartami. teraz system stał się kompatybilny z dużo większą liczbą kart.



- Udało mi się dodać logi zrzucane po RS232. Dzięki temu mogłem sprawdzić, że parametry związane z operacjami dyskowymi (disk, track, sector, dma) są ustawiane poprawnie.



- Poprawnie jest też liczony adres 128 bajtowego sektora (0..3) wewnątrz



512 bajtowego bloku odczytanego z karty.

- Zawartość odczytywana z karty jest konsystentna z jej obrazem, wygenerowanym na komputerze (przynajmniej była w przypadku wszystkich wyrywkowych testów)



- Sektory są poprawnie kopiowane do miejsca docelowego (wskazywanego przez parametr ustawiany w procedurze SETDMA) z bufora karty CF.



- Po rozruchu systemu dosteję prompta i jestem w stanie wykonywać komendy.



Niestety, system wciąż nie działa stabilnie. Najważniejsze problemy wyglądają następująco:



- Wykonanie komendy DIR daje niekonsystentne zachowanie. Czasem (rzadko) wyprintuje ona zawartość dysku. Zwykle jednak printowany jest tylko jeden plik (ASM.COM) albo następuje zawieszenie systemu.



- Polecenie TYPE z parametrem w postaci pliku tekstowego (np. ASM) printuje tylko średnik, zamiast jego zawartości.



- Mogę załadować niektóre mniejsze programy, jednak nie działają one do końca poprawnie (o ile działają w ogóle). Np. taki DDT czasem wywala się przy starcie (wyświetlając w pętli tekst powitalny) albo tylko raz na kilka(naście) wydanych poleceń "D" printuje kolejnego hexdumpa.



To co zrobiłem do tej pory:



- Upewniłem się czy stos jest wszędzie prawidłowo podmieniany i przywracany oraz czy wszystkie PUSH-owane wartości rejestrów są poprawnie z niego zdejmowane. Faktycznie po drodze znalazłem kilka błędów, ale wydaje mi się, że wszystkie one zostały usunięte.



- Upewniłem się czy wartości rejestrów są przed użyciem zachowywane na stosie i przywracane po użyciu. Tu też znalazłem kilka problematycznych fragmentów, ale wydaje mi się, że wszystkie udało mi się wyeliminować.



- Dodałem print sprawdzający wwartość SP. Nie wygląda na to, żeby gdzieś był problem z nadpisywaniem czegoś przez stos.



Dodatkowo nie sądzę, żeby problem był sprzętowy - komputerek był w stanie stabilnie obsługiwać TinyBasic-a.



Ktoś ma pomysł jak to dalej debugować?


troche dziwaczne. Gdyby był problem z dyskiem rzadko, to by IMO więcej potrafił pokazać.

A to sugeruje problemy z odczytem dysku.

Albo ... masz CCP wczytanego błędnie do pamięci.

A co się dzieje dalej - działa dalej z błędami, czy restartujesz? Może coś uszkadza CCP w pamięci?

A to jest potrzebne? nie pamiętam juz.

A gdzie ten stos wskazuje? Niestety zapomniałem, gdzie powinien.

a) przerwania tam masz? Prawidłowo odzyskują rejestry przy powrocie? b) ta konwersja sektorów 512/128 na pewno poprawnie działa?

c) napisałbym pare krótkich programów testujących poszczególne funkcje BIOS/BDOS. Moze nawet wpisac hex do pamięci przez RS232/DDT.

d) policzylbym na PC sumy kontrolne wszystkich sektorów, zapisał gdzieś w pamięci, i dodał sprawdzenie w procedurach dyskowych.

e) albo odczytywać sektor dwa razy, porównać, jak są błędy, to powtarzać.

e) mozesz chyba przesledzic przez DDT co się dzieje w CCP po wpisaniu DIR czy TYPE

f) ... bankowanie pamięci na pewno działa poprawnie?

J.

Napisać emulator.

Ja, do mojego 6502, *najpierw* napisałem emulator.

formatting link

Absolutnej pewności mieć nie mogę, ale wygląda na to, że karta CF jest czytana poprawnie. Wcześniej (przed buforem na liniach danych) miałem problemy na niektórych kartach, głównie w przypadku odczytu wartości

0xFF. Teraz te problemy zniknęły.

Printowałem sobie wyrywkowo zawartość buforów i porównywałem je z obrazem karty na dysku - wszystko było ok. Teraz DDT działa u mnie na tyle dobrze, że też wyrywkowo porównałem sobie bajt po bajcie kilka fragmentów pamięci z plikami lst. I też nie widzę przekłamań. Będę musiał dodać liczenie sumy kontrolnej z fragmentu obrazu i porównywać je z wartością liczoną z zawartości pamięci po załadowaniu. Wtedy będę miał pewność, że wszystko jest ładowane poprawnie.

Jeśli chodzi o TYPE, to po prostu wraca do prompta i pozwala na wydawanie kolejnych poleceń. Ale w międzyczasie zauważyłem jeszcze jedną rzecz. ten średnik to po prostu pierwsza litera obydwu plików, które chciałem wyprintować (to pliki źródłowe, zaczynające się od komentarza). Żeby to potwierdzić, wrzuciłem na kartę jeszcze jeden plik tekstowy i faktycznie wyprintowała się tylko jego pierwsza litera. Czyli z jakiegoś powodu TYPE poprzestaje na tym jednym znaku. Jasne, to może być problem wynikający z jakiegoś przekłamania (chociaż byłoby to wyjątkowo konsystentne przekłamanie) ale może to też być powodem tego, że mój bios psuje coś w działaniu wyższych warstw. Tyle tylko, że parę razy już sprawdziłem, czy przypadkiem któryś z rejestrów nie jest permanentnie nadpisywany i nie mogę się dopatrzeć takiej sytuacji.

Pewien nie jestem. Może wyższe warstwy już to robią. Jednak w przypadku BIOS-a nie mam pewności co się działo na chwilę przed jego zawołaniem, więc chyba dobrą praktyką jest zrobienie kopii zapasowej na stosie jakiegoś rejestru, jeśli mam zamiar wykorzystać go w roli "pola roboczego". Oczywiście nie odnosi się to do tych rejestrów, w których finalnie i tak zwracany jest wynik operacji.

BDOS ma swój własny stos. Gdy jest wołany tymczasowo podmienia na niego stos użytkownika. Ponieważ stos ten jest relatywnie niewielki, a niektóre z operacji w moim BIOS-ie zrzucają trochę bajtów, w swoim kodzie robię to samo - przy wejściu do niektórych procedur zapisuję wartość SP, podmieniam na swój stos BIOS-a, po czym po skończonej robocie przywracam oryginalną wartość.

Tak, przerwania są. Inicjowane są jeszcze przed CP/M-em i wykonują kilka niskopoziomowych operacji I/O. Patrzyłem na ich kod, ale nie widzę niczego podejrzanego. Zresztą TinyBasic nie miał żadnych problemów ze stabilnością, a przerwanie mieszające w rejestrach procesora dość szybko by go zmasakrowało.

Wyprintowałem wartości i wszystko się zgadza. Sektory zmieniają się w zakresie 0..3, a liczony adres początku danych w buforze to adres_bufora_cf + (sektor * 128)

Pamiętasz jak to się robiło?

Na chwilę obecna nie jest używane. System korzysta w tej chwili tylko z

32kB pamięci zmapowanej na stałe na fragment przestrzeni adresowej. Mam jeszcze drugie 32kB w postaci dwóch banków po 16kB, ale one nie są w tej chwili wykorzystywane. Gdy uda mi się rozwiązać ten problem, pewnie poupycham w nich różne bufory pomocnicze (np. do odczytu/zapisu CF albo przewijania ekranu)..

Hmm... Jednak wygląda na to, że przerwania też najwyraźniej są jednym z czynników, które muszę wziąć pod uwagę. W ramach eksperymentu dodałem instrukcję DI na wejściu procedur BIOS-a oraz EI tuż przed powrotem z nich. Oczywiście tam, gdzie mogłem - w przypadku procedur związanych z klawiaturą nie mogłem sobie na to pozwolić, bo pobieranie kodów wciskanych klawiszy odbywa się w przerwaniach. Efekt jest taki, że teraz DDT działa w sposób znacznie bardziej konsystentny - teraz kolejne polecenia "D" powodują dumpowanie pamięci, nie mam już pustych linijek. Niemniej DIR nadal nie printuje całości, a TYPE zwraca tylko pierwszy znak z pliku.

Generalnie odnoszę wrażenie, że system działa na tyle stabilnie, że byłem w stanie spokojnie napisać i przetestować na nim BIOS-a, opierając się na printach debugowych. Zachowanie pisanego kodu było na tyle jednoznaczne, że byłem w stanie wyszukiwać i poprawiać pojawiające się w nim błedy. Wczoraj przykładowo dodałem obsługę procedury WBOOT. Myślę, że spokojnie mógłbym dodać obsługę WRITE oraz optymalizację odczytów/zapisów (żeby nie czytać z/pisać do cztery razy do tego samego sektora karty CF, jeśli już ma się jego zawartość w buforze).

Wygląda to tak, jakby BIOS działał zgodnie z założeniami, ale coś psuło funkcjonowanie wyższych warstw. I jasne, nie mogę wykluczyć problemu z przekłamaniem podczas ładowania, tylko byłoby to dziwne, żeby problem wyjątkowo konsystentnie dotykał CCP/BDOS, nie wprowadzając chaosu do BIOS.a

Wygląda to trochę tak, jakby coś w moim kodzie psuło działanie wyższych warstw, tylko nie jestem w stanie stwierdzić co...

Dlaczego zakładasz, że ten CPU jest w pełni kompatybilny? Albo jednak występują w pewnych okolicznościach (zasilanie/zakłócenie/grzanie) problemy sprzętowe? Taka analogia: w latach 90 w szczycie handlu przetaktowanymi składkami 486DX4/Pentium kolega testował takiego składaka używając gcc. Odpalał kompilację jądra i jeśli gcc zaczęło się wywalać w losowych miejscach na bank był problem sprzętowy. Co ciekawe taki składak praktycznie nie zdradzał problemu przy bocie, ładowania systemu i działania "na spokojnie". Problem dopiero ujawniał się przy obciążeniu gcc. Sprzęt był uznawany za sprawny gdy 5x pod rząd bez błędu przeszło make clean && make bzImage

Mnie to brzmi jakby jakieś funkcje twojego BIOSu kłóciły się z przerwaniami, które ten sam BIOS obsługuje – gdzieś brak synchronizacji w dostępie do wspólnych danych.

Wyższe warstwy wołają funkcje BIOSu i jak akurat trafi przerwanie, to się coś sypie. Wygląda, że problem jest głównie w przypadku dostępu do dysku – sprawdziłbym wszystko czego te procedury dotykają i co z tego jest w jakikolwiek sposób używane też w funkcjach obsługi przerwań.

Druga sprawa – czy przerwanie w nieodpowiednim momencie nie psuje krytycznego timingu w operacjach I/O? Może akurat wstawia jakąś milisekundę opóźnienia gdzie nie powinno? I np. gubisz jeden sektor, bo w buforze już kolejny wylądował.

Pozdrawiam, Jacek

Bo nigdy nie natknąłem się choćby na wzmiankę, żeby MCY7880 miał być w jakimś stopniu niekompatybilny z oryginalnym 8080 Intela. A temat jest dosyć dobrze rozpoznany i np. drobne różnice w zachowaniu klonów Z80 z NRD w stosunku do oryginału Ziloga są dośc dobrze opisane. Zresztą... Celem, który przyświecał inżynierom zza żelaznej kurtyny było zbudowanie procesora, który pozwoli na odpalanie ukradzionego oprogramowania z zachodu.

Poza tym - projekt nie jest nowy i ma już kilka lat. Zaczynałem od uruchamiania na tym sprzęcie prostego migania diodą, potem dodawałem sterowniki do kolejnych peryferiów. Następnie odpalałem jakieś proste wprawki asemblerowe, aż w końcu udało mi się uruchomić na nim TinyBasic, będący jednak już dość złożonym oprogramowaniem jak na tego typu antyczną architekturę. Byłoby czymś dziwnym, gdyby problemy sprzętowe ujawniły się dopiero teraz - przy okazji portowania CP/M. To znaczy inaczej - problemy sprzętowej były i dały o sobie znać na etapie pisania bootloadera CF. Pomogło jednak dodanie bufora na liniach danych.

To nie są te czasy, nie ta technologia. Ten procesor nie dostosowuje swojej mocy obliczeniowej do wykonywanego zadania i nie zwalnia, gdy nie ma nic do roboty. Teoretycznie mogłyby pojawić się problemy wynikające z problemów z zasilaniem albo pojemności linii (zwłaszcza, że prototyp jest zbudowany na płytce uniwersalnej za pomocą kynaru) ale to całoby o sobie znać znacznie wcześniej.

Problem ewidentnie wygląda na programowy, do tego mocno powtarzalny i specyficzny.

Tyle tylko, że tam tak naprawdę nie ma zbyt wielu rzeczy wspólnych. Obsługa przerwań (realizowana za pomocą 8259) jest inicjowana przy rozruchu sprzętu, jeszcze przed odpaleniem bootloadera. Potem w kluczowych momentach, przy starcie CP/M co najwyżej wyłączam przerwania globalnie. Procedury obsługujące przerwania siedzą w EPROM-ie, który jest na stałe dostępny w przestrzeni adresowej. Na chwilę obecną są to:

  1. UART RX - obecnie niezaimplementowane - po prostu wraca
  2. UART TX - obecnie niezaimplementowane - po prostu wraca
  3. Klawiatura - pobiera jeden bajt z rejestru klawiatury i zapisuje jego wartość w zmiennej. Potem wraca. W tej chwili to jedyne dane pobierane w przerwaniu, które są także używane w BIOS-ie.
  4. TIMER 8253 - Inkrementuje szenstastobitową zmienną "systick" i przeładowuje przerwanie pisząc do rejestru układu. Potem wraca. Na chwilę obecną ta zmienna nie jest używana w BIOS-ie.
  5. RTC - czyści flagę przerwania w układzie przerwania, potem inkrementuje szesnastobitową zmienną "rtctick" (zliczającą sekundy). Potem wraca. Na chwilę obecną zmienna ta nie jest używana w BIOS-ie.
  6. Przerwanie układu graficznego (TMS9918). Na chwilę obecną nieużywane

- po prostu wraca.

  1. Wektory dwóch nieużywanych przerwań po prostu wracają.

Teoretycznie to mogłoby powodować problemy w przypadku fizycznego dysku z obracającym się tależem. Ale czy przypadkiem karta CF nie powinna być odporna na takie zachowania? Ona po prostu wystawia flagi i dane na magistralę. Poza tym w chwili obecnej (eksperymentalnie) wyłączyłem globalnie przerwania w procedurze odczytu z dysku. To powinno zupełnie rozwiązać problemy o takiej genezie.

Postęp duży, ale - prawidłowo odtwarzasz rejestry po przerwaniu?

Pamieci video tam nie masz, to jest RS na terminal ?

przerwania, stos, bufory danych ...

J.

Wydaje mi się, że tak. Niemniej patrząc w kod teraz uświadomiłem sobie jeszcze jedną rzecz, która naprowadziła mnie na trop możliwej przyczyny. Kilka źródeł w Internecie mocno zalecało wydzielenie osobnego stosu dla BIOS-a i podmienianie go przynajmniej w tych procedurach, które będą z niego najbardziej intensywnie korzystały. Powodem takiego stanu rzeczy jest fakt, że BDOS posiada stosunkowo mały stos. I teraz jeszcze raz rzuciłem okiem na procedury obsługi przerwań napisane dobrych parę lat temu. Widzę, że nawet te obecnie niezaimplementowane są "szablonami" w których bezsensownie jest cały kontekst. Pewnie parę lat temu przygotowałem je pod uzupełnienie treścią i nigdy tego nie zrobiłem. W czasach TinyBasic-a nie miało to znaczenia, bo używałem pojedynczego, sporego stosu. Teraz jednak BDOS podmienia stos na swój własny, o ograniczonym rozmiarze. Istnieje więc spora szansa, że odpalające się przerwania kumulują się i stos zostaje przepełniony.

Jeśli mam rację, to wyłączanie przerwań zaraz po wejściu do procedur BIOS-a w pewnym stopniu chroniło także stos BDOS-a (bo do momentu podmiany stosów ciągle operujemy na starym). Okienko czasowe w którym przerwanie mogło spowodować nadpisanie pamięci nieco się zmniejszało. Niemniej przerwania odpalające się poza BIOS-em ciągle mogą spowodować problem. Mam nadzieję, że optymalizacja wykorzystania stosu w procedurach obsługi przerwań wystarczy do rozwiązania tego problemu.

Pamięć wideo jest, ale w postaci osobnego układu na osobnej magistrali. TMS9918 posiada swój własny VRAM, którego nie współdzieli z systemem, a z procesorem komunikuje się przez kilka rejestrów w przestrzeni IO. RS w tej chwili służy tylko do debugowania. Za systemowy terminal robi telewizor CRT oraz klawiatura od peceta, obsługiwana przez 8242.

No i hipoteza się potwierdziła. Wyłączyłem przerwania zupełnie przy inicjacji CPM. Potrzebowałem ich tak naprawdę tylko do obsługi klawiatury, więc przepisanie procedur jej obsługi na pooling rozwiązało problem. Teraz CP/M zaczął działać normalnie - DIR jest printowany w całości, podobnie jak zawartość plików w przypadku polecenia TYPE. Nie wszystko jeszcze działa poprawnie - muszę dopisać procedurę WRITE oraz wprowadzić kilka optymalizacji, ale teraz przynajmniej wiem, że system działa.

No to teraz ciekawe - zmieniales jakies rejestry w przerwaniu, czy stos cos zajezdzał.

Jak czytam, ze CCP startuje program ze stosem na 8 słów ... troche mało. Co prawda program powinien zaraz ustawic swój stos, ale nadal cos mało.

A swoja droga - nie pamietam, ale zeby przerwania działaly, to BIOS musi ustawic odpowiednie instrukcje pod adresem 56 i ewentualnie innymi ... wpisujesz ?

J.

Stos. Konkretnie stos BDOS/CCP. System został zaprojektowany w czasach, gdy RAM był drogi, więc pamięć była używana oszczędnie. Kilka źródeł ostrzegało, że jego rozmiar jest niewielki i pisząc BIOS warto podmieniać SP na osobny, wydzielony obszar pamięci. Nie wziąłem pod uwagę, że przerwania mogą się odpalać w dowolnym momencie, w pewnych sytuacjach prowadząc do przepełnienia stosu BDOS/CCP i nadpisania pamięci.

Podejrzewam, że Garry Kildall mógł zwyczajnie nie wziąć pod uwagę możliwości, że w tle będą działały przerwania.

To chyba chodzi o przerwania wywoływane instrukcjami RST. Pozwalają one za pomocą szybkich (jednobajtowych) instrukcji wykonać skok pod adres zapisany właśnie pod tymi adresami. Ja mam przerwania zrealizowane za pomocą 8259, gdzie wektory (i same procedury obsługi) przerwań są na stałe zaszyte w EPROM-ie, w górnej części przestrzeni adresowej.

Trochę szkoda, bo zależało mi na przerwaniach timera i RTC pracujących w tle, jednak obejdę się bez tego.

Być może, ale czy nie troche dziwne ? W zasadzie, jednozadaniowy komputer osobisty ... no ale jak z terminalem po RS232, to w zasadzie musi mieć przerwania ... prawie musi.

Tak, tylko w prawdziwym przerwaniu te rozkazy są generowane zewnętrznie.

Hm, jak to zrobiłes?

Wiki cos mówi, ze on z 8085 może wspólpracować, ale o 8080 to google milczy.

Alez możesz, tylko trzeba zmniejszyc zużycie stosu/założyć własny. Albo ... źródla CP/M jak widzę są dostępne, przekompilować z wiekszym stosem.

J.

A, ok, doczytałem, 8259 wysyła instrukcje CALL z adresem.

J.

Większość projektów komputerków retro jakie widziałem i tak korzysta z poolingu.

Już dokładnie nie pamiętam. Chyba po prostu skopiowałem rozwiązanie ze schematu jakiegoś innego, przetestowanego projektu.

8080 i 8085 są niemal identyczne. Różnice sprowadzają się do połowy magistrali adresowej multipleksowanej liniami danych, braku konieczności korzystania z dodatkowych układów (generator sygnałów zegarowych + kontroler magistrali), obecności wejścia i wyjścia szeregowego (i związanych z nimi przerwań) oraz oczywiście zasilania pojedynczym napięciem 5V.

Pewnie tak. Obawiam się jednak, że nie będzie to takie proste. Z tego co pamiętam sporo założeń związanych z działaniem systemu opiera się na tym, że określone elementy kodu (np. BIOS-u) znajdą się w określonych miejscach, w stosunku do innych elementów. Jeśli zacznę zmieniać rozmiar buforów umieszczonych wewnątrz BDOS-a, może mi się to wszystko rozjechać i bez przepisania większej części kodu nie będzie tak łatwo tego naprawić.

Nie chce mi się patrzeć do tych źródeł, ale jeśli w miarę przyzwoicie napisany, to nie powinno być większych problemów.

A w dodatku ten stos możesz umieścic gdzies wysoko, oryginalne miejsce pozostawić, a zmienić tylko adresy ładujące.

J.

Swoją drogą, ktoś może kojarzy do czego można wykorzystać to wejście i wyjście szeregowe w 8085? Da się na tym zrobić implementację np. UART-a, bez konieczności stosowania zewnętrznych układów? Jak wygląda w takim przypadku ustalanie baudrate-u?

Join the Discussion

Have something to add? Share your thoughts — no account required.

Didn't find your answer?

Ask the community — no account required