OF EXECUTIVE SCREEN DUMPS AND CLOCKS ------------------------------------ By Gordon Wilk An article in a recent issue of Foghorn showed how to print the screen of the OS-1. The screen in the Executive is also memory mapped and can be sent to the printer; however it is harder to do so since the screen is hidden away in bank seven of the memory. Since page 438 of the Executive Reference Manual tells how to switch banks, my first thought was to switch bank 7 in ( it only occupies locations C000-CFFF hex, well above where my program would load) and use CP/M3's BDOS function 112 (List Block) to print the screen. This did not work and my efforts may be informative to others. Function 112, List Block, would not have worked in any event since the internal screen is 128 columns wide of which only the first 80 are used. Unlike the OS-1 horizontal scrolling is entirely in software and the "invisible" columns are never used; actually they are filled with "U"s for some reason. Only the first 80 columns ever contain usable information. A more serious flaw in my plan was due to the fact that the Osborne BIOS does not play according to the rules when it switches banks, as it does on virtually every call. The bank switching method on page 438 of the Executive Reference Guide tells how to restore the banks to the state existing before the switch. The BIOS does not do this. Apparently it assumes that all calls originate from a user program in bank one and just restores bank one. Thus if one executes a BDOS call while bank 1 and 7 are active, the call will return with only bank one active. The first rule becomes: You cannot make BDOS calls while bank 7 or 8 is switched in! This also means that SID (and DDT) cannot be used while to trace programs during a bank switch (SID and DDT use BDOS calls to print information to the screen). This discovery led me to the plan of switching bank 7 in, moving the entire screen from C000-CFFF to A000-AFFF which lies in bank one even when bank 8 is switched in and then printing the moved screen image a line at a time. Here is the program that does it: ; SNAP A screen dump routine ; ;------ EQUATES ------------------------- ; ; BDOS LOCATIONS BOOT EQU 00 ;warmboot BDOS EQU 05 ;BDOS call ; ; PROGRAM CONSTANTS CR EQU 0Dh ;carraige return SCRN1 EQU 0A000H ;put the screen here in bank 1 SCRN8 EQU 0C000H ;the screen is here in bank 8 SCRNSIZ EQU 0CBFH-SCRN8 ;screen length SCRNWID EQU 80 ; 80 column wide screen SCRNLN EQU 24 ; 24 lines per screen ; ;---------------MACROS---------------------- ; ; ;;Bank switching macro ONE EQU 01H SEVEN EQU 41H EIGHT EQU 81H BANK MACRO NUMBER ;;Enable bank # MVI A,NUMBER OUT 0 ENDM ; BLKMOVE MACRO FROM,TO,LENGTH ;;Block move LXI H,FROM LXI D,TO LXI B,LENGTH DB 0EDH,0B0H ;z80 LDIR ENDM ;------------------------------------------------ ; MAIN ;----------------------------------------------- ORG 100H ;OMIT THESE TWO LINES FOR A SUBROUTINE LXI SP,STAK ; BANK SEVEN ;enable screen BLKMOVE SCRN8,SCRN1,SCRNSIZ ;Move screen to bank 1 BANK ONE ;Back to bank one MVI B,SCRNLN ;B=lines LXI H,SCRN1 ;HL=adr of byte to print ;Loop for each line LINLOOP MVI C,SCRNWID ;C=columns ;Loop for each column COLOOP MOV E,M ;put char in E CALL LCHAR ;PRINT IT INX H ;next column DCR C ;decrment col count SUB A ; CMP C ; is it zero yet? JNZ COLOOP ;NO, not at end of line DCR B ;EOL - decrement line count CMP B ; is it zero yet? JZ EXIT ;YES - done LXI D,128-80 ;NO, skip over line extensions DAD D JMP LINLOOP ;Go do next line EXIT MVI E,CR ;End with CR CALL LCHAR CALL BOOT ; and out *change to RET for a sub ; LCHAR PUSH B ;Save BC and HL PUSH H MVI C,05 ;List out CALL BDOS POP H ;restore POP B RET ; DS 64 ;Stack STAK DW 0 END The program is written to be assembled with the MAC macro assembler delivered with CP/M3 and makes use of the macro facilities to increase readability. The program begins after the "MAIN" heading. The two BANK macro invocations expand into the code needed to switch banks. The BLKMOVE macro provides a call using the Z80 LDIR opcode (here written as a DB) which performs the block move. The remainder of the code just prints out the first 80 characters of each 128 character line for the 24 lines of the screen. This code could have been written to run faster, but its execution is limited by the speed of the printer and there is no need for tight code. To use this program, type it with WordStar in the "N" mode into a file named "SNAPSHOT.ASM". Then assem- ble it with the cammand MAC SNAPSHOT. This will produce a .HEX file ( as well as .SYM and .PRN files) which must be converted to a .COM file with the command HEXCOM SNAPSHOT. After all this, turn your printer on, get something worthwhile on the screen and type SNAPSHOT . Once the program is running correctly, I suggest converting it into a subroutine which can be included in another program and called from within that program. As a subroutine the "A> SNAPSHOT " line need not appear in the printout. The conversion can be done by changing one line and omitting two others as shown in the listing. Another fine article in FOGHORN located the Executive's clock and showed how to access it. The clock referrred to in the article stored the date and time in hours, minutes and seconds, which is fine if all one wants is to read the time of day. On the other hand, if you want to time the running of a program and its subroutines, seconds are much too coarse a measure. C/80 and other languages include a profiler to do this; such profilers need a clock which counts sixtieths or smaller fractions of a second. There is such a clock in the Exec, but, you guessed it, it's not in bank one. The clock, two bytes that tick in milliseconds (approximately), is at location 23E8 hex in bank 8. Getting at it is an even harder job than getting at the screen. While the bank 7 only overlays high memory from C000 hex upward, bank 8 overlays the first 4k bytes of bank one. Any program trying to read or write in bank 8 must originate at a location higher than 4000 hex. While an independent program could easily be written to do this (using the ORG directive in MAC), what is needed for a program timer is a subroutine and there is no easy way of guaranteeing where a subroutine will link to a program written in a higher level language. The solution is to make use of CP/M+'s unique RSX feature. An RSX (Resident System Extension) is a program automatically loaded in high memory, just below the BDOS common memory which can then be called and used like a BDOS call. Writing an RSX is not actually hard, it just has more steps than needed with an ordinary program; once written an RSX can be used by programs written in any language allowing BDOS calls -- this includes Pascal and C amongst others. Here is the program. Type it with WordStar in "N" mode as a file called "TIME.ASM": ; TIME RSX ; Reads or sets the internal clock (tick) ; Usage: ; Assemble, HEXCOM and GENCOM this file to a NULL COM file ; Before using, load it by calling TIME ; ; In calling program ; To set clock to 00 call BDOS with DE = 0 ; To read clock call BDOS function 60 with DE > 0 ; ; RSX HEADER ; SERIAL DB 0,0,0,0,0,0 ;filled in by loader START JMP MAIN ;jump to start of program NEXT DB 0C3H ;JMP DW 0 ;filled in by loader PREV DW 0 ;ditto REM DB 0FFh ;Dump after one use ;This should be 0 if it is not to be ;deleted after on use NON DB 0 ;Always load NAM DB 'TIME ' ;Must be exactly 8 chars LOD DB 0,0,0 ;filled in by loader ; MAIN: MOV A,C ;Is this func 60 CPI 60 ; if not JNZ NEXT ; pass the call to next routine or BDOS MVI A,81H ;Switch Bank 8 in OUT 0 SUB A ;Does E = 0 ? CMP E ; JZ ZERO ; If so, go reset clock ; If not, LHLD 23E8H ;load clock bits JMP ONE ZERO LXI H,0 ;Store 00 in clock bytes SHLD 23E8H ONE MVI A,01 ;Switch back to Bank 1 OUT 0 MOV D,H ;DE=HL MOV E,L RET ;with clock in DE and HL END ; The equates labeled "RSX HEADER" is information needed by the loader to know this as an RSX and properly load it. The program just reads the clock bytes and returns with them in both the DE and HL registers. This routine actually violates the official conventions for RSX calls, but no harm is done as long as this is the calling program does not call any other RSX's via funtion 60. The official convention is that when function 60 is called the DE register should contain the address of a parameter block containing the addresses of the parameters. Since there is only one para- meter passed and returned, I passed them directly through the DE register; the return was duplicated in the HL register for the convenience of C/80 which expects parameters to be returned in HL. The following steps are needed to prepare the program: Assemble it with RMAC: RMAC TIME Link it as a Page Relocatable Program: LINK TIME [OP] Rename it an RSX: REN TIME.RSX=TIME.PRL Create a null program to load it: GENCOM TIME [NULL] Load it: TIME The program will now be loaded and remain in memory throughout the running of the next program executed. At the end of that program the RSX will automatically be erased. Now you need a program that will make the BDOS call and print out the clock so you can test the program. Unfortunately BASIC cannot make a BDOS call, though it could call an assembly language USR routine that did. For clarity, here is a simple C/80 program to make the call and print the results: #include "PRINTF.C" main() { static unsigned tick; while(1) /* Do forever */ { tick = clock(); /* Read the clock */ printf("\n\t%u",tick); /* Print it */ } } clock() { #asm LXI D,1 ; read clock MVI C,60 ; BDOS function 60 CALL 5 ; Call BDOS #endasm return; } This will continuously print the tick count until you stop it with a ^S ^C. In BASIC the "clock" function would have to written and assembled as a separate assembler program and then called by the USR function. In JRT Pascal, the call could be made direclty but the register data structure makes the program more complicated. Timer or profiler routines read the clock at entry to a routine then again at exit and subtract the two counts. The difference is the time spent in the routine. The times are cumulated for each call to the function and then printed on exit from the program. The CPROF.C program provided with C/80 is such a routine which can be easily modified to use this RSX by substituting BDOS calls for the references to TICCNT in the source code. When used as such a timer, this RSX has an accuracy of +/- 256 ticks. lls for the references to TICCNT in the so