• Please review our updated Terms and Rules here

Changing CP/M 2.2 start location

nockieboy

Experienced Member
Joined
May 12, 2017
Messages
261
Location
UK - Kent
Hi all,

Context:
I'm in the middle of adding my video driver to my CP/M 2.2 BIOS so I can see CP/M on the screen instead of via the console. My computer is DIY, my video card is FPGA DIY, my knowledge of CP/M isn't great as I was too young to use it much when it was a commercial product and I'm not a programmer by trade.

My video driver is over 500 bytes in size, which means I need to move CP/M from its current 0xCE00 starting location in memory down a couple of pages to 0xCB00 to allow room for the video driver in the BIOS.

Rightly or wrongly, I'm doing this by adjusting the CPMSTART equate at the top of the CPM22.ASM source assembly. In my BIOS, the ccp equate is set to CPMSTART so will be 0xCB00. BDOS and BIOS bases are calculated from the ccp address.

I'm assembling CPM and its BIOS in Windows, then installing it onto CF card on my Z80 computer after pasting the hex into the ROM monitor via a serial console. I am not editing and assembling CPM on the Z80 system itself, so GENCPM and MOVCPM aren't options (necessarily).

Problem:
It seems that CP/M isn't too happy about reloading the CCP. When I load MBASIC, BBCBASIC, or some other TPA program, then quit it using its usual method, the system hangs. No error messages, no prompt, nothing. I'm fairly sure it's because the CCP is being re-loaded at the wrong address and when the program exits, it is jumping to where it thinks the CCP should start when actually it has been loaded elsewhere (probably two pages further up in memory).

All TPA programs work fine if I assemble CP/M starting at 0xCE00. So what am I missing to get it working reliably again? Have I missed something in changing the start address for CP/M anywhere? :(
 
My first guess would be that the address for the CCP is hard-coded into the warm-boot routine, and that needs to be changed to account for the new location of CCP after adding more to the BIOS. This probably also applies to the BDOS, depending on how those two are reloaded in warm-boot (when the BIOS got bigger, both BDOS and CCP got shifted downward to new locations). Both the load address and the JMP to the start address need to be located and changed, and if the BIOS calls the BDOS for anything, that address also needs to change. Sounds like the BDOS address must be OK as you are able to run programs, but you probably want to restructure your CPM22.ASM such that all entry/load addresses are using references to their actual locations, and not constant values.
 
My first guess would be that the address for the CCP is hard-coded into the warm-boot routine, and that needs to be changed to account for the new location of CCP after adding more to the BIOS.

Hi Durgadas! :) Yes, that was my initial thoughts as well, but I've searched the CPM source for any hard-coded references to 0xCE00 and there are none.

This probably also applies to the BDOS, depending on how those two are reloaded in warm-boot (when the BIOS got bigger, both BDOS and CCP got shifted downward to new locations). Both the load address and the JMP to the start address need to be located and changed, and if the BIOS calls the BDOS for anything, that address also needs to change. Sounds like the BDOS address must be OK as you are able to run programs, but you probably want to restructure your CPM22.ASM such that all entry/load addresses are using references to their actual locations, and not constant values.

Yes, I'm pretty sure BDOS is working fine. I didn't/forgot to mention in the OP above, but SOME TPA programs exit fine back to the CCP. It would appear that those that hang are the ones trying to reload the CCP. The ones that don't perform a 'quick and dirty' exit and just jump straight back to the CCP - or perhaps it's the other way around? One of the programs I've written, which exits cleanly back to the CCP without causing a hang, quits by calling
TERMCPM, or function 0, in the BDOS.

Here's a list of the programs I've tried to illustrate the point:

DIR - OK in both CP/M versions
SURVEY - ditto
PEEK (my own program mentioned above) - OK in both

BBCBASIC - hangs in the 0xCB00 version of CP/M
SYSINFO - ditto
MBASIC - ditto

Both the variations of BASIC load programs, execute, perform fine until I try to exit back to the CCP. BDOS appears to be an integral part of the CP/M 2.2 source.
 
Not all programs return to CP/M the same way. Some (usually large) programs return to warm start, some don't.
It depends on weather the program has overwritten CCP or not.

CBIOS warm start has the address of CCP hardcoded somewhere (or can calculate it)

joe
 
Not all programs return to CP/M the same way. Some (usually large) programs return to warm start, some don't.
It depends on weather the program has overwritten CCP or not.

CBIOS warm start has the address of CCP hardcoded somewhere (or can calculate it)

joe

Hi joe,

The programs that don't hang the system (that I have the source to, at least) exit back to the CCP this way:

At the start of the program, it stores CP/M's stack pointer:

Code:
	LD		HL,0000H
	ADD		HL,SP
	LD		(OLDSP),HL			; Store CP/M's Stack Pointer

Then, later on when the user or program exits:

Code:
EXIT:
	LD		HL,(OLDSP)			;GET OLD STACK POINTER
	LD		SP,HL				;RESTORE IT
	RET							;STACK POINTER CONTAINS CCP'S STACK LOCATION

So I presume that programs that DO hang the system are using the BDOS call for TERMCPM. Not sure about the code comments though, they don't seem to make a lot of sense (to me at least).
 
