• Please review our updated Terms and Rules here

Program load not 0100h best practive

MykeLawson

Experienced Member
Joined
Mar 21, 2014
Messages
377
All of the documentation I have discusses the 'SAVE' command writing data to the disk starting at address 0100h. But what is considered best practice if you write a program designed to run at say address D000h getting that written to disk? I can't find a parameter that you can specify to the 'SAVE' command to use as an alternate start point. I suspect, and hope, it is just something really stupid I am overlooking. Thanks.
 
Well, CP/M programs load into 100H and start there. There are no other options. If the program relocates (part of) itself to another address (like DDT does), then you get into this dilemma. If you have enough disk space, you can just save everything from 100h to whatever the end of the interesting code is. But, if it was self-relocated then you have the task of piecing that back into something that loads and starts at 100H. I've used very large SAVEs to capture images of the OS, but you have to be able to handle a large binary in whatever you're using to examine it. If you can off-load it to Linux, for example, then it is easy. It really depends on what you plan to do with the pages you SAVE.
 
Yep, I was afraid of that. So, I guess I will have to incorporate the 'save' capability into the serial download program I want to write. I need to think through a process to do that. I guess I could write the serial port download program to first relocate to say D000h, perform the serial download into 0100h, and then jup back to CP/M when complete. then i could just use the SAVE command to save as normal....
 
If you have a program that receives over the serial port, can't it just do BDOS calls to write that to disk? Or is it a timing (handshake) issue? You essentially have all of memory from the end of your download program until the start of the BDOS to use as a buffer, so how large of a program do you need to download? Depends on your CP/M system, but that (the "TPA") is usually at least 48K if not closer to 60K. Very few, if any, COM files are that large.
 
I'd probably start off with something like this:
org 0100h
ld hl,code_origin ;start of code to transfer
ld bc,code_end-code_start+1 ;length of code to transfer
ld de,0DC00h ;target of transfer
ldir ;Z80 transfer instruction
jp 0DC00h

That is what the designer of the board I am using (Donn @ CPUville) uses to load his RAM based monitor program so it can be used to load software through the serial port on his card. While I could just stick with that, I want to use the serial port on his CPU card for a dedicated serial terminal. Until I have the dedicated terminal up and running, that is what I use now, in conjunction with RealTerm.
 
I guess I'm not sure what you're trying to do here. I thought you had CP/M running and you wanted a way to download a program over the serial port and save it to a CP/M file. I'm not seeing the need to have that download program run in high memory... a COM image can be placed anywhere in memory as long as you don't plan to execute it there. If you're just putting it in memory so you can save it out to disk, it doesn't need to be at 100H.
 
Hi Mike,

I think I can see what you're trying to do -

It might be easier creating a bootloader for your driver, eg, a .COM file that loads the driver into whatever memory location you want, then executes it.

If you write a generic one, it could load in the first record, then pick up both the load point and start point from the file itself, before jumping there to begin execution - much like CP/M 3 COM files do - Just preceed it with a jump in case someone tries to load it directly and execute via the code base. EG,

eg, Bootload.asm - Loads in the file in the first parameter and examines it to see where to relocate it in memory. The syntax of your assembler will be different, but shouldn't be too hard to get it working. ( Mostly reverse the EQUs and change the Hex numbers to whatever it accepts ).


Code:
EQU    BDOS,$0005
EQU    DESTINATION,$0083    ;     Location to load at.
EQU    JUMPTO,$0085        ;     Location to jump to.

.ORG $100
BOOTLOADER:
        LD        DE,$0080    ; DMA area.
        LD        C,$1A
        CALL    BDOS
    
        LD        DE,$005C    ; The FCB from the command line.
        LD        C,$0F        ; Open the file.
        CALL    BDOS        ; Load the first record.
        AND        $FC        ; Mask lower two bits.
        JR        NZ,BOOTERROR
    
        LD        DE,$005C
        LD        C,$14        ; Serial Read.
        CALL    BDOS
        AND        $FC        ; Mask lower two bits. Last test, next error is EOF.
        JR        NZ,BOOTERROR
    
        LD        DE,(DESTINATION)    ; The file tells us where we want to send the file.
        LD        (FILEDEST),DE
        LD        HL,(JUMPTO)            ; The file tells is the place to execute from.
        LD        (EXECUTEFROM),HL

