          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 <CR>.  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