Having debugged a similar issue, it appears that some programs like MBASIC do not exit via return to CCP, BDOS function 0 or a jump to location 0. Instead they appear to use the BIOS jump vector directly. It also appeared that some of them simply use the address from the jump at BIOS+3 and thus a Z80 JR/NOP sequence in the vector can fail.

As previously mentioned, the BIOS requires both it's own address and that of the BDOS when it initializes low memory and I choose to re-initialize low memory on both cold and warm boots. BDOS function 0 simply jumps to the BIOS+3 jump vector and it is assumed that the BIOS immediately follows the BDOS.

The copy of CPM22.ASM that I have does not contain the label CPMSTART. Instead it has a "MEM EQU ##" statement which defines the number of KB and is later followed by "ORG (MEM-7)*1024". Likewise it doesn't have the label TERMCPM.
 
Ordinarily, there's no difference between a "jmp 0" and "mvi c,0; call bdos" return to CP/M. Both should jump to the BIOS warm boot entry. The other option, for programs that *know* they won't overwrite the CCP, is to return directly to CCP - either by saving the CCP stack and restoring it (as the code above shows) or just by using very little stack. MBASIC certainly will overwrite the CCP, as it uses all of memory up to the BDOS (and a lot of stack). since programs that return directly to CCP work, it would appear that something in the BIOS warm boot routine is not taking into account the new size of the system (and associated BDOS/CCP start addresses). Either the reload is using the wrong addresses, or else the "jump to CCP" in warm boot is using the wrong address (it would seem). If you can share the CPM22.ASM code, I/we might be able to track this down.
 
I'll let you guys bang on about this, but the original statement by the thread poster said something about not being able to use MOVCPM becasue he wasn't building on a CP/M system. I don't follow. I run an emulator on my PC (22NICE) that doesn't even have a CCP or BDOS per se and I can run MOVCPM--just be sure to disable the "SYNCHRONIZATION ERROR" check.
 
The copy of CPM22.ASM that I have does not contain the label CPMSTART. Instead it has a "MEM EQU ##" statement which defines the number of KB and is later followed by "ORG (MEM-7)*1024". Likewise it doesn't have the label TERMCPM.

CPMSTART may be a label that I've added at some point in the past, or is specific to the copy of CPM source code that I have. It's just a label that is used for the .ORG command to set the address for CBASE. TERMCPM is probably a label I use in my code rather than a generally-accepted label used in CPM. It's basically just 0. So BDOS TERMCPM is the same as BDOS 0.


Ordinarily, there's no difference between a "jmp 0" and "mvi c,0; call bdos" return to CP/M. Both should jump to the BIOS warm boot entry. The other option, for programs that *know* they won't overwrite the CCP, is to return directly to CCP - either by saving the CCP stack and restoring it (as the code above shows) or just by using very little stack. MBASIC certainly will overwrite the CCP, as it uses all of memory up to the BDOS (and a lot of stack). since programs that return directly to CCP work, it would appear that something in the BIOS warm boot routine is not taking into account the new size of the system (and associated BDOS/CCP start addresses). Either the reload is using the wrong addresses, or else the "jump to CCP" in warm boot is using the wrong address (it would seem). If you can share the CPM22.ASM code, I/we might be able to track this down.

Okay, here's the relevant part (or at least I hope it's everything that's relevant) of my BIOS for CP/M 2.2:

Code:
;------------------------------------------------------------------------------
; CP/M memory settings
;------------------------------------------------------------------------------
ccp				.EQU	CPMSTART			; Base of CCP
bdos			.EQU	ccp + 0806h			; Base of BDOS
bios			.EQU	ccp + 1600h			; Base of BIOS

;------------------------------------------------------------------------------
; Set CP/M low memory data, vector and buffer addresses.
;------------------------------------------------------------------------------
iobyte			.EQU	03h					; Intel standard I/O definition byte.
userdrv			.EQU	04h					; Current user number and drive.
tpabuf			.EQU	80h					; Default I/O buffer and command line storage.

#if gpu
#include "GPU_EQU.ASM"
#endif

;------------------------------------------------------------------------------
; Supervisor Module Equates & Serial Identifiers
;------------------------------------------------------------------------------
CIC				.EQU	'%'					; Command Initiator Character
EOL				.EQU	'#'					; End Of Line character for serial comms

; Serial comms settings
;
SER_BUFSIZE		.EQU	68
SER_FULLSIZE	.EQU	50
SER_EMPTYSIZE	.EQU	8
RTS_HIGH		.EQU	0E8H
RTS_LOW			.EQU	0EAH
SIOA_D			.EQU	$00
SIOA_C			.EQU	$02
SIOB_D			.EQU	$01
SIOB_C			.EQU	$03

; INTerrupts
;
int38			.EQU	38H
nmi				.EQU	66H

; CP/M disk settings
;
blksiz			.equ	4096				; CP/M allocation size
hstsiz			.equ	512					; host disk sector size
hstspt			.equ	32					; host disk sectors/trk
hstblk			.equ	hstsiz/128			; CP/M sects/host buff
cpmspt			.equ	hstblk * hstspt		; CP/M sectors/track
secmsk			.equ	hstblk-1			; sector mask

;compute sector mask
;secshf		.equ	2		;log2(hstblk)

wrall			.equ	0					; write to allocated
wrdir			.equ	1					; write to directory
wrual			.equ	2					; write to unallocated

;------------------------------------------------------------------------------
; CF registers
;------------------------------------------------------------------------------
CF_CONTROL		.EQU		$FF				; CF control port
CF_BASE			.EQU		$10
CF_DATA			.EQU		CF_BASE + 0		; Data (R/W)
CF_FEATURES		.EQU		CF_BASE + 1		; Features (W)
CF_ERROR		.EQU		CF_BASE + 1		; Error register (R)
CF_SECCOUNT		.EQU		CF_BASE + 2		; Sector count (R/W)
CF_SECTOR		.EQU		CF_BASE + 3
CF_CYL_LOW		.EQU		CF_BASE + 4
CF_CYL_HI		.EQU		CF_BASE + 5
CF_HEAD			.EQU		CF_BASE + 6
CF_STATUS		.EQU		CF_BASE + 7		; Status (R)
CF_COMMAND		.EQU		CF_BASE + 7		; Command (W)
CF_LBA0			.EQU		CF_BASE + 3		; LBA bits 0-7 (R/W, LBA mode)
CF_LBA1			.EQU		CF_BASE + 4		; LBA bits 8-15 (R/W, LBA mode)
CF_LBA2			.EQU		CF_BASE + 5		; LBA bits 16-23 (R/W, LBA mode)
CF_LBA3			.EQU		CF_BASE + 6		; LBA bits 24-27 (R/W, LBA mode)

;------------------------------------------------------------------------------
;CF Features
;------------------------------------------------------------------------------
CF_8BIT			.EQU		1
CF_NOCACHE		.EQU		082H

;------------------------------------------------------------------------------
;CF Commands
;------------------------------------------------------------------------------
CF_RESET		.EQU		$04
CF_READ_SEC		.EQU		020H
CF_WRITE_SEC	.EQU		030H
CF_IDENTIFY		.EQU		0ECH
CF_SET_FEAT		.EQU		0EFH

;------------------------------------------------------------------------------
; Terminal equates
;------------------------------------------------------------------------------
;LF						.EQU		0AH			
;FF						.EQU		0CH			
;CR						.EQU		0DH			

;------------------------------------------------------------------------------
; MMU variables
;------------------------------------------------------------------------------
MMU_CTRL:		.EQU		$3C		; IO address for the MMU
MMU_Banks:		.EQU		$FF		; Number of available memory banks
MMU_A0:			.EQU		$38		; IO address for Area 0 settings
MMU_A1:			.EQU		$39		; IO address for Area 1 settings
MMU_A2:			.EQU		$3A		; IO address for Area 2 settings
MMU_A3:			.EQU		$3B		; IO address for Area 3 settings

;================================================================================================
		.ORG	0050H		; 'Low Storage' scratch pad for MMU / variables

; Single-byte character buffers for use by HexToNum
CHR_BUF_D:		.DS		1
CHR_BUF_E:		.DS		1		
CHR_BUF_M:		.DS		1
CHR_BUF_O:		.DS		1

; Single-byte character buffer for use by PHEX
PHX_BUF:		.DS		1

MMU_Area0:		.DS		1		; Holds the current mapping for Area 0 (0-16K)
MMU_Area1:		.DS		1		; Holds the current mapping for Area 1 (16-32K)
MMU_Area2:		.DS		1		; Holds the current mapping for Area 2 (32-48K)
MMU_Area3:		.DS		1		; Holds the current mapping for Area 3 (48-64K)
MMU_Lock:		.DS		1		; Bit-mask for locked Areas (holding critical data like the stack, these MMU vars)
								; LSN, 4 bits, corresponding to 4 Areas, 1-locked, 0-free - MSN not used
								; Used by BANK command to prevent accidental trashing of system memory
CPMLDR_CLK:		.EQU	0042H	; Temp location to store CLK for CPMLDR

;================================================================================================
		.ORG	bios		; BIOS origin (default E600h)
;================================================================================================
; BIOS jump table.
;================================================================================================
		JP	boot			;  0 Initialize.
wboote:
		JP	wboot			;  1 Warm boot.
		JP	const			;  2 Console status.
		JP	conin			;  3 Console input.
		JP	conout			;  4 Console OUTput.
		JP	list			;  5 List OUTput.
		JP	punch			;  6 punch OUTput.
		JP	reader			;  7 Reader input.
		JP	home			;  8 Home disk.
		JP	seldsk			;  9 Select disk.
		JP	settrk			; 10 Select track.
		JP	setsec			; 11 Select sector.
		JP	setdma			; 12 Set DMA ADDress.
		JP	read			; 13 Read 128 bytes.
		JP	write			; 14 Write 128 bytes.
		JP	listst			; 15 List status.
		JP	sectran			; 16 Sector translate.