FILELOOP:                ; Here's where we load in and transfer all the sectors until the file is read.

        LD        DE,(FILEDEST)
        LD        HL,$0080
        LD        BC,$80
        LDIR
        LD        (FILEDEST),DE

        LD        DE,$005C
        LD        C,$014
        CALL    BDOS
        AND        A
        JR        Z,FILELOOP            ; Only continue while A=00.
        DEC        A                    ; And of file = 01.
        JR        NZ,BOOTERROR        ; if it wasn't 01, then it's an error.
        
        LD        HL,(EXECUTEFROM)
        JP        (HL)   
    
DB 'Variables:'   
FILEDEST:        DW    $0000     ; File destination counter.     
EXECUTEFROM:    DW    $0000    ; Jump point.
            
BOOTERROR:
        LD        DE,ERROR
        LD        C,$09
        CALL    BDOS
        JP        $0000
        
ERROR:    db    'Oops, something went wrong. Try BOOTLOAD <filename> then Enter.',$0A,$0D,'$'





and then you can write your driver with the appropriate information in the code itself, eg;

Code:
EQU BDOS,$0005

.ORG $DC00
JP    ROUTINE
DW    $DC00
DW    $DC00

ROUTINE:
    LD    DE,MADEIT
    LD    C,9
    CALL  BDOS
    ret

 MADEIT: db 'OK, were at DC00.',$0A,$0D,'$'




The above code can be compiled as anything - eg, BIN, DRV, whatever.

If the first code compiles as "BOOTLOAD.COM" and the second code compiles as "DC00.DRV" (the second name is arbitrary) then the
type in
a> BOOTLOAD.COM DC00.DRV

And it will load in DC00.DRV, check the load and execute vectors, copy it to that location, then start executing it in case it needs to do something to insert itself as a driver ( eg, hook a RST, rewrite a jump etc... )

Is that kind of what you're trying to do?

David
 
Last edited:
@MykeLawson I'm still trying to see why you need a driver at all. Anything you load into high memory will be wiped out on the next warm boot (unless you take special steps to install it as a RSP/RSX), so it is really a one-shot program (has to be loaded each time you use it). If the goal is to download something and save it to a file, I think that can just be built as a single program (serial port download and save to file in the same program, won't take up much space). Depending on the serial protocol you use (synchronous or "fire hose"), you either save as you go (using very little memory) or else you buffer the whole download and then save it (limits the download to about the size of your TPA, which you have even with a driver in high memory).
 
I'm not entirely sure either, but the alternative I thought was to save memory other than at 0100 to disk, in which case a short save routine would work too - After all, whether it's the save in the CCP or SAVEMEM.COM makes no different to CP/M that I can see, since any commands will execute at 0100 and if there's no desire to save that memory space, there should be no trouble loading up a command from the disk and then using that to save the desired location?

I think I need L plates too. That would be a nice addition to the forum... Since I'm still pretty new to writing CPM apps. ( Less than a month's experience, and if it's by the hour, less than a week total ) so what I say may not necessarily be correct :unsure:
 
Well, my intention is to have a program, that when executed, loads at 0100 and then relocates to say D800 to continue executing. As it continues executing, it's purpose is to read in data from a dedicated serial port and place that data starting at 0100. when the data transfer is complete, the program returns control to CP/M. once back in CP/M, the SAVE command could be utilized to write the downloaded data to disk. But at some point, I may incorporate the ability to save direct to disk from the serial port into the program.
 
OK, so your end-to-end goal is to receive a program image over the serial port and save it into a file on disk. For that, there is no need to store the program image at 0100H before saving to disk, and thus no need to relocate part of your program into high memory (unnecessary complexity). In order to make a COM file that the CCP can load and run, the file just needs to have the program image in it. It makes no differences where in memory that image was before being written to the file. All of the CP/M utilities that create COM files will write the image from whatever memory address(es) contain the data - none of them try to create the program image (over the top of themselves) at 0100H.

