Memory Mapper using 16 KB segments

By rolandve

Master (219)

Аватар пользователя rolandve

25-05-2020, 21:44

My program is running out of memory fast because each node in the linked list costs around 6 bytes. If you only have 24 KB left, that's not much. So looking through MSX-DOS2 I see a lot of memory management functions and mapper routines. I think I understand how they are supposed to work. Popolony's library has an extended bios function that refers to MSXDos2 BIOS memory management ($04). But on Grauw's page I can't find how to use that information. In which register do I put $04?

First question: if I use MSX-DOS 2 for memory management, do I still need to use the slot-extended slot mechanism?

My goals is as follows. I want to store a node somewhere in memory and in that node, I store the address of the next node, together with all the required information like for example, the segment of that next node and its offset in that segment. I am under the impression that under MSX-DOS2 a segment is 16 KB so I could store around 2600 nodes in a segment. If the counter reaches 2600 I request a new segment and put the next node in that fresh segment.

Is there a way to allocate a segment, store data in that segment and retrieve data from that segment? RD_SEG and WR_SEG together with ALL_SEG look like the right calls. Grauw's page specifies that it always goes through page 2. I suppose that terminates my program that is huge/fat and also lives in page 2.

Does anyone have some examples in TP of how to use MSX-DOS2 memory mapper functions? Grauw's page although impressive is sometimes complex to understand, for example:

" These are accessed through a jump table which is located in the MSX-DOS system area. The contents of the jump table are as follows:" while the MSX-system area is not described. "At address 0005h is a jump instruction to the start of the resident part of MSX-DOS which is used for making MSX-DOS calls" Is this the MSX-DOS system area?

I think/hope some code will make it clearer Smile
Thanks

Для того, чтобы оставить комментарий, необходимо регистрация или !login

By Grauw

Ascended (9272)

Аватар пользователя Grauw

25-05-2020, 22:57

Quote:

First question: if I use MSX-DOS 2 for memory management, do I still need to use the slot-extended slot mechanism?

Yes, because there can be multiple memory mappers in the system. When you allocate a segment, you should set the flags to allocate from primary and secondary mappers, remember the slot of the segment you allocated, and select it before use.

This is particularly important for the MSX turboR FS-A1ST, which has 256K of memory, 160K of which is available to the program in DOS2 / DRAM mode, even less when using COMMAND 2.44. Usually the largest mapper is selected as primary, however on the turboR the internal mapper is default for performance reasons. If your program does not support secondary mappers you will run out of memory on the turboR ST once you use much more than 128K or so in total. If you only allocate up to 4 segments or so though it’s ok to do so from the primary mapper only.

Quote:

Grauw's page specifies that it always goes through page 2. I suppose that terminates my program that is huge/fat and also lives in page 2.

No, it will switch the segment back after reading. What it means though is that you need to make sure the mapper’s slot is selected in page 2.

Quote:

Grauw's page although impressive is sometimes complex to understand

If it’s any consolation, this documentation was written by ASCII, not by me Smile.

Quote:

These are accessed through a jump table which is located in the MSX-DOS system area. The contents of the jump table are as follows:" while the MSX-system area is not described. "At address 0005h is a jump instruction to the start of the resident part of MSX-DOS which is used for making MSX-DOS calls" Is this the MSX-DOS system area?

The system area is in the upper memory, and the MSX-DOS system area is somewhere above the stack and below F800H (BIOS system area start).

The location of the jump table can be determined through an extended BIOS call.

As for some code to make it clearer, I’m not sure it will help because it’s not written as an example, nor is it in Turbo Pascal, but here’s the code that deals with mappers in VGMPlay:

https://hg.sr.ht/~grauw/neonlib/browse/src/Mapper.asm?rev=%40
https://hg.sr.ht/~grauw/vgmplay-msx/browse/src/MappedBuffer....

By rolandve

Master (219)

Аватар пользователя rolandve

26-05-2020, 12:31

@Grauw, thanks for your reply.

I usually learn from playing/disecting code and looking at the technical specifications. Your documents written by ASCII makes sense, They were the experts and didn't have to think about the readers of their documents. I don't get ASCII anyway. Why implement this construct in the BIOS, create a functionality for a ramdisk that can easily take 1 MB (so must extensively use these functions) and yet not implement a decent alloc() function at OS level for developers that can contain more then 64KB.

By rolandve

Master (219)

Аватар пользователя rolandve

01-06-2020, 10:29

Hi,

So I took a cup of coffee, and started reading this code. The assembly itself is not the challenge, the programming language of the assembler itself is more complicated. The snippets you posted with a MACRO I think I understand. I suppose Mapper.XXXXX with a DB 0 below it means: assembler assign a location for a double byte to structure member XXXXX of Mapper.

Is it correct that this is a part of a series of include files? There are references and/or variable names that are not defined in the code. I'm curious: I see a lot of 'jp 0' so the PC is set to 0. I'm very obvious an absolute noob here, but wouldn't that reset the machine?

cheers

By ToriHino

Hero (561)

Аватар пользователя ToriHino

01-06-2020, 11:33

The mapper functions from DOS2 are retrieved as a jump table by calling the ' Get mapper support routine address' function using EXTBIO (function 2 of EXTBIO). These jump addresses are then used to replace the 'jp 0' codes.