;================================================================================================
; Disk parameter headers for disk 0 to 15
;================================================================================================
dpbase:
	 	.DW 0000h,0000h,0000h,0000h,dirbuf,dpb0,0000h,alv00
	 	.DW 0000h,0000h,0000h,0000h,dirbuf,dpb,0000h,alv01
	 	.DW 0000h,0000h,0000h,0000h,dirbuf,dpb,0000h,alv02
	 	.DW 0000h,0000h,0000h,0000h,dirbuf,dpb,0000h,alv03
	 	.DW 0000h,0000h,0000h,0000h,dirbuf,dpb,0000h,alv04
	 	.DW 0000h,0000h,0000h,0000h,dirbuf,dpb,0000h,alv05
	 	.DW 0000h,0000h,0000h,0000h,dirbuf,dpb,0000h,alv06
#if CPM64
	 	.DW 0000h,0000h,0000h,0000h,dirbuf,dpbLast,0000h,alv07
#else
		.DW 0000h,0000h,0000h,0000h,dirbuf,dpb,0000h,alv07
        .DW 0000h,0000h,0000h,0000h,dirbuf,dpb,0000h,alv08
        .DW 0000h,0000h,0000h,0000h,dirbuf,dpb,0000h,alv09
        .DW 0000h,0000h,0000h,0000h,dirbuf,dpb,0000h,alv10
        .DW 0000h,0000h,0000h,0000h,dirbuf,dpb,0000h,alv11
        .DW 0000h,0000h,0000h,0000h,dirbuf,dpb,0000h,alv12
        .DW 0000h,0000h,0000h,0000h,dirbuf,dpb,0000h,alv13
        .DW 0000h,0000h,0000h,0000h,dirbuf,dpb,0000h,alv14
        .DW 0000h,0000h,0000h,0000h,dirbuf,dpbLast,0000h,alv15
#endif

; First drive has a reserved track for CP/M
dpb0:
	.DW 	128 		; SPT - sectors per track
	.DB		5   		; BSH - block shift factor
	.DB		31  		; BLM - block mask
	.DB 	1   		; EXM - Extent mask
	.DW		2043 		; (2047-4) DSM - Storage size (blocks - 1)
	.DW 	511 		; DRM - Number of directory entries - 1
	.DB 	240 		; AL0 - 1 bit set per directory block
	.DB 	0  			; AL1 -            "
	.DW 	0   		; CKS - DIR check vector size (DRM+1)/4 (0=fixed disk)
	.DW 	1   		; OFF - Reserved tracks

dpb:
	.DW 	128 		; SPT - sectors per track
	.DB 	5   		; BSH - block shift factor
	.DB 	31  		; BLM - block mask
	.DB 	1   		; EXM - Extent mask
	.DW 	2047 		; DSM - Storage size (blocks - 1)
	.DW 	511 		; DRM - Number of directory entries - 1
	.DB 	240 		; AL0 - 1 bit set per directory block
	.DB 	0   		; AL1 -            "
	.DW 	0   		; CKS - DIR check vector size (DRM+1)/4 (0=fixed disk)
	.DW 	0   		; OFF - Reserved tracks

; Last drive is smaller because CF is never full 64MB or 128MB
dpbLast:
	.DW 	128 		; SPT - sectors per track
	.DB 	5   		; BSH - block shift factor
	.DB 	31  		; BLM - block mask
	.DB 	1   		; EXM - Extent mask
	.DW 	1279 		; DSM - Storage size (blocks - 1)
						; 511 = 2MB (for 128MB card), 1279 = 5MB (for 64MB card)
	.DW 	511 		; DRM - Number of directory entries - 1
	.DB 	240 		; AL0 - 1 bit set per directory block
	.DB 	0   		; AL1 -            "
	.DW 	0   		; CKS - DIR check vector size (DRM+1)/4 (0=fixed disk)
	.DW 	0   		; OFF - Reserved tracks

;================================================================================================
; Cold boot
;================================================================================================
boot:
	DI							; Disable interrupts.
	LD		SP,biosstack		; Set default stack.

	; Set up the memory map as follows:
	; Area 0 - Bank 0 (RAM)
	; Area 1 - Bank 1 (RAM)
	; Area 2 - Bank 2 (RAM)
	LD		A,0
	OUT 	(MMU_A0),A			; Set Area 0 to Bank 0
	LD		A,1					; 
	OUT 	(MMU_A1),A			; Set Area 1 to Bank 1
	LD		A,2					; 
	OUT 	(MMU_A2),A			; Set Area 2 to Bank 2
	
	; Update the stored values for the Area/Bank mappings
	XOR		A
	LD		(MMU_Area0),A		; Record Area 0 mapped to 00
	LD		A,1
	LD		(MMU_Area1),A		; Record Area 1 mapped to 01
	LD		A,2
	LD		(MMU_Area2),A		; Record Area 2 mapped to 02
	
	; Initialise SIO
	LD		A,$00
	OUT		(SIOA_C),A
	LD		A,$18				; Write into WR0: channel reset
	OUT		(SIOA_C),A
	LD		A,$04				; Select Write Register 4 which
	OUT		(SIOA_C),A			; sets the serial baud rate
	; Need to get system clock speed (CLK) which should be available
	; to calculate baud rate based on 4 or 8 MHz system clock.
	; If not available, default to 4 MHz clock divider (/32).
	; This will maintain constant 125kbaud on Port A whatever the system clock.
	LD		A,(CLK)				; Get CLK speed
	LD		(CPMLDR_CLK),A		; Copy it to temp location where CP/M 3's CPMLDR will look for it
	CP		'2'					; 2 MHz clock - set baud at double default
	JP		Z,baud_2
	CP		'8'					; 8 MHz clock - set baud at half default
	JP		Z,baud_8
