• Please review our updated Terms and Rules here

CP/M 2.2 Source Code

I could not say whether or not the user number is in 000 004 or not, So far I have not gotten that far to change a user number, so this spot has always been zero. My CP/M Operating System Manual is dated first printing July 1982.

It occurred to me that another 'temporary' solution for my problem would be to move the CP/M stacks. I tried moving the CCP stack and this didn't change anything. I moved the STKAREA which is the BDOS stack to high memory and this solved the current problem of overwriting the DISKNO byte. Although this allows the program to progress a little farther, there is still a problem and the program stalls.

I'm in the function call #13 RESET DISK SYSTEM. I'm at the point of reading the directory. The code is setting up a read of the first sector of track #2. I haven't determined if that has been done yet. I'm a little confused on what is actually happening here. I think that the program will look at the entire directory and determine and record what allocation blocks have been used. I'm working through the code on how that works. What should I expect, my disk was formatted and there is only the system on the first two tracks. So, would there should not be any allocation blocks used except those for the directory. By the way, I know that a checksum is an error checking function. Seems that there is a checksum here for the directory, what exactly does that do? Is it checking the extent, a file, both? Mike.
 
I've always dealt with 0004 by setting it to the boot drive during boot and leaving it alone otherwise. CP/M Plus (aka CP/M 3.0) is a very different animal from CP/M 2.2, so any commentary dealing with it is not particularly useful.

The checksum is used to determine if the disk in the drive has been changed. So, if you try to open a file for writing on a new disk without first issuing a RESET DISK SYSTEM call, the system will barf out a BDOS error message and set the drive status to read-only and pretty much demand that you reboot. If this weren't the case, the system might use the allocation map from the disk prior to the change--and that would be a disaster.

The checksum method isn't by any means foolproof, but it's better than nothing..
 
...another 'temporary' solution...would be to move the CP/M stacks...

First make sure you know the direction the stack moves as it fills with use. If you get that wrong, it will likely cause you chaos early.

Make sure you initialize the stack before any calls, push, pops etc are invoked. You initialize the stack at the "top" or *highest address of the block you reserve for the stack. Calls and Pushes place a 16 bit value onto the stack by: (1) decrementing the stack pointer from where it was, (2) copy the High byte of the 16 bit value into the memory location pointed to by the stack, (3) decrement the stack again, and (4) copy the Low byte of the 16 bit value into the location pointed at by the stack. Thus loading a 16-bit value onto the stack, decrements the stack pointer two bytes toward the memory origin 0000h.

*You'll notice that since Call and Push begin with a decrement of the stack pointer, the address loaded when initializing the stack actually isn't used. Example: LD SP,8000h followed by LD HL,8800h and PUSH HL. The stack is (1) decremented to 7FFFh and (2) 88h is stored there, then (3) the stack is decremented again to 7FFEh and (4) 00h is stored there. The point is that the stack doesn't use the address value 8000h. Thus you can write it another way, LD SP,7FFFh+1 ;this states the use location 7FFF as the *used* top of stack. Note that LD SP,FFFFh+1 is equivalent to LD SP,0h due to the 16-bit rollover the 0000h is decremented to FFFFh.

A trick I used to make sure code was doing something was to write a quick routine to fill a block of memory with a value like 11H so that when I dump memory, I can tell where the 11s have be disturbed. You could do a quick fill loop in the block you reserved for stack and then initialize the stack. Do a dump command from your monitor to make sure it hasn't used all the stack space (no more 11s viewed). If everything is fine, the stack should have used some of the block and the rest still has the 11s. Of course if the stack has been changed, then you still may have troubles to diagnose.

To keep an eye on the stack while CP/M is running you could hijack the BIOS to print the current stack every time CP/M prints a particular character in the BIOS console character out. When a ">" is to be output to the console send the stack first like "FFD0>" while you're sanity checking your BIOS. If you hit ENTER repeatedly at the OS prompt and the value changes progressively... you have a stack problem. Possibly a Pop-Push or Call-Ret mismatch.

11H makes it easier to see any other hex values in a scrolling hex dump of memory because the one's look like vertical lines and every other character will break that pattern with pixels out of that column. Most people would use 00H but a "0" can look like an "8" when its rolling by quickly as are a few other characters hard to spot among a field of zeroes.

