Disk Devices Low-level communication with a block-structured data storage device like a 3.5-inch disk drive or a hard disk is managed by an assembly-language subroutine called a disk driver. (This name is conventional only - a disk driver may actually communicate with a block storage device that is not a disk drive.) We say "low level" because the disk driver is the subroutine every operating system command eventually calls to access the disk, and it is the disk driver that directly manipulates the 1/0 locations that control the operation of the drive. The important tasks conventional disk drivers perform are z Moving the disk's read/write head over any track on the disk z Identifying data blocks within each track z Reading and writing data blocks z Reading the write-protect (or other) status of the disk z Formatting the disk The driver for a 5.25-inch drive performs these tasks using several disk 1/0 locations for controlling the disk stepper motor, storing a byte on a disk, reading a byte from a disk, and sensing the write-protect status of the disk. Under ProDOS 8, it is relatively easy to add a custom disk driver (such as one for controlling a RAMdisk) to the system - it's just a matter of changing a few bytes in the system global page to tell ProDOS 8 where you've loaded the driver and what slot and drive numbers you want to assign to it. The only difficult part is deciding where to put the driver so that it won't be overwritten by applications. GS/OS has a more formal mechanism for adding disk drivers, but we do not discuss them here; GS/OS comes with drivers for all the disk drivers you're ever likely to need. If you do need to know about how to write CS/OS disk drivers, refer to GS/OS Reference, Volume 2. In this chapter, we investigate just how GS/OS and ProDOS 8 determine what disk devices are available and how they keep track of the disk drivers associated with each 287 of these devices. We also review the general characteristics of a ProDOS 8 disk and learn how to write one from scratch. HOW GS/OS AND PRODOS 8 KEEP TRACK OF DISK DEVICES When GS/OS and ProDOS 8 boot up, one of the first things they do is - many disk devices are connected to the system and how they may be ac example, through a card in a slot or a RAM-based driver. (GS/OS also character devices.) We see how these operating systems identify disk devices next section. GS/OS Device Scan When you boot GS/OS, it scans the IIcs system looking for both block-structure devices and character devices. When it identifies a device for which a driver the SYSTEM/DRIVERS/ subdirectory on the boot disk, it loads the driver memory and installs it. Apple currently provides drivers for 3.5- and 5.25-inch drives, SCSI drives, and the console (the standard keyboard input and output system). If no driver for the device exists on disk, CS/OS tries to generate in memory on the fly; it can generate character drivers for printer and modem and disk drivers for most SmartPort devices. The disk devices CS/OS cannot driver for are the 5.25-inch disk drive and the HD20SC SCSI hard disk. GS/OS assigns a unique device number and device name to each device it finds the system. It assigns device numbers consecutively, beginning with 1, and the names begin with a period and can be up to 31 characters long (for example, .,r and .APPLEDISK3.5). Unlike ProDOS 8, CS/OS does not use unit numbers are derived from slot and drive numbers) to identifl' disk devices. You can use GS/OS DInfo command to determine the names of all the devices in the system. ProDOS 8 Device Scan Table 7-1 lists all the system global page locations ProDOS 8 uses to manage devices. ProDOS 8 stores the number of active disk devices, less 1, in DEVCNT ($11F31)z the system global page. It stores the physical locations of the disk devices (that is, their slot and drive numbers) in encoded form in a 14-byte table beginning at DEVLST ($BF32). As Figure 7-1 shows, the high-order 4 bits of each entry in this table hold the drive and slot number in packed form, and the low-order 4 bits hold ani identification code unique to the type of disk device installed (5.25-inch drive, 3.S inch drive, HD20SC hard disk, and so on). You can also use the ProDOS 8 ON - LINE command to determine the slot and drive locations of all the disk drives in the system. 288 Disk Devices Table 7-1 ProDOS 8 global page areas used for disk drive identification Address Symbolic Name Description $BF10 DEVADR01 "No device connected" address $BF12 DEVADR11 Slot 1, drive 1 driver address $BF14 DEVADR21 Slot 2, drive 1 driver address $8F16 DEVADR31 Slot 3, drive 1 driver address $BF18 DEVADR41 Slot 4, drive 1 driver address $BF1A DEVADRS1 Slot 5, drive 1 driver address $BF1C DEVADR61 Slot 6, drive 1 driver address $BF1E DEVADR71 Slot 7, drive 1 driver address $8F20 DEVADR02 "No device connected" address $BF22 DEVADRI2 Slot 1, drive 2 driver address $8F24 DEVADR22 Slot 2, drive 2 driver address $BF26 DEVADR32 Slot 3, drive 2 driver address $BF28 DEVADR42 Slot 4, drive 2 driver address $BF2A DEVADR52 Slot 5, drive 2 driver address $BF2C DEVADR62 Slot 6, drive 2 driver address $8F2E DEVADR72 Slot 7, drive 2 driver address $8F30 DEVNUM Device code for the last device accessed $8F31 DEVCNT Number of active devices minus 1 $8F32 DEVLST Table of active disk device codes (14 entries in table) NOTE: The format of the entries in DEVLST and DEVNUM is the same as shown in Figure 7-1, except that the low-order 4 bits of DEVNUM are always 0. Figure 7-1 The format of DEVLST ($BF32) table entries Each byte in the 14-byte DEVLST table holds the slot, drive, and disk identification number in a special packed format: 7 6 5 4 3 2 1 0 DR SLOT DISK_ID where DR = 0 for a drive 1 device = 1 for a drive 2 device SLOT = 1-7 (slot number for the device) DISK - ID = $0 for a 5.25-inch disk drive = $B for a 3.5-inch disk drive = $F for the [RAM device = the high-order 4 bits stored at $CnFE if a disk controller adhering to the extended protocol is being used NOTE: The /RAM device is logically equivalent to a slot 3, drive 2 disk drive. Its DEVLST entry is $BF. How GS/OS and ProDOS 8 Keep Track of Disk Devices 289 Suppose you are using a two-drive Apple lIe with an extended 80-column teM installed in the auxiliary slot and a disk controller card installed in slot 6. Pr""" sets up DEVCNT and DEVLST as follows: DEVCNT ($8F31) DEVLST ($BF32) $02 <--- three devices $E0 slot 6, drive 2 $60 <--- slot 6, drive 1 $BF <--- slot 3, drive 2 (IRAN) $00 11 zero entries $00 ProDOS 8 reserves a 32-byte area beginning at $BF10 for use as a disk driver table. This table holds the addresses of the disk driver to be used for each of the possible slot and drive combinations and 2 impossible ones (slot 0, drive 1 and ' drive 2). The first part of the table, from $BI'10 to $BF1F, holds the addresses for eight drive 1 devices in ascending slot order (~7); the second part holds information for the eight drive 2 devices. Since a disk controller card cannot reside in slot 0 (a slot that doesn't even exist the Apple lIe, IIc, or 1Ics), ProDOS 8 uses the two slot 0 entries in the disk vector table for a special purpose: to hold the address of the subroutine that MLI error $28 if ProDOS 8 calls it. This is the code for the "no device connec error. If the vector table entry for a given slot and drive combination is this at' ProDOS 8 has not assigned a disk device to that slot and drive. The six most common entries in the disk driver vector table are as follows: $D000 disk driver for a standard 5.25-inch disk drive (in bank-switched RAM $FF00 disk driver for the [RAM RAMdisk volume (in bank-switched I'lAfl) $DEAC address of "no device connected" error subroutine (in bank-swltch~ RAM) $Cn0A UniDisk 3.5 and Apple IIcs SmartPort (n = slot number of the controller card) $Cn4E Apple II Memory Expansion card (n = slot number of the memory card) The first three addresses are those used by ProDOS 8 version 1.7 only. (The others are fixed in ROM on firmware or controller cards.) They may change when Apple releases later versions of ProDOS 8. 290 Disk DevicesHOW GS/OS AND PRODOS 8 IDENTI~ DISK DEVICES To connect a disk device to an Apple II, you generally attach it to a disk controller card located in a peripheral expansion slot. (The Ilc and Ilcs both have built-in disk controllers, so no card is necessary.) This card is responsible for booting the disk and, in some cases, for transferring data between the Apple and the disk medium. A controller card holds a program in ROM that occupies the address space from $Cn00 to ~CnFF (where n is the slot number) and, sometimes, from $C800 to $CFFF. For standard 5.25-inch disk controllers, this program is capable of only transferring a short loader program from the disk medium into 11AM and executing it; this loader then reads in the rest of the disk operating system from disk. (This is where the term booting comes from: The operating system picks itself up by its own bootstraps.) Other controllers may contain code that performs much more sophisticated tasks, such as reading or writing any block on the disk, doing status checks, and formatting a disk. Intelligent controllers with these capabilities are used with 3.5-inch disks and hard disks. Apple currently uses an intelligent controller called a SmartPort for 3.5-inch drives and RAMdisk memory cards. (A SmartPort is built in to the II&s and newer models of the Ilc.) When ProDOS 8 or CS/OS first starts up, it examines each slot (beginning with 7 and working down to 1) to determine whether a controller card for a disklike device is present. A controller card contains the following unique pattern of bytes in its ROM (n is the slot number): $Cn0l $20 $Cn03 $00 $Cnos $03 The value of the byte stored at $Cn07 is also important. If the three identification bytes are present and location $Cn07 contains $3C, and if the controller is in a higher-numbered slot than any other disk controller, the original Apple II system Monitor program in ROM (the one in the II Plus or the original lIe) automatically boots the disk in the drive when you turn the system on. Unfortunately, $Cn07 cannot contain $3C in the ROM of a controller for a disk device other than a 5.25-inch disk drive because the Apple Pascal operating system erroneously believes any such device is a 5.25-inch disk drive. As a result, it is not possible to automatically boot from a hard disk or a 3.5-inch disk when using a system with the original Monitor program. You can automatically boot a non-5.25-inch disk device'if you have an Apple Ilcs or an enhanced Apple lIe. This is because the system fi1onitor in these computers identifies a bootable disk drive by the presence of the first 3 identification bytes only. If you want to know if the disk controller is a SmartPort (perhaps so that you can take advantage of the special SmartPort commands described later in this chapter), check location $Cn07. If it contains $00, it is a SmartPort. How GS/OS and ProDOS 8 Identijy Disk Devices 291 When ProDOS 8 or CS/OS finds the 3 identification bytes, it looks at the stored at $CnFF to determine the exact type of controller it has found. If contains $00, ProDOS 8 and CS/OS consider the card a 5.25-inch disk controller standard 16-sector-per-track ROMs. In this case, ProDOS 8 places the a device code in the DEVLST table and the address of the internal 5.25-inch device driver in the ProDOS 8 disk driver vector table. Note that it actually two entries in each tahle since each 5.25-inch disk controller can have two drives volumes) attached to it. (They are referred to as drive 1 and drive 2.) The disk itself ultimately determines if there is actually a drive 2 device attached and "device not connected" error code if an attempt is made to access it and it is not If $CnFF contains $FF, CS/OS and ProDOS 8 consider the card a 5.25-inch controller with 13-sector-per-track ItOMs. (This was the disk formatting scheme-'- by Apple's original 5.25-inch drive controller.) CS/OS and ProDOS 8 do not this type of controller card and so ignore it. If $CnFF contains any other value, GS/OS and ProDOS 8 assume the controller has a device driver entry point located in ROM at $CnXX, where XX is value stored at $CnFF. If bits 0 and 1 of the byte stored at $CnFE are both I describe the meaning of these bits in the next section), ProDOS 8 stores this in the device driver vector table and adds an appropriate device code to DEVL (The low-order 4 bits of the DEVLST entry are set equal to the high-order 4 bits the byte at $CnFE.) If one, or both, of bits 0 and 1 of $CnFE are 0, CS/OS ProDOS 8 ignore the disk controller. ProDOS 8 identifies three special "disk" devices in quite a different way. If it running on an Apple lIe with an extended 80-column card (the one with 64K auxiliary 11AM on it), or on an Apple IIc or IIcs, ProDOS 8 installs a special device, called a 11AMdisk, as the slot 3, drive 2 disk device. The medium for this disk is the 64K auxiliary memory space on the lIe, IIc, or IIcs, and disk 1/0 operations simply involve the movement of data blocks between auxiliary and main memory. The volume name for this 11AMdisk is always 111AM. GS/OS and ProDOS 8 create another type of 11AMdisk using memory on the Apple IIcs Memory Expansion card (or equivalent) if the Control Panel Minimum 11AM Disk Size parameter is not set to zero. This 11AMdisk is called 111AM5. The third special device, again available on the IIcs only, is a ROMdisk. Although Apple's memory card doesn't support ROMdisk memory, several independent suppliers have cards that do. Despite the name ROMdisk, the memory for the disk could also be in battery backed-up static or dynamic 11AM, EEPROM, or EPItOM. EXTENDED PROTOCOL FOR DISK CONTROLLER CARDS Apple has also defined a special extended controller card ROM protocol that manu- facturers of disk devices and disk controller cards must adhere to if their devices are to work properly with CS/OS and ProDOS 8. (The 5.25-inch disk controllers do not actually follow this protocol and are handled as special cases by GS/OS and ProDOS 292 Disk Devices 8.) This protocol defines the use of 4 bytes in the controller card ROM space as follows (n is the slot number of the card): z $CnFC and $CnFD. The total number of blocks on the volume is stored here (low-order byte first). This information is for the benefit of formatting programs that also initialize the volume directory and volume bit map on disk. The controller for the old 5-megabyte ProFile hard disk has the number $2600 (9728) stored here. If the number is $0000 (as it is for most controller cards), you must send a status request to the disk driver to determine the volume size; the number of blocks comes back in the X register (low) and Y register (high). We see how to make status requests in the next section. z $CnFE. This is the device characteristics byte. Each bit holds miscellaneous information about the device: bit 7 1 = the disk medium is removable bit 6 1 = the device is interruptible bits 5,4 The number of drives (or volumes) on the device (0-3). An even value (0 or 2) indicates one drive; an odd value (1 or 3) indicates two drives. bit 3 1 = the device driver supports format bit 2 1 = the device driver supports write bit 1 1 = the device driver supports read bit 0 1 = the device driver supports status The controller for the UniDisk 3.5 has the value $BF stored at $CnFE. This means the disk medium is removable (bit 7 = 1); the UniDisk 3.5 is not interruptible (bit 6 = 0); two volumes are supported (bits 5,4 = 11); and the device driver for the UniDisk 3.5, located in ROM on the controller card, supports format (bit 3 = 1), write (bit 2 = 1), read (bit 1 = 1), and status (bit 0 = 1) operations. z $CnFF. This byte contains the offset (from $Cn00) of the address of the ProDOS 8 disk driver for this device. If the byte at $CnFE indicates that the device can be read from and its status can be read (that is, bits 0 and 1 of the byte stored at $CnFE are both 1), the driver address is stored in the "drive 1" portion of the device driver vector table in the ProDOS 8 global page when ProDOS 8 is first booted. If the byte at $CnFE indicates that two drives are attached to the controller, the address of the device driver is also stored in the "drive 2" portion of the table unless ProDOS 8 is able to determine that a second drive is not actually connected. After the vector table is updated, bits ~7 of the byte stored at $CnFE are stored in the low-order 4 bits of the DEVLST entry for the device. The controller for the UniDisk 3.5 has the value $0A stored at $CnFF, and its DEVLST entry is of the form nB, where n is the controller slot number. This means the address of the disk driver is $Cn0A. Extended Protocol for Disk Controller Cards 293 Special Cases $CnFF contains $00 for a 16-sector 5.25-inch disk controller and $FF for a 1 5.25-inch disk controller. In these situations, GS/OS and ProDOS 8 .. special meaning to the values stored at $CnFC, $CnFD, and $CnFE. If ProDOS 8 finds a 16-sector controller, it assumes the disk medium is a volume of 280 blocks and uses its own internal disk driver to communicate - CS/OS uses a similar driver it loads from the SYSTEM/DRIVERS/ su.. CS/OS and ProDOS 8 ignore the older 13-sectorS.2S-inch disk controller. COMMUNICATING WITH A PRODOS 8 DISK DRIVER Just before ProDOS 8 calls a disk driver subroutine, it sets up four parameters th microprocessor's page zero area that serve to inform the disk driver of the operation to be performed. These parameters define the tye of disk operation write, format, or check device status), the slot and drive number of the disk device, address of the 512-byte (one block) data transfer buffer to be used, and the block The four parameters are stored in locations $42 to $47 and have the loll meanings: z COMMAND ($42). This location holds the command code for the disk to be performed. Four codes are defined: 0 Check device status. On return, the carry flag is clear and the accumulator is zero if the device is ready to accept read and write commands. Moreover, the number of blocks on the disk is in the X register (low) and Y register (high) but only if the device's controller ROM adheres to the 1 2 3 extended ProDOS 8 protocol (remember that 5.25-inch disk controllers do not). If the device is not ready to accept read and write commands, the carry flag is set, and the accumulator contains an MLI error code. The standard drivers for 3.5- and 5.25-inch drives return an error code on a status request if the disk medium is write-protected (error $28) or no disk is in the drive (error $2F). Read one block from the disk. Write one block to the disk. Format the disk. When you format a disk, special address marks are set up to allow each sector to be identified by the disk driver. Generally, the formatting process does not also set up the boot record, volume directory, and bit map blocks; this must be done by making write requests. (The driver for /RAM is an exception.) The format request is actually not supported by the standard 294 Disk Devices 5.25-inch device driver because of space limitations; instead, a separate utility program (such as Filer on the ProDOS 8 master disk) must be used to format a diskette or hard disk and to lay out the boot record, volume directory, and bit map. The source code for the standard diskette formatting subroutines (called F0RMATTER) can also be licensed from Apple for use in other formatting programs. The format request is supported by the /RAM driver and the 3.5-inch disk driver. z SLOT-DRIVE ($43). These locations hold the drive and slot numbers of the disk device to be accessed, in the following format: bit 7 bits 4,5,6 bits 0,1,2,3 0 (drive 1) or 1 (drive 2) slot number (1-7) always 0 For example, a slot 6, drive 2 device would be represented as 11100000 ($E0). z BUFFER - I11'R ($4~$45). These locations hold the address (low-order byte first) of the start of a 512-byte area of memory that holds the image of the block to be written to the disk (COMMAND = 2) or that will hold the block read from the disk (COMMAND = 1). BUFFER PTR should also be properly set up before malcing a format request (COMMAND = 3) because the formatting subroutines for some disk devices (like /11AM) may use the buffer area for temporary data storage. BLOCK - NUM ($4~$47). These locations hold the number (low-order byte first) of the block on the disk to be written to (COMMAND=2) or read from (COMMAND = 1). The disk driver performs the 1/0 operation dictated by these parameters and then returns control to the caller. If no error occurred, the carry flag is clear, and the accumulator is zero. Errors can occur, of course, when ProDOS 8 communicates with a disk device. The disk drivers flag error conditions in the standard MLI way: by setting the carry flag and placing an appropriate MLI error code in the accumulator. Table 7-2 shows the error codes and conditions supported by the ProDOS 8 disk driver for standard 5.25-inch disk drives. Any other properly implemented disk driver will identify and report these error conditions in the same way. THE SMARTPORT CONTROLLER A SmartPort is the intelligent device controller Apple now uses to interface to all its high-capacity disk drives, including the UniDisk 3.5, Apple 3.5 Drive, and HD20SC SCSI hard disk. The SmartPort firmware can handle up to 127 devices chained The SmartPort Controller 295 Table 7-2 ProDOS 8 disk driver error codes $28 $2B $2F No disk device is connected The medium is write-protected The device is off-line together to the same SmartPort, but the Apple power supply gives out well then - for the SmartPort on the II&s, for example, Apple recommends c' more than four 3.5-inch drives. As we mentioned earlier in this chapter, the SmartPort firmware has the same basic identification bytes as any other ProDOS-compatible disk controller. A location $Cn07 serves to uniquely identify the controller as a SmartPort, however, SmartPort ID type byte at $CnFB gives you a little more information about SmartPort: bit 0 1 = supports RAMdisk card bit 1 1 = supports SCSI devices bit 2 [reserved] bit 3 [reserved] bit 4 [reserved] bitS [reserved] bit 6 [reserved] bit 7 1 = supports extended commands The SmartPort assigns a unique unit number (from $01 to $7F) to each L.. connected to it. The numbers it assigns are consecutive, starting with $01. SmartPort controller itself is unit number $00.) Programs use the unit number identify the device a SmartPort command is directed to. In general, the SmartPort assigns unit numbers to devices in the order they in the chain of devices. But on the II&s, the SmartPort considers any BOMdisk /11AM5 11AMdisk (the 11AMdisk you set up with the Control Panel) in the system to L part of the SmartPort chain and assigns unit numbers to them first. To complic matters further, if the startup device (set using the Control Panel) is a SmartPort device, the SmartPort rearranges unit numbers to ensure the startup device has a unit number of $01. The only safe way to determine which device corresponds to a given unit nutnber is to use the SmartPort's Status command (see below). Many ProDOS 8 commands use slot and drive parameters to identify a disk device, so ProDOS 8 automatically assigns slot and drive combinations to SmartPort unit numbers when it first boots up. Assuming the SmartPort is in slot 5, ProDOS 8 assigns 296 Disk Devicesthe first four SmartPort devices to slot 5, drive 1; slot 5, drive 2; slot 2, drive 1; and slot 2, drive 2. It ignores any other devices that may be connected to the SmartPort. The phantoming of the third and fourth devices to slot 2 is necessary because ProDOS 8 has space for only two drives per slot in its disk driver vector table. Using SmartPort Commands The SmartPort firmware provides several commands a program can use to communi- cate with a disk device. Under ProDOS 8, you won't have to use them for common types of disk operations because you can use the disk driver commands described in the previous section instead. Under GS/OS, you can probably get by with the DInfo, DRead, DWrite, DStatus, and DControl commands. You will have to use SmartPort commands to obtain extended status information and to perform special control operations, however. To use a SmartPort command, you must first determine the dispatch address of the command interpreter. This address is always 3 bytes past the standard ProDOS 8 device driver entry point, so its offset into page $Cn00 is the value stored at $CnFF plus 3. You call a standard SmartPort command much as you call a ProDOS 8 MLI command: JSR DISPATCH DISPATCH = $Cn00+($CnFF)+3 DFB CMONUM ;SmartPort command number DA PARM_BLK ;Pointer to Smart Port parameters 8CS ERROR ;Carry set if error occurred where DISPATCH is the SmartPort dispatch address, CMDNUM is the SmartPort command number, and PARM - BLK is a command-specific parameter block. (If CS/OS is active on a IIcs, you must call the SmartPort dispatcher in emulation mode with code that resides in bank $00.) If an error occurs, the carry flag is set, and the accumulator contains the error code. If the operation was successful, the carry flag is clear, and the accumulator is zero. If bit 7 of the SmartPort ID type byte at $CnFB is 1 (and it is for the IIcs SmartPort), the SmartPort also supports extended SmartPort commands. The com- mand number for an extended command is the same as the number for the corre- sponding standard command except that bit 6 is set to 1. That means, for example, if the standard command number is $01, the extended command number is $41. You call extended commands just like standard commands except that the pointer to the parameter block contains a long address (4 bytes) rather than a short address (2 bytes). This permits access to a parameter block located anywhere in the IIcs's 16Mb memory space. The other difference between a standard and extended command is the strncture of the parameter block for the command, as we see below. The SmartPort Controller 297 Important: The IIcs SmartPort clobbers several locations in the caller's direct page (IIcs ROM version 01) or true zero page (original II&s ROM) you call a SmartPort command. The affected locations are $57 through these locations are important to your application, save them before a call and restore them afterward. All SmartPorts support a standard set of commands so that ProDOS 8 or GS/O5 communicate with it properly. The ones you probably will never use in an I: are ReadBlock, WriteBlock, Format, and Init (you can use ProDOS 8 disk dri GS/OS commands instead) as well as Open, Close, Read, and Write (appropriate character devices only). Let's now take a close look at the two remaining Status and Control. Status Command The Status command is for determining the status of any device in the chain or the SmartPort controller itself. Its command number is $00 (standard) or (extended), and the standard parameter block looks like this: parameter count (byte, always $03) unit number (byte, from $00 to $7E) status list pointer (low byte) status list pointer (high byte) status code (byte, from $00 to $FF) The extended parameter block uses a 4-byte pointer to the status list instead (low-ord~ bytes first). You must reserve space for the status list before calling the Status command There are four possible values for the status code byte: $00 return device status "'I, ',':+',,',7, ~~, ~.trn'I. h'l.nck. $02 return newline status $03 return device information block Of these, you probably won't use code $01 or $02 very often. Code $01 returns a device.dependent control block, up to 256 bytes long, preceded by a length byte; a length byte of $00 means the block is 256 bytes long. Code $02 is for character devices only. Code $00 (return device status) returns 4 or 5 bffes in the status list depend- ing on whether a standard or extended call is made. The first bffe is a general device status byte: 298 Disk Devices bit 0 1 = disk switched (block device only) or 1 = device is open (character device only) bit 1 1 = device is interrupting bit 2 1 = medium is write-protected (block device only) bit 3 1 = device allows formatting bit 4 1 = a disk is in the drive bit 5 1 = device allows reading bit 6 1 = device allows writing bit 7 1 = block device 0 = character device Note that the disk-switched bit is 1 if a disk has been ejected and another disk (perhaps the same one) has been inserted since the last status check. But this bit is significant only if the device supports disk-switched errors; it does if bit 6 of the subtype byte returned by the code $03 status command is 1 (see below). Of Apple's SmartPort devices, only the Apple 3.5 Drive for the IIcs supports these types of errors. (The UniDisk 3.5 does not.) The next 3 bytes (standard call) or 4 bytes (extended call) hold the size of the device in blocks. These bytes are zero if the device is a character device. The SmartPort handles a Status call differently if the unit number is $00. In this case, it returns an 8-byte status list describing the status of the SmartPort controller itself: byte 0 number of devices the SmartPort controls byte 1 interrupt status (no interrupt if bit 6 is set) byte 2 manufacturer of driver: $00 = unknown $01 = Apple $02 = third-party driver Bytes 3 through 7 are reserved. Code $03 (return device information block) returns more detailed status informa- tion in the status list. The form of the list after a standard call is as follows: device status (byte) block size (low byte) block size (medium byte) block size (high byte) ID string length (byte) ID string (16 bytes) device type (byte) device subtype (byte) version (2 bytes) The SmartPort Controller 299 For an extended call, the block size field occupies 4 bytes instead of 3. The device status and block size bytes are the same as those returned by a code $00 call. The ID string is a sequence of up to 16 standard ASCII cL high-order bit of each character is 0) representing the name of the <' 16-character string space is padded with spaces if necessary. The device type byte tells you the general nature of the device you're dealing The currently defined values are as follows: $00 Memory expansion card RAMdisk $01 3.5-inch disk drive $02 ProFile-type hard disk $03 Generic SCSI hard disk $04 R0Mdisk $05 SCSI CD-R0M $06 SCSI tape or other SCSI sequential device SCSI hard disk [reserved] SCSI printer 5.25-inch disk drive [reserved] [reserved] Printer Clock Modem $07 $08 $09 $0A $0B $0C $00 $0E $0F The subtype byte indicates some of the characteristics of the device: bit 7 1 = supports extended SmartPort commands bit 6 1 = supports disk-switched errors bit 5 1 = nonremovable medium The other 5 bits are reserved. Version is a word (low-order byte first) describing the version number of SmartPort device driver. Control Command The Control command sends control information to a device. Its command number $04 (standard) or $44 (extended), and the standard parameter block looks like this: parameter count (byte, always $03) unit number (byte, from $00 to $7E) control list pointer (low byte) control list pointer (high byte) control code (byte, from $00 to $FF) 300 Disk Devices The extended parameter block uses a 4-byte pointer to the control list instead (low-order bytes first). The Control command understands five general control codes, only one of which (eject medium) is particularly useful to most applications: $00 (device reset), $01 (set device control block), $02 (set newline status), $03 (service device interrupt), and $04 (eject medium). Device-specific control codes are numbered $05 and above. The most useful control code for most applications is the one that causes a 3.5-inch disk to eject automatically. For the UniDisk 3.5 SmartPort card and the internal IIcs SmartPort, the eject control code is $04, and the control list contains two $00 bytes. (For a summary of other device-specific control codes, see Chapter 7 of Apple tics Firmware Reference.) Remember to use the eject command with 3.5-inch drives only. You can easily check whether you're dealing with a 3.5-inch drive by using the Status command. If you are, the device type byte is $01, the block size is $000640, and the ID string is DISK 3.5. If you need to be convinced to do a Status check first, keep in mind that revisions A and B of the SCSI card (a SmartPort device) for Apple's HD205C hard disk use a control code of $04 to format the disk! That's not an operation you want to perform accidentally. (For revision C of the SCSI card, the $04 code is the eject code.) THE PRODOS 8 RAMDISK: THE /RAM VOLUME We saw earlier that ProDOS 8 automatically installs a special 11AMdisk driver if you are using an Apple IIcs, Apple IIc, or Apple lIe with an extended 8O-column text card and creates a special volume called /RAM. (Apple II Plus users are out of luck.) All these systems have 64K of auxiliary memory that maps to addresses in exactly the same way as the standard 64K of main 11AM memory usually used for program and data storage. In this auxiliary memory, the 11AMdisk driver stores the volume direc- tory, volume bit map, and file blocks. Figure 7-2 shows a map of the usage of auxiliary memory by /RAM. Since no slow-moving mechanical parts are used to perform "disk" operations (all 1/0 operations simply involve block moves from one part of memory to another), the 11AMdisk responds much more quickly than a conventional disk drive. But its contents are tempo- rary, so you must be careful to transfer any files from it to a permanent disk medium before turning off the Apple or rebooting ProDOS 8, or you will lose all of your data. Characteristics of the /RAM Volume When ProDOS 8 initializes the /11AM volume, it allocates only one volume directory block (block 2; recall that standard disks use four directory blocks). This means there is room for only 12 entries in the volume directory, not the usual 51. If files are created inside subdirectories, however, you can store as many files as will fit on the volume. When ProDOS 8 first initializes the /11AM volume, 119 blocks are available for file storage. (They are numbered from 8 to 126.) Since a 64K space is normally capable of holding 128 512-byte blocks, you might be wondering about the "missing" 9 blocks. The Pro DOS 8 RAI$tdisk: The 111AM Volume 301 Figure 7-2 A map of auxiliary memory usage on the Apple lIe, lIe, and IIcs with ProDOS 8 active Auxiliary bank-switched RAM $EO00 $DO00 ~BFrF Auxiliary memory $Ocoo 0 00 0 00 -0400 0200 0100 0000 Bank vectors $FEO0..$FFF9 not used Bank2 /RAM block storage area (blocks 2,3,8.. I 26) Unused but reserved On llc only: $800..$87F is serial input buffer $880..$8FF is keyboard buffer Video RAM (80-column mode) Part of /RAM device driver used by /RAM device driver Two of these are relatively easy to track down: One is used for the volume directory (block 2) and another for the volume bit map (block 3). There is no room in auxiliary memory for the other seven blocks (0, 1, ~7, and 127) because space must be reserved to support the /RAM disk driver itself ($000~$03FF), the 80-column text screen ($0400-$07FF), the keyboard and serial input buffers on the Apple lIe ($080~$08FF), and the auxiliary memory interrupt vectors ($FFFA-$FFFF). Thus these seven blocks are marked as "in use" in the 111AM volume bit map. The areas of auxiliary memory that the 111AM volume or its driver does not use are as follows: 302 Disk Devices $0~$3B, $4~$FF $090~$0BFF $FE00-$FFF9 Despite the apparent availability of these areas, they should be considered reserved for future use by later versions of ProDOS 8 and must not be used by nonsystem software. The first 8K of memory allocated for use by files stored in 1RAM maps to locations $200~$3FFF in auxiliary memory. This same space is used whenever you activate page 1 of the double-width high-resolution graphics display mode available on the IIcs, IIc, or lIe. If you are going to use this graphics mode while 111AM is active, you must first prevent any meaningful program from being stored at these locations. The easiest way to do this is to ensure that the first file saved to 111AM is a dummy file exactly 8K bytes long. You can do this by entering the following command from Applesoft command mode: BSAVE /RAM/OUMMY,A$2000,E$3FFF The second 8K area used to store files in 111AM is mapped to locations $4000-$5FFF, the same area used as the second page of double-width high-resolution graphics. You can protect this page by saving another dummy file that is 8K long. Removing and Reinstalling 111AM You may want your application to use the auxiliary memory area for purposes other than as a convenient file-storage device. Other common uses for auxiliary memory are as a data buffer for a printer spooler or as an input buffer for a communications program. But before you start overwriting the BAM volume with such data, you must remove the 111AM volume from the system in an orderly manner. If you don't, the system could crash when ProDOS 8 tries to interpret what you've written to auxiliary memory as directory, bit map, or file information. It's actually quite simple to remove the 111AM device from the system. 1. Examine MACHID ($BF98) to see if you're running in a 128K system. (Bits 4 and 5 of MACHID will both be 1 if you are.) 111AM can exist in only a 128K system. 2. Check that 111AM has not already been removed by locating the $BF device code (slot 3, drive 2) among the active entries in the DEVLST table. You should also check for any entry of the form $BX, where X = $3, $7, or $B; by convention, these slot 3, drive 2 devices, though not equivalent to 111AM, will also use the first bank of auxiliary memory. (Cards such as RamWorks III and MultiRAM have several banks of auxiliary memory available.) The actual $BX byte stored in DE""L8T must be saved if you later want to reinstall the 111AM device. The ProDOS 8 RAMdisk: The 111AM Volume 303 3. Remove the $BX entry from the DE"'LST table by moving higher-addressed active entries down one position (starting with the lowest-addressed one). 4. Replace the slot 3, drive 2 entry in the device vector table (at $BF2~$BF27) with the address stored at the slot 0, drive 1 entry (at $BF1~$BF11). (This will be the address of the subroutine that generates a "no device connected" error condition.) The original slot 3, drive 2 entry must be saved if you later want to reinstall the 1RAM device. 5. Decrement DEVCNT ($BF31). 6. Make an ON - LINE call with unit - num set to $B0. This frees up an internal buffer so that you can have more disk volumes active at once. After you perform these steps, the 1RAM device disappears from ProDOS 8, and auxiliary memory can be safely used for other purposes. When your application ends, it should reinstall 111AM. Do this by performing the following steps: 1. As a precaution, veri~ that you have not already reinstalled 111AM by checlcing for a slot 3, drive 2 device code in DEVLST. 2. Restore the original slot 3, drive 2 device vector that you saved before 111AM was disconnected. 3. Move each active entry in DEVLST to the next higher memory location (starting with the highest-addressed entry), and then store the 111AM device code (that you saved before 111AM was disconnected) at the first entry in the list (at $BF32). 4. Increment DEVCNT ($BF31). 5. Initialize the volume directory and volume bit map of the 111AM device by setting up the disk driver parameters for a format request ($42 = 3, $43 = $B0, $4~ $45 = 512-byte buffer address) and then calling the disk driver. Since the 111AM device driver resides in bank 1 of bank-switched RAM, you must enable that bank by reading $C08B twice in succession before making the call. When the call ends, reenable the Applesoft and motherboard ROMs by reading $C082. Here is a subroutine that performs all these chores: LDA #3 Format code STA $42 LDA #$B0 ;Unit number code STA $43 LDA $73 ;Set buffer address STA $44 ; to HIMEM LDA $74 STA $45 LDA $C08B Read/write enable bank1 304 Disk DevicesLDA $C08B ; (where the driver is) JSR T0RAM LDA $C082 ;Reenable Applesoft R0Ms RTS T0RAM JMP ($BF26) ;Call the /RAM driver After you reinstall 111AM like this, it is once again available for use as a file-storage device. WRITING A PRODOS 8 DISK DRIVER The best way to learn about disk drivers and how ProDOS 8 installs them is to actually write one. In this section, we do just that by creating a driver for an 8K version of 111AM called 1RAM8. It is suitable for use in an Applesoft programming environment and can be used by all ProDOS 8 users (unlike 111AM, which is not available to Apple II Plus users). The BAMdisk driver itself resides in page three, and the "disk" storage space it uses is located from $0800 to $27FF. We ensure that Applesoft programs do not conflict with the RAMdisk storage space by setting the Applesoft start-of-program pointer at $67-$68 to $2801 and then initializing the other Applesoft pointers and data areas by executing a NEW command. Before we begin to create the disk driver, let's outline the steps to follow to remedy the Applesoft conflict, bind the driver into ProDOS 8, and then initialize the RAM-- disk. This is really a five-step process. The first step in the procedure is to adjust the Applesoft pointers so that when you enter or load BASIC programs, they will not overwrite the 111AM8 volume: LDA #$01 ;Starting address (low) STA $67 ;Program pointer (low) IDA #$28 Starting address (high) STA $68 ;Program pointer (high) LDA #0 STA $2800 JSR $0648 ;Applesoft NEW command (Applesofi insists that the byte preceding the start of the program, $2800, be set to $00.) Second, a slot and drive number for our new device must be selected. This is most easily done by examining the DEVLST table to see what combinations are already in use and picking one that isn't. I,et's assume that slot 3, drive 1 is available. We then must store $30 in the DEVLST table (this is the code for a slot 3, drive 1 device; see Figure 7-1) and increment DEVCNT. Here's the code to do it: 10A #$30 INC DEVCNT 1DY DEVCNT STA OEV1ST,Y ;DEV1ST code for slot 3, drive 1 ;Adding one device ;DEVCNT now points to next available position in DEVLST ;Stuff device code in OEY1ST Writing a Pro DOS 8 Disk Driver 305 The next step is to install the address of the disk driver in the disk driver vector (low-order byte first). The address of the slot 3, drive 1 entry in this table is Here s how to store the address: LOA #RAMOISK STA $8F17 ;Get low-order address byte ;Get high-order address byte BAMDISK is the address of the disk driver that performs the 1/0 operations. (We what it looks like in a moment.) Finally, we must initialize the volume directory block and the volume bit map. before we can do this, we must know three things: z The number of directory blocks z The block number of the volume bit map block z The number of blocks on the volume Since it's unlikely we'll be saving very many files in the 8K 111AM8 volume, we can some space by using just one directory block (instead of the four used on standard This block must be located at block 2 to conform to ProDOS conventions. The volume bit map block will be stored at block 3, leaving a total of 14 blocks- for file storage. To keep the file storage area contiguous, we assign these blocks numbers 4 through 17 and mark blocks 0 and 1 as in use in the volume bit map. (We can't use block 0 for file storage anyway since ProDOS uses a zero entry in a file index block as a placeholder for a sparse file.) This means ProDOS will think the volume size is 18 blocks (instead of 16), but that will not matter since the two extra blocks will not be available for file storage. Since a 1 bit in the volume bit map indicates a block is free, the volume bit map block must begin with a $0F byte (blocks ~ in use, blocks ~7 free), followed by an $FF byte (blocks 8-15 free) and a $C0 byte (blocks 16 and 17 free). The remaining bytes in the block will never be used but should be set to zero. With this background information, it is relatively simple to initialize 111AM8. The first step is to prepare an image of thc volume directory block and then use the WRITE - BLOCK command to write it to block 2. (You may want to review Chapter 2 for a description of the structure of such a block.) Every byte in the block will be zero except the following: $04 storage type code and name length ($F4) $O5-$08 ASCII string for "RAM8" ($52 $41 $40 $38) $22 access code ($C3) $23 entry length ($27) $24 entries per block ($00) 306 Disk Devices $27-$28 block number for volume bit map ($0003) $29-$2A number of blocks on volume ($0012) Since the directory links (at $00-$01 and $02-$03 in the block) are both zero, this will be the only block that ProDOS examines for files in the volume directory. The final step in the initialization procedure is to write an image of the volume bit map to block 3. Now all we have to do is write the special 111AM8 disk driver. Before we begin, we must decide what memory locations will be used to hold each block in the volume. A convenient mapping scheme to use is as follows: block 2 --> $8OO-$9FF block 3 --> $AOO-$BFF block 4 --> $COO-$OFF block 17 --> $26OO-$27FF (The driver returns an error code if a block number greater than 17 is requested.) With this scheme in place, the page number for a given block is equal to twice the block number plus 4. This number can be easily calculated by the driver subroutine. (To simplily the driver, we also assign block 0 to $40~$5FF and block 1 to $60~$7FF even though these blocks are never used.) As we saw earlier in this chapter, when the disk driver takes control, certain parame- ters are set up in zero page by the calling program. One of these parameters is a command code that indicates what type of operation is to be performed: read, write, check status, or format. To save space, our driver won't include the formatting code, so we ignore all format requests. Status requests will also be ignored because such requests are meaning- less in the context of a RAMdisk. Here's what the driver will look like: (required by ProDOS 8) Save zero page locations ;Check block number (high) ;Error if not zero Check block number (low) Is it out of bounds? It's >=18, so error Multiply block by 2 .... and add 4 to get ;starting page of block CLD LDA $6 STA ZPSAVE IDA $7 STA ZPSAVE+1 LDA $47 BNE IOERROR LDA $46 CMP #18 BCS IOERROR ASL CLC ADC #4 STA $7 Writing a ProDOS 8 Disk Driver 307 LDA #0 STA $6 LDA $42 ;Get command code CMP #3 ;Format? BEQ EXIT Yes, so exit normally CMP #0 ;Check status? BEQ EXIT Yes, so exit normally CMP #1 ;Read? BEQ READ Yes, so branch CMP #2 ;Write? BEQ WRITE Yes, so branch EXIT CLC ;CLC ==> no error LDA #0 EXIT1 PHP PHA LDA ZPTEMP ;Restore zero page locations STA $6 LDA ZPTEMP~1 STA $7 PLA Restore error code PLP ;Restore carry status RTS IOERROR SEC ;SEC ==> error occurred LDA #$27 1/0 ERROR code READ WRITE ZPTEMP BNE EXIT1 (always taken) ["read" -,'ubroutine] JMP EXIT ["write" subroutine] JMP EXIT DS 2 ;Temporary storage space Note that the driver must begin with the CLD instruction that ProDOS 8 checks to s~ a valid driver is installed. The first part of the driver saves the contents of two zero locations we're going to overwrite and then checks whether the requested block (stored at $4~$47) is within the allowable range. If it isn't, the driver ends with the flag set and the error code for "I/O error" ($27) in the accumulator. The next part simply calculates the address of the requested block and stores it two consecutive zero page locations ($~$7) so that the driver can access the block data using the 6502 indirect indexed addressing mode. The bodies of the READ and WRITE subroutines are both very simple to '+ The READ code is responsible for moving the block of data from the address - 308 Disk Devices calculated to the address specified by the caller. (This address is stored at $4~$45.) The WRITE code performs just the opposite transfer. Here are the two subroutines that will do the trick: READ LDY #0 RI LDA ($6),Y Get block data STA ($44),Y ; and move it to caller's buffer INY BNE RI Branch until 256 bytes done INC $6 Move to second half INC $44 R2 LDA ($6),Y Get block data STA ($44),Y ; and move it to caller's buffer INY BNE R2 WRITE WI W2 Branch until 256 bytes done DEC $44 JMP EXIT LDY #0 LDA ($44),Y ;Get data from caller's buffer STA ($6),Y ; and move it to "disk" block INY BNE RI Branch until 256 bytes done INC $44 ;Move to second half INC $6 LDA ($44),Y ;Get data from caller's buffer STA ($6),Y ; and move it to "disk" block INY BNE R2 Branch until 256 bytes done DEC $44 JMP EXIT As you can see, an 1/0 operation is simply the movement of a 512-byte block of data from one area of memory to another. Table 7-3 shows the complete source listing for a slightly embellished form of this driver. One additional feature it includes is the marking of pages 3 and ~7 as "in use" in the system bit map in the ProDOS 8 global page to prevent the 111AM8 volume from being overwritten. Any attempt to load a file into these areas (using BLOAD or BRUN) results in a "no buffers available" error. Use the BRUN command to install the driver program, and then prove to yourself that it exists by entering the command: CATALOG /RAM8 (or CATAL0G,S3,DI) You should see a standard CATALOG listing followed by an indication that there are 14 blocks free and 4 blocks used, as expected. You can now save files to 111AM8 as you would to any other volume. Writing a ProDOS 8 Disk Driver 309 Table 7-3 The 111AM8 disk driver program 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 2000: A0 00 34 2002: 89 F4 20 35 2005: 99 00 03 36 2008: C8 37 2009: CO 7C 38 2008: 00 F5 39 40 41 42 43 44 45 46 2000: AD 58 BF 47 2010: 09 10 48 2012: 80 58 BF 49 2015: A9 FF 50 310 Disk Devices * ProDOS RAMdisk disk driver * * This driver controls a 8K RAMdisk * volume called /RAM8. * * Copyright 1985-1988 Gary B. Little * Last modified: August 26, 1988 RAMPTR EQU $6 Pointer to RAMdisk block COMMAND EQU $42 Command code BUFFER EQU $44 Buffer address BLOCK EQU $46 Block number TXTTAB EQU $67 ;Applesoft program pointer INITBLK EQU $3000 ;Block buffer MLI EQU $BF00 ;MLI interface DEVADR01 EQU $BF10 ;Start of disk driver table DEVCNT EQU $BF31 ;# of disk devices (minus 1) DEVLST EQU $BF32 ;Table of slot, drive for disks BITMAP EQU $BF58 0RG $2000 ;Start of system bit map * Move device driver code into place: LDY #0 M0VEC0DE LDA BEGIN,Y STA RAMDISK,Y INY CPY #END-RAMDISK BNE M0VEC0DE * Mark pages 3, 8. .27 as "in use" * in the system bit map. This * prevents /RAM8 or its driver * from being overwritten by BL0AD.* LDA BITMAP 0RA #$10 Block 3 bit = 1 STA BITMAP LDA #$FF Table 7-3 Continued 2017: 80 59 BF 51 STA BITMAP+1 ;Blocks 8. .15 201A: 8D 5A BF 52 STA BITMAP+2 ;Blocks 16. .23 2010: AD 5B BF 53 LDA BITMAP+3 2020: 09 F0 54 0RA #$F0 2022: 80 5B BF 55 STA BITMAP+3 56 2025: AD C7 20 57 LOA SLFAKE 2028:0A 58 ASL 2029:0A 59 ASL 202A: 0A 60 ASL 202B: 0A 61 ASL 202C: AC C8 20 62 LDY OFAKE 202F: CO 01 63 CPY #1 2031: F0 02 64 BEQ SETOS 2033: 09 80 65 ORA #$80 2035: 80 OF 20 66 SETDS STA NEWORSL 67 68 Check for existing device: ;Block 24. .27 bits = 0 ;Multiply slot by 16 Drive 1? ;Yes, so branch ;Set bit 7 ("drive 2" bit) 2038: AC 31 BF 69 LOY DEVCNT 203B: B9 32 BF 70 DUPCHECK LOA DEVLST,Y ;Get existing slot, drive 203E: CO OF 20 71 CMP NEWDRSL ;Same as RAMdisk slot, drive? 2041: DO 01 72 BNE OC1 73 2043: 00 74 BRK ;Crash if duplicate found 75 2044:88 76 OC1 OEY 2045: 10 F4 77 BPL OUPCHECK ;No, so on to next device 78 2047: EE 31 BF 79 INC DEVCNT ;Add "disk" drive 204A: AC 31 BF 80 LOY DEVCNT 2040: AD OF 20 81 LOA NEWDRSL 2050: 99 32 BF 82 STA DEVLST,Y ;Save slot, drive code 83 2053: AD C7 20 84 LDA SLFAKE ;Get slot # 2056: 0A 85 ASL ;x2 to step into table 2057: AC C8 20 86 LDY OFAKE 205A: CO 01 87 CPY #1 Drive 1? 205C: F0 03 88 BEQ FIXTABLE Yes, so branch 89 205E: 18 90 CLC 205F: 69 10 91 ADC #16 Offset to drive 2 table 92 2061: A8 93 FIXTABLE TAY 2062: A9 00 94 LOA #RAMDISK 2069: 99 11 BF 97 STA DEVAOR01+1,Y 98 99 ************************ Writing a ProDOS 8 Disk Driver 311 Table 7-3 Continued 101 and initialize program space. 102 ************************************ 206C: A9 01 103 LDA #1 206E: 85 67 104 STA TXTTAB 2070: A9 28 105 LDA #$28 2072: 85 68 106 STA TXTTAB+1 2074: A9 00 107 LDA #0 2076: 8D 00 28 108 STA $2800 ;Must begin with $00 byte 2079: 20 4B D6 109 JSR $D64B ;Applesoft "NEW" command 110 111 **~~~~~~~~~~~~~~~~~~~~~~~~ 112 Initialize the RAMdisk 113 ~~~~~~~~~~~~~~~~~~~~~~~~~~ 207C: 20 E4 20 114 JSR ZER0BLK 115 207F: A0 00 116 LDY #0 2081: B9 C9 20 117 D0NAME LDA Y0LNAME,Y 2084: F0 06 118 BEQ SETLEN 2086: 99 05 30 119 STA INITBLK+5,Y ;Put volume name in buffer 2089: C8 120 INY 208A: DO F5 121 BNE DONAME 122 208C: 98 123 SETLEN TYA 208D: 09 FO 124 0RA #$F0 ;Set "directory" bits 208F: BD 04 30 125 STA INITBLK~4 ;Save file type ~ name length 126 127 Store misc. volume parameters: 2092: AO 22 128 LDY #$22 2094: B9 AC 20 129 DOPARMS LDA INITPARM-$22,Y 2097: 99 00 30 130 STA INITBLK,Y 209A: C8 131 INY 209B: CO 2B 132 CPY #$2B 2O9D: DO F5 133 BNE DOPARMS 134 2O9F: A9 02 135 LDA #2 2OA1: 80 E2 20 136 STA BLKNUM ;Writing to block 2 2OA4: A9 00 137 LDA #0 20A6: 80 E3 20 138 STA BLKNUM+1 20A9: 20 07 20 139 JSR DOWRITE 140 141 *~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 142 Fix up the volume bit map 143 ******~~~~~~~~~~~~~~~~~~~~~~~ 2OAC: 20 E4 20 144 JSR ZEROBLK 2OAF: A9 OF 145 LDA #$OF 0. .3 in use / 4..7 free 2OB1: 80 00 30 146 STA INITBLK 2OB4: A9 FF 147 LDA #$FF ;8..15 free 20B6: 80 01 30 148 STA INITBLK+1 312 Disk Devices Table 7-3 Continued 20B9: A9 CO 149 LDA #$C0 20BB: 8D 02 30 150 STA INITBLK+2 151 20BE: EE E2 20 152 INC BLKNUM 20C1: 20 D7 20 153 JSR DOWRITE 154 20C4: 4C 00 03 155 JMP $3D0 156 20C7: 03 157 SLFAKE DFB 3 20C8: 01 158 DFAKE DFB 1 159 20C9: 52 41 40 160 VOLNAME ASC 'RAM8',00 20CC: 38 00 161 20CE: C3 162 INITPARM DFB $C3 20CF: 27 163 DFB $27 2000:00 164 DFB 13 2001: 00 00 165 OW 0 2003: 03 00 166 OW 3 2005: 12 00 167 OW 18 168 ;16, 17 free ;Change to block 3 ;Reconnect ProD0S hooks ;RAMdisk slot ;RAMdisk drive Volume name ;Access code Entry length ;Entries/block ;File count ;Block for bit map Total blocks 169 ******************************* 170 * Write a block to the device 171 ******************************* 2007: 20 00 BF 172 DOWRITE JSR MLI 200A: 81 173 DFB $81 WRITE_BLOCK command 20DB: DE 20 174 0A CMDLIST 2000:60 175 RTS 176 200E: 03 177 CMDLIST DFB 3 200F: 00 178 NEWDRSL DS 1 Drive and slot 20E0: 00 30 179 0A INITBLK 1/0 buffer 20E2: 00 00 180 BLKNUM Din 0 ;Block # gets filled in here 181 182 *** 183 Zero the block 184 ****************** 20E4: A9 00 185 ZEROBLK LDA #0 20E6: A8 186 TAY 20E7: 99 00 30 187 ZB1 STA INITBLK,Y 20EA: C8 188 INY 20EB: DO FA 189 BNE ZB1 2OE0: 99 00 31 190 ZB2 STA INITBLK+256,Y 2OF0: C8 191 INY 2OF1: 00 FA 192 BNE ZB2 2OF3: 60 193 RTS 194 195 BEGIN EQU 196 Writing a ProDOS 8 Disk Driver 313 Table 7-3 Continued 199 for the /RAM8 volume. 200 **~~~*~*~~*~~*~~~~~~~~~~~~~~~ 201 202 ORG $300 203 204 RAMDISK EQU 205 0300: D8 206 CLD ;(Required by ProDOS) 207 208 * Save zero page locations: 0301: AS 06 209 LDA RAMPTR 0303: 8D 7A 03 210 STA ZPTEMP 0306: AS 07 211 LDA RAMPTR+1 0308: 8D 7B 03 212 STA ZPTEMP+1 213 214 *********************~~~~~*~*** 215 Check for block range error 216 ****************~~~*~*~~~~~*~~~ 030B: AS 47 217 LDA BL0CK~1 ;Check block number (high) 0300: DO 34 218 BNE IOERROR ;Error if not zero 030F: AS 46 219 LDA BLOCK ;Check block number (low) 0311: C9 12 220 CMP #18 Is it out of bounds? 0313: B0 2E 221 BCS I0ERROR It's >=18, so error 222 223 ********************************** 224 Convert block # to RAM address 225 *****************~*~***~~~~~~~~~~~ 0315: 0A 226 ASL Multiply block by 2 0316:18 227 CLC 0317: 69 04 228 ADC #4 ;... and add 4 to get 0319: 85 07 229 STA RAMPTR+1 starting page of block 031B: A9 00 230 LDA #0 0310: 85 06 231 STA RAMPTR 232 233 ~**~~~~~~~~~~~~~~~~~~~ 234 Check command code 235 031F: AS 42 236 LDA COMMAND 0321: C9 03 237 CMP #3 0323: FO OC 238 BEQ EXIT 0325: C9 00 239 CMP #0 0327: FO 08 240 BEQ EXIT 0329: C9 01 241 CMP #1 032B: F0 1B 242 BEQ READ 0320: C9 02 243 CMP #2 032F: F0 30 244 BEQ WRITE 245 314 Disk Devices Get command code ;Format? ;Yes, so exit normally ;Check status? ;Yes, so exit normally ;Read? ;Yes, so branch Write? Yes, so write Table 7-3 Continued 0331: 18 246 EXIT CLC 0332: A9 00 247 LDA #0 0334:08 248 EXIT1 PHP 0335:48 249 PHA 0336: AD 7A 03 250 LDA ZPTEMP 0339: 85 06 251 STA RAMPTR 033B: AD 7B 03 252 LDA ZPTEMP+1 033E: 85 07 253 STA RAMPTR+1 0340:68 254 PLA 0341:28 255 PLP 0342:60 256 RTS 257 0343:38 258 I0ERR0R SEC 0344: A9 27 259 LDA #$27 0346: DO EC 260 BNE EXIT1 261 ;CLC ==> no error ;Restore error code ;Restore carry status ;SEC ==> error occurred ;I/0 ERROR code ;(always taken) 262 ****************************** 263 Perform READ command by 264 transferring data from the 265 * RAM to the data buffer. 266 ****************************** 0348: A0 00 267 READ LDY #0 034A: B1 06 268 FR0MCARD LDA (RAMPTR),Y 034C: 91 44 269 STA (BUFFER),Y 034E: C8 270 INY 034F: DO F9 271 BNE FR0MCARD 0351: E6 07 272 INC RAMPTR+1 0353: E6 45 273 INC BUFFER+1 0355: B1 06 274 FC1 LDA (RAMPTR),Y 0357: 91 44 275 STA (BUFFER),Y 0359: C8 276 INY 035A: DO F9 277 BNE FC1 035C: C6 45 278 DEC BUFFER+1 035E: 4C 31 03 279 JMP EXIT 280 281 *********~*~****~~~~~~~~~~~~~~ 282 Perform WRITE command by 283 transferring data from the 284 * data buffer to the RAMcard.* 285 *****~~~****~~~~~~~~~~~~~~~~~~ 0361: A0 00 286 WRITE LDY #0 0363: B1 44 287 T0CARD LDA (BUFFER),Y 0365: 91 06 288 STA (RAMPTR),Y 0367: C8 289 INY 0368: DO F9 290 BNE T0CARD 036A: E6 45 291 INC BUFFER+1 036C: E6 07 292 INC RAMPTR+1 036E: B1 44 293 TC1 LDA (BUFFER),Y 0370: 91 06 294 STA (RAMPTR),Y Writing a ProDOS 8 Disk Driver 315 Table 7-3 Continued 0375: C6 45 297 DEC BUFFER+1 0377: 4C 31 03 298 JMP EXIT 299 037A: 00 00 300 ZPTEMP DS 2 301 302 END EQU When you use the /RAM8 disk driver, be careful not to run any graphics prograrns that use the primary high-resolution graphics screen. The video RAM buffer this screen uses ($2000-$3FFF) overlaps the /RAM8 block storage area. Moreover, the Applesofr program must not overwrite the device driver in page 3, or the storage space itself, with POKE statements. If you want to avoid these memory conflicts, you can relocate the disk driver (and its corresponding storage space) to an area above HIMEM and the BASIC.SYSTEM general-purpose file buffer using the techniques described in Chapter 5. You can remove the /RAM8 device from the system using the technique described above for the removal of the /RAM volume. You will also have to clear the appropriate bits in the system bit map, reset the Applesoft program pointer to $801, and execute an Applesoft NEW command to initialize other important Applesoft data pointers. 316 Disk Devices In Chapter 2, we saw that the directory entry for each file on a disk formatted for the ProDOS file system contains 4 bytes for the time and date the file was created and 4 more bytes for the time and date it was last modified. Most other file systems save similar time and date information. The ProDOS file system's date-stamping feature is very useful, especially for those who routinely save several versions of the same file on different disks. Three months later you won't have to guess which one is the latest version; all you have to do is compare modification dates. The BASIC.SYSTEM CATALOG command displays these dates when it lists the names of the files on disk. GS/OS and ProDOS 8 determine the current time and date by accessing a real-time clock/calendar chip interfaced to the microprocessor. On the IIcs, this chip is an integral part of the system and does not occupy a slot or port; on the lIe and II Plus, you must add an optional clock card. There are also clocks available for the slotless IIc. A computer clock contains special integrated circuits that allow it to keep track of the current time and date independently of the microprocessor. It is the Apple's digital watch, if you like. Clocks keep the correct time even when the Apple is turned off because they are powered by batteries. ProDOS 8 uses a special assembly-language program, called a clock driver, to transfer the time and date from the card to the Apple in an understandable 'form. ProDOS 8 comes with internal clock drivers for the built-in II&s clock and for any clock card that understands a standard set of time-related commands originally used in Thunderware's Thunderclock. ProDOS 8 automatically installs the correct driver into the system when it first boots up. If there is no recognizable clock, ProDOS 8 installs a null driver, and application programs should ask the user to enter the correct time and date if that information is needed. GS/OS always installs a driver for the built-in clock on the Apple IIcs. In this chapter, we examine how ProDOS 8 deals with time issues in general. In particular, we see how it detects the presence of a clock card, how it installs the clock driver, and how to design and install your own ProDOS 8 clock driver for a nonstand- ard clock. (Since GS/OS has a built-in driver for the IIcs clock, you will never have to install your own driver; therefore GS/OS has no mechanism for installing custom clock 317 drivers.) We also go through some useful examples of how to make the time and date capabilities of GS/OS and ProDOS 8. HOW GS/OS AND PRODOS BREAD THE TIME AND DATE Whenever ProDOS 8 needs to know the time and date it always makes the JSR DATETIME. The code starting at DATETIME ($BF06) is either a 1 instruction (if no ProDOS-compatible clock is in the system) or a 3-byte JMP tion that passes control to a ProDOS 8 clock driver (if a compatible clock is In either case, the 2 bytes at $BF07-$BF08 always hold the address of the start ProDOS 8 clock driver space. The clock driver reads the time and date from the clock and stores the - special format at TIME ($BF92-$BF93) and DATE ($BF9~$BF91) in the global page. Figure 8-1 describes the format used. If no clock driver is present, and DATE are not modified because the RTS instruction stored at DAi ($BF06) immediately bounces control back to the caller. The only way to set the and date in this situation is to write directly to the TIME and DATE locations. The approved method of determining the date and time in a ProDOS 8 is to use the GET-TIME command. Recall from Chapter 4 that you can do executing a subroutine like this one: JSR $BF00 Make a call to the MLI DFB $82 ;GET_TIME 0A $0000 Dummy parameter table RTS When this subroutine finishes, the TIME and DATE locations contain the cur time and date in the format described above. GS/OS has no equivalent operating system command for returning the current and date. If you want the time and date, you must use two commands in the - IIcs Miscellaneous Tool Set: ReadTimeHex and ReadAsciiTime. ReadTimeHex (toolbox command $0D03) returns the current time and date eters as binary numbers. Here's how to call it from 65816 full native mode: PHA ;Space for results PHA (eight bytes) PHA PHA LDX #$0D03 ;ReadTimeHex JSL $E10000 PLA WeekDay (high) PLA ;Month (high), Day (low) PLA ;CurYear (high), Hour (low) PLA ;Minute (high), Second (low) 318 Clocks Figure 8-1 The formats of the ProDOS 8 DATE and TIME bytes (a) DATE ($BF9~$BF91) 7 6 5 4 3 2 1 0 Y6 Y5 Y4 Y3 Y2 Y1 YO M3 $BF91 7 6 5 4 3 2 1 0 M2 M1 MO D4 D3 02 01 DO $BF90 The year is encoded as Y6 Y5 Y4 Y3 Y2 Y1 YO (bits 1-7 of the high-order byte). Only the last two digits of the year are stored (that is, 89 for 1989). The month is encoded as M3 M2 M 1 MO (bits 5-7 of the low-order byte and bit 0 of the high-order byte). January is month 1, and December is month 12. The day of the month is encoded as D4 D3 D2 D1 DO (bits 0=1 of the low-order byte). For example, November 30, 1989, would be stored as follows: High-order byte Low-order byte 1 0 1 1 0 0 1 1 0 1 1 1 1 1 1 0 YYYYYYYYYYYYY M MMMMM DDDDDDDDD Year ($59) Month ($0B) Day ($1E) 1989 November 30 (b)TIME ($BF92-$BF93) 7 6 5 4 3 2 1 0 0 0 O H4 H3 H2 HI HO $BF93 7 6 5 4 3 2 1 0 O 0 MS M4 M3 M2 M1 MO $BF92 The hour is encoded as H4 H3 H2 HI HO (bits 0=1 of the high-order byte). The hour is stored in military (24-hour) format. The minute is encoded as MS M4 M3 M2 M1 MO (bits ~ of the low-order byte). For example, 9:20 p.m. (21:20) would be stored as follows: High-order byte 0 0 0 1 0 1 0 1 HHHHHHHHH Hours ($15) 21 Low-order byte 0 0 0 1 0 1 0 0 Minutes ($14) 20 How GS/CS and ProDOS 8 Read the Time and Date 319 The values ReadTimeHex returns are as follows: WeekDay 1..7 1 = Sunday, 2 = Monday, and so on Month 0. .11 0 = January, 1 = February, and so on Day 0. .30 day of month minus 1 CurYear 0. .99 current year minus 1900 Hour 0. .23 hour in military format Minute 0. .59 Second 0. .59 ReadAsciiTime (toolbox command $0F03) returns a 20-byte ASCII-encoded string describing the current time and date. Here is how to call it: PushPtr TimeString ;Pointer to string area LDX #$0F03 ;ReadAsciiTime JSL $E10000 RTS TimeString DS 20 ;Space for time string Note that the time string returned is not preceded by a length byte. The always exactly 20 bytes long, and the high-order bit of each byte is set to 1. The format of the time string depends on the settings of the date and time in the Control Panel. There are six possibilities: mm/dd/yy HH:MM:SS XM dd/mm/yy HH:MM:SS XM yy/mm/dd HH:MM:SS XM mm/dd/yy HH:MM:SS dd/mm/yy HH:MM:SS yy/mm/dd HH:MM:SS XM = AM or PM 24-hour military format The first format listed here is the Control Panel's default. HOW PRODOS 8 IDENTIFIES A CLOCK CARD When you first boot ProDOS 8 on a system other than the IIcs, ProDOS 8 examines each peripheral expansion slot in the system for a standard clock card. ProDOS 8 identifies such a card by the following unique pattern of bytes in the card's dedicated $Cn00-$CnFF ROM space (n is the slot number): $Cn00 $08 $Cn02 $28 $Cn04 $58 $Cn06 $70. 320 ClocksIf it finds a clock card, ProDOS 8 installs its standard clock driver and changes the RTS opcode ($60) at $BF06 to a JMP opcode ($4C). Since the 2 bytes following this opcode contain the address of the clock driver space (low-order byte first), the driver takes control whenever a program executes a JSR $BF06 instruction. Actually, a program should always use the GET-TIME command to read the time and date; the GETTIME command handler is what calls the clock driver directly. The built-in IIcs clock does not occupy a slot or port, so ProDOS 8 can't identify it by checking bytes in BOM. Instead, it simply checks to see what Apple II model it is running on; if it's a IIcs, it installs the IIcs clock driver. ProDOS 8 also sets the clock bit (bit 0) of the machine identification byte, MACHID ($BF98), to 1 if it finds a clock. WRITING AND INSTALLING A PRODOS 8 CLOCK DRIVER If you are using a nonstandard clock, you must write and install your own ProDOS 8 clock driver. Two examples of nonstandard clocks are a clock interfaced through the serial port of a IIc and a clock on a multifunction peripheral card that does not occupy a phantom slot. Writing a clock driver is no easy feat since it requires detailed information concern- ing how the clock circuitry is interfaced to the Apple and the procedure a programmer must follow to extract time and date information from the card. If you re lucky, the manufacturer of the card will have a detailed technical reference manual that contains this information. But more commonly you will have to beg, borrow, or steal this information before you can get started. Happily, manufacturers of nonstandard clock cards have already written their own ProDOS 8 clock drivers and include them on disk with their hardware. The general characteristics of a clock driver are: z It must start with a CLD instruction. z It must read the time and date from the clock card and store the results in the proper format in the global page TIME ($BF92-$BF93) and DATE ($BF90- $BF91) locations. Once you write a driver, you must move it to an area of memory that other programs will not use. The best available area is the one the very clock driver you are replacing uses; you can always find the starting address of this area at $BF07-$BF08 (low-order byte first). If you choose to use the standard driver area (and we do recommend this selection), keep several important considerations in mind: z Never assume the standard clock driver will reside at the same position in every version of ProDOS 8. To ensure your driver will run properly at any address that might be stored at $BF07-$BF08, you should avoid using JMP and Writing and Installing a Pro DOS 8 Clock Driver 321 JSB instructions or storing data within the main body of the driver. If you the code will not be relocatable, and you will need to patch it to resolve - internal absolute address references after you move it to its new position. z Make sure your clock driver is no longer than 125 bytes. ProDOS 8 this amount of space for its standard drivers, and Apple has guaranteed - amount of driver space. z Before moving your clock driver into position, write-enable bank 1 of switched BAM by reading from location $C08B twice in succession. standard clock driver resides in bank-switched BAM.) After the move, the Applesoft and system monitor BOM area by reading from location $C082. The next step in the installation procedure is to set up a JMP instruction at that points to your clock driver. Do this by storing $4C (the JMP opcode) at and the address of the driver at $BF07-$BF08 (low-order byte first). If you have loaded the driver at the address of the standard clock driver, you can skip the latt~ step since the correct driver address will already be in place. Finally, you should set bit 0 of MACHID ($BF98) to 1 to indicate that a clock h~ been installed in the system. Do this by executing the following short piece of code: LDA MACHID ;Get ID byte 0RA #$01 ;Store a 1 in bit 0 STA MACHID ;Update ID byte The easiest way to install a clock driver is to make the installation program part of the STARTUP program, whichautomatically runs when ProDOS 8 executes the BASIC. SYSTEM Applesoft interpreter. TIME/DATE UTILITY PROGRAMS An Applesoft Time and Date Variable Some dialects of BASIC have a special variable called TIME$ that always contains the current time in the standard HH:MM:SS form. This variable is very useful when a program needs to display the current time, automatically time-stamp printed reports, calculate elapsed times, perform benchmarking studies, and so on. You can use the READ.TIME subroutine in Table 8-1 to return the time and date in the form DD-MM-19YY HH:MM in any Applesoft string variable you speci(y. Alter loading the subroutine, use it by executing the following statement from within an Applesofi program: CALL 768,TM$ TM$ represents the name of the variable that is to hold the time string. 322 Clocks Table 8-1 READ.TIME, a program to load the time and date into an Applesoft string variable 1 ************************************** 2 READ.TIME 3 4 This program reads the time and 5 * date and stores it in an Applesoft 6 string variable. The syntax is 7 8 CALL 768,TM$ 9 10 The TM$ string has the form 11 DD-MM-19YY HH:MM 12 13 Copyright 1985-1988 Gary B. Little 14 15 Last modified: August 28, 1988 16 17 ************************************** 18 FRET0P EQU $6F Bottom of string space 19 VARPNT EQU $83 20 21 IN EQU $200 22 23 MLI EQU $BF00 24 DATE EQU $BF90 25 TIME E0U $BF92 26 27 CHKCOM EQU $DEBE 28 PTRGET EQU $DFE3 29 GETSPACE EQU $E452 30 MOVSTR EQU $E5E2 31 32 0RG $300 33 0300: 20 00 BF 34 JSR MLI 0303:82 35 DFB $82 0304: 00 00 36 0A $0000 37 38 "Unpack" the time: 39 0306: AD 92 BF 40 LDA TIME 0309: 8D B8 03 41 STA MINUTES 030C: AD 93 BF 42 LDA TIME+1 030F: 8D B9 03 43 STA HOURS 0312: AD 90 BF 44 LDA DATE 0315: 29 IF 45 AND #$1F 0317: 80 BA 03 46 STA DAY 031A: AD 91 BF 47 LDA DATE+1 0310: 80 BC 03 48 STA YEAR ;Pointer to string data Input buffer ;Entry point to MLI ;Year + Month + Day ;Minutes + Hours ;Skip comma ;Locate a variable ;Get string space for "A" chars ;Move string to free space ;Call the MLI and select GET_TIME command (no parameter table) Get minutes and save them ;Get hours and save them ;Get "day" bits (0. .4), strip "month" bits, and store correct number ;Get "year" bits (1. .7) and month bit (0). Time/Date Utility Programs 323 Table 8-1 Continued 0326:6A 51 R0R 0327:4A 52 LSR 0328:4A 53 LSR 0329:4A 54 LSR 032A: 4A 55 LSR 032B: 80 BB 03 56 STA MONTH 57 59 032E: A2 O0 60 LDX #0 0330: 8E B7 03 61 STX TIMEPOS Clear ptr to time string 0333: 8A 62 FORMTIME TXA 0334:48 63 PHA 0335: BD BD 03 64 LDA FORMAT,X ;Get formatting byte 0338:08 65 PHP 0339: AE B7 03 66 LDX TIMEPOS 033C: 28 67 PLP 0330: 30 1E 68 BMI NOTNUM Branch if not number 033F: A8 69 TAY ;Get time code in Y 70 0340: B9 B8 03 71 LDA TIMEDATA,Y ;Get binary time/date data 0343: 20 92 03 72 JSR CONVERT ;Convert to BCD 0346: 48 73 PHA ;Save number 0347: 4A 74 LSR Move "tens" digit to 0348: 4A 75 LSR ; lower 4 bits by 0349: 4A 76 LSR 034A: 4A 77 LSR 034B: 09 30 78 0RA #$30 0340: 90 00 02 79 STA IN,X 0350: E8 80 INX 0351:68 81 PLA 0352: 29 OF 82 AND #$OF 0354: 09 30 83 ORA #$30 0356: 90 00 02 84 STA IN,X 0359: E8 85 INX 035A: 4C 63 03 86 JMP T0NEXT 87 0350: 29 7F 88 N0TNUM AND #$7F 035F: 90 00 02 89 STA IN,X 0362: E8 90 INX 0363: 8E B7 03 91 TONEXT STX TIMEP0S 0366:68 92 PLA 0367: AA 93 TAX 0368: E8 94 INX 0369: ED DC 95 CPX #12 036B: DO C6 96 BNE F0RMTIME 97 324 Clocks shifting right four times ;Convert to ASCII digit ;Get original number back ;Isolate units digit ;Convert to ASCII digit Strip high bit for Applesoft Insert punctuation Go to next position At end of template? ;No, so keep going Table 8-1 Continued 98 Move string to bottom of string space: 99 036D: AD B7 03 100 LDA TIMEP0S ;Get length of string 0370: 20 52 E4 101 JSR GETSPACE ;Make room for it 0373: A2 00 102 LDX #0 0375: A0 02 103 LDY #2 ;Y/X point to string 0377: 20 E2 E5 104 JSR M0VSTR ;Move the string (length in A) 105 106 Point Applesoft variable to time/date string. 107 The string is now positioned at the bottom 108 of string space and is pointed to by FRET0P. 109 037A: 20 BE DE 110 JSR CHKC0M ;Skip over comma 037D: 20 E3 DF 111 JSR PTRGET 0380: AD B7 03 112 LDA TIMEP0S ;Get length of string 0383: AD 00 113 LDY #0 0385: 91 83 114 STA (VARPNT),Y ;... and save it 0387: C8 115 INY 0388: AS 6F 116 LDA FRET0P 038A: 91 83 117 STA (VARPNT),Y Save address (low) 038C: C8 118 INY 0380: AS 70 119 LDA FRET0P+1 038F: 91 83 120 STA (VARPNT),Y ;Save address (high) 0391:60 121 RTS 122 123 124 Binary to BCD Conversion 125 Number must be 0...99 12 *** 0392: 80 B6 03 127 CONVERT STA TEMP 0395: 8E B5 03 128 STX XSAVE 0398: A9 00 129 LDA #0 039A: F8 130 SED 039B: A2 06 131 LDX #6 0390: 4E B6 03 132 NEXTBIT LSR TEMP 03A0: 90 04 133 BCC NOllEIGHT 03A2: 18 134 CLC 03A3: 70 AE 03 135 ADC BIMDEC,X 03A6: CA 136 NOWEIGHT DEX 03A7: 10 F4 137 BPL NEXTBIT 03A9: 08 138 CLD 03AA: AE B5 03 139 LDX XSAVE 03AD: 60 140 RTS 141 Put # into work area ;Start with a 0 result ,Use decimal arithmetic ;Examine bits 0...6 ;Move low bit into carry Branch if it was zero else add it to result ,Count down to -1 Branch if more to go ,Return to binary arithmetic 03AE: 64 32 16 142 BINDEC DFB $64,$32,$16 ;These are the weights of 03B1: 08 04 02 143 DFB $08,$04,$02 ;the low 7 bits in 03B4: 01 144 DFB $01 ;a byte (in BCD) 145 03B5: 00 146 XSAVE DS 1 Temporary X location Time/Date Utility Programs 325 Table 8-1 Continued O3B7: 00 148 TIMEPOS DS 1 149 150 TIMEDATA EQU 151 O3B8: 00 152 MINUTES DS 1 ;Minutes (0.. .59) 03B9: 00 153 HOURS DS 1 ;Hours (0. .23) 03BA: 00 154 DAY DS 1 Day of month (l...31) 03BB: 00 155 MONTH DS 1 ;Month of year (1. ..12) 03BC: OO 156 YEAR DS 1 ;Year (O...99) 157 158 Formatting template for "'DD-MM-19YY HH:MM" 159 (digits refer to entries in TIMEDATA table) 160 161 FORMAT EQU 162 03BD: 02 163 DFB 2 03BE: AD 03 AD 164 DFB "-",3,"-" 03C1: B1 B9 04 165 OFB "1","9",4 O3C4: AD AD 01 166 DFB $AO,$AO,1 O3C7: BA 00 167 DFB ":",O When you call READ.T1ME, it first uses the ProDOS 8 CETT1ME command to read the current time and date into the ProDOS 8 global page locations. It then unpacks the year, month, and day data from the DATE locations and stores each of them in its own temporary location. The hours and minutes are already unpacked, but they are also transferred to temporary locations. After unpacking, READ.TIME begins to assemble the ASCII time string in the Applesoft input buffer starting at $200. It does this by scanning a special template string that contains either ASCII characters or single-digit time codes. The ASCII characters are transferred directly to the time string. When a time code is encoun- tered, however, the corresponding time parameter is loaded, converted to a binary- coded decimal (BCD) number, and then stored as two consecutive ASCII digits in the time string. Next, READ.T1ME moves the string from the input buffer to the main Applesoft string space in the high end of memory to ensure the string will not be overwritten the next time your program executes an Applesoft INPUT statement. This is done using two Applesoft ROM subroutines called GETS PACE ($E4B2) and MOVSTB ($E5E2). When you call CETSPACE with the string length in the accumulator, it makes room for the string by lowering FRETOP ($6F-$70), the pointer to the bottom of string space, by the appropriate number of bytes. MOVSTR moves a string of length A, pointed to by Y (high) and X (low), to this free space. 326 Clocks Once the time string is in position, READ.T1ME locates the TM$ variable in the Applesoft variable table by executing the following two instructions: JSR CHKC0M JSR PTRGET CHKCOM ($DEBE) and PT11GET ($DFE3) are two more Applesofi ROM subroutines. The first instruction advances the Applesofr program pointer by 1 byte, effectively slcip- ping over the comma separating the CALL address from the variable. The second instruction stores the address of the 3-byte descriptor that defines the string variable in VARPNT ($83) and VARPNT + 1 ($84). The first byte in the descriptor is the length of the string; the next 2 bytes contain the pointer to the contents of the string. The final step is to store the new string length and pointer in the descriptor. The length (T1MEPOS) is stored in the first descriptor byte, and the pointer to the string, found at FRETOP ($6F) and FRETOP+ 1 ($70), is stored in the other 2 bytes. Setting the Time and Date on a Clockless Apple Even if you do not have a clock in your Apple II, you can still date- and time-stamp a file by explicitly storing the current date and time in the ProDOS 8 global page locations just before saving the file to disk. This is somewhat inconvenient, but it's better than nothing. If you can survive with just the correct date, life becomes much easier because you have to set the date only once when you first turn the computer on (assuming, perhaps naively, that you don't work past midnight). The TIMEDATE program in Table 8-2 lets you enter a time and date in English. After you do so, the program converts the information into the encoded format used by ProDOS 8 and then stores it in the ProDOS 8 global page locations. Time/Date Utility Programs 327 Table 8-2 TIMEDATE, a program to manually set the time and date. 3 REM DECEMBER 21, 1987 100 NOTRACE : TEXT : PRINT CHR$ (21): SPEED= 255: NORMAL : HOME 110 DIM MT$(12) 140 FOR I = 1 TO 12: READ MT$(I): NEXT 150 DATA JANUARY,FEBRUARY,MARCH,APRIL,MAY,JUNE,JULY,AUGUST, 160 165 SEPTEMBER,OCTOBER,N0VEMBER,DECEMBER PRINT "PR0D0S TIME/DATE SETTER" PRINT "COPYRIGHT 1985-1987 GARY B. LITTLE" 170 T1 = 49042: REM $BF92 (MINUTES) 180 T2 = 49043: REM $BF93 (HOURS) 190 T3 = 49040: REM $BF90 (MMMDDDDD) 200 T4 = 49041: REM $BF91 (YYYYYYYM) 400 VTAB 6: CALL - 958: INPUT "ENTER YEAR (1900-1999): 19";A$: YR = VAL (A$): IF YR < 0 OR YR > 99 OR A$ = "" THEN 400 500 VTAB 7: CALL - 958: INPUT "ENTER MONTH (JAN...DEC): ";A$:M$ = 501 IF A$ = "" THEN 500 505 FOR I = 1 TO LEN (A$): IF ASC ( MID$ (A$,I,1)) > = 96 THEN B$ = B$ + CHR$ ( ASC ( MID$ (A$,I,1)) - 32): GOTO 507 506 B$ = B$ + MID$ (A$,I,1) 507 NEXT :A$ = B$ 510 FOR I = 1 TO 12: IF A$ = MT$(I) OR A$ = LEFT$ (MT$(I),3) THEN MT = 1:1 = 12: NEXT : GOTO 600 520 NEXT : GOTO 500 600 VTAB 8: CALL - 958: INPUT "ENTER DAY OF MONTH (1-31): ";A$: DY = VAL (A$): IF DY < 1 OR DY > 31 THEN 600 720 VTAB 9: CALL - 958: INPUfi "ENTER HOUR (0-23): ";A$: HR = VAL (A$): IF HR < 0 OR HR 23 OR A$ = "" THEN 720 800 VTAB 10: CALL - 958: INPUT "ENTER MINUTES (0-59): ";A$: MN = VAL (A$): IF MN < 0 OR MN > 59 OR A$ = "" THEN 800 1000 PRINT : PRINT "PRESS ANY KEY TO SET": PRINT "THIS TIME AND DATE: ";: GET A$: PRINT A$ 1010 POKE T1,MN 1020 POKE T2,HR 1030 POKE T4,2 YR + INT (MT / 8) 1040 POKE T3,32 * (MT - 8 INT (MT / 8)) + DY 1050 HOME : PRINT "THE TIME AND DATE HAVE NOW BEEN SET." 328 Clocks