baud_4:
	LD		A,$84				; (84h = CLK/32, 1 stop bit, no parity) ($84)
	JP		baudXt				; Jump to set baud rate
baud_2:
	LD		A,$44				; CLK/16, 1 stop bit, no parity ($44)
	JP		baudXt				; Jump to set baud rate
baud_8:
	LD		A,$C4				; CLK/64, 1 stop bit, no parity ($C4)
baudXt:
	OUT		(SIOA_C),A			; Set baud rate

	LD		A,$01				; Select Write Register 1
	OUT		(SIOA_C),A
#if io_int
	LD		A,$18				; Interrupt on all Rx chars
#else
	LD		A,$00				; No interrupts
#endif
	OUT		(SIOA_C),A

	LD		A,$03				; Select Write Register 3
	OUT		(SIOA_C),A
	LD		A,$E1				; Rx 8 bits/char, Rx enable
	OUT		(SIOA_C),A

	LD		A,$05				; Select Write Register 5
	OUT		(SIOA_C),A
	LD		A,RTS_LOW			; DTR, Tx 8 bits/char, Tx enable
	OUT		(SIOA_C),A

	LD		A,$00				; Select Write Register 0
	OUT		(SIOB_C),A			;
	LD		A,$18				; Channel reset
	OUT		(SIOB_C),A			;

	LD		A,$04				; Select Write Register 4 which
	OUT		(SIOB_C),A			; sets the serial baud rate
	LD		A,$C4				; CLK/64, 1 stop bit, no parity
	OUT		(SIOB_C),A			;

	LD		A,$01				; Select Write Register 1
	OUT		(SIOB_C),A			;
#if io_int
	LD		A,$18				; Interrupt on all Rx chars
#else
	LD		A,$00				; No interrupts
#endif
	OUT		(SIOB_C),A			;

#if io_int
	LD		A,$02				; Select Write Register 2
	OUT		(SIOB_C),A			;
	LD		A,$E0				; INTERRUPT VECTOR
	OUT		(SIOB_C),A			;
#endif

	LD		A,$03				; Select Write Register 3
	OUT		(SIOB_C),A			;
	LD		A,$E1				; Rx 8 bits/char, Rx enable
	OUT		(SIOB_C),A			;

	LD		A,$05				; Select Write Register 5
	OUT		(SIOB_C),A			;
	LD		A,RTS_LOW			; DTR, Tx 8 bits/char, Tx enable
	OUT		(SIOB_C),A			;

#if gpu
	CALL	INIT_GPU			; Initialise the GPU
	DI
#endif

#if io_int
	LD		A,$FF				; Interrupt vector in page FF
	LD		I,A
#endif

	CALL	printInline
	.BYTE	$0C
	.TEXT	"uCOM CP/M BIOS "
	.TEXT	"1.6.104"
#if io_int
	.TEXT	"i"
#else
	.TEXT	"p"
#endif
#if gpu
	.TEXT	"-GPU"
#endif
	.TEXT	" by J.Nock 2017-20"
	.BYTE	CR,LF,CR,LF
	.TEXT 	"CP/M 2.2 "
	.TEXT	"Copyright"
	.TEXT	" 1979 (c) by Digital Research"
	.BYTE	CR,LF,0

	CALL	CF_INIT
	CALL	CF_WAIT
	LD 		A,CF_8BIT		; Set IDE to be 8-bit
	OUT		(CF_FEATURES),A
	LD		A,CF_SET_FEAT
	OUT		(CF_COMMAND),A
	CALL	CF_WAIT
	LD 		A,CF_NOCACHE	; No write cache
	OUT		(CF_FEATURES),A
	LD		A,CF_SET_FEAT
	OUT		(CF_COMMAND),A
	XOR		A				; Clear I/O & drive bytes.
	LD		(userdrv),A
#if io_int
	LD		(serABufUsed),A
	LD		(serBBufUsed),A
	LD		HL,serABuf
	LD		(serAInPtr),HL
	LD		(serARdPtr),HL
	LD		HL,serBBuf
	LD		(serBInPtr),HL
	LD		(serBRdPtr),HL
	EI
#endif
	JP		GOCPM

;================================================================================================
; Warm boot
;================================================================================================
wboot:
#if io_int
	DI								; Disable interrupts.
#endif
	LD		SP,biosstack		; Set default stack.
	; Interrupt vector in page FF
	LD		A,$FF
	LD		I,A
	LD		B,11 				; Number of sectors to reload
	LD		A,0
	LD		(hstsec),A
	LD		HL,ccp