The only case I've seen that is close to this is the Microsoft L80 linker for M80, which asks an obscure question at the end of the link phase, and if you answer wrong it will copy/load the newly-created program into it's designated start address (normally 0100h, but beware if it's not) and try to run it. You really don't want to run the downloaded program before saving it, as that disrupts the memory image and many programs are not designed to run from a "dirty" image of themselves.
 
That makes sense. The problem then is your compiler. It needs to compile code for DC00 and write it down load, eg, 0200 - The easy solution is to write a VERY BIG program as Doug said - that would still do what you want - Just have an .ORG DC00 in the second half and jump there.

In a smaller package, without customising your assembler, or writing the whole thing with JRs instead of JPs, you can't just relocate some code easily that I can image - maybe others will have an idea... But the code I posted will do what you want - You just compile the DC00 code under a different name, then use the loader to load it into that memory and execute it. It means two files, but thats all -

If you want to do it in the same file, with a normal assembler, you'll need to record every jump with an assembler directive and rewrite them all later, which would be a bit messy.

I like the idea - you're using the serial as a source of the .COM you want. You can exit from DC00 via a jump to $0000, and then you should be able to use save.

Regards
David
 
One other thought - since the objective is to save the file, you could also just download the serial into higher memory (eg, 0300 upwards ) and then write your own save routine, beginning at 0300. That would work also, but not with the save command. Still you could read the destination filename out of the FCB at 005c and just write that after you buffer all the serial.
 
Then I'm confused. If my serial port load program is assembled and saved as a .COM program, when it is executed, it gets read from disk into address 0100. then it would begin to start downloading the data from the serial port at sat 0200. and then when the download is complete, control returns to CP/M. but the data i downloaded is a program assembled to run from 0100. how do i tell SAVE to start saving from address 0200 for a program that executes at 0100?
 
My point is that the complexity of building this whole two-part relocatable program exceeds the complexity of just learning how to use BDOS calls to write a file. Then you have one program that receives from the serial port and writes that to a file. This is what all the existing programs do, like KERMIT, XMODEM, etc.

If you're not worried about serial port overrun, you might not even need any new program - especially if you transfer the program as a HEX file. That is, provided your CP/M allows access to the serial port you want to use. The command "PIP FILE=RDR:" will "download" whatever is sent on the device designated RDR on your CP/M. If you are downloading HEX, then PIP will know when an EOF is (the Crtl-Z in the HEX file).
 
Yeah, but where's the fun in using something that already exists; at least for these small and simple things? LOL Going through this process gets me more acquainted with chunks of code that I can pull from, and get ideas from. And as I do so, learn more about CP/M through baby steps I can get comfortable with along the way. Some will pan out, some won't. To me this hobby is a learning experience. Heck, last night my system just up and died. Turns out the driver chip for the system clock had failed. So, now I back and gonna start testing out my serial card to find any design flaws, and start moving toward using it as a dedicated port to load code. Fun times!
 
Sure, make your own path. At some point, though, you should learn how to handle files with BDOS calls.
 
Thanks. Oh I will. The second iteration of this effort will be a straight 'serial to disk' program. I am trying my best to keep from altering the CP/M load I have. That is a beast that I am just not yet knowledgeable enough on.
 
Many assemblers have a "phase" (or similar) directive to allow pieces of code to be ORGed differently, but still be contiguous in the output file. The drawback of using a fixed address is that it only works on specific systems where that address range does not conflict with the OS. But, making a "floating" relocatable is a huge can of worms.
 
Hi Myke, That code I put together was pretty close to what you want - have a look at how I did the function calls. Anyway, I've been going through the BDOS write routines lately, so I'll explain the algorythm. There's not many steps, and what you need is some code to determine how long the file is after you download it. I'll use your 0200 as an example starting point.


* Call Set DMA and set the DMA address to 0200 - DE=0200 and call function 26 - ie, C=26 then Call BDOS ( BDOS=0005)
* Call Open File. Check the result is between 0 and 3 or exit otherwise. ( Anything else in an error ) Function 15 opens the file. The filename should already be at 005C when you started the program, so DE=005C before call.
* Call Write record - Function 21. DE should be set to 005C ( the FCB) before each call. - This will write from 0200 to 027F
* Increment DMA - Set DE=0280 and call function 26 again.
* Set DE=005C and Call write record - Function 21. and repeat setting DMA and writing until all records of the file are written.
* Set DE=005C, Call Close File (Function 16) to write the FCB to the Disk Extent.

That's it. It's just a few lines of code to save memory to disk. Since you save from the start, what was at 200 will be loaded at 100 when you reload the program you just saved.

You can then run it like this (assuming you called your program xtrans);

xtrans file.com

That puts the FCB for file.com automatically at 005C - So you just need to point to the FCB and the function calls for Open, Write and CLose do all the work - And moving the DMA area saves you having to copy the file in chunks to a static DMA location.

Regards
David.
 
Back
Top