home *** CD-ROM | disk | FTP | other *** search
/ CP/M / CPM_CDROM.iso / jsage / znode3 / tcj / tcj34bmm.ws < prev    next >
Encoding:
Text File  |  1994-09-02  |  33.0 KB  |  858 lines

  1. advcpm.b34    Advanced CP/M for TCJ #34
  2.  
  3. .h1    =    main heading
  4. .h2    =    secondary heading
  5. @I{...}    =    set in italics
  6.  
  7.                 Advanced CP/M
  8.             Extending the Operating System
  9.  
  10.  
  11.                Bridger Mitchell
  12.  
  13. [ the usual sidebar on Bridger]
  14.  
  15. Sooner or later, the CP/M user bumps up hard against the limitations
  16. of the operating system and wonders -- can something be done?  Yes,
  17. CP/M can be enhanced at several levels.
  18.  
  19. .h2  Command Processor Extensions
  20.  
  21. A great deal of effort has been directed to improving the external,
  22. command-processing part of CP/M.  The "command shell" is readily
  23. replaced; it is the most immediately noticed component; and it
  24. is can be extended by placing added code in files that
  25. can be manipulated by the existing system.  Z3PLUS, NZ-COM, and ZCPR34
  26. are the latest achievements at this level; they both replace the
  27. command processor and provide well-defined resident buffers for
  28. communication between successive tasks.  There have been a number of
  29. other replacement command processors, such as CNIX, CONIX, and QPM.
  30.  
  31. .h2 BDOS and BIOS extensions
  32.  
  33. The next level of extension is adding new operating system functions
  34. -- extra BDOS calls.  CP/M Plus provides a particularly convenient
  35. method, called a Resident System Extension (RSX), for adding such
  36. capabilities.  Under CP/M Plus, an RSX can be attached to a program
  37. that need extended services and loaded automatically along with the
  38. original program.  It is rather easy to modify existing BDOS
  39. functions, for example, to keep statistics on the frequency with which
  40. key files are accessed.  
  41.  
  42. More elaborate RSXs have been developed for CP/M Plus to
  43. emulate the CP/M 2.2 BIOS file functions so that programs that make
  44. direct BIOS calls can run under CP/M Plus by attaching the necessary
  45. RSX but leaving the program itself unchanged.  The most complex CP/M
  46. Plus RSX is the brand-new Z3PLUS system.  Within the single RSX are
  47. the ZCPR34 command processor and program and RSX loader, the Z-System
  48. buffers for named directories, resident commands, messages, command
  49. line, file control block, and environment descriptor.
  50.  
  51. However, more basic changes to CP/M 2.2, such as adding time and
  52. datestamping or redirection of console and printer services, are much
  53. more difficult.  They are necessarily inner extensions to CP/M and
  54. require intimate knowledge of the BDOS and BIOS.
  55.  
  56. Another area that frequently needs extension is an interface to the
  57. hardware.  The original BIOS may provide for only a limited number of
  58. disk formats.  Function keys that transmit escape sequences may
  59. need to be timed and mapped into defined character strings.  Printers
  60. could need character translation tables to generate control sequences
  61. or foreign characters.  These system extensions are primarily BIOS
  62. modifications.
  63.  
  64. The most extensive extension of CP/M 2.2, and the most complex piece
  65. of software I have ever written, is BackGrounder ii.  It is installed
  66. as an RSX that makes extensive modifications to selected BIOS and BDOS
  67. functions, replaces the command processor, and adds virtual swapping
  68. memory to the basic system.  The result is effectively @I(two) virtual
  69. CP/M computers on one machine, with user-controlled ability to switch
  70. between two running programs at any time, and to preserve the exact
  71. screen display of each on most terminals.
  72.  
  73. .h2 RSXs for CP/M 2.2
  74.  
  75. An RSX is not necessarily the first, or the best, choice for adding
  76. capability to a computer system.  The operating system can be extended
  77. by rewriting the BIOS (provided the source code is still available!).
  78. This is the most fundamental approach.  Specific to one computer, it
  79. is permanent and totally non-portable.
  80.  
  81. The system can be extended for only a single application, as when a
  82. program is run by first loading a debugger.  Quick and dirty,
  83. this approach is sometimes useful for temporarily modifying
  84. system behavior or pre-testing a more permanent approach.
  85.  
  86. A resident system extension is a versatile, intermediate
  87. approach.  An RSX can be installed and left in place to extend
  88. the system for as long as it is needed, then removed to restore
  89. the original system and full memory.  If well designed, it can be
  90. quite portable.   It can be used to add both BDOS and BIOS
  91. features to the vanilla CP/M 2.2 system.  And several RSXs can be used
  92. together to combine system power.
  93.  
  94. In this issue's column I discuss System Extensions that modify the
  95. behavior of the BDOS and/or BIOS.  Because these services must
  96. essentially be available at all times (whenever a BDOS or BIOS call is
  97. made), the extension code must be _resident_.  This is unlike
  98. extensions of the command processor, which can be reloaded from the
  99. system tracks (or from a file) when a new command is ready for
  100. processing.
  101.  
  102. I will limit the examples to extensions for the CP/M 2.2 operating
  103. system (and its clones).  CP/M Plus already includes RSX capability for
  104. BDOS (but not BIOS) extensions and it also includes provision for
  105. character-device redirection (so that, for example, the printer output
  106. can be diverted into a file).  The basic ideas, however, carry over to
  107. CP/M Plus.  Indeed, I have used them effectively in implementing both
  108. Z3PLUS and DosDisk on CP/M Plus computers.
  109.  
  110. .h2 No Free Lunch
  111.  
  112. Although smart coding and clever uses of the RSX concept can greatly
  113. extend the vanilla CP/M system, there are limits imposed by a 64K
  114. address space Z-80 operating system.  As I was preparing this column a
  115. potential DosDisk customer called, hoping that DosDisk would let him
  116. run Lotus 1-2-3 on his Z-80 computer with 8 inch disks!  I trust that
  117. anyone who has read this far has somewhat more modest expectations.
  118.  
  119.  
  120. .h1 The Conventional CP/M 2.2 System
  121.  
  122. In the remainder of this column I will describe how an RSX modifies
  123. the conventional memory map and flow of control, using a standard RSX
  124. header data structure.  We will then turn to a complete example.
  125.  
  126. In order to see how RSXs fit into the CP/M 2.2 system, let's first
  127. review a few key points about a "vanilla" system.
  128.  
  129. The end of the TPA (transient program area), the memory available to
  130. application programs, is specified by the address stored on page-zero
  131. at location 0006.  Initially, that value is the address of the entry
  132. to the BDOS, and a "call 5" instruction will jump directly to the
  133. BDOS.  Beginning at the address whose value is stored at 0006, memory
  134. is said to be "protected" -- it is not available to applications, and
  135. data and code in that area should remain "resident" from one
  136. application to the next.
  137.  
  138. The command processor is assembled or linked to be loaded in the top
  139. 2K of the TPA memory.  The command processor is not resident, and that
  140. 2K of memory is available to applications.  Therefore, on each
  141. warm-boot -- a jump or call to 0000 -- the BIOS reads a fresh copy of
  142. the command processor into high memory.  (It may also read in a fresh
  143. copy of the BDOS, depending on the implementation in the BIOS.)
  144.  
  145. Figure 1 shows the essential flow of control.  The address at 0006 is
  146. "bdos+6", the entry to the BDOS, and the TPA extends from 100h to
  147. bdos+5.  A jump to 0000 vectors to "bios+3", which jumps to "bioswb",
  148. the BIOS warm-boot routine.  That routine reloads the command
  149. processor, installs fresh values of "bios+3" and "bdos+6" at 0001 and
  150. 0006, and then jumps to the command processor entry "ccp+3" to process
  151. the next command.
  152.  
  153. .h1 The Standard RSX Header
  154.  
  155. In order to insert a resident extension into this scheme we must
  156. deal with memory management, BDOS and BIOS calls, provide for
  157. removal of the RSX when it is no longer needed, and anticipate
  158. the loading of other RSXs while this one is active.
  159.  
  160. In addition to being intrinsically complex, implementing all of this
  161. is tedious and error-prone.  Several years ago, to provide a standard
  162. approach, I established a Plu*Perfect Systems RSX header -- a uniform
  163. data structure at the beginning of every RSX -- to permit several RSXs
  164. to coexist peacefully.  And for this column I extended JetLDR to
  165. recognize a relocatable RSX module in named-common ZRL relocatable
  166. format and load it automatically.
  167.  
  168. The header (Figure 2) begins with three jumps to routines in the RSX
  169. -- to the BDOS intercept routine "rsxstart", to the warm-boot
  170. intercept routine "rsxwb", and to the removal routine "rsxremove".
  171.  
  172. Next come three addresses.  First, "rsxwba" is the address of the
  173. (original) BIOS warmboot vector which is used to, hopefully,
  174. correct the problems created by programs that erroneously change
  175. the contents of 0001.  The second word is the "protect" address,
  176. the lowest byte in the RSX that must be in protected memory. 
  177. This is followed by the address of the nul-terminated ascii
  178. name of the RSX.
  179.  
  180. Two jumps and a final word complete the header.  The chain of a
  181. warm-boot (jump 0000) flows through "rsxnext", which points to
  182. either the next-higher RSX, or to the command processor entry.
  183. Similarly, the call BDOS chain flows through "next", which points
  184. to either the next-higher RSX, or to the BDOS entry.   The final
  185. item, "nextwb" holds the address from the BIOS warm-boot vector
  186. at the time the RSX is loaded, so that it can be restored by the
  187. remove routine.
  188.  
  189.  
  190. ---------------------------------------------------------------------
  191.          Figure 2.  Standard RSX Header
  192.  
  193. rsx:    jp    rsxstart    ; BDOS intercept        ;  00
  194.     jp    rsxwb        ; warm-boot intercept        ; +03
  195.     jp    rsxremove    ; remove-rsx entry        ; +06
  196. rsxwba:    dw    $-$        ; original 0001 value        ; +09
  197. rsxprot:dw    rsx        ; lowest rsx address        ; +0B
  198.     dw    rsxname        ; -> rsx name            ; +0D
  199. rsxnext:jp    $-$        ; -> next wb or ccp entry    ; +0F    
  200. ;
  201. next:    jp    $-$        ; -> next rsx or bdos        ; +12
  202. nextwb:    dw    $-$        ; original bios+4 addr        ; +15
  203.  
  204. ---------------------------------------------------------------------
  205.  
  206. .h1 Adding an RSX to Memory
  207.  
  208. In order to add an RSX to a running system, we must arrange to
  209.  
  210.   - allocate sufficient memory for the RSX
  211.   - deduct that memory from the available TPA, so that
  212.     an application will not attempt to use the memory occupied by the RSX
  213.   - prevent the BIOS from undoing the memory allocation at the next warm-boot
  214.  
  215. We will locate the RSX in high memory, immediately below the command
  216. processor -- see Figure 3.  To do this, we change the value at 0006 to
  217. point to the first byte of the RSX, and arrange to have a jump
  218. instruction there that (eventually) causes control to enter the BDOS
  219. (this is the initial jump in the standard RSX header).  The TPA is now
  220. reduced to the area 100h to "rsx1-1".
  221.  
  222. To keep the BIOS routine "bioswb" from undoing our handiwork by
  223. resetting the value of 0006, we must either modify the "bioswb"
  224. routine, get control back @I{after} that routine has completed, or
  225. prevent it from executing.  Prevention is the only portable solution;
  226. and it must be done intelligently.
  227.  
  228. .h2 The System Loadstone
  229.  
  230. The "obvious" method of bypassing the warm-boot routine is to change
  231. the address at 0001 to point to a copy of the (modified) BIOS jump
  232. table located in the RSX.  However, this would be fundamentally wrong,
  233. because this is the @I{one} address in the CP/M system that must
  234. remain fixed.  Why?  Consider what will happen when a second RSX is
  235. being loaded.  It will be unable to locate the BIOS and therefore
  236. cannot correctly intercept BIOS functions!
  237.  
  238. @I{The warm-boot address at 0001 should never be altered!}  It is
  239. the one fixed point, the lodestone, of the entire CP/M system.
  240. Instead, the warm-boot chain should be intercepted at the BIOS
  241. vector (at bios+4) and redirected to the RSX from that point.
  242.  
  243. Unfortunately, this absolutely essential role of 0001 has apparently
  244. not been widely understood.  Both Digital Research (in XSUB) -- the
  245. designers of CP/M! -- and MicroPro (in the WordStar "R" command)
  246. occasionally commit the error of changing 0001, and their mistakes
  247. have been perpetuated by several public-domain programs as well.
  248.  
  249. Figure 3 shows a correctly modified warm-boot chain with the RSX
  250. installed. At 0000, control continues to jump to the BIOS, where it is
  251. @I(then) redirected to "rsx1+3" and then to the RSX's warm-boot
  252. routine.  Eventually, control flows to the copy of the command
  253. processor that is already in memory, bypassing the bios warmboot
  254. routine and without reloading the command processor. We will cover the
  255. details of the RSX warmboot routine in the example below.
  256.  
  257.  
  258. .h2 A Second RSX
  259.  
  260. Adding a second RSX is similar to loading the first one (see
  261. Figure 4) and involves splicing the new RSX into the BDOS and
  262. warmboot chains.  Memory is allocated below the first RSX, 0006 is
  263. redirected to the head of rsx2 where BDOS calls are intercepted
  264. and eventually vectored to rsx1.  The warm-boot intercept at
  265. bios+4 is redirected to rsx2+3, which eventually vectors to
  266. rsx1's warmboot routine.
  267.  
  268. .h2 Removing an RSX
  269.  
  270. The standard RSX header includes an entry that is called to
  271. remove the routine.  It deallocates the RSX's memory and removes
  272. itself from the BDOS and warm-boot chains.  It also removes
  273. its BIOS intercepts, if any.  If there is more than one RSX in
  274. memory, the lowest one must be removed first, to ensure that
  275. the addresses (or data) that are restored are correct.  Each
  276. RSX's remove routine first determines that it is, in fact, lowest
  277. before executing the rest of the removal code.
  278.  
  279. The standard header anticipates that most RSXs will be self-removing,
  280. provided that they are the bottom RSX when the remove entrypoint is
  281. called.  However, some RSXs, such as DateStamper and DosDisk, make
  282. extensive modifications to the BIOS and BDOS.  A good deal of
  283. additional code and data would have to be resident in the RSX for
  284. these RSX's to be self removing.  Therefore, these RSXs are removed
  285. by their own customized loaders.  If the remove entrypoint is called
  286. by any other program it will do nothing except to return the carry
  287. flag clear, indicating that the RSX has not been removed.
  288.  
  289.  
  290. .h1  OKDRIVES, or, Who's On Line?
  291.  
  292. This is the third Advanced CP/M column, and by now some readers
  293. will have anticipated my penchant for turning to an actual
  294. application to illustrate the technical subject at hand.  This
  295. example grew out of a conversation with Ben Grey, who was seeking
  296. a general-purpose method of limiting access to certain drives on
  297. a remote system.
  298.  
  299. Many users have computers with a mix of actual and "missing"
  300. logical drives.  For example, A: and B: might be floppies and M:
  301. a ram disk.
  302.  
  303. How can an application program determine whether it should
  304. attempt to use a particular drive?  Simply trying to select the
  305. drive with a BDOS call is playing Russian roulette.  If the drive
  306. is invalid, or doesn't exist, the BDOS will complain with an
  307. error message and terminate the application with extreme
  308. prejudice -- an abrupt warm-boot.
  309.  
  310. ZCPR34 allows the user to specify a vector of valid drives,
  311. stored in the Z-System extended external environment at offset
  312. 34h.  The ZCPR34 command processor tests this vector before
  313. attempting to log in a drive, and the same test can be made by an
  314. application once it determines that it is indeed running in a
  315. system with an extended external environment.  The extensions
  316. are listed in my previous Advanced CP/M column.
  317.  
  318. Frequently, however, the programmer wants his application to run
  319. smoothly on other CP/M systems that aren't (yet) running ZCPR34. 
  320. And users occasionally need a method of taking an erratic drive
  321. "off line" for maintenance. Is there a general, portable method
  322. of (1) determining which drives are currently available? and (2)
  323. making individual drives inaccessible?
  324.  
  325. OKDRIVES in Figure 5 is one method of dealing with this
  326. challenge.  It is a small RSX that maintains a vector of valid
  327. drives and monitors all BIOS disk-select calls.  If a drive is
  328. selected that is not in the vector, it returns a select error. 
  329. Otherwise, it allows the BIOS to select the drive.  In order to
  330. set and change the list of valid drives, the RSX adds one BDOS
  331. function to the system. That function call serves the dual
  332. purpose of setting the valid (and invalid) drives, and reporting
  333. what the current setting is.
  334.  
  335. .h2 Structure of the RSX
  336.  
  337. OKDRIVES is written to be assembled into a ZRL (Z-ReLocatable)
  338. file and loaded with JetLDR, which I also described in the
  339. previous column.
  340.  
  341. The RSX is made up of the standard RSX header, custom code to perform
  342. the operations on the valid-drives vector, and an initialization
  343. section.  I have written the RSX in a quasi-modular fashion, so that
  344. almost all of the tedious code to install and remove the RSX can be
  345. copied in a few blocks and reused in any other RSX.
  346.  
  347. This RSX uses one extended BDOS function number -- 241.  When the
  348. RSX has been installed and the BDOS is called with this function
  349. number, it will set a new vector of valid drives (if DE is
  350. non-zero), or report the current vector (when DE is zero).  For
  351. example, to make drives A:, B: and D: valid, call the BDOS with C
  352. = 241, DE = 11 = 1011b.  
  353.  
  354. The RSX must have a module name of the form "RSX...", so that
  355. JetLDR can identify it.  Following that, the standard header
  356. begins the code segment at label "rsx:".
  357.  
  358. Next come the unique data and code for this RSX, beginning with
  359. its nul-terminated ascii name and the vector of valid drives. 
  360. The static value of the vector is assembled with all drives
  361. enabled.
  362.  
  363. The action begins at "rsxstart".  Every BDOS call is intercepted here
  364. and tested for this RSX's function number.  Most of the time, it will
  365. be some other function, and so control jumps to "next" -- and then on
  366. to either an RSX immediately above this one, or the BDOS.  However,
  367. when the function number is for this RSX, the routine proceeds to test
  368. DE for zero and either load the valid-drives vector into HL, or set
  369. the vector to the value in DE.  In either case, the RSX returns to the
  370. caller.  
  371.  
  372. That's all there is to the added BDOS function.  But to make it work,
  373. the RSX must also intercept the BIOS select-disk routine, at
  374. "rsxseldsk".  The code checks the requested drive number against the
  375. valid-drives vector, using a simple loop that does a 16-bit left shift
  376. into the carry flag.  If the drive is not active, it returns the BIOS
  377. select error (HL = 0).  Otherwise, it continues to the BIOS
  378. select-disk routine.
  379.  
  380. .h2 Housekeeping Code
  381.  
  382. These two routines -- "rsxstart" and "rsxseldsk" -- do all of the
  383. work; the rest is necessary housekeeping.  The initialization
  384. code is needed only to verify conditions and set up the RSX, so
  385. it is assembled, beginning at label "init" in the _INIT_ named-common
  386. address space.  JetLDR will relocate this code into a working buffer
  387. in low memory and execute it there; it takes up no space in the
  388. resident system extension.  JetLDR relocates the code segment of the
  389. RSX, allocating space for it immediately below the command processor
  390. or the lowest RSX already in memory.  
  391.  
  392. Initialization involves verifying that the RSX can be correctly
  393. loaded, linking the rsx into both the warm-boot and BDOS call
  394. chains, and installing any additional BIOS intercepts needed for
  395. this particular RSX.
  396.  
  397. The "initlp" code checks each RSX in memory, beginning with the
  398. lowest, to see if an RSX with the same name is already in memory.
  399. If so, it (indirectly) calls the routine "custom_twin".  This routine
  400. can accept, or reject a duplicate RSX; for OKDRIVES a duplicate RSX
  401. would be an error.
  402.  
  403. Provided no duplicate RSX has caused termination, the search eventually
  404. reaches the end of the RSX warmboot chain.  If there is no RSX currently
  405. in memory, then the "rsxnext" address is set to the ccp entry.
  406. However, if one or more RSXs are already resident, the address is
  407. set to the warmboot address in the header of the currently lowest RSX.
  408.  
  409. Next, the RSX's warmboot routine is linked into the BIOS's
  410. warmboot chain, and the RSX's BDOS entry is linked into the
  411. chain that begins on page-0 with the jump instruction at 0005.
  412.  
  413. The final initialization step is to call "custom_init".  For this
  414. particular RSX, that code links the select-disk intercept
  415. routine into the BIOS jump table.
  416.  
  417. Now, look back at the "rsxremove" routine.  It is in the code
  418. segment, because the remove function must be resident within the
  419. RSX.  It first calls "custom_remove", to take care of unlinking
  420. the BIOS select-disk intercept.  Then it unlinks the warmboot
  421. intercept and exits with the carry flag set to signal successful
  422. removal of the RSX.
  423.  
  424. There is one more step to unlinking the RSX.  When the next
  425. warm boot occurs, it will be processed by the RSX immediately
  426. above this one, or, if there is none, by the BIOS.  In either
  427. case, that routine will set a new "protect" address on page-zero at
  428. 0006.  Of course, this RSX must have an essentially similar
  429. routine; it is at "rsxwb".  It first calls "fix0001", a
  430. precaution that ensures the correctness of the pointer at 0001 to
  431. the actual BIOS jump vector.
  432.  
  433. Next, it determines whether this RSX is, in fact, the lowest RSX
  434. in memory;  it does this by checking the BIOS jump's address
  435. against the RSX address.  Only if it is indeed the lowest does it
  436. set the protect address on page-zero.  Finally, it jumps to the next
  437. higher RSX, taking care to first load the current drive/user byte
  438. into C, in case the next "rsx" is in fact the command processor.
  439.  
  440. The details of managing the two linked lists -- one pointing
  441. upward to higher RSXs, the other pointing initially downward from
  442. the BIOS and then to successively higher RSX's -- @I{are}
  443. tedious, but necessary.  But now, with JetLDR, most of the
  444. work can be avoided, and only the particular, custom elements
  445. of an RSX need to be specially coded.
  446.  
  447. .h1  Extending System Extensions
  448.  
  449. As the wag said, "I like standards, because there are so many to
  450. choose from!"  The Plu*Perfect Systems RSX header is just one way
  451. to add resident extensions to CP/M 2.2.  But it is well tested,
  452. provides for BIOS as well as BDOS system extensions, and takes
  453. care to be compatible with other RSXs.
  454.  
  455. I have used these RSX structures in BackGrounder ii -- as well as
  456. its spooler, printer redefinition module, and secure memory
  457. allocator -- in DateStamper, and in DosDisk, achieving a high
  458. degree of compatibility between them.  More recently, Carson
  459. Wilson, Joe Wright and others have adopted the Plu*Perfect
  460. Systems header structure to add such extensions as quad-density
  461. disk drivers to a BIOS and resident conditional-execution
  462. processing (IF.COM) to ZCPR34.
  463.  
  464. This experience shows that a uniform RSX header can, indeed,
  465. allow diverse programs and applications to work together. 
  466. Several older programs, including versions of BYE for remote
  467. operation of a CP/M system, and ZEX for in-memory submit
  468. processing could be revised to use this interface, so that
  469. other RSXs could be run while those extensions are active.
  470.  
  471. In a CP/M 2.2 system that includes additional memory, it is
  472. possible to make system extensions "resident" without subtracting
  473. from scarce memory for applications.  Malcom Kemp has pioneered this
  474. approach, called Banked System Extensions, by defining a similar BSX
  475. header and providing loading/removal service in the XBIOS system
  476. for the SB180 and SB180/FX computers.  I developed both a
  477. banked-memory DateStamper and a DosDisk for this system.  In fact,
  478. I am completing this column on an XBIOS system with banked
  479. DateStamping and a DOS disk in drive C:, with no loss of TPA!
  480.  
  481. The next time you wish your computer had some missing capability,
  482. consider whether it might be added as an RSX.  Those features may
  483. already be available in products such as DosDisk, BackGrounder ii, or
  484. Z3PLUS.  If not, you may be able to code the routines yourself and bring
  485. your system to new levels of performance and versatility.
  486.  
  487.  
  488.  
  489.  
  490. Fig. 1  CONVENTIONAL MEMORY MAP
  491.  
  492. 0000: jp bios+3 --->----------------------->----------------------------*
  493. 0005: jp bdos+6 --->*                             ccp+3:                *-> bios+3: jp bioswb
  494.                     * --------------------->---------------> bdos+6:                 ...  
  495.                                                                         bioswb: ...
  496.                                                                                     jp ccp+3
  497. 0100: tpa, to bdos+5
  498.  
  499.  
  500. Fig 3.  MEMORY MAP WITH ONE RSX
  501.  
  502.                 *-->------------------------>----------------------->---*
  503. 0000: jp bios+3-*         *-----------------<-----------------------<---+------------------<---*
  504. 0005: jp rsx1   ----------+> rsx1: jp entry1   *-> ccp+3:               *--> bios+3: jp rsx1+3-*
  505.                           *--> +3: jp rsx1wb  /          *-> bdos+6:
  506. 0100: tpa, to rsx1-1               ...       /          /
  507.                                +F: jp ccp+3-*          /
  508.                             next1: jp bdos+6 ---------*
  509.                            rsx1wb: ...
  510.                                    jp rsx+F
  511.                            entry1: ...
  512.                                    jp next1
  513.  
  514.  
  515.  
  516.  
  517.  
  518. Fig. 4  MEMORY MAP WITH TWO RSXs
  519.  
  520.                 *-->------------------------->---------------------------------------->---*
  521. 0000: jp bios+3 *         *------------------<----------------------------------------<---+------------------<---*
  522. 0005: jp rsx2   ----------+> rsx2: jp entry2     rsx1: jp entry1   *-> ccp+3:             *--> bios+3: jp rsx2+3-*
  523.                           *--> +3: jp rsx2wb    *->+3: jp rsx1wb  /          *-> bdos+6:
  524. 0100: tpa, to rsx2-1               ...         /       ...       /          /      
  525.                                +F: jp rsx1+3--*    +F: jp ccp+3-*          /
  526.                             next2: jp rsx1      next1: jp bdos+6 ---------*
  527.                            rsx2wb: ...         rsx1wb: ...
  528.                                    jp rsx2+F            jp rsx1+F
  529.                            entry2: ...         entry1: ...
  530.                                    jp next2             jp next1
  531.  
  532.  
  533.  
  534.  
  535. Here, the labels "ccp", "bios", and "bdos" refer to the base address of the corresponding CP/M operating system
  536. segment.  The entry to the bdos is at bdos+6; don't confuse it with the common equate for the page-zero
  537. vector: "bdos equ 0005".
  538.  
  539.  
  540.             Figure 5
  541.  
  542. title    okdrives.asm 6/25/88    (c) 1988 Bridger Mitchell
  543. ;
  544. ;
  545. ; This rsx sets the vector of valid drives allowed by the bios.
  546. ; If called with de == 0, it returns the current valid-drives vector.
  547. ;
  548. ; usage to set valid drives:
  549. ;
  550. ;    ld    c,DRIVEFN
  551. ;    ld    de,<vector>    ; bit 0 = A:, ..., bit 15 = P:
  552. ;    call    5
  553. ;
  554. ; usage to determine currently-valid drives:
  555. ;
  556. ;    ld    c,DRIVEFN
  557. ;    ld    de,0000
  558. ;    call    5
  559. ;
  560. ;
  561. ; We need an extended bdos function number.
  562. ;
  563. DRIVEFN    equ    241    ; 0F1h
  564. ABORT    equ    0ffh
  565. ;
  566. ;
  567. ; Name the REL image with "RSX" plus 0-3 characters of identification.
  568. ; In this case, we've used the rsx's bdos function number (241).
  569.  
  570. name    RSX241        ;"RSX" required
  571.  
  572. ;
  573. ; All of the code within the bracketed regions is the same for any rsx
  574. ; loaded by JetLDR.  It can be copied intact when creating a different rsx.
  575.  
  576. ;
  577. ; *---------- Plu*Perfect Systems RSX Extended Header----------------*
  578. ;/                                      \
  579. ;
  580. ; The rsx code goes in the CSEG (code segment).
  581. ;
  582. CSEG
  583.  
  584. rsx:    jp    rsxstart                    ;  00
  585.     jp    rsxwb                        ; +03
  586.     jp    rsxremove                    ; +06
  587. rsxwba:    dw    $-$                        ; +09
  588. rsxprot:dw    rsx                        ; +0B
  589.     dw    rsxname                        ; +0D
  590. rsxnext:jp    $-$        ; -> next wb or ccp entry    ; +0F    
  591. ;
  592. next:    jp    $-$        ; -> next rsx or bdos        ; +12
  593. nextwb:    dw    $-$                        ; +15
  594. ;\                                     /
  595. ; *-----------------------------------------------------------------*
  596. ;
  597. ;
  598. ; The custom code for this rsx begins here.
  599. ;
  600. ;
  601. rsxname:db    'OKDRIVES',0      ; nul-terminated name of rsx.
  602. ;
  603. vector:    dw    1111111111111111b ;  <-- set bits for valid drives
  604. ;               PONMLKJIHGFEDCBA  ;  <-- must be terminated by 'B' char.
  605. ;
  606. ;
  607. ; This RSX's bdos function.
  608. ;
  609. ; enter: c  =  DRIVEFN
  610. ;     de == 0 to get current ok-drives vector
  611. ;        de != 0 to set the current vector to de
  612. ; return:
  613. ;     hl = vector of ok drives
  614. ;
  615. rsxstart:
  616.     ld    a,c
  617.     cp    DRIVEFN        ; if not our function
  618.     jr    nz,next        ; .. on to the next rsx/bdos
  619.     ld    a,e        ; set vector?
  620.     or    d
  621.     jr    nz,set        ; ..yes
  622. get:    ld    hl,(vector)    ; no, return the drive vector in hl
  623.     ld    a,l        ; return a != 0
  624.     ret
  625. ;
  626. set:    ex    de,hl
  627.     set    0,l        ; ensure drive A: always valid
  628.     ld    (vector),hl    ; save the new drive vector
  629.     ld    a,l        ; and return it in hl
  630.     ret
  631.  
  632. ;
  633. ; The bios seldsk intercept
  634. ;
  635. ; enter: c = requested drive
  636. ; exit:  hl == 0 if drive not allowed
  637. ;     else continue to bios seldsk
  638. ;
  639. rsxseldsk:
  640.     ld    hl,(vector)    ; shift ok-drives vector left
  641.     ld    a,16
  642.     sub    c
  643. rsxs1:    add    hl,hl
  644.     dec    a
  645.     jr    nz,rsxs1
  646.     ld    hl,0000        ; prime error return
  647.     ret    nc        ; NC if bit wasn't set
  648. jseldsk:jp    $-$        ; jmp to bios seldsk routine
  649. ;
  650. ;
  651. ; Restore this rsx's particular patches.
  652. ;
  653. custom_remove:
  654.     ld    hl,(jseldsk+1)    ; restore bseldsk address
  655.     ld    (bios+1ch),hl    ; to bios jmp vector
  656.     ret
  657.  
  658. ;
  659. ; *----------------  Standard RSX Code  -----------------------------*
  660. ;/                                      \
  661. ;
  662. ; The warm-boot intercept.
  663. ;
  664. rsxwb:                .new
  665.     call    fix0001        ; ensure correct page 0
  666.     ld    hl,(bios+4)    ; does bios wb addr
  667.     ld    de,rsx+3    ; point at us?
  668.     or    a
  669.     sbc    hl,de
  670.     jr    nz,rsxwb1    ; no, we're not the bottom rsx
  671.     ld    hl,(rsxprot)    ; we are, set our protect address
  672.     ld    (0006),hl
  673. rsxwb1:    ld    bc,(0004h)    ; get c = logged du for ccp
  674.     jp    rsxnext        ; in case we're top rsx
  675. ;
  676. ;
  677. ; The removal routine.
  678. ;
  679. rsxremove:
  680.     call    custom_remove    ; do extra restoration for this rsx
  681. ;
  682.     ld    hl,(nextwb)    ; get saved original warmboot addr
  683.         ld    (bios+4),hl    ; and restore it to bios jmp vector
  684. ;
  685. ; When the caller terminates to a warmboot,
  686. ; the next module (or bios, if none), will correct 0006.
  687. ;
  688. ; Set CY flag to inform the removal tool that this routine
  689. ; has indeed taken action. (Some RSX's are not self-removing).
  690. ;
  691. fix0001:ld    hl,(rsxwba)    ; restore (0001) in case an errant
  692.     ld    (0001h),hl    ; application has tampered with it 
  693.     scf            ; set CY to signal success
  694.     ret
  695. ;
  696. ;
  697. ; Before loading an RSX, JetLDR will first check for protected memory.
  698. ; If it detects that memory is protected by a non-RSX header (e.g. a debugger)
  699. ; it will cancel the load.  Otherwise, JetLDR will call any
  700. ; code in the _INIT_ named common, after the rsx module has been
  701. ; loaded and relocated.  This code will be located in non-protected
  702. ; memory, and takes no space in the RSX.
  703. ;
  704. ; Return parameter: A = 0 indicates a good installation
  705. ;            A = ABORT = 0FFh = not installed
  706. ;
  707. common    /_INIT_/
  708. ;
  709. ; Install the rsx.  This code is standard for all rsx's,
  710. ; except for:
  711. ;    custom_init
  712. ;    custom_twin
  713. ;
  714. init:    ld    hl,(0006)    ; hl = possible rsx, or bdos
  715.     ld    c,0        ; initialize count of rsx's
  716. ;
  717. initlp:    push    hl        ; stack (possible) rsx base address
  718.     ld    de,09         ; if candidate is an rsx
  719.     add    hl,de        ; ..the wbaddr will be here
  720.     ld    e,(hl)        ; get address
  721.     inc    hl
  722.     ld    d,(hl)
  723.     ld    hl,(0001)    ; and compare
  724.     or    a
  725.     sbc    hl,de
  726.     pop    hl
  727.     jr    nz,inittop    ; warmboot addr not there, stop looking
  728. ;
  729. ; we have an rsx in memory, is it our twin?
  730. ;
  731.     inc    c        ; count an rsx found
  732.     push    hl
  733.     call    ckname
  734.     pop    hl
  735.     jr    z,twin
  736. ;
  737.     ld    de,0Fh+1    ; that rsx was't a twin, check for more
  738.     add    hl,de        ; get addr of next rsx's wboot jmp
  739.     ld    a,(hl)
  740.     inc    hl
  741.     ld    h,(hl)
  742.     ld    l,a
  743.     dec    hl        ; back up to head of that next rsx
  744.     dec    hl
  745.     dec    hl
  746.     jr    initlp        ; now check that rsx
  747. ;
  748. ; we're at the top of the (possibly empty) rsx chain 
  749. ;
  750. inittop:
  751.     inc    c        ; any rsx's found?
  752.     dec    c
  753.     ld    hl,ccp+3    ; prepare to use ccp entry address
  754.     jr    z,setnext    ; ..no
  755. ;
  756.     ld    hl,(0006)    ; yes, use bottom rsx's address
  757. ;
  758. setnext:
  759.     ld    (rsxnext+1),hl    ; save the next addr
  760.                 ; in the rsx chain to bdos/ccp
  761. ;
  762. ; install the rsx into the running system
  763. ;
  764.     ld    hl,(bios+4)    ; save the bios's wb addr
  765.     ld    (nextwb),hl    ; in the header
  766.  
  767.     ld    hl,rsx+3    ; point the bios wb jump
  768.     ld    (bios+4),hl    ; at the rsx wb vector
  769.  
  770.     ld    hl,bios+3    ; store wb addr
  771.     ld    (rsx+09),hl    ; in rsx header word
  772.  
  773.     ld    hl,(0006)    ; get addr of next rsx or bdos
  774.     ld    (next+1),hl    ; and install it
  775.  
  776.     ld    hl,rsx        ; finally, protect the rsx
  777.     ld    (0006),hl
  778. ;
  779.     call    custom_init    ; take care of extras 
  780.     ret
  781. ;
  782. ckname:    ld    de,0dh        ; offset to candidate rsx name pointer
  783.     add    hl,de
  784.     ld    a,(hl)        ; get address
  785.     inc    hl
  786.     ld    h,(hl)
  787.     ld    l,a
  788.     ld    de,rsxname    ; compare to our name
  789. ckname1:ld    a,(de)
  790.     cp    (hl)
  791.     ret    nz
  792.     inc    (hl)        ; candidate must be nul-terminated
  793.     dec    (hl)
  794.     jr    nz,ckname2
  795.     or    a        ; ..at our same byte
  796.     ret
  797. ckname2:inc    hl
  798.     inc    de
  799.     jr    ckname1
  800.     
  801. ;
  802. ; Handle the case of a previously-loaded copy of this RSX.
  803. ;
  804. twin:    call    custom_twin
  805.     ret
  806. ;\                                     /
  807. ; *-----------------------------------------------------------------*
  808. ;
  809. ; Custom initialization code goes here.
  810. ;
  811. ;
  812. ; Do the particular patches for this RSX.
  813. ; Note: this code is in the _INIT_ segment.
  814.  
  815. custom_init:
  816.     ld    hl,(bios+1ch)    ; get bseldsk address
  817.     ld    (jseldsk+1),hl    ; install it in rsx
  818. ;
  819.     ld    hl,rsxseldsk    ; divert bios jump
  820.     ld    (bios+1ch),hl    ; to the rsx
  821.     ret
  822. ;
  823. ; This particular rsx should not be duplicated.
  824. ; A different rsx might wish to query the user here,
  825. ; print a warning, or whatever.
  826. ;
  827. custom_twin:
  828.     ld    a,ABORT
  829.     ret
  830. ;
  831.  
  832. ; Include identification info in the REL image.
  833. ; JetLDR will display the bytes up to the first NUL byte
  834. ; when the RSX is loaded.
  835. ;
  836. ;
  837. common    /_ID_/
  838. ;
  839.     db    'OKDRIVES: RSX prevents bios logins'
  840.     db    13,10
  841.     db    'Use BDOS function 241 (0F1h) to set de = drive vector',0
  842.  
  843. ;
  844. ; Include whatever other named-commons are needed for this RSX.
  845. ; JetLDR will resolve these labels for us.
  846. ;
  847. common    /_BIOS_/
  848. bios    equ    $
  849.     
  850. common    /_CCP_/
  851. ccp    equ    $
  852.  
  853.  
  854.     end    ;okdrives.asm
  855.  
  856.  
  857.  
  858.