...The code is setting up a read of the first sector of track #2. I haven't determined if that has been done yet...

Another good place to do the fill block with 11s is when you want to test if the code is loading a sector where you expect. Inject a quick fill loop and then request the sector. Dump that block and see if the 11s are still there or whether there is sensible data from the sector. Make sure the bounds of the move are where you designated.
 
Last edited:
Dallas, those are some interesting methods. Some of which I do use. Thanks for mentioning them. I've made a little more progress. I have found that the program does read Track #2 Sector #1 (at least I think it does, reason I say this is that all the non Track 1 & 2 sectors are 345) and has placed 345 Octal in the directory buffer at the correct location. Maybe I should copy a file to the disk and test that. I would use IMD to make a copy of a file, but would that also make a directory entry? Don't know right now. I could also, just change some of the data in Track #2 Sector #1 as a test. Tomorrow I hope to look at the check sum routine and see if that is working. Mike
 
Thanks for the suggestions. I have 22disk, but have not figured out how to use it yet. Dallas, I'll look into what you are talking about, interesting. Mean while I was doing more reading and investigating on the internet and found a robust CBIOS example. And it is rather long. I'm wondering how this long CBIOS, and the CCP and BDOS, which in this case is just under 8K is fit onto only two tracks on the disk, which is 6.5K. Is the directory moved to a later spot on Track #2? How does everything know that there is a shift? Is this what the block shift and mask are about? Does this affect the BDOS entry points? Wouldn't BDOS have to be pushed down in memory to get this longer CBIOS in place? Mike
 
Chuck has few pages back suggested, in post nr.40 that allocating more space for directory (with directory mask bits) will prevent CP/M from storing data right after directory allocation block(s). Read it again, that gives answer to your question.
 
22Disk is a simple menu-driven program. Not complex at all--and it understands at least (I've lost count) 500-odd different CP/M formats. The author (modesty forbids me naming him) probably has seen more CP/M formats than anyone else alive. Said author also wrote a DOS-based CP/M 2.2 emulator, so I suppose he knows what's going on inside the thing.
 
Thanks for the info. I read Chucks post about the parameter block a bunch of times, but the Track Offset just didn't register at the time. Maybe I was a bit over whelmed by all the stuff ahead of it. So it's just a matter of increasing this number in the parameter block from 2 to 3. This would provide another, 3 and quarter k for an increased CBIOS. Thanks. As far as the 22DISK program goes, I just haven't had time to even try it, maybe I should take some time. It's been busy here these last few months. The dog died, so did the microwave, daughter moved in, car's on the fritz, wife is sick. So I'm busy, fixing stuff, providing for, dealing with the weather and I'm not as young or fast as I once was. So, who doesn't have problems? That's why I really appreciate the help I get here. Thanks again, Mike.
 
I've been reviewing my CBIOS code with respect to ENABLE and DISABLE Interrupts. I found a place where I left an EI out and maybe be causing trouble. How should interrupts (since I'm unaccustomed to using them) be enable/disabled. At first I had thought that I should disable them at the start of my cold start loader, until I got all the ports initialized, then turn them on and leave them on. Later, I thought it was wise to disable interrupts in my Interrupt Service Routine. This is a short routine with only 6 machine instructions in it. Sets a flag and returns. As things progressed I added DI/EI in other places, during issuing commands to the FDC, etc. Now there is quite a hand full of them in the routines. How should these instructions be used, what is the logic behind it? I noticed that the CP/M code does have many of them. Maybe I should clean house? Thanks Mike.
 
When an interrupt hits and the ISR is entered, interrupts are disabled and the flags are pushed onto the stack along with the interrupt return address. When the ISR exits, the flags are restored to their state before the interrupt. So, strictly speaking, you don't need an EI in your ISR.

If you have a lengthy ISR, then it's probably better to depend on the 8259 to mask the interrupt that caused your ISR routine to be entered and enable interrupts. This allows other interrupts to be serviced while your ISR is running.

Of course, if you're using an 8259, you should issue a nonspecific EOI to the 8259 just before you exit the ISR.

Normally, you won't see many DI/EI pairs in most code.
 