rdSectors:
	CALL	CF_CMD_RDY
	; Set LBA address
	LD		A,(hstsec)
	OUT 	(CF_LBA0),A
	LD		A,0
	OUT 	(CF_LBA1),A
	OUT 	(CF_LBA2),A
	LD		A,0E0H
	OUT 	(CF_LBA3),A
	LD 		A,1
	OUT 	(CF_SECCOUNT),A
	PUSH 	BC
	CALL 	CF_CMD_RDY
	LD 		A,CF_READ_SEC
	OUT 	(CF_COMMAND),A
	CALL 	CF_DAT_RDY
	LD 		C,4
rd4secs512:
	LD 		B,128
rdByte512:
	CALL	CF_DAT_RDY
	IN 		A,(CF_DATA)
	LD 		(HL),A
	INC 	HL
	DEC 	B
	JR 		NZ, rdByte512
	DEC 	C
	JR 		NZ,rd4secs512
	POP 	BC
	LD		A,(hstsec)
	INC		A
	LD		(hstsec),A
	DJNZ	rdSectors

;================================================================================================
; Common code for cold and warm boot
;================================================================================================
GOCPM:
	XOR		A				; 0 to accumulator
	LD		(hstact),A		; host buffer inactive
	LD		(unacnt),A		; clear unalloc count

	LD		HL,serialInt	; Address of serial interrupt.
	LD		($40),HL

	LD		HL,tpabuf		; Address of BIOS DMA buffer.
	LD		(dmaAddr),HL
	LD		A,0C3h			; Opcode for 'JP'.
	LD		(00h),A			; Load at start of RAM.
	LD		HL,wboote		; Address of jump for a warm boot.
	LD		(01h),HL
	LD		(05h),A			; Opcode for 'JP'.
	LD		HL,bdos			; Address of jump for the BDOS.
	LD		(06h),HL
	LD		A,(userdrv)		; Save new drive number (0).
	LD		C,A				; Pass drive number in C.

#if io_int
	IM		2
	EI						; Enable interrupts
#endif

	JP		ccp				; Start CP/M by jumping to the CCP.

And here's the initial bit of the CPM 2.2 source code. There's too much to post here so I've restricted it to the start, up to CBASE.

Code:
;**************************************************************
;*
;*             C P / M   version   2 . 2
;*
;*   Reconstructed from memory image on February 27, 1981
;*
;*                by Clark A. Calkins
;*
;**************************************************************

;------------------------------------------------------------------------------
; TASM Compiler settings for MINICOM
;------------------------------------------------------------------------------
true		.EQU 	-1
false		.EQU 	!true

;------------------------------------------------------------------------------
; CPM Compiler Options
;------------------------------------------------------------------------------
CPM64		.EQU	false	; Set to true to compile 64MB CF card BIOS version
							; If 'false', 128MB BIOS version is compiled.
NOCAPS		.EQU	false	; True to stop CCP forcing uppercase

;------------------------------------------------------------------------------
; Assembly Options 
;------------------------------------------------------------------------------
gpu			.EQU	true	; Include GPU driver
gpu_stat	.EQU	false	; Status bar at bottom of window
io_int		.EQU	true	; Interrupt-driven IO?

#if gpu
CPMSTART	.EQU	0CB00H	; Start location for CPM with GPU-supporting BIOS
#else
CPMSTART	.EQU	0CE00H	; Start location for CPM with standard BIOS
#endif
;------------------------------------------------------------------------------

IOBYTE		.EQU	3		;i/o definition byte.
TDRIVE		.EQU	4		;current drive name and user number.
ENTRY		.EQU	5		;entry point for the cp/m bdos.
TFCB		.EQU	5CH		;default file control block.
TBUFF		.EQU	80H		;i/o buffer and command line storage.
TBASE		.EQU	100H	;transient program storage area.
;
;   Set control character equates.
;
CNTRLC		.EQU	3		;control-c
CNTRLE		.EQU	05H		;control-e
BS			.EQU	08H		;backspace
BKSP    	.EQU    08H     ;backspace (GPU)
TAB			.EQU	09H		;tab
LF			.EQU	0AH		;line feed
FF			.EQU	0CH		;form feed
MCLS       	.EQU   	0CH    	;clear screen (GPU)
CR			.EQU	0DH		;carriage return
CNTRLP		.EQU	10H		;control-p
CNTRLR		.EQU	12H		;control-r
CNTRLS		.EQU	13H		;control-s
CNTRLU		.EQU	15H		;control-u
CNTRLX		.EQU	18H		;control-x
CNTRLZ		.EQU	1AH		;control-z (end-of-file mark)
DEL			.EQU	7FH		;rubout
EOS			.EQU	'$'

;------------------------------------------------------------------------------
; Insert ROM BANK_ID information for Z80 Minicom
;------------------------------------------------------------------------------
.ORG	$FFF0
#if CPM64
BANK_ID:	.BYTE	$04		
MAP2AREA:	.BYTE	$03
BANK_NAME:	.BYTE	"CPM 2.2 64"	; 10 chars (must be less than 12 for CPM)
			.BYTE	0
