Once my CP/M program has finished doing whatever it's supposed to do, or the user chooses some sort of "quit" option, how do I return the user back to the CCP? Can I just do a ret
? Or do I need to jump to some location or something like that?
4 Answers
Basically: Both. The programmer can decide whether they want to re-load the CCP after the program has finished or preserve it and simply return. Depends on your memory requirements:
You can perform a warm start by coding a
rst 0 or
jmp 0
(Both do the exact same thing, the jump just needs two bytes more memory).
This re-initializes the system and re-loads the transient part of the CCP (which obviously must be present on the logged-in disk). If you do that, you can consider the memory occupied by that transient part "yours" and use it for your program.
If you left the original stack intact that was present when your program was first executed, you can restore that stack pointer and simply return to the CCP
; end of program, return to CCP
LHLD OLDSP ; restore initial stack pointer
; (must have been saved before)
SPHL
RET ; to the CCP
This spares the end user the inconvenience of waiting for a warm start (which normally really isn't that long) until the prompt comes back. It does, however, limit your program's available memory (not really significantly, but on CP/M, every byte might count), because you need to preserve the CCP's environment (code and stack) while your program is running.
Smaller utilities typically used the latter way, while bigger, memory-hungry programs used the first approach.
To find the lowest address occupied by the CCP, you would typically consult BDOS function 6 (if you want to preserve it) - I would assume most small utilities didn't even do that, but rather used trial and error to find out whether the CCP is still "alive" after they're done - or use the system's base address (at $0006) as your upper limit if you intend to use CCP memory for your program.
When a transient program is started, the stack pointer of the new program points to an 8-level stack created by the CCP, the current (topmost) entry holds the CCP return address - So, if you don't allocate your own stack, you can use up to six subroutine levels from that CCP memory (one should be left reserved to be able to do system calls from any subroutine) and return to the CCP.
-
Presumably very small utilities with that know they won't overflow the original stack or overwrite the CCP could simply just
ret
at the end of the program, then, right? How big and where is the stack that CP/M 2.2 gives to user programs, and where does the CCP start small (24K?) and "regular sized" (64K) CP/M systems?– cjsCommented Apr 9 at 10:43 -
1Actually, CP/M doesn't "give" any stack to transient programs - They have to set it up themselves in the TPA. For small utilities, you might get away with re-using a few bytes from the CCP's stack, but it can't be a lot. All of DR example programs set up their own stack and so should yours. You have the choice of restoring the CCP stack and return as pointed out above, but that's it. Also, there isn't a "standard" memory size below CCP - There are a lot of 3PP CPPs, and resident utilities that go between BDOS/BIOS and the CCP, so there really is no rule of thumb. But 8-10k should really be OK– tofroCommented Apr 9 at 10:54
-
1But generally, if you only use a few bytes of stack (CP/M does indeed guarantee 7 * 2 bytes and does guarantee that it won't use that stack in system calls) and your program is reasonably small, there shouldn't be problem doing that.– tofroCommented Apr 9 at 10:57
-
2@Raffzahn While we're nit-picking: No, a RST 0 isn't necessarily the same thing as pressing the RESET button - It does execute the same code, yes, but a hardware reset may do a lot more things than that (I'm assuming that's what your "(usually)" meant to say).– tofroCommented Apr 9 at 11:36
-
2@Raffzahn Disagree with "not recommended by DR" - Some DR example programs do exactly that, and the manuals point out the preconditions you have to meet.– tofroCommented Apr 9 at 11:38
Jump to location 0: jp 0
or rst 0
in Z80 assembly language. I confirmed this under RunCPM Version 6.1.)
Further information on using CP/M APIs can be found in the CP/M 2.2 manual section 5, CP/M 2 System Interface. Among other things, it states:
The transient program is called from the CCP. Thus, it can simply return to the CCP upon completion of its processing, or can Jump to BOOT to pass control back to CP/M. In the first case, the transient program must not use memory above CBASE, while in the latter case, memory up through FBASE-1 can be used. ... Upon entry to a transient program, the CCP leaves the stack pointer set to an eight-level stack area with the CCP return address pushed onto the stack, leaving seven levels before overflow occurs. Although this stack is usually not used by a transient program (most transients return to the CCP through a jump to location 0000H) it is large enough to make CP/M system calls because the FDOS switches to a local stack at system entry.
The CP/M memory map looks like this:
The start of the FDOS (BDOS+BIOS) is found in the word at location BOOT+$06, which is almost invariably just $06. There is no way to query the OS for the size of the CCP, but under CP/M 2 the CCP is a fixed size of $800 bytes. (This is fixed by the area allocated to it in the OS tracks by the system generation process. However, this does assume you are using the standard CCP or a replacement that follows the same conventions as the DR code. I don't know if things like ZCPR do this.)
-
5Why not rst 0? It would save you a couple of bytes and the OS should tidy up the stack and whatever. Commented Apr 9 at 7:24
As requested, here's the link to the documentation:
From the CP/M Plus Operating System Programmers Guide, p- 1-8:
Transient programs can terminate execution by jumping to location 0000H in the Page Zero region. This location contains a jump instruction to BIOS base+3, which contains a jump instruction to the BIOS warm start routine. The BIOS warm start routine loads the CCP into memory at location 100H and then passes control to it.
-
This is very specific to CP/M 3.0. In 2.2, the CCP does not reside at the lower end of the TPA (but rather at the top end)– tofroCommented Apr 9 at 14:41
-
@tofro: I would have thought the most logical way to set up the CCP would have been to have it load at 100h, but when spawning another program copy itself to the top of memory and set the stack pointer to point just below it. A program could test SP against its memory requirements, and either leave the stack pointer (and everything above it) alone, or set the stack pointer to top of memory and push zero. In either case, the program could then exit by doing a RET with the stack at the top level.– supercatCommented Apr 9 at 14:53
-
@supercat In CP/M 2.x, the CCP is one piece and loaded just below the BDOS/BIOS at the top of the TPA. In CP/M 3.x, the CCP is significantly larger and consists of two pieces, one, the "resident" part lives just below the system like in 2.x, the other, "transient" part is loaded like a "normal" program into the lower TPA at $100. You can, of course, create your own application stack and push a zero address, then simply RETurn when your program terminates (which will then do the warm start dance). You should, however, not assume that SP points to the end of the TPA when your program starts.– tofroCommented Apr 9 at 15:19
-
1@tofro BDOS function 6 only returns the bottom address of the CCP in CP/M 1.3, not any later versions.– john_eCommented Apr 9 at 16:10
-
1@supercat Note CP/M is far from what we would expect from an OS today - Once you have a look at the file handling support (you need to fill - and maintain - "File Control Block" structures instead of working with handles, for example), you'll really feel its age.– tofroCommented Apr 9 at 19:06
To add to the other answers, here's how the CP/M 2 Interface Guide describes the initial program environment:
Upon entry to a transient program, the CCP leaves the stack pointer set to an eight level stack area with the CCP return address pushed onto the stack, leaving seven levels before overflow occurs. Although this stack is usually not used by a transient program (i.e., most transients return to the CCP though a jump to location 0000H), it is sufficiently large to make CP/M system calls since the FDOS switches to a local stack at system entry. The following assembly language program segment, for example, reads characters continuously until an asterisk is encountered, at which time control returns to the CCP (assuming a standard CP/M system with BOOT = 0000H):
BDOS EQU 0005H ;STANDARD CP/M ENTRY
CONIN EQU 1 ;CONSOLE INPUT FUNCTION
;
ORG 0100H ;BASE OF TPA
NEXTC: MVI C,CONIN ;READ NEXT CHARACTER
CALL BDOS ;RETURN CHARACTER IN <A>
CPI '*' ;END OF PROCESSING?
JNZ NEXTC ;LOOP IF NOT
RET ;RETURN TO CCP
END