I only use one interrupt, the one for the FDC, everything else is polled, here is my ISR
Code:
ISR,   DI
         LXIH INTFLAG
         MVIA 377
         MOVMA            /Set Interrupt Flag
         MVIA 141
         OUT PICCW1     /PIC EOI
         EI
         RET
I removed all the other DI/EI, except those around initializing ports at the start and those in the ISR. This works in my test Read/Write program for the floppy and the Cold Start Loader, which is in memory away from CP/M, but again I'm having trouble getting the CP/M code to work correctly when accessing the CBIOS. With out the EI's in the RDISK routine of CBIOS it won't work. I seem to need the EI at the beginning of the RDISK routine, yet not in my CSL or test program. I want to look thru the CP/M code for the DI/EI's, there is an EI in GOCPM of the BIOS. Mike
 
Just curious about your ISR, Mike. Mine would look like this:

MYISR:
PUSH PSW (saves A and the flags)
EI (8259 is still blocking interrupts)
MVI A,255 (377 octal)
STA INTFLAG
MVI A,32 (40 octal - nonsepcific EOI)
OUT PICCWI
POP PSW (restore A and flags)
RET (exit)

Note that you need to save any registers that you'll be using. At a minimum, that's (A, flags)
 
Thanks for the input. Did I get the EOI wrong? I'll have to look again, maybe mine was specific. Anyway, I seem to be having trouble with my code. Since I'm a little smarter now since 'we' struggled though to this point, I think I'm going take some time and do a rewrite and place more code (maybe all) into the CBIOS and not in the EPROM. There has to be something happening between the CBIOS and the EPROM code. This may take a while. Anyway, Thanks a bunch, Mike
 
Hi Mike, Did you update your ISR to save the registers as Chuck pointed out? Without that, what ever code happens to be running at the time of the interrupt will be in a bad state.
 
Yes sir, fixed it right up, but there still is a problem. I've been adding traps in certain places to try and find the problem. Sometimes I get a snare and sometimes not. Both are telling. At this point, the CP/M code will read the directory into the directory buffer, but then it seems to wonder off into the pucker brush. I'm suspect of my code. Although this time the ISR was not the problem, it is still a good thing to fix it up so that it doesn't present a problem down the line. I made a conditional trap that I have placed in the results phase of my read command. Then I want to look at all the important variables. So I'm on it. I've been attending to some other home problems and haven't been able to spend much time on this. The 1916 Model T was acting up and I think I got that straighten out last night. Thanks for the help, Mike
 
Mike_Z said:
...been adding traps...Sometimes I get a snare and sometimes not...

Verify that you're using opcodes before the conditional, that update the flag you're using. Moves usually do not update flags as it would seem useful; the reason why you have to avoid updating flags on every register update is that you'd waste too many bytes updating the flags again... it works better to be able to do several operations before using the flags.

Where are you placing your stack pointer and how much space have you reserved?

Remember to initialize the stack at the high address of the reserved block. If you reserved the block from FFE0h to FFFF, to save one byte (i.e. be more efficient) you'd initialize the stack to FFFFh+1 which is the same as initializing it to zero due to roll-over. When you do a stack operation that adds a value to the stack, it decrements the pointer *before* putting the value into the stack, so 0000h-1=FFFFh which is where the first value goes.

An stack trap that will work for an unleveled RET would be to place a trap-return address at FFFFh and FFFEh and initialize the stack to FFFDh+1. Now it there is a RET issued when the stack is at its starting point, it will restore the trap-return address into the program counter and execute code from there. This gives you a chance to print a stack under-run error, request a CR to acknowledge, and then reboot to your monitor.

Note that the RET trap won't work for POPs or an POP imbalance that has offset expected 16-bit return addresses to half way (i.e. picking up the high address from one return address and the low address from another return address on the stack. When you digest that construct, you see that PUSH POP imbalances can quickly create a very random crash, where as CALL RET imbalances tend to eventually walk the stack outside of its reserved space (i.e. operations seem to run longer before crashing badly as it overwrites variables in RAM at one end of the reserved stack block, or the other).
 
Last edited:
One of the more common tank traps for new 8080 coders is the knowledge of what flag bits are affected by what instructions., particularly the N, Z and C bits For example, DCR will affect all condition codes but C; DCX affects no condition codes; DAD affects only C, etc.
 
Back
Top