Image Map Image Map
Page 1 of 2 12 LastLast
Results 1 to 10 of 11

Thread: Help Booting CP/NOS

  1. Default Help Booting CP/NOS

    First, an overview: I constructed a simple dual-Z80 system in an FPGA, and the CPUs are connected via a pair of 8-bit FIFOs that are basically virtual serial ports. My goal is to have a 'server' CPU running a CP/NET server (currently being written in Turbo Pascal) under CP/M, and one or more 'client' CPUs running CP/NOS - the diskless variant of CP/NET. I followed the excellent documentation found here:, and was able to build a CP/NOS ROM image using z80pack and load it into my 'client' CPU. I then implemented the CP/NET packet protocol in my TP 'server' program on the server CPU running normal CP/M 2.2 (and which has a console), and started implementing the required functions. They basically map one-to-one to CP/M BDOS calls, so it's pretty straightforward. So far so good.

    So the current state of things: my server takes the client out of reset and then starts monitoring its console and network ports.

    1) The client prints a nice 'login' CP/NOS message: "64k CP/NOS vers 1.2. LOGIN=" and the server injects a 'cr/lf' in response.
    2) the client sends a LOGIN packet that the server ACKs
    3) The client sends a command to select disk 4 for some reason.
    4) The client then sends a command to open "CCP.SPR", and the server responds with the appropriate FCB.
    5) CCP.SPR is 25x128-byte records long - the client then sends 4 consecutive READ_SEQUENTIAL commands, reading the first 4 records of CCP.SPR.

    So far everything is going swimmingly, but then everything goes off in the weeds and the client just sends an endless stream of (properly formatted) FREE_DRIVE requests with DIDs set to 0,50,128,5, and 14 over and over again (rather than the expected '0' for the server).

    My main problem is that I only have the assembly for the BIOS and SNIOS, so I don't know what the boot process on the CP/NOS client is actually supposed to be doing. Presumably it's trying to fetch the CCP so it can present a command line for the user, but I'm not sure what's happening that it's going astray. I'm relatively inexperienced with CP/M, so any advice (or debugging tips) would be appreciated.

  2. #2


    I'm looking again at CP/NOS after 35 years, but have been using CP/NET a lot in recent years. The FREE_DRIVE function is typically part of the "warm boot", and so it may be the case that your client in is a warmboot loop (perhaps something keeps failing, jumping to 0000, and repeating). From the other CP/NET messages you describe, my first guess is that your network config table is not initialized (might have garbage in it). That table provides CP/NET with knowledge of what servers are on the network, and some BDOS functions are mapped to "all active" servers. So, if your network config table had garbage, CP/NET might be trying to access servers that don't exist.

    Yes, CP/NOS will need to load the CCP.SPR over the network, and (of course) that must be done by the warm boot routines. I've not dug in deep enough yet, so can't say what component actually does that. As it happens, I am in the process of disassembling the various CP/NOS REL files to get a better picture of the whole boot process.

    But I think the first thing to check is the network config table. Make sure it is initialized with valid data - especially the drive maps A:-P:.
    - Doug

  3. #3


    The other thing to check is why the READ_SEQUENTIAL of CCP.SPR stops after 4 records. Can you examine the response message(s)? An error from the server during loading of CCP is likely to send you into a warmboot loop.
    - Doug

  4. Default

    Thanks! That jiggled some brain cells and I made some more progress debugging. The network config table (just using the defaults in the source) does explain why it was trying to access drive 4 on boot:

    if cpnos
    ; Initial Slave Configuration Table
    db 0000$0000b ; network status byte
    db slave$ID ; slave processor ID number
    db 84h,0 ; A: Disk device
    db 81h,0 ; B: "
    db 82h,0 ; C: "
    db 83h,0 ; D: "
    db 80h,0 ; E: "
    db 85h,0 ; F: "
    db 86h,0 ; G: "
    db 87h,0 ; H: "
    db 88h,0 ; I: "
    db 89h,0 ; J: "
    db 8ah,0 ; K: "
    db 8bh,0 ; L: "
    db 8ch,0 ; M: "
    db 8dh,0 ; N: "
    db 8eh,0 ; O: "
    db 8fh,0 ; P: "
    db 0,0 ; console device
    db 0,0 ; list device:
    db 0 ; buffer index
    db 0 ; FMT
    db 0 ; DID
    db slave$ID ; SID
    db 5 ; FNC
    initcfglen equ $-initconfigtbl

    One symptom I had noticed after the slave started spewing requests was that I needed to reload the FPGA bitstream (with the already initialized RAMs) rather than just reset things to get it back to a sane state, telling me that the CP/NOS code was getting trampled (when it was intended to live in a ROM, and therefore should never be written to). I made the 4KB region where the code lived (0xE000-0xEFFF) read-only, and the slave then proceeded to fetch the first 16 blocks of CCP.SPR and then try to close the file.

    When I was creating my ROM, I tried following the instructions from the CP/NET reference manual, which says:

    Locate the code segment where the ROM sits in the address space of the finished system. At least 1K (400 hexadecimal bytes) of RAM must be allocated for data segments. If the code segments are to be loaded into high memory (at F000H for a 4K system) , data must be explicitly linked, using the D option, at least 1K in front of the code segments. For example,


    Unfortunately, running that with the version of LINK that comes in the CP/M 2.2 image in z80pack results in a zero-byte output file, which I think is a bug (I think it is LINK 1.3 in the provided image). For some reason, it will only produce a valid output if the data segment is higher in address space than the code. To get around this, I tried running with [LE000,DF000], which indeed produces a reasonable looking code image. My suspicion is that the data from the file read requests is being written 'down' from the data segment (which would normally just be into the TPA), so it was trampling my CP/NOS image (hence it going off in the weeds after only 4 reads, whereas it did 16 reads when I made that region read-only).

    I suspect things would 'just work' if I could actually run LINK as the example shows, with the data segment below the code segment. Anyone have suggestions?

  5. #5


    One thing is that the link command is producing a COM file - is that what you intended? It's been a long time since I've used DRI tools to generate a ROM image, but it seems to me you need more options. Also, the total "dseg" region for all those files is much bigger than the 4K you allow by "DEC00". Also, much of that "dseg" is pre-initialized, so I'm not sure how that fits into the idea of a ROM (can ROM code assume any values are already in RAM? or is your monitor loading a chunk of ROM into RAM to pre-initialize the data?). Also, as I am looking at the CPNDOS code right now, some of that code in self-modifying so I don't think it will work as a ROM. I vaguely recall some talk about putting this in ROM, but not sure anyone ever proved it could be done.

    You link syntax is also a little strange to me, I would expect:


    although I do seem to recall some ambiguity in their syntax.
    - Doug

  6. #6


    I did some experimentation. I think the confusion is about what "load address" means. That specifies where the lowest address in the image will be located in memory. I get a more-resonable image when I use this "[LEC00,PF000,DEC00]", which tells link that the "cseg" is "orged" at F000, the dseg is orged at EC00, and the image is arranged in the file as if the first byte will be loaded into EC00. This places the first byte of dseg in the first byte of the COM file, then after dseg it will put the cseg - padded to 0400 bytes as needed.

    I still think you have an issue with the size you are allowing for the dseg. 1K is not enough, and I think it was resulting in your CP/NOS code trashing itself since the dseg overlapped with the cseg. Setting your cseg to R/O only partially fixed the problem: the code was no longer being trashed, but the data variables did not have the right values to operate correctly.
    - Doug

  7. Default

    Yes, according to their documentation you can just set a starting point other than 0x100 using [L$$$$] (so trying to execute the resulting .COM wouldn't work, but if you load it at the right location it should work fine). I think your syntax might work as well, but I was quoting verbatim from the CP/NET documentation I linked in my first post. When you equate CPNOS = true in the CPNIOS.ASM, it does indeed copy a bunch of data from ROM over into the data segment to initialize things, since it assumes the data segment is uninitialized when CPNOS = true.

    So the question is, how can I link a copy of CPNOS with the data in the right location?

  8. #8


    Looking at CPNIOS.ASM, I see that it copies the network config table into RAM, and has a "well behaved" dseg with no initialized data. CPNDOS.REL definitely does not have that, and I don't think that CPBDOS.REL has it either (I think both have initialized data in parts of their dseg). When I dump the REL data, I see plenty of initialized data within the dseg. It may be the case that CPNIOS is ready to be put in ROM, but the rest of CP/NOS is not. I'm still analyzing CPNDOS, and there may be some code in there to copy data from cseg to dseg. Still, the self-modifying code gives me pause. Still, using the options I suggested might help. It should prevent the dseg from overlapping the cseg.

    Documentation from that era, in general - and CP/NET specifically, was not perfect. It was a fairly big deal to turn around a new version, and even if DRI fixed mistakes you can be sure what rev of the document was used for the scan you link to. DRI documentation was pretty good, but not 100%. That was the way of the world though - you had to read documentation with a little skepticism. What you want to do is probably possible, but you can't depend on every detail in that document being correct - or even having been tried out by anyone.

    You're a little ahead of me, or I might be able to try out some of this on a simulator. I will be working on a boot ROM that would load the image off a CP/NET server, though, so not quite the same situation. The early mention of CP/NOS in the DRI document does suggest that the ROM is more of a bootstrap loader, which might indicate that you do something like copy a ROM image into RAM, and run it from RAM. In other words, your "ROM monitor" copies a "CP/NOS bank of ROM" into RAM, and then executes it.

    Reviewing the REL files, it does seem as though they use only 551 bytes of dseg, not counting your custom CPBIOS and CPNIOS. So, maybe 1K is enough, but you must prevent LINK from overlapping them.
    - Doug

  9. #9


    I did some more investigation. As a simple case, "[LF000,DEC00]" seems to do the right thing. It appears to discard the dseg region and ORG dseg at EC00 (and ORG cseg at F000), and the first byte of the COM file is the byte to be loaded at F000. So, I'm not seeing what went wrong in your case, although I may need to get your code to find out more.

    CPNDOS.REL is a little strange, but comparing source code to the original NDOS source I can see how it evolved. CPNDOS contains some initialized data in dseg, but all of that is "refreshed" from cseg data during cold start.

    The coldstart part of CPNDOS does the following:

    1. Zero out the data region of CPBDOS (61 bytes starting at external symbol address "BDOSDS").
    2. Copy initialized data template from cseg to dseg. This includes the CCP.SPR fcb as well as some other critical variables.
    3. Call the SNIOS NTWKIN function.
    4. Take over certain BIOS functions (setup intercepts) - specifically: warmstart, console, I/O and LST: - by modifying the BIOS jump vectors.
    5. Call SNIOS CNFTBL function.
    6. Setup new "page 0" to intercept BDOS references (make CPNDOS appear to programs as the BDOS).
    7. Copy serial number and initial NDOS entry JMP into dseg (since this data varies, it cannot be in cseg).
    8. Jump to NDOS warm start.

    I'll describe NDOS warmstart later. The above procedure does make some assumptions about various components.

    Looking at the "stub" module CPNOS.REL, which is only a JMP to BIOS, it appears that the BIOS coldstart function must perform limited initialization and then transfer control to the CPNDOS via "JMP NDOS+3" (the CPNDOS coldstart entry). The sample CPBIOS does that, so I'm assuming yours also does it. CPNOS.REL places a JMP at the first byte in the cseg, which would be F000 in your case. So, CPBIOS "cboot" is the first thing to get control, and then it transfers control to CPNDOS coldstart, which sets up things and does a NDOS warmstart - which will load the CCP.SPR.

    One thing I will note: NDOS coldstart depends on the BIOS having setup both the JMP vector at 0000 and the one at 0005. Both of those are expected to have valid addresses, which are used to chain the NDOS into the BDOS call path. But, again, if you followed the example code you should be doing that.

    Based on what I've found out now, I can't explain your issue unless your CPBIOS+CPNIOS dseg (plus CPNDOS+CPBDOS) exceeded the 1K allocated for it in the LINK command. If you want to share your code, I can do some more investigation.
    - Doug

  10. Default

    I actually found a 4KB CP/NOS ROM image here:, and was actually able to just load that at 0xF000 and boot it directly (and see it fetch the full CCP.SPR, close the file and then issue a SEARCH_FOR_FIRST command that I haven't implemented on my server yet).

    So for now i'll consider myself unblocked, but I'll be sure to post again if I get stuck on something.

    My larger goal with this project is to maybe try to build something like a 32-core Z80 system at ~100 MHz or so, and then have a simple windows-like GUI environment where I can run concurrent applications by just using independent processors. I acquired some monster FPGA boards a while ago that have been begging for a good project =)


Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts