From 730aae8a89fd9ef8296bbd2ce4a33d4f6c4eb579 Mon Sep 17 00:00:00 2001 From: Anatoliy Belyanskiy Date: Fri, 3 Nov 2023 02:28:29 +1000 Subject: [PATCH] +- BIOS function for ACEX reload by custom bitstream --- Shared_Includes | 2 +- src/bios/exp/BIOS_FUNC.asm | 20 +++++- src/bios/exp/EXP.asm | 15 +++-- src/bios/exp/FUNC_LOW_PRINT.ASM | 4 +- src/bios/exp/FUNC_RAM_ROM_DRV.ASM | 70 ++++++++++++++------- src/bios/exp/FUNC_SERVICE.asm | 100 +++++++++++++++++++++++++++++- src/bios/shared/RECOVERY.IMG | Bin 98304 -> 98304 bytes 7 files changed, 175 insertions(+), 36 deletions(-) diff --git a/Shared_Includes b/Shared_Includes index a15861c..8c5550f 160000 --- a/Shared_Includes +++ b/Shared_Includes @@ -1 +1 @@ -Subproject commit a15861c07cab60eaf09c28226d0c7761b4fd8235 +Subproject commit 8c5550f810d173790e4665d610328b9247b00f02 diff --git a/src/bios/exp/BIOS_FUNC.asm b/src/bios/exp/BIOS_FUNC.asm index d408fd5..c935205 100644 --- a/src/bios/exp/BIOS_FUNC.asm +++ b/src/bios/exp/BIOS_FUNC.asm @@ -709,10 +709,26 @@ FN_5x_Parser: EX (SP),HL RET -EXP_FNS: ; Вход в функцию из TR-DOS - POP AF +; Вход в функции БИОС из TR-DOS +EXP_FNS:; отключаем запись в экран спектрума + EX (SP),HL + IN A,(RGADR) + LD L,A + LD A,#C0 + OUT (PORT_Y),A + LD A,H + EX (SP),HL ; (SP) = port_y + ; CALL EXP_FNS_RST18 CALL DOS_ON + ; возвращаем запись в экран спектрума + EX (SP),HL + PUSH AF + LD A,L + OUT (RGADR),A + POP AF + POP HL + ; возврат JP EXP_FNS_RET ;! ! ! ! ! ! ! ! diff --git a/src/bios/exp/EXP.asm b/src/bios/exp/EXP.asm index 5134b63..a92cd0b 100644 --- a/src/bios/exp/EXP.asm +++ b/src/bios/exp/EXP.asm @@ -49,7 +49,7 @@ RST_10: JP EXP_FNS_RST18 RST_18_1: CALL EXP_FNS_RST18 - JR EXP_FNS_2_RET +.exit: JR EXP_FNS_2_RET ;======================================= @@ -772,6 +772,8 @@ set_config: ; JR NZ,NO_SETUP_2 +;[---------------------------------------------------------------------] + MODULE Reset_Handler start: IN A,(SLOT3) @@ -853,21 +855,22 @@ init_rom_address EQU #8200 ;!HARDCO ;!FIXIT сделать выбор грузить да/нет ПЗУ спектрума при старте, [-------] ; если нет - то воткнуть этот код: ; LD HL,PROG_NO_ROM - ; LD DE,#C000 + #38 + ; LD DE,#C000 + Spec_Page.no_zx_rom ; LD BC,PROG_NO_ROM.size ; LDIR ; LD HL,RAM_BIOS_PROG - ; LD DE,#C000 + #08 + ; LD DE,#C000 + Spec_Page.to_bios ; LD BC,RAM_BIOS_PROG.Length ; LDIR -;[---------------------------------------------------------------------] +;--------------------------------------- Spec_Page_handler_OK: pop af OUT (SLOT3),A ENDMODULE +;[---------------------------------------------------------------------] ; ;************************************** ; @@ -1370,7 +1373,7 @@ Length EQU $-Setup_Starter.Start ;---------------------[ ЗАГЛУШКИ ДЛЯ #41 СТРАНИЦЫ]----------------------; ;-------------[RST 08] ; RAM_BIOS_PROG: ; for CALL BIOS in #41 page -; DISP #08 +; DISP Spec_Page.to_bios ; PUSH AF ; LD A,ROM.BIOS ; OUT (SYS_PORT.ROM),A @@ -1382,7 +1385,7 @@ Length EQU $-Setup_Starter.Start ; ;-------------[RST 38] ; PROG_NO_ROM: -; DISP #38 +; DISP Spec_Page.no_zx_rom ; DI ; LD A,#FF diff --git a/src/bios/exp/FUNC_LOW_PRINT.ASM b/src/bios/exp/FUNC_LOW_PRINT.ASM index a9c3d75..26a953c 100644 --- a/src/bios/exp/FUNC_LOW_PRINT.ASM +++ b/src/bios/exp/FUNC_LOW_PRINT.ASM @@ -585,13 +585,13 @@ WIN_SET_ZG: ; LP_SET_ZG: RET .SET: IN A,(SLOT1) LD (SYS_PAGE.COPY_SLOT1),A - LD A,#FF + LD A,SHARED_PAGE OUT (SLOT1),A EXX LD BC,Port_All_Mode IN A,(C) LD (SYS_PAGE.SYS_WORK1),A - AND #FE ; accelerator and keyboard interrupt off + AND #FE ; Spectrum Screen on, accelerator and keyboard interrupt off OUT (C),A EX AF,AF' LD B,A diff --git a/src/bios/exp/FUNC_RAM_ROM_DRV.ASM b/src/bios/exp/FUNC_RAM_ROM_DRV.ASM index 7b64620..fee71f7 100644 --- a/src/bios/exp/FUNC_RAM_ROM_DRV.ASM +++ b/src/bios/exp/FUNC_RAM_ROM_DRV.ASM @@ -30,7 +30,7 @@ EMM.GetMemSize: ; -;----------------------------------------------------------------------; +;----------------------------------------------------------------------; !FIXIT SAFE_RGADR ; Инициализация распределения памяти. EMM.InitMem: PUSH BC @@ -107,7 +107,7 @@ RESERVED_PAGES: ; -;----------------------------------------------------------------------; +;----------------------------------------------------------------------; !FIXIT SAFE_RGADR ; Получить блок памяти N bytes, ; Вход: B - число необходимых блоков ; Выход: L,A - КЛЮЧ RAM-Disk/код ошибки @@ -228,7 +228,7 @@ EMM.FreeMem: LD A,SYS_PAGE OUT (SLOT1),A - LD H,high (SYS_PAGE.RAMD_FAT - #4000 - #4000) + LD H,high (SYS_PAGE.RAMD_FAT - #4000 - #4000) ; !FIXIT SAFE_RGADR LD A,L EMM_F3M_L1: LD L,A @@ -345,6 +345,38 @@ EMM.GetMemPageNext: ; Вход: A - блок, HL - адрес буфера - 256 байт. ; Выход: HL - адрес блока, B - длина блока в страницах ОЗУ ;EMM_FN5M: +; EMM.GetMemBlkPages: +; PUSH DE +; PUSH HL +; EX DE,HL +; LD B,0 +; LD L,A + +; .loop: LD A,L +; LD (DE),A +; INC DE +; AND A +; JR Z,.error +; CP #FF +; JR Z,.end +; ; +; IN A,(SLOT1) +; LD C,A +; LD A,SYS_PAGE +; OUT (SLOT1),A +; LD H,high (SYS_PAGE.RAMD_FAT - #4000 - #4000) +; LD L,(HL) +; LD A,C +; OUT (SLOT1),A +; ; +; INC B +; JR NZ,.loop + +; .error: SCF +; .end: POP HL +; POP DE +; RET + EMM.GetMemBlkPages: PUSH DE PUSH HL @@ -352,7 +384,10 @@ EMM.GetMemBlkPages: LD B,0 LD L,A - LD A,R + IN A,(SLOT1) + LD C,A + LD A,SYS_PAGE + OUT (SLOT1),A .loop: LD A,L LD (DE),A @@ -362,34 +397,24 @@ EMM.GetMemBlkPages: CP #FF JR Z,.end ; - IN A,(SLOT1) - LD C,A - LD A,SYS_PAGE - OUT (SLOT1),A LD H,high (SYS_PAGE.RAMD_FAT - #4000 - #4000) LD L,(HL) - LD A,C - OUT (SLOT1),A - ; INC B JR NZ,.loop .error: SCF -.end: POP HL +.end: LD A,C + OUT (SLOT1),A + POP HL POP DE RET - -; .end: POP HL -; POP DE -; AND A -; RET ;----------------------------------------------------------------------; ; ; -;----------------------------------------------------------------------; +;----------------------------------------------------------------------; !FIXIT SAFE_RGADR ;RAMD_R_W: ; ╔════════════════════════════════════════════════╗ ; ║ RD/WR SECTOR ║\ @@ -493,7 +518,6 @@ BLK_RD_WR: BIT 7,H JR NZ,.BLK_PAGE1 ; !!!!! JR NZ,BLK_PAGE1 ? -.BLK_PAGE3: ; !TODO переделать на SLOT ????? LD C,SLOT3 IN C,(C) OUT (SLOT3),A @@ -852,7 +876,7 @@ GET_RAMD_ST: ; -;----------------------------------------------------------------------; +;----------------------------------------------------------------------; !FIXIT SAFE_RGADR ; RAM-Disk A, BLK - B BLK_TO_RAMD: CP SYS_PAGE.RAMD_KEYS.NUM @@ -896,7 +920,7 @@ BLK_BUSY: ; -;----------------------------------------------------------------------; +;----------------------------------------------------------------------; !FIXIT SAFE_RGADR ; RAM-Disk A RAMD_CLEAR: CP SYS_PAGE.RAMD_KEYS.NUM @@ -931,7 +955,7 @@ RAMD_CLEAR: ; -;----------------------------------------------------------------------; +;----------------------------------------------------------------------; !FIXIT SAFE_RGADR ; разделить блок памяти на два блока ; A - блок, B - длина первого блока после разделения ; выход: A - блок 1, B - блок 2 @@ -969,7 +993,7 @@ EMM.DivMemBlocks: ; -;----------------------------------------------------------------------; +;----------------------------------------------------------------------; !FIXIT SAFE_RGADR ; слить два блока памяти в один ; А - блок 1, B - блок 2 ; выход: А - блок diff --git a/src/bios/exp/FUNC_SERVICE.asm b/src/bios/exp/FUNC_SERVICE.asm index b710798..123add6 100644 --- a/src/bios/exp/FUNC_SERVICE.asm +++ b/src/bios/exp/FUNC_SERVICE.asm @@ -100,7 +100,7 @@ RST_CONF: .CHOOSE_CNF: ;!TEST CP PG_Sp2000 - LD DE,SP2000_ConfID + ;LD DE,SP2000_ConfID JR Z,.INIT_ACEX ; @@ -117,13 +117,109 @@ RST_CONF: SCF RET -.INIT_ACEX: +.INIT_ACEX: ;!FIXIT ;!TODO НЕДОДЕЛАНО!!!! + DI + ; устанавливаем нулевую карту портов + LD A,CNF_PORT.CNF_0 + OUT (SYS_PORT.ON),A + ; сохраняем воткнутые страницы в Spec_Page + IN A,(SLOT0) + LD E,A + IN A,(SLOT1) + LD D,A + IN A,(SLOT2) + LD L,A + IN A,(SLOT3) + LD H,A + LD A,Spec_Page + OUT (SLOT3),A + LD (Spec_Page.page_0),DE + LD (Spec_Page.page_2),HL + ; достаём адрес возврата в вызывающую функцию и сохраняем в Spec_Page + POP DE + LD HL,RST_18_1.exit + AND A + SBC HL,DE + JR NZ,1F ; NZ - если вызов был по RST #18 + POP DE ; если вызов был в ОЗУ по RST 8 +1: LD (Spec_Page.RET_addr),DE + ; проверяем размер блока с конфой и получаем страницы конфы + PUSH BC + LD HL,Spec_Page.bitstream_pages + LD A,B + CALL EMM.GetMemBlkPages + LD A,B + POP BC + LD B,A + JR C,.INIT_ACEX.ERROR + CP 4+1 ;!HARDCODE количество страниц в кэш для бистрима плюс одна + JR NC,.INIT_ACEX.ERROR + PUSH BC + ; закидываем в Spec_Page прогу для заливки конфы + LD HL,.INIT_ACEX.PROGRAM + LD DE,Spec_Page.init_acex + LD BC,.INIT_ACEX.PROGRAM.Size + LDIR + ; + POP BC + ; B = size of ram block in pages + CALL Spec_Page.init_acex + ; ; .. ... ... .. ;!TODO сделать функцию по заливке своей конфы, перехвату ресета. + ; *. Вход в подфункцию только через RST 08 или #18 + ; *. Сохранить все страницы пользователя в SYS_PAGE для перехвата ресета + ; *. Достать со стека адрес возврата и сохранить в SYS_PAGE для перехвата ресета + ; *. Сохранить куда-нибудь значение стека + ; * ; .. ... ... .. LD BC,256*BIOS.REINIT.HARD_RESET + BIOS.REINIT JP_to_BIOS +.INIT_ACEX.ERROR: + POP BC + LD A,(Spec_Page.page_2) + OUT (SLOT2),A + LD A,(Spec_Page.page_3) + OUT (SLOT3),A + SCF + RET +; +.INIT_ACEX.PROGRAM: + DISP Spec_Page.init_acex + ; B = size of ram block in pages + ; + IN A,(FastRAM.ON) + XOR A + EXX + LD HL,Spec_Page.bitstream_pages + LD BC,SLOT2 + EXX +.load_loop: + EXX + OUTI + EXX + PUSH BC + OUT (FastRAM.SLOT0),A + LD HL,0 + LD DE,#8000 + LD BC,#4000 + LDIR + ; + INC A + POP BC + DJNZ .load_loop + + ; ... .... + ; acex loading and flags + ; ... .... + XOR A + OUT (FastRAM.SLOT0),A + IN A,(FastRAM.OFF) + RET + ENT +.INIT_ACEX.PROGRAM.Size EQU $-.INIT_ACEX.PROGRAM +; ;*************************************** ;!TEST diff --git a/src/bios/shared/RECOVERY.IMG b/src/bios/shared/RECOVERY.IMG index 7e91f242511551e2cacd0e7272367617b50e12e6..3b3e561fa72b16f07bfa9999c35158d0added5fd 100755 GIT binary patch delta 8574 zcma)h3s_TUw&>mgLU@Jj5FrV#>}(!MFc4xSkV*gnQK_PUk1B%pWjoVRtL+J>5cKw| zV>@$;XWP;d9Ua>PXrtWa}(Lj*|?;arLI>DX->1lVahTdat0OWL-9#Qxh*rTC*<{3b0Z zF2QqXWAUzfa~WH)*vtMpg?(8f#=f*1O%)&bQjA?UE=aRtLY(cF-X^h_vY+OaNy&=E zgk>@QMSK~?pM_3YG5e{W4aT{CI&`yGDd;F=uTk(k(f`8B6!j@pdxqHYk|%RTdl&!4 zK=)dTYMVjR_n+k(o>WnF#fBWy(t`cHM~D8LYJJKYy!!e*03zY3AAE7@@=n{!^dCN< zsP>_)rLPI_X8P8mr}1ZpHkeL{8jhxhdkw!kUoT|yLB!82t$>97}gPCDx=<3R>iq}(}UB;uS5j0UPST9Z- zb}*ylzozC8?CYuIA5wEL*JG)pPI95hFO{m8^q`NjycC?muGKKlG~i$?{UsDb5<PSD7z`!sz)b{XFS%_UnFqWHm@UJt5N;NsIecq+iA;C5Ox-9`uaY6rEx0+1YvL*u&{tHd z?x@$w1`&zQF(M=Sze$K0TL4yI4i-6I7(Lyagr zofj^V;j+#*8de*GLhR+H3zvcsBGW2)Ffa;8(DG;g{(AYyAmem8J!WQ<1AKo^kgQQi z3*}csFDgSX0Ln-<2tYdUJ6)&bq4wm^`jovx8WX*vUXgK1j)XlWM|>C(1MJ5VfBBEu zPwAg8liUAaG77Ix=gI#+WxR)5hr;B6un}3m7mxe{k<*%JW*o*my0XELlc!|V<4Se1 zk_C&i<%+#SZb~^B>d~+Z@}_O{s|}gjLZx$btXxx~zN+9`JPJ-Xu4HR6rfo`o^SBax z5q$PhA*N7|)0Z0-Y)w**a-`uKA%5+~9EwT+bwY^N06b~1pe0WJEZ-7YiF!d;9td)e zb3X)B>{sa?D|Uey4NQJ7694pzblq}woxuVQFs_Uu^0|Jg@cW%+Ci3!FMdo>tCPAj@ z{mM{}b_Q4742@~Sp|PaD&dau+KC?XAz9TatTV7xixCZ(fxpR82ayf`dU;>3C1iBGo z{)C`^IlmI^iw)=86`2v>?-7wOSYV-ZdLJ+b3qAgC%K*5MBE0M@v9hQL?SJ92zv3EEu68Uf zcRY|8nuGFz5W7%WMvCP!*xU5cOmtX<5lWBrLx>Mk^ z`@LPUac*N)oO@{&N(&BlQPj+yvTcHtPX-*E9L^B)-KF zPyrIwD*l{RV!p)Y6#ze8%bI)JJ6DDUID5O2Tc0^`W8ZmqcL6F7Cg(u4P!`O3#usKo zGH`Vl1SVN&p~vr?j7sW3>_3Ef5-?jAf$)Csyehsu&jlj0P*H*856A-$*UH(qAT6Ao zxB+yKNu-5Nu*iQ>F5;YhKi%6U@`d&kgmxGDLam`KBzZvSo2QwLY{bFSI*fG{_+2kQ)&Au+DBxGhYPb_Gngy*5`%V^CulJ z=?U%53+>4V!2mV%p;9AMSZH-YXj!3Lpg@`a8^k~`@IW4agcZmS-H}Caq=So1MtlrZ zha}=#ex1{4oX80PTK_%c+2@@HL(T�p&*&0(7NmfRRh!)cLvOwnAKn1l?LFO#`VR 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 BU19(L delta 8515 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 I;x}Xe3lX~@hX4Qo