#else
BANK_ID:	.BYTE	$05		
MAP2AREA:	.BYTE	$03
#if NOCAPS
BANK_NAME:	.BYTE	"CPM 2.2 NC"	; 10 chars (must be less than 12 for CPM)
#else
BANK_NAME:	.BYTE	"CPM 2.2 128"	; 11 chars (must be less than 12 for CPM)
#endif
			.BYTE	0
#endif

;------------------------------------------------------------------------------
;   Set origin for CP/M
;------------------------------------------------------------------------------
	.ORG	CPMSTART

;------------------------------------------------------------------------------
CBASE:	
	JP	COMMAND		;execute command processor (ccp).
	JP	CLEARBUF	;entry to empty input buffer before starting ccp.
I can't remember off-hand how to attach files to the forum posts, but if anyone can point me in the right direction I'll happily attach the whole BIOS and CPM source.

EDIT: Worked it out. ;)

View attachment CPM22.txt
View attachment CBIOS.txt
 
Last edited:
It's obvious to me that your CPM22 file has been modified from the original distribution but there's no change history or indication of the changes. I'm not going to go through it or your CBIOS but will give a couple of comments based on an initial glance.

If you look at your assembly listing you'll see that the CPM22 module includes the jumps for the CBIOS at the end since it assumes separate assemblies and that your BIOS will overlay this area. Assuming the #include "CBIOS.ASM" brings in your CBIOS.TXT routine, it then ORGs back over this area and duplicates the jump vector. This relies on your assembler / linker figuring out what you really meant. Much cleaner to change the CPM22 vector to be "BOOT EQU $; WBOOT EQU BOOT+3; CONST EQU BOOT+6" etc. and then there is no need for the ".ORG bios".

I would suggest changing the CBIOS ".ORG 0050h" to be 0040h which is defined by CP/M as available for the CBIOS whereas 0050h-005Bh is "Not currently used; reserved". Note that you'll then also need to change the .EQU on label CPMLDR_CLK. Perhaps some of your applications are assuming that since this area is reserved for CP/M but not used that they can also use this area.

You might want to use something like DDT to verify that low memory and the jump table are initialized the way you expect.
 
sorry if this posts more than once.... 4th try.

rd4secs512:
LD B,128
rdByte512:
CALL CF_DAT_RDY
IN A,(CF_DATA)
LD (HL),A
INC HL
DEC B
JR NZ, rdByte512
DEC C
JR NZ,rd4secs512
POP BC
LD A,(hstsec)
INC A
LD (hstsec),A
>>>>need a DEC B here?
DJNZ rdSectors

oops... i misread DJNZ as JNZ
 
Last edited:
It's obvious to me that your CPM22 file has been modified from the original distribution but there's no change history or indication of the changes. I'm not going to go through it or your CBIOS but will give a couple of comments based on an initial glance.

Yeah, amateur programmer here. :lookroun:

If you look at your assembly listing you'll see that the CPM22 module includes the jumps for the CBIOS at the end since it assumes separate assemblies and that your BIOS will overlay this area. Assuming the #include "CBIOS.ASM" brings in your CBIOS.TXT routine, it then ORGs back over this area and duplicates the jump vector. This relies on your assembler / linker figuring out what you really meant. Much cleaner to change the CPM22 vector to be "BOOT EQU $; WBOOT EQU BOOT+3; CONST EQU BOOT+6" etc. and then there is no need for the ".ORG bios".

Okay, I think I understand. I've changed the CPM22 jump table to this:

Code:
;**************************************************************
;*
;*        B I O S   J U M P   T A B L E
;*
;**************************************************************
;
bios:	.EQU	$	; Set BIOS start

BOOT:	.EQU	$	; These are overlayed by the BIOS jump table
WBOOT:	.EQU	BOOT+3
CONST:	.EQU	BOOT+6
CONIN:	.EQU	BOOT+9
CONOUT:	.EQU	BOOT+12
LIST:	.EQU	BOOT+15
PUNCH:	.EQU	BOOT+18
READER:	.EQU	BOOT+21
HOME:	.EQU	BOOT+24
SELDSK:	.EQU	BOOT+27
SETTRK:	.EQU	BOOT+30
SETSEC:	.EQU	BOOT+33
SETDMA:	.EQU	BOOT+36
READ:	.EQU	BOOT+39
WRITE:	.EQU	BOOT+42
PRSTAT:	.EQU	BOOT+45
SECTRN:	.EQU	BOOT+48

I've moved the 'bios' definition out of the CBIOS code to the start of the CPM22 jump table, so instead of having a fixed size calculation working out the address of 'bios' in CBIOS, it is now defined in CPM22 and is now tolerant of any code changes in the CPM or BDOS code. Ditto for 'bdos' - instead of being calculated as 'ccp + 0806h', 'bdos' is now defined as the same address as FBASE, the BDOS entry point. I hope that's right.

I would suggest changing the CBIOS ".ORG 0050h" to be 0040h which is defined by CP/M as available for the CBIOS whereas 0050h-005Bh is "Not currently used; reserved". Note that you'll then also need to change the .EQU on label CPMLDR_CLK. Perhaps some of your applications are assuming that since this area is reserved for CP/M but not used that they can also use this area.