Get mapper support routine address

	Parameter:	A = 0
			D = 4
			E = 2
	Result:		A = total number of memory mapper segments
			B = slot number of primary mapper
			C = number of free segments of primary mapper
			DE = reserved
			HL = start address of jump table

By rolandve

Master (219)

Аватар пользователя rolandve

01-06-2020, 20:03

Thanks, that almost looks "to easy" for my understanding.

var 
    regs : tregs;

begin;
      with regs do
          begin;
             a:=0;
            d:=4;
            e:=2;
            EXTBIO(regs);
            writeln(a);
            writeln(b);
            writeln(c);
            writeln(hl);
end;
repeat until keypressed;
end.

128 (I've got 2 MB, so that's correct)
131 ( 1 000 0011 - slot is expanded page 1 seems to live in slot 3 )
127 ( I'm running MSX-DOS2, so this seems to be the amount of available memory )
-3584 ( TP can handle integers but when printing its signed, this normal )

Now if I am correct: that address I got is HL+0h (ALL_SEG). Is this address, the address or the routine itself? Since its a jump table I would guess the first, but checking. If that is correct, I would need to load A=0,B=0 for the main mapper call the routine's living at the address HL and in return I would get a segment (a) and a slot(b). Is it that simple?

Thanks!

By rolandve

Master (219)

Аватар пользователя rolandve

01-06-2020, 20:57

I moved forward assuming that jump table is indeed a list of addresses pointing too the actual routine. Based on that I wrote the next piece of code


Procedure CALL( Var regs : TRegs;nAddr:integer );
Var

        nA, nF         : Byte;
       nHL, nDE, nBC  : Integer;
       nIX, nIY       : Integer;
Begin
 nA  := regs.A;
 nHL := regs.HL;
 nDE := regs.DE;
 nBC := regs.BC;
 nIX := regs.IX;
 nIY := Swap( regs.IY );
  InLine( $F5/                  { PUSH AF      ; Push all registers  }
          $C5/                  { PUSH BC                            }
          $D5/                  { PUSH DE                            }
          $E5/                  { PUSH HL                            }
          $DD/$E5/              { PUSH IX                            }
          $FD/$E5/              { PUSH IY                            }
          $3A/nA/               { LD A , (nA )                       }
          $ED/$4B/nBC/          { LD BC, (nBC)                       }
          $ED/$5B/nDE/          { LD DE, (nDE)                       }
          $2A/nHL/              { LD HL, (nHL)                       }
          $DD/$2A/nIX/          { LD IX, (nIX)                       }
          $FD/$2A/nIY/          { LD IY, (nIY)                       }
          $CD/nAddr/          { CALL nAddr           }
          $32/nA/               { LD (nA ), A                        }
          $ED/$43/nBC/          { LD (nBC), BC                       }
         $ED/$53/nDE/          { LD (nDE), DE                       }
          $22/nHL/              { LD (nHL), HL                       }
          $DD/$22/nIX/          { LD (nIX), IX                       }
          $FD/$22/nIY/          { LD (nIY), IY                       }
          $F5/                  { PUSH AF                            }
          $E1/                  { POP HL                             }
          $22/nF/               { LD (nF), HL                        }
          $FD/$E1/              { POP YI       ; Pop all registers   }
          $DD/$E1/              { POP IX                             }
          $E1/                  { POP HL                             }
          $D1/                  { POP DE                             }
          $C1/                  { POP BC                             }
          $F1/                  { POP AF                             }
          $FB                   { EI                                 }
        );

  (* Update caller register struct *)
  regs.A  := nA;
  regs.F  := nF;
  regs.BC := nBC;
  regs.DE := nDE;
  regs.HL := nHL;
  regs.IY := nIY;
  regs.IX := nIX;
End;

var 
regs : tregs;
	tAddr: integer;
begin;
	with regs do
		begin;
			a:=0;
			d:=4;
			e:=2;
			EXTBIO(regs);
			writeln(a);
			writeln(b);
			writeln(c);
			writeln(hl);
			a:=0;
			bc:=0;
			de:=0;
			tAddr:=hl;
			hl:=0;
			IY:=0;
			iX:=0;
			f:=0;
			call(regs,tAddr);
			writeln(f);
			writeln(a);
			writeln(b);
		end;
end.

outputs:
128
131
127
-3584
0
0
1
----HANG--------- Reset required

TP can pass variables by value or by reference (put a VAR before it). In this case I am passing the actual address from register HL and try to do a call to that address.. Mmmm. anyone got a clean way to do this? or a direction for the solution?
Thanks!

By rolandve

Master (219)

Аватар пользователя rolandve

02-06-2020, 07:29

Found a logical error. TP passes the address to nAddr as a new memory address (by val) or the address to the actual value. So I do a "call" to the jump table. I need to figure out, how to use that memory and then load memory at an other address.

By rolandve

Master (219)

Аватар пользователя rolandve

03-06-2020, 06:49

Consider this closed. The program terminates and returns to dos after the call.

By Manuel

Ascended (16855)

Аватар пользователя Manuel

03-06-2020, 09:21

Please show the solution for people reading this thread later....

By rolandve

Master (219)

Аватар пользователя rolandve

03-06-2020, 18:29

LOL, if I could I would. It doesn't work, so I gave up. Thats what I meant when I said: the program end after the call. Using that function terminates my program so it doesn't do what I want it to do. So its no solution.