I'll leave that for the moment, I don't want to make too many changes until I get this warm-boot bug fixed. I will start looking at DDT and learning how to use it, seems it's going to be the tool to identify the problem. So I could theoretically step through the warm/cold boot procedures using DDT? :confused:
 
I would warn against making "gratuitous" changes at this point, your code was working just fine before you made the BIOS larger. Sure, there are plenty of "review comments" that more-experienced programmers would make, but introducing too much change without knowing what the problem is will just complicate the debug process.

I'm not immediately seeing anything wrong with the parts you posted, but the proof might be in the assembler listing file - by verifying that the addresses being assembled into actually match your expectations. Since this runs correctly after cold boot, I have to assume that the basic addresses are correct, so it may center around the warm boot routine. For example, the assembly symbol 'ccp' might only be used during warm boot (cold boot already has the CCP+BDOS+BIOS loaded by the ROM) and so you wouldn't notice if it were "wrong" until you did a warm boot.

Brings up a question, though: how does the ROM decide where to load CP/M into memory? Make certain the ROM load address is the same as what is compiled into the BIOS warm boot (as 'ccp') - i.e. make sure all the math is right.
 
If all the warm boot procedure looks correct, another thing to look into is the GPU code - what effect it could be having on the bios. I don't see the code, so have to guess how it is working. Does it operate as the system console in place of the serial port? or does it work in-tandem with the serial port console? Is it active for these runs where you see the problem?

Might be a bit of a side-track, but does your system have any sort of core dump capability? Something to consider, if not. Maybe reserve a raw chunk of the CF card (large enough to contain all of your RAM), and then write ROM routines to dump RAM into the chunk. Then you can extract that chunk (either in CP/M or on a host PC) and analyze the "dump". I've found this to be very useful on another system I've been working on recently. I'm not sure how your ROM operates, and how much of RAM gets scribbled-on by the ROM, but there are ways to minimize that.
 
I will start looking at DDT and learning how to use it, seems it's going to be the tool to identify the problem. So I could theoretically step through the warm/cold boot procedures using DDT?
The most basic use of DDT would be to display low memory and verify that the warm boot address and BDOS addresses are correct along with any relevant BIOS information 40h-5Bh.
DDT<CR>
D0,5F<CR>

Another are of interest would be your jump vector and the DDT display parameter is simply the start and end address:
Dstart,end<CR>

Exit from DDT is via CTL-C which will act as another test of your warm boot routine after verifying that memory is as you expect.

In the event you want to change memory with DDT, you can use the "S" command. This isn't as intuitive but is invoked with "Sstart_address<CR>". You can either enter one byte of new hex data or a <CR> will increment to the next address. Terminating the S command is by entering ".<CR>".

Single stepping and breakpoints in boot routines can get tricky since parts of memory will be overlayed as part of the boot process. My suspicion would be that part of memory is not being initialized as you expect or is being overlayed, possibly by your new GPU code. If neither of those is obvious, I'd double-check the warm boot code reading of the CCP/BDOS and verify rhat it's coming from the correct disk area and going to the correct memory address.
 
I recall having to write my own monitor/single-stepper in the CBIOS boot code. DDT uses too much of the CP/M functionality to be useful for debugging CP/M. If this is a system you've designed, the boot ROM should include a monitor with some debugging capabilities anyway.
 
A quick way to narrow down whether the GPU code is causing a problem would be to replace the "CALL GPU_TX" with 3 NOPs (keeping the rest of the code, and length, identical). Then, we can focus on either the GPU or the warm boot.
 
Brings up a question, though: how does the ROM decide where to load CP/M into memory? Make certain the ROM load address is the same as what is compiled into the BIOS warm boot (as 'ccp') - i.e. make sure all the math is right.

:crazy: Doug, you're a star. :D You've hit the nail on the head. Thanks to your question, I went and checked the INSTALL command in a little more detail, which copies the CPM image from ROM and writes it onto Sector 0 of the CF card. It was only by doing that, that I checked out the CPMLOAD routine and stumbled across an equate in the code causing the ROM to load CP/M at CE00h. So even though CPM was compiling correctly for the new CB00h starting address, it was getting loaded in by the ROM at CE00h...

CPM loads fine and I'm able to run MBASIC and quit back to the CCP whilst viewing it all on my monitor now! Thanks everyone for your help and suggestions.

All I've got to do now is successfully move my video circuit from prototype onto a custom PCB... soldering a 144 pin QFP FPGA is testing my soldering skills at the moment, this video card is the most complex thing I've ever created...

20200314_144238.jpg
 
Great news! good luck going forward. Must be nice to have a screen now. Curious: how are you handling the keyboard in that situation? I guess, for now, you're just using the serial port as the keyboard?
 
Great news! good luck going forward. Must be nice to have a screen now. Curious: how are you handling the keyboard in that situation? I guess, for now, you're just using the serial port as the keyboard?

Yes, the serial console is acting as the keyboard for the moment until I get a working video card up and running and can develop the PS2 keyboard interface in the FPGA (I'm finding the prototype video card a little too unstable for me to get a working PS2 interface up and running).
 
Back
Top