Loxone Miniserver Internals

I’ve looked into a lot of the internals of the Loxone Miniserver (the CAN bus protocols, the SD card file system, etc) and written it down with a lot of sample code at https://github.com/sarnau/Inside-The-Loxone-Miniserver. For the ambitious i’ve also provided an STM32 ARM implementation of the Loxone Link/Loxone Tree protocol for a lot of hardware at https://github.com/sarnau/LoxLink

Communication protocol of the Proteus EcoMeter TEK603

The receiver device has a USB port, which has a CP2102 USB<->RS232 converter. With 115200 baud, 8N1 it is possible to communicate with the device.

Package format

All data to or from the device is transmitted as a package.

  • 2 byte header (‘SI’ = 0x53,0x49)
  • 2 byte length of the complete package (16 bit, big-endian)
  • 1 byte command (1: data send to the device, 2: data received from the device)
  • 1 byte flags:

    • bit 0: set the clock (hour/minutes/seconds) in the device on upload
    • bit 1: force reset the device (set before an update of the device)
    • bit 2: a non-empty payload is send to the device
    • bit 3: force recalculate the device (set on upload after changing the Sensor Offset, Outlet Height or the lookup table)
    • bit 4: live data received from the device
    • bit 5: n/a
    • bit 6: n/a
    • bit 7: n/a
  • 1 byte hour – used to transmit the current time to the device

  • 1 byte minutes
  • 1 byte seconds

  • 2 byte eeprom start (16 bit, big-endian) – unused in live data

  • 2 byte eeprom end (16 bit, big-endian)

  • n bytes payload

  • 2 byte CRC16 (16 bit, big-endian)

The app never always read more than about 800 bytes of data at once, e.g. reading the history is done in 4 reads:

  • 0x0000..0x0031 Header
  • 0x0032..0x0289 Offsets
  • 0x028a..0x03e5 This 365 entry history
  • 0x03e6..0x06d0 ..continuation of the history

Live data

With an open connection and without significant change of the fluid level, the current level is send every 30/60 minutes (water/oil) to the computer. There seems to be no way to request it. Because the wireless transmitter only sends the data in that frequency, it probably makes very little sense.

If the level changes (refilling the tank) it updates much more often.

Live data has the following payload:

  • 1 byte temperature in Fahrenheit + 40. The get the temperature in Celcius you need to calculate it like this => temperatureC = ((temperatureF - 40 - 32) / 1.8)
  • 2 byte sensor level in cm (16-bit, big-endian)
  • 2 byte usable capacity in l (16-bit, big-endian)
  • 2 byte full capacity of the tank in l (16-bit, big-endian)

EEPROM content

The EEPROM has three distinct areas:

  • Header and configuration
  • tank air space table (liters of tank volume based on the level above the fluid (0..300cm))
  • 365 days of average usage – a round-robin buffer

Header and configuration

If there is no description, the value is unknown.

  • 0x00: 0xcc (204) Magic byte, always checked to test the communication with the device.
  • 0x01: 0x18 0x3D 0xff 0xca
  • 0x05: 0x03 Type of the tank A..D (0..3) The tank offset table is calculated by the device based on the setting (A-C). If the table is modified via a computer, the type is set to D (3).
  • 0x06: 0x0096 Height of the tank in cm (here: 150) tankHeight
  • 0x08: 0x0048 Width of the tank? (Probably entered during configuration)
  • 0x0a: 0x0064 Height of the tank without the rounding? (Probably entered during configuration)
  • 0x0c: 0x2710 Volume of the tank (10000l)
  • 0x0e: 0x01
  • 0x0f: 0x00

  • 0x10: 0x00

  • 0x11: 0x00 0/1 Alarm Off/On
  • 0x12: 0x01
  • 0x13: 0x002d 45 Index to todays entry in the history historyIndex. Because the history starts yesterday, you need to start with the next index for yesterday.
  • 0x15: 0x02
  • 0x16: 0x00
  • 0x17: 0x43
  • 0x18: 0x00
  • 0x19: 0x34
  • 0x1a: 0x00
  • 0x1b: 0x3a
  • 0x1c: 0x05db (16-bit, big-endian, cost of fuel: 0.00 – 15.00 currency/l)
  • 0x1e: 0x00
  • 0x1f: 0x00

  • 0x20: 0x00

  • 0x21: 0x00
  • 0x22: 0x00
  • 0x23: 0x00
  • 0x24: 0x00
  • 0x25: 0x00
  • 0x26: 0x00
  • 0x27: 0x00 Sensor Offset sensorOffset in cm (0..70cm)
  • 0x28: 0x08 Outlet Height outletHeight in cm (0..70cm)
  • 0x29: 0x1b
  • 0x2a: 0x00
  • 0x2b: 0x03
  • 0x2c: 0x1c
  • 0x2d: 0x00
  • 0x2e: 0x94
  • 0x2f: 0x00
  • 0x30: 0x00 0xDD

Tank Air Space Table tankAirSpaceTable

The distance from the bottom of the transmitter to the top of the fluid. The table has one value per cm tankHeight – up to 300cm is supported. If the distance between the top and the transmitter is 0, the capacity is the maximum value. This table is calculated by the tank type A-C automatically, but can be customized to match any shape (tank type D). It is used to convert the level in cm into liter.

The outletHeight is an offset that is added to the level to return the volume of the tank. It can be used to define a reserve.

History

The history table is 365 entries large. Each entry is 3 bytes. It is a cyclic buffer with an index pointer in the header pointing at todays position. To go into the past increment the index and wrap around at 365 till you hit today again.

The 3 bytes have this format: 0xaa 0xbc 0xcc

  • ‘aa’ changes up/down a bit, but it is also not clear what it means.
  • ‘b’ also unknown. I’ve only seen values of 1 or 2 for it.
  • ‘ccc’ is the average oil usage in 0,1l on that day (divide by 10 to get liters). 0 if there was no oil used on a day.

The level of the tank based on todays usage is estimated in liters as follows:

  usage = ccc / 10 - sensorOffset
  lowValue = tankAirTable[(int)usage]
  highValue = tankAirTable[(int)usage+1]
  usage = highValue + (lowValue - highValue) * (1 - (usage - (int)usage))
  usage = usage - tankAirTable[tankHeight - outletHeight]

Loxone MicroSD Cards

Loxone states the following on their website:

Use only Loxone SD cards for the Miniserver. All SD cards have their own CPU which manages the flash memory. For optimum performance, Loxone OS accesses many low-level functions of the SD card, unlike, for example, a digital camera.

Wow, this is exciting! What mystery low level functions are they using? Time to find out.

The server ships with a 4GB Micro SD Card, which is of the SDHC type.

SD cards exists in many different varieties, but have to follow the specifications from the SD Group. This includes the communication on the bit level, as well as how commands are executed. However, there are options to have custom commands or at least non-mandatory commands. It is also possible to only support a subset of commands for a manufacturer, if they supply specific SD cards. Loxone could do that, but it is not probable, just because it makes it really hard for them to switch vendors for their SD cards, if prices and or availability changes. You don’t want to release an update to the Miniserver for different types of SD cards.

As I already looked at the mainboard of the Miniserver, it seems the CPU is talking directly to the SD card. That is not a surprise, considering that even Arduinos can do that easily. The Miniserver does that with the help of some gate logic, probably for buffering. I didn’t really care to look into the exact implementation details too much here.

Considering that the physical communication is standardized and that the actual firmware is read from the SD card by the boot code from the flash memory, I don’t expect too much special code for reading. And I am correct: the initialization phase for the SD card seems to follow the specification from the SD Group perfectly and can detect all types of SD cards up to SDHC.

The detailed information collected during initialization about the card can be requested from Loxone Config via “Detailed Device Info”:

SD-Test: SD performance read=403kB/s write=345kB/s no error (0 0), ManufactorerID 2 Date 2016/3 CardType 2 Blocksize 512 Erase 0 MaxtransferRate 25000000 RWfactor 2 ReadSpeed 22222222Hz WriteSpeed 22222222Hz MaxReadCurrentVDDmin 3 MaxReadCurrentVDDmax 5 MaxWriteCurrentVDDmin 5 MaxWriteCurrentVDDmax 1 Usage:1.71%

What does these things mean?

  • read = measured data read performance in kB/s after 1MB of reading a test file
  • write = measured data read performance in kB/s after 1MB of writing a test file
  • no error (0 0) = number of errors with this SD card (first number/error code = 0: no error, 1: read error, 2: verify error, second number: number of errors)
  • ManufactorerID (yes, that is probably an original Austrian typo) = The manufacturer of the card. The ID is assigned by the SD Group.
  • Date = Manufacturing date of the card
  • CardType = 0 = unknown, 1 = SDv2, 2 = SDHC, 3 = SDv1 – detected during initialization
  • Blocksize = 512 bytes (I think it this is true for all cards)
  • Erase = DATA_STAT_AFTER_ERASE from the SCR register. Defines the data status after erase.
  • MaxtransferRate = TRAN_SPEED from the CSD register. Maximum data transfer rate per one data line in bit/s
  • RWfactor = R2W_FACTOR from CSD register (0 = 1, 1 = 2 (write half as fast as read) , 2 = 4, 3 = 8, 4 = 16, 5 = 32)
  • ReadSpeed = 133333333 / (2 * ((133333333 / (2 * MaxtransferRate)) + 1))
  • WriteSpeed = 133333333 / (2 * ((133333333 / (2 * MaxtransferRate)) + 1)) (always identical to ReadSpeed)
  • MaxReadCurrentVDDmin = VDD_R_CURR_MIN from CSD register (0=0.5mA; 1=1mA; 2=5mA; 3=10mA; 4=25mA; 5=35mA; 6=60mA; 7=100mA)
  • MaxReadCurrentVDDmax = VDD_R_CURR_MAX from CSD register (0=1mA; 1=5mA; 2=10mA; 3=25mA; 4=35mA; 5=45mA; 6=80mA; 7=200mA)
  • MaxWriteCurrentVDDmin = VDD_W_CURR_MIN from CSD register (0=0.5mA; 1=1mA; 2=5mA; 3=10mA; 4=25mA; 5=35mA; 6=60mA; 7=100mA)
  • MaxWriteCurrentVDDmax = VDD_W_CURR_MAX from CSD register (0=1mA; 1=5mA; 2=10mA; 3=25mA; 4=35mA; 5=45mA; 6=80mA; 7=200mA)
  • Usage = How much of the SD card is used by data

 

The maximum supported capacity is 16GB, so do not use larger ones.

All the technical information is reported from the SD card and only for information. After initialization, what else does the Miniserver do to communicate to the card?

  • Reading blocks via Block Read (CMD17)
  • Writing blocks via Block Write (CMD18)
  • Erasing blocks by simply writing empty blocks
  • Repair after errors: power cycle the SD card and reinitialize the card (just as when the server is booting)

There are three different types of SD Card errors possible:

  • Hardware errors during reading/writing. They occur within the low-level communication with the SD card itself. In this case a repair is automatically tried.
  • CRC error. The Loxone filesystem has checksums over each block. If they don’t match, it is an error – this could potentially happen, if the SD card is defect. But the software also tries to do a repair.
  • SD card is full. The Miniserver tries to write data, but there is no more space on the SD card.

The Miniserver only uses mandatory commands, this means every single SD card in the market has to implement them. Therefore any SD card from any good manufacturer should work just fine in the Miniserver. That said: there are Chinese manufacturers who sell SD cards with a limited actual capacity (e.g. 2GB) but which identify themselves as e.g. 16GB cards. This will result in data loss!

Inside the Loxone Miniserver

I’d like to explain some technical details of the Loxone Miniserver. The Miniserver (as well as all extensions) are ARM based, just like all modern mobile phones. It is clocked at just 8MHz.

The Miniserver is ARM based. The CPU is booting from a 512kb flash memory. This code then loads the actual operating system from the SD Card into the additional 64MB of memory and executes it from there.

CPU, Flash Memory, SRAM

The CPU is an Atmel AT91SAM9G20 from Microchip. It is a 3,3V 400MHz ARM926 with 32kb internal SRAM and 64kb internal ROM. It is paired with a serial interface Flash memory (AT25DF041A from Adesto Technologies), which is updatable by Loxone – it is one of two chips mounted on the back of the board. This flash memory also contains non-volatile memory used by the Miniserver, like encryption keys, which are not stored on the SD card. The other important chips are two SD RAM chips (H57V2562GTR from SK Hynix) as additional memory with 256MBit each adding another 64MB of memory.

Ethernet

The Ethernet is connected with another Chip from Microchip, the KSZ8051RNL1. Which is a 10BASE-T/100BASE-TX Automotive Physical Layer Transceiver. It doesn’t offer a lot, so most of the load for the different protocols TCP/IP and UDP, ARP, DHCP, etc. are all handled by the CPU.

SD Card

The SD Card is accessed with some logic gate directly. Nothing special here.

RTC Clock

The Miniserver has a battery backed CMOS Real-Time Clock (RTC) via a PCF2123 from NXP Semiconductors. This allows the system to run without an internet connection, while still having a valid time. During boot it is set if possible by testing various NTP servers.

Relays / Digital Out

The Relays are HF33024-HLT, which are isolated from the CPU by two ADuM3401 from Analog Devices.

Analog Out

The Analog Out are driven by a AD5724 also from Analog Devices, which is a a complete, quad, 12-/14-/16-Bit, serial input, unipolar/bipolar voltage output DAC. They are driven to have a 10V output range with a 12-bit resolution.

Analog In

The Analog Ins are driving by a TV1544 from Texas Instruments. It is a CMOS 10-bit switched-capacitor successive-approximation (SAR) analog-to-digital (A/D) converter.

Digital In

The digital inputs are read by a HVS882 from Texas Instruments. It is an 8-channel digital input serializer, which can handle up to 34V at the inputs with a flexible current limiter – it therefore also protects the CPU from damage. This is the other chip, which is mounted on the back of the board.

Loxone Link

The Loxone Link bus is standard CAN bus connected via a CAN controller (MCP2515, also Microchip) to the CPU. It is using a standard CAN Transceiver (SN65HVD232D from Texas Instruments) to protected the Miniserver from defects on the bus. The Miniserver also has a 120Ω resistor built-in, so it has to be on the end of the CAN bus. The CAN bus is clocked at 125kHz (which allows up to 500m of cable length for the Loxone Link bus).

All packages are using the extended frame format. The identifier is therefore always 29-bit (0…0x1FFFFFFF) and the data package is always 8 bytes long. Any CAN bus monitor hardware will work just fine with the Loxone Link bus.

The Loxone Link bus is a strict Master-Slave bus. The Miniserver as the master talks to the extensions, the extensions send data to the Miniserver. Extensions never talk to each other. The Miniserver can either multicast to all extensions or to specific extensions via direct commands. In the update case, it can send the update to all extensions of a certain type at the same time.

Photo with Labels of the Mainboard

Loxone Miniserver Mainboard

Loxone UDP/HTTP Command Parser Syntax

Loxone can parse incoming data e.g. via UDP/HTTP to detect variables. I found their documentation not really good. It feels like it is almost an art to setup these parse strings, while it is actually quite simple.

The string is searched in the incoming data stream. If the full string is detected, it succeeds and returns the value (0.0 being the default). If it didn’t match the whole pattern within the stream, it resets and continues again to parse the data. Only if the whole string was parsed fully, will the matching stop.

Possible characters in the parse string

Matching individual characters

  • 7-bit ASCII characters: they are taken as-is and need to match
  • \\ matches a single \ (0x5C)
  • \n matches a LF (0x0A)
  • \r matches a CR (0x0D)
  • \t matches a TAB (0x09)
  • \xAB match a hex byte AB. This allows matching non-ASCII characters. \xAB would match 0xAB.
  • \a match a letter (A-Z, a-z)
  • \b match a TAB or space (0x09, 0x20)
  • \m match a letter or digit (A-Z, a-z, 0-9)
  • \d match a digit (0-9)
  • \. matches any byte (= skips/ignores one byte)

Matching multiple characters

  • \# match any number digits and . , or -. This should match a regular floating point number and continue after the number.
  • \w match any number of letters and digits (A-Z, a-z, 0-9). This should match a word and continue after that word.
  • \s123 skips/ignores 123 bytes in the incoming data stream. All digits following \s are using to build a number, the value can be really large – more than is ever needed.

Searching

  • \iXXX\i searches the string XXX and continues after that string with matching. The string can have standard UNIX control characters (\a, \b, \f, \n, \r, \t, \v plus the hex extension: \xAB).

Storing a value

The returned value is always a 64-bit floating point number, which can result in rounding issues for certain integer values.

  • \1…\8 stores the following byte as part of a 64-bit binary integer result. \1 is the LSB, \8 is the MSB. Sign-extension can be applied.
  • \h (hex value) stores the following ASCII-hex data (0-9, a-f, A-F) as a 32-bit MSB integer result. First invalid character ends the value. The value is returned as-is, no sign extension is applied.
  • \v (value) stores a value created from ASCII characters. Any number of spaces before the values are ignored. Then the optional sign (+ or -) can follow, plus more spaces. A &nbps; is ignored after the sign as well. The number is any number of digits (0-9) followed by (, or .) plus more digits. Then an exponent (E or e) can follow plus more digits. The first character not matching the described number will end the value. The resulting number is obviously a floating point value.
  • If \f (factor?) is part of the search term, the value will always be 0.0. This feels like a bug in the code and \f was probably thought to be a multiplication factor for the incoming value.

It is possible to have several \h and \v in the search pattern. Only the last one will be used. Same with \1, etc. If more than one \1 occurs, the first one will be ignored as well.

Charge Master 2016 Serial Protocol

Charge Master 2016 Serial Protocol

The Voltcraft (Conrad) Charge Master 2016 has a completely different protocol from the CM2010 and other devices.

The USB port is still a serial port which is /dev/cu.SLAB_USBtoUART on my Mac. Every second the device sends a package with 19200 baud 8N1. I’ve written a simple python script to demonstrate reading and interpreting the data. For actual use you should add some error handling.

Only one byte in the slot header is unknown (it seems the Windows software is also not using it) and the device header has a few semi-unknown ones (the version, the temperature, etc) – only the chemical setting is actually used by the Window software.

The full source code can be found on GitHub https://github.com/sarnau/cm2016.

ELV Mobile Alerts

Mobile Alerts

This document tries to describe every detail of the Mobile Alerts sensors, which are sold by ELV in Germany, but are also available at the common suspects (Amazon, etc). Be careful buying at Amazon: certain sensors mention Mobile Alerts, but seem to be designed for the US. They are not compatible with the ELV Mobile Alerts ones!

Mobile Alerts is covering mostly climate sensors, but also contains moisture and door/window sensors plus a sound detector, which acts as a gateway for smoke sensors.

The Mobile Alerts are a version of the LaCrosse Alerts Mobile system made for the European market.

Detailed Infomation

Mobile Alerts Sensors

ELV is selling affordable weather sensors http://www.elv.de/ip-wettersensoren-system.html, which can be monitored via an iOS application. To allow that all data is transmitted via a gateway to a server, which the application can access. Sadly the protocol is not documented. I don’t think anybody has documented the way the URL has been constructed, especially the MD5 hash over the first part of it.

This sample code integrates sensors into fhem and I wrote it for Mac OS X, but it should work on Linux, too.

All information (and more) is available on GitHub as well.

To integrate the sensors into fhem, add the sensors to the iOS application and check if they show up. You need to note all sensor IDs. The ID is 6 bytes long and represented as a hexadecimal number. This same code supports sensors of type 2 (temperature only) and type 3 (temperature and humidity), which can be recognized via the first two characters of the ID.

You have to adjust the following things in the code:

  • sensors = … Here you have to add all sensors IDs as strings
  • downloadPath This needs to be adjusted to point to the log folder of fhem.
  • vendorid You might want to change the UUID to a new one (just run uuidgen from the terminal)

Python Code

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys
import os
import json
import pprint
import datetime
import hashlib
import time

downloadPath = os.path.expanduser('~/fhem-5.7/log')
pp = pprint.PrettyPrinter()

sensors =  set(['021122334455',...,'031122334455'])

def parseJSON(jj):
    if not jj['success']:
        print '#%d %s' % (jj['errorcode'],jj['errormessage'])
        return
    for device in jj['result']['devices']:
        #pp.pprint(device)
        deviceTypeId = device['devicetypeid']
        deviceName = device['name']
        print '%s %s [%d] #%d %s' % (device['deviceid'], deviceName, device['lowbattery'],len(device['measurements']),datetime.datetime.fromtimestamp(device['lastseen']).strftime('%Y-%m-%d %H:%M:%S'))
        year = -1
        month = -1
        logPath = ''
        for measurement in device['measurements']:
            idx = measurement['idx']
            transmitTime = datetime.datetime.fromtimestamp(measurement['c'])
            measurementTime = datetime.datetime.fromtimestamp(measurement['ts'])
            tx = measurement['tx']
            mm = measurement
            del mm['idx']
            del mm['c']
            del mm['ts']
            del mm['tx']

            if year != measurementTime.year or month != measurementTime.month:
                if len(logPath):
                    f = open(logPath, "w")
                    for d in sorted(data):
                        f.write('%s %s\n' % (d,data[d]))
                year = measurementTime.year
                month = measurementTime.month
                logPath = downloadPath + '/S_SENSOR_%s-%d-%02d.log' % (deviceName.upper().replace(' ','_'),year,month)
                data = {}
                try:
                    for line in open(logPath).readlines():
                        vals = line[:-1].split(' ')
                        data[' '.join(vals[:-1])] = vals[-1]
                except:
                    pass
            dataKey = measurementTime.strftime('%Y-%m-%d_%H:%M:%S') + ' SENSOR_' + deviceName.upper().replace(' ','_') + ' '
            if deviceTypeId == 2:
                data[dataKey + 'TEMP:'] = '%.1f' % (measurement['t1'])
            elif deviceTypeId == 3:
                data[dataKey + 'TEMP:'] = '%.1f' % (measurement['t1'])
                data[dataKey + 'FEUCHTIGKEIT:'] = '%.1f' % (measurement['h'])
            else:
                pp.pprint(mm)
        if len(logPath):
            f = open(logPath, "w")
            for d in sorted(data):
                f.write('%s %s\n' % (d,data[d]))


import urllib
import urllib2

devicetoken = 'empty'               # defaults to "empty"
vendorid = '1FB220C4-CC15-4195-97CF-8BE4FD3DAE72'   # iOS vendor UUID (returned by iOS, any UUID will do). Launch uuidgen from the terminal to generate a fresh one.
phoneid = 'Unknown'                 # Phone ID - probably generated by the server based on the vendorid (this string can be "Unknown" and it still works)
version = '1.21'                    # Info.plist CFBundleShortVersionString
build = '248'                       # Info.plist CFBundleVersion
executable = 'Mobile Alerts'        # Info.plist CFBundleExecutable
bundle = 'de.synertronixx.remotemonitor'    # [[NSBundle mainBundle] bundleIdentifier]
lang = 'en'                         # preferred language

request = "devicetoken=%s&vendorid=%s&phoneid=%s&version=%s&build=%s&executable=%s&bundle=%s&lang=%s" % (devicetoken,vendorid,phoneid,version,build,executable,bundle,lang)
request += '&timezoneoffset=%d' % 60        # local offset to UTC time
request += '&timeampm=%s' % ('true')        # 12h vs 24h clock
request += '&usecelsius=%s' % ('true')      # Celcius vs Fahrenheit
request += '&usemm=%s' % ('true')           # mm va in
request += '&speedunit=%d' % 0              # wind speed (0: m/s, 1: km/h, 2: mph, 3: kn)
request += '&timestamp=%s' % datetime.datetime.utcnow().strftime("%s")  # current UTC timestamp

requestMD5 = request + 'asdfaldfjadflxgeteeiorut0ß8vfdft34503580'  # SALT for the MD5
requestMD5 = requestMD5.replace('-','')
requestMD5 = requestMD5.replace(',','')
requestMD5 = requestMD5.replace('.','')
requestMD5 = requestMD5.lower()

m = hashlib.md5()
m.update(requestMD5)
hexdig = m.hexdigest()

request += '&requesttoken=%s' % hexdig

request += '&deviceids=%s' % ','.join(sensors)
#request += '&measurementfroms=%s' % ('0,' * len(sensors))
#request += '&measurementcounts=%s' % ('50,' * len(sensors))

http_header = {
                "User-Agent" : "remotemonitor/248 CFNetwork/758.2.8 Darwin/15.0.0",
                "Accept-Language" : "en-us",
                "Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
                "Host" : "www.data199.com:8080",
                }

# create an urllib2 opener()
opener = urllib2.build_opener()

# create your HTTP request
req = urllib2.Request('http://www.data199.com:8080/api/v1/dashboard', request, http_header)

# submit your request
while True:
    res = opener.open(req)
    parseJSON(json.loads(res.read()))
    print '-' * 40
    time.sleep(10*60)   # wait for 10 minutes

Atari ST Book: BIOS/XBIOS ROM listing

Atari ST ROM fully commented BIOS/XBIOS ROM listing for TOS 2.06, TOS 3.06 and – especially – the ST-Book ROM. I tried to collect the original comments from the Atari developers, when available. Also available on GitHub.

ROM_TOS206  EQU 1                               ; regular TOS 2.06 ROM
ROM_TOS306  EQU 0                               ; regular TOS 2.06 ROM
ROM_STBOOK  EQU 0                               ; ROM for the ST Book (variation of TOS 2.06)

osroml:     bra.s     reseth                    ; os_entry - BRA to reset handler
IF ROM_TOS306
            DC.W      $0306                     ; os_version - TOS version number
ELSE
            DC.W      $0206                     ; os_version - TOS version number
ENDIF
            DC.L      reseth                    ; os_reseth -> reset handler
            DC.L      osroml                    ; os_beg -> base of OS
            DC.L      endos                     ; os_end -> end BIOS/GEMDOS/VDI ram usage
            DC.L      reseth                    ; os_exec/os_rsv1 << unused, reserved >>
            DC.L      gem_mupb                  ; os_magic -> GEM memory usage param. block
IF ROM_STBOOK
            DC.L      $03101992                 ; os_date - Date of system build ($YYYYMMDD)
ELIF ROM_TOS206
            DC.L      $11141991                 ; os_date - Date of system build ($YYYYMMDD)
ELIF ROM_TOS306
            DC.L      $09241991                 ; os_date - Date of system build ($YYYYMMDD)
ENDIF
            DC.W      $0000                     ; os_conf - OS configuration bits
IF ROM_STBOOK
            DC.W      $186a                     ; os_dosdate - DOS-format date of system build
ELIF ROM_TOS206
            DC.W      $176e                     ; os_dosdate - DOS-format date of system build
ELIF ROM_TOS306
            DC.W      $1738                     ; os_dosdate - DOS-format date of system build
ENDIF
            DC.L      ospool_base               ; p_root -> base of OS pool
            DC.L      kb_shift                  ; pkbshift -> keyboard shift state variable
            DC.L      gemdos_pid                ; p_run -> GEMDOS PID of current process
            DC.L      $000000                   ; p_rsv2 << unused, reserved >>

*+
* [ROM based system]
* reseth - System reset handler
*
*  Gains control of the system upon power-up reset,
*  or when the RESET button is pressed,
*  or after a really messy system crash.
*
*-
reseth:     move      #$2700,sr                 ; super mode, no interrupts
IF ROM_TOS306
            move.w    #$100,(fifo).w
            move.w    #0,(fifo).w
ENDIF
            reset                               ; reset hardware

*+
*  [ROM based system]
*  Check for a diagnostic cartridge;
*  if one is inserted, load a return address
*  into A6 and jump into the cat's entry point.
*
*-
            cmpi.l    #$fa52235f,(cartbase).l   ; is the magic number there?
            bne.s     reset1                    ; (no)
            lea       reset1(pc),a6             ; a6 -> return address
            jmp       (cartbase+4).l            ; execute diagnostic cartridge

*+
*  [ROM based system]
*  Is this is a warm reset, setup the memory
*  controller configuration register so that
*  the reset-bailout vector has something to
*  stand on ....
*
*-
reset1:

IF ROM_TOS306
            move.l    #$808,d0
            movec     d0,cacr

            moveq     #0,d0                     ; set vector base to address 0
            movec     d0,vbr
            pmove     ($e35fe4).l,tc            ; 0L - disable PMMU
            pmove     ($e35fe4).l,tt0           ; 0L
            pmove     ($e35fe4).l,tt1           ; 0L
            frestore  ($e35fe4).l               ; 0L - clear FPU

            btst      #0,(scu_gp1).w            ; memconfig valid?
            beq.s     reset3                    ; (no)
ENDIF

            lea       ret_1(pc),a6              ; load return addr
            bra       val_memval                ; check memory configuration validity
ret_1:      bne.s     reset3                    ; (invalid -- don't set anything up)
            move.b    (memcntlr).w,(memconf).w  ; initialize memory controller
reset2:

IF ROM_STBOOK
            move.w    (STConfig).w,d0           ; Configuration on ST Book
            cmp.b     #$fc,d0                   ; power pressed while the ST Book is closed?
            beq.s     reset2b                   ; yes => do not execute reset vector
            move.w    (tt_mcu+4).l,d0           ; ST Book: ???
            and.b     #6,d0                     ; check bit 1 & 2
            bne.s     reset2c
reset2b:    clr.l     (resvalid).l
reset2c:
ENDIF

*+
*  [ROM based system]
*  RESET bailout vector check.
*  Check to make sure we have a clean, well-bred
*  bailout vector. The high byte must be zero,
*  it must be even, and cannot be entirely zero.
*
*-
            cmpi.l    #$31415926,(resvalid).w   ; is the resvalid the magic number?
            bne.s     reset3                    ; (no)
            move.l    (resvector).w,d0          ; d0 = reset bailout vector
IF !ROM_TOS306
            tst.b     (resvector).w             ; bits 24..31 must be zero
            bne.s     reset3                    ; (they aren't, so punt)
ENDIF
            btst      #0,d0                     ; the vector must be even
            bne.s     reset3                    ; (it isn't, so punt)
            movea.l   d0,a0                     ; a0 -> reset handler
            lea       reset2(pc),a6             ; a6 -> return address
            jmp       (a0)                      ; execute reset bailout

*+
*  Initialize PSG output ports.
*  Make port A and B output-only,
*  initialize floppy select lines (so
*  that none are selected)
*-
reset3:     lea       (psgsel).w,a0             ; a0 -> giselect, giwrite-2
            move.b    #7,(a0)                   ; set porta & portb to output
            move.b    #$c0,2(a0)
            move.b    #14,(a0)                  ; deselect disks
            move.b    #7,2(a0)

*+
*  Determine 50hz or 60hz.
*  The hardware RESETs to 60hz. Check a bit in the
*  ROM configuration byte to see if we have to twiddle
*  the hardware into 50hz mode.
*
*-
IF ROM_TOS306
            move.b    #1,(v_shf_mod).w          ; Switch to 640x200x2
ELSE
            btst      #0,(osroml+29).l          ; check bit: configured for 50hz?
            beq.s     notpal                    ; (nope -- we're good ol' NTSC)
            lea       ret_1b(pc),a6
            bra       waitvbl                   ; a short delay for PAL
ret_1b:     move.b    #2,(synmod).w             ; yes -- twiddle to 50hz
notpal:
IF ROM_STBOOK
            move.b    #2,(v_shf_mod).w          ; Switch to 640x400x1
            move.b    #$80,(lcdPowerControl).w  ; LCD display on
            tst.w     (tt_mcu).l                ; ST Book: ???
ENDIF
ENDIF

*+
*  Initialize palette registers to
*  their default values
*
*-
            lea       (palette).w,a1            ; a1 -> hardware reg
            move.w    #15,d0                    ; setup 16 colors
            lea       colors(pc),a0             ; a0 -> table of default colors
sysic1:     move.w    (a0)+,(a1)+               ; copy palette assignment
            dbra      d0,sysic1                 ; (loop for more colors)

*+
*  On a ROM system, put the screen (temporarily)
*  at $10000, so icon-drawing routines won't
*  blow any any system variables.
*-
            move.b    #1,(v_bas_h).w            ; set high ptr
            clr.b     (v_bas_m).w               ; set low ptr

*+
*  [ROM based system]
*  Determine how much memory there is, and initialize
*  the memory controller configuration register
*
*-
IF ROM_TOS306
            btst      #0,(scu_gp1).w            ; memconfig valid?
            beq.s     checkmem                  ; (no)
ENDIF
            move.b    (memcntlr).w,d6           ; d6 = memory controller configuration
IF ROM_TOS306
            move.b    d6,(memconf).w            ; setup memory controller configuration register
ENDIF
            move.l    (phystop).w,d5            ; d5 -> (possible) top of physical mem
            lea       ret_2(pc),a6              ; load return address
            bra       val_memval                ; check if the memory configuration is valid
ret_2:      beq       reset4

IF ROM_STBOOK
*--- init vars + hardware:
            move.b    #%1010,d6                 ; setup controller for 2Mb/2Mb - only valid configuration on a ST Book
            move.b    d6,(memconf).w            ; setup memory controller

*--- write test-pattern to determine memory configuration:
            move.l    #$400000,d5               ; d5 -> physical top of memory (4Mb)
            move.l    #$6161964,d0              ; 16th June 1964...
            move.l    #$3251987,d1              ; 25th March 1987...
            move.l    d0,($300010).l
            move.l    d1,($300014).l
            cmp.l     ($300010).l,d0            ; test if 4MB is actually installed
            bne.s     ret_2a
            cmp.l     ($300014).l,d1
            beq.s     ret_2b
ret_2a:     move.l    #$100000,d5               ; d5 -> physical top of memory (1Mb)

ret_2b:     lea       ($8000).l,sp

ELIF ROM_TOS206
* First we try to configure the memory controller

            clr.w     d6
            move.b    #$a,(memconf).w           ; default: setup controller for 2Mb/2Mb

            movea.w   #$8,a0
            lea       ($200008).l,a1            ; + 2Mb
            clr.w     d0
chkpatloop: move.w    d0,(a0)+                  ; fill 512-8 bytes with a test pattern
            move.w    d0,(a1)+
            add.w     #$fa54,d0
            cmpa.w    #$200,a0
            bne.s     chkpatloop

            move.b    #90,(v_bas_l).w           ; wrote low byte of video address
            tst.b     (v_bas_m).w               ; touch the middle byte (this should reset the low byte)
            move.b    (v_bas_l).w,d0
            cmp.b     #90,d0                    ; low byte not reset?
            bne.s     chkmem1
            clr.b     (v_bas_l).w               ; try a different low byte value
            tst.w     (palette).w               ; touch the color palette
            tst.b     (v_bas_l).w               ; low byte changed?
            bne.s     chkmem1
            move.l    #$40000,d7                ; 256Kb offset
            bra.s     chkmem1b
chkmem1:    move.l    #$200,d7                  ; 512 byte offset
chkmem1b:   move.l    #$200000,d1               ; 2Mb = maximum size per bank

chkmemloop: lsr.w     #2,d6                     ; shift memory configuration down by a bank (bank 1 is in bits 0..1, bank 0 is in bits 2..3)

            movea.l   d7,a0                     ; + 512/256Kb bytes
            addq.l    #8,a0
            lea       chkmem3(pc),a4
            bra       memchk
chkmem3:    beq.s     chkmem7                   ; bank is not working =>

            movea.l   d7,a0
            adda.l    d7,a0                     ; + 1024/512Kb byte
            addq.l    #8,a0
            lea       chkmem4(pc),a4
            bra       memchk
chkmem4:    beq.s     chkmem6                   ; bank has 512Kb of memory =>

            movea.w   #$8,a0                    ; + 0 bytes
            lea       chkmem5(pc),a4
            bra       memchk
chkmem5:    bne.s     chkmem7                   ; bank is empty =>

            addq.w    #4,d6                     ; 4+4 = 1000 2Mb bank size
chkmem6:    addq.w    #4,d6                     ; 4   = 0100 512Kb bank size
chkmem7:    sub.l     #$200000,d1               ; - 2Mb
            beq.s     chkmemloop
            move.b    d6,(memconf).w            ; set memory configuration

ELIF ROM_TOS306
checkmem:
            move.w    #5,d6
            move.b    #$a,(memconf).w
            moveq     #0,d0
            move.l    d0,($1008).w
            move.l    d0,($100c).w
            move.l    #$6161964,d0
            move.l    d0,($0008).w
            cmp.l     ($1008).w,d0
            bne.s     checkmem1
            move.l    #$4251987,d0
            move.l    d0,($000c).w
            cmp.l     ($100c).w,d0
            beq.s     checkmem2
checkmem1:  move.w    #$a,d6
checkmem2:  move.b    d6,(memconf).w
ENDIF

IF ROM_TOS206 || ROM_TOS306
* memory is configured, set the stack pointer
            lea       ($8000).l,sp

* test the memory in 128Mb blocks to find out the maximum installed
* physical memory. A failure (bus error or a memory checksum error)
* terminates the search.
            movea.l   (busexception).w,a4
            lea       chkmemx(pc),a0            ; if a bus error occurs, we are done, too
            move.l    a0,(busexception).w
            move.w    #$fb55,d3
            move.l    #$20000,d7                ; 128Mb is the page size for testing
            movea.l   d7,a0                     ; 128Mb is the minimum physical memory
chkmem8:    movea.l   a0,a1                     ; test just below the current top of memory
            move.w    d0,d2
            moveq     #$2a,d1                   ; test 43 words
chkmem9:    move.w    d2,-(a1)                  ; write test pattern
            add.w     d3,d2
            dbra      d1,chkmem9

            movea.l   a0,a1
            moveq     #$2a,d1                   ; check 43 words
chkmem10:   cmp.w     -(a1),d0                  ; compare the test pattern
            bne.s     chkmemx                   ; test failed => we are done
            clr.w     (a1)                      ; erase memory after testing
            add.w     d3,d0
            dbra      d1,chkmem10

            adda.l    d7,a0                     ; advance to the next page
            bra.s     chkmem8

chkmemx:    suba.l    d7,a0                     ; the last page didn't work
            move.l    a0,d5                     ; end of the physical memory
            move.l    a4,(busexception).w
ENDIF

*+
*  [ROM based system]
*  Clear memory from $400 to 'd5' (phystop)
*-
            movea.w   #etv_timer,a0             ; start at the beginning
            move.l    d5,d4                     ; where to end
            moveq     #0,d0
clm_1:      move.l    d0,(a0)+                  ; initialize all the memory
            move.l    d0,(a0)+
            move.l    d0,(a0)+
            move.l    d0,(a0)+
            cmpa.l    d4,a0                     ; done?
            bne.s     clm_1                     ; (loop for more bytes)

*+
*  [ROM based system]
*  Indicate that memory has successfully
*  been sized and tested.  Set two variables
*  to magic values ...
*
*-
            move.b    d6,(memcntlr).w           ; save configuration byte
            move.l    d5,(phystop).w            ; save physical top-of-memory
            move.l    #$752019f3,(memvalid).w   ; indicate memory was configured
            move.l    #$237698aa,(memval2).w    ; ditto (paranoia variable)
            move.l    #$5555aaaa,(memval3).w    ; ditto #2 (paranoia variable)
IF ROM_TOS306
            move.l    #fchkmemex,(busexception).w
            move.w    #0,(tt_mcu+224).l
fchkmemex:

* test the memory in 128Mb blocks to find out the maximum installed
* physical fast mem. A failure (bus error or a memory checksum error)
* terminates the search.
            move.l    #fchkmemx,(busexception).w    ; if a bus error occurs, we are done, too
            move.w    #$fb55,d3
            moveq     #0,d0
            move.l    #$20000,d7                ; 128Mb is the page size for testing
            movea.l   #$1020000,a0              ; start address of fast mem area + 128Mb
fchkmem8:   movea.l   a0,a1                     ; test just below the current top of memory
            move.w    d0,d2
            moveq     #$2a,d1                   ; test 43 words
fchkmem9:   move.w    d2,-(a1)                  ; write test pattern
            add.w     d3,d2
            dbra      d1,fchkmem9

            movea.l   a0,a1
            moveq     #$2a,d1                   ; check 43 words
fchkmem10:  cmp.w     -(a1),d0                  ; compare the test pattern
            bne.s     fchkmemx                  ; test failed => we are done
            clr.w     (a1)                      ; erase memory after testing
            add.w     d3,d0
            dbra      d1,fchkmem10

            adda.l    d7,a0                     ; advance to the next page
            bra.s     fchkmem8

fchkmemx:   suba.l    d7,a0                     ; the last page didn't work
            cmpa.l    #$1000000,a0              ; it failed right at the beginning of the fast mem?
            bne.s     fchkmemno                 ; (no)
            suba.l    a0,a0                     ; then we have no fast mem
fchkmemno:  move.l    a0,d5                     ; end of the fast mem
            move.l    d5,(ramtop).w             ; top of fast mem
            move.l    #$1357bd13,(ramvalid).w   ; ramtop is valid (ramvalid == RAMMAGIC)
            bset      #0,(scu_gp1).w            ; memconfig is valid
ELSE
            clr.l     (ramtop).l                ; no FASTRAM available
            move.l    #$1357bd13,(ramvalid).w   ; ramtop is valid (ramvalid == RAMMAGIC)
ENDIF

reset4:     movea.l   #_supstk+2048,sp          ; setup supervisor stack

IF ROM_STBOOK
*+
*  Initialize interrupt vectors
*
*  The exception vectors are pointed to a cold boot (_coldboot)
*  during startup.
*
*  Trap 2 and Divide-by-zero are pointed at an rte
*
*  The HBLANK, VBLANK, line 1001 [someday: line 1111), trap 13, trap 14,
*  and "extended" trap vectors are initialized appropriately.
*
*-
            lea       _rte(pc),a3               ; a3 -> handy RTE
            lea       _rts(pc),a4               ; a4 -> handy RTS

*--- setup 64 vectors:
            lea       (_coldboot).l,a1          ; a1 -> during boot all exception trigger a coldboot, which erases all memory and resets
            lea       (busexception).w,a0       ; a0 -> interrupt RAM
            move.w    #$3f,d0                   ; d0 = count
sei1:       move.l    a1,(a0)+                  ; write vector
            dbra      d0,sei1                   ; (loop to write more vectors)
            move.l    a3,(divzeroexception).w   ; divide-by-zero vector -> rte

            move.l    a3,(Level7IRQ).w          ; level #7 interrupt -> rte (power exception in a ST Book)
            moveq     #6,d0
            lea       (Level1IRQ_TTVME).w,a1
sei2:       move.l    #_rte,(a1)+               ; level #1 ... level #6 to RTE
            dbra      d0,sei2

*--- install OS interrupt vectors:
            move.l    #vbl,(Level4IRQ_VBL).w    ; vblank handler
            move.l    #hbl,(Level2IRQ_HBL).w    ; hblank handler
            move.l    a3,(trap_aesvdi).w        ; (empty) trap#2 handler
            move.l    #trp13h,(trap_bios).w     ; trap #13 handler
            move.l    #trp14h,(trap_xbios).w    ; trap #14 handler
            move.l    #line1010,(lineAexception).w ;line 1010 handler
            move.l    a4,(etv_timer).w          ; default timer-tick vector -> rts
            move.l    #_critich,(etv_critic).w  ; default critical error handler
            move.l    a4,(etv_term).w           ; default terminal vector -> rts

*+
*  Setup the vblank deferred vector list.
*  (This data structor is ugly,
*   but we seem to be stuck with it).
*
*-
            lea       (_vbl_list).w,a0          ; a0 -> default list of vbl locs
            move.l    a0,(_vblqueue).w          ; install ptr to them
            move.w    #7,d0                     ; clear vbl vectors
avbl:       clr.l     (a0)+                     ; one at a time
            dbra      d0,avbl

            lea       (tconstat).l,a0
            movea.w   #xconstat,a1
            moveq     #31,d0
tconl:      move.l    (a0)+,(a1)+
            dbra      d0,tconl

            movea.l   (busexception).w,a0
            movea.l   sp,a1
            move.l    #vmeinit,(busexception).w
            move.b    #$40,(vme_mask).w         ; Enable IRQ6 from VMEBUS/MFP
            move.b    #$14,(sys_mask).w         ; VSYNC & HSYNC enable in the VME Bus System Control Unit
vmeinit:    move.l    a0,(busexception).w
            movea.l   a1,sp
ENDIF

            clr.b     ($aa6).l
IF ROM_STBOOK
            sf        (STEFlag).l               ; ST Book is not an Atari STE
ELSE
            movea.l   sp,a6
            move.l    #mwinitex,(busexception).w
            clr.w     (sndmactl).w
            st        ($a02).l                  ; microwire interface available
            lea       mwinitdata(pc),a0
            move.w    (a0)+,(mwmask).w
            bra.s     mwinit3

mwinitdata: DC.W      $0ffe                     ; LMC 1992 mask
            DC.W      $09d1                     ; 10-011-101000-1 = LCM - Master Volume - 0 db volume (max) - end-bit
            DC.W      $0aa9                     ; 10-101-010100-1 = LCM - Left channel volume - 0 db volume (max) - end-bit
            DC.W      $0a29                     ; 10-100-010100-1 = LCM - Right channel volume - 0 db volume (max) - end-bit
            DC.W      $090d                     ; 10-010-000110-1 = LCM - Trebble control - 0 db (linear) - end-bit
            DC.W      $088d                     ; 10-001-000110-1 = LCM - Bass control - 0 db (linear) - end-bit
            DC.W      $0803                     ; 10-000-000001-1 = LCM - Mixer - DMA + YM2149 - end-bit
            DC.W      $0000                     ; 0 = end of list

mwinit1:    move.w    d0,(mwdata).w
mwinit2:    tst.w     (mwdata).w
            bne.s     mwinit2
mwinit3:    move.w    (a0)+,d0
            bne.s     mwinit1
mwinitex:   movea.l   a6,sp

IF ROM_TOS206
            move.b    #90,(v_bas_l).w           ; write low byte of video address
            tst.b     (v_bas_m).w               ; access the medium byte
            move.b    (v_bas_l).w,d0            ; (which should reset the low byte on an STE!)
            cmp.b     #90,d0                    ; reset?
            bne.s     nostedetect               ; yes => STE detected
            clr.b     (v_bas_l).w               ; clear the low byte again
            tst.w     (palette).w               ; access the color palette
            tst.b     (v_bas_l).w
nostedetect:sne       (STEFlag).l               ; <>0 => no STE hardware available
ELSE
            sf        (STEFlag).l               ; no STE hardware available
ENDIF
ENDIF

*+
*  Clear OS bss (from 'endosbss' to 'ostext')
*-
            movea.l   #ostext,a1                ; a1 -> end
            movea.l   #endosbss,a0              ; a0 -> start

*--- common code to clear memory:
            moveq     #0,d0                     ; quick zero
clrm_1:     move.w    d0,(a0)+                  ; clobber a word
            cmpa.l    a0,a1                     ; at end?
            bne.s     clrm_1                    ; (no -- loop for more words)

IF ROM_TOS306
            bsr       setupPMMU                 ; setup PMMU translation table
ENDIF
*+
*  Setup display base,
*  clear display memory.
*
*-
            movea.l   (phystop).w,a0            ; video_base = phystop - 0x8000
IF !ROM_TOS306
            suba.l    #$8000,a0                 ; screen size = 32kb
            move.w    #$7ff,d1                  ; d1 = # 16-byte chunks to zero
ELSE
            suba.l    #$25900,a0                ; screen size = 154kb
            move.w    #$258f,d1                 ; d1 = # 16-byte chunks to zero
ENDIF
            move.l    a0,(_v_bas_adr).w
            move.b    (_v_bas_adr+1).w,(v_bas_h).w ;load high addr
            move.b    (_v_bas_adr+2).w,(v_bas_m).w ;load low (really, medium) addr
            moveq     #0,d0                     ; quick zero
clrm_2:     move.l    d0,(a0)+                  ; zero a longword
            move.l    d0,(a0)+                  ; zero a longword
            move.l    d0,(a0)+                  ; zero a longword
            move.l    d0,(a0)+                  ; zero a longword
            dbra      d1,clrm_2                 ; (loop for more longwords)

*+
*  Initialize all kinds of OS variables
*
*-

*--- OS parameters:
            movea.l   osroml+20(pc),a0          ; get pointer to magic
            cmpi.l    #$87654321,(a0)           ; is the magic there?
            beq.s     usem
            lea       (osroml+8).l,a0           ; yes -- use numbers there
usem:       move.l    4(a0),(end_os).w          ; init end-of-OS pointer
            move.l    8(a0),(exec_os).w         ; init default-shell pointer

*--- Disk vectors:
            move.l    #_dskinit,(hdv_init).w    ; initialization
            move.l    #_floprw,(hdv_rw).w       ; read/write absolute sectors
            move.l    #_getbpb,(hdv_bpb).w      ; media change inquiry
            move.l    #_mediach,(hdv_mediach).w ; get BIOS parameter block
            move.l    #_boot,(hdv_boot).w       ; boot-from-device

            move.l    #_lstostat,(prv_lsto).w
            move.l    #_lstout,(prv_lst).w
            move.l    #_auxostat,(prv_auxo).w
            move.l    #_auxout,(prv_aux).w
            move.l    #_scrdmp,(scr_dump).w

*--- Randoms:
            move.l    (_v_bas_adr).w,(_memtop).w ;_memtop = _v_bas_ad
            move.l    (end_os).w,(_membot).w    ; set bottom of memory (for DOS)
            move.w    #8,(nvbls).w              ; default number of vbl queue entries
            st        (_fverify).w              ; enable write-verify
            move.w    #3,(seekrate).w           ; set default seek-rate
            move.l    #_diskbuf,(_dskbufp).w    ; set pointer to disk buffer
            move.w    #-1,(_prtcnt).w           ; initialize print-count
            move.l    #osroml,(_sysbase).w      ; -> base of OS
            move.l    #savend,(savptr).w        ; register-save pointer for traps 13&14
            move.l    #_rts,(swv_vec).w         ; ignore monitor changes for now
            clr.l     (_drvbits).w              ; remove all drives
            move.l    #keybellsnd,(bell_hook).w
            move.l    #keyclicksnd,(kcl_hook).w

            bsr       sysbase2ram               ; copy sysbase into RAM and patch os_conf to contain os_dosdate

*--- Cookies:
            lea       (cookieBuffer).l,a0
            move.l    a0,(_p_cookies).w
            move.l    #'_CPU',(a0)+
            moveq     #0,d1                     ; 68000 CPU
            movea.w   #illegalexception,a2
            movea.l   (a2),a3
            movea.l   sp,a1
            move.l    #cookieCPU,(a2)
            move      ccr,d0                    ; first exists in the 68010
            moveq     #10,d1                    ; 68010 CPU
            extb.l    d0                        ; first exists in the 68020
            moveq     #20,d1                    ; 68020 CPU
            movec     cacr,d0
            bset      #9,d0
            movec     d0,cacr
            movec     cacr,d0
            bclr      #9,d0
            beq.s     cookieCPU
            moveq     #30,d1                    ; 68030 CPU
            movec     d0,cacr
cookieCPU:  movea.l   a1,sp
            move.l    a3,(a2)
            move.l    d1,(a0)+
            sne       (_longframe+1).w          ; 68010+ have a longer interrupt stack frame
IF ROM_STBOOK
            move.l    #'_VDO',(a0)+             ; setup VDO cookie: Video hardware
            move.l    #$10001,(a0)+             ; 1,1 => Atari STE, ST-Book
            move.l    #'_MCH',(a0)+             ; setup MCH cookie: Machine type
            move.l    #$10001,(a0)+             ; 1,1 => Atari STE, ST-Book

            move.b    #$7f,d0
            tst.b     (STEFlag).l
            bne.s     cookieSTE
            move.l    #'_SWI',(a0)+             ; setup SWI cookie: DIP configuration switches
            moveq     #0,d0
            move.w    (STConfig).w,d0
            lsr.w     #8,d0
            move.l    d0,(a0)+                  ; all DIP switches as a bit mask 0..7
cookieSTE:  moveq     #3,d1                     ; bit 0: PSG, bit 1: 8-bit DMA
            move.l    #'_SND',(a0)+             ; setup SND cookie: Sound hardware
            btst      #7,d0                     ; DIP switch 7 on?
            bne.s     cookieSND                 ; (punt)
            bclr      #1,d1                     ; no 8-bit DMA sound
cookieSND:  move.l    d1,(a0)+
            btst      #6,d0                     ; DIP switch 6 on?
            bne.s     cookieFDC                 ; (punt - no HD floppy)
            move.b    #8,(dsb0).l               ; select HD density for drive A
            move.l    #'_FDC',(a0)+             ; setup FDC cookie: Floppy disk controller
            move.l    #$1415443,(a0)+           ; 'FDC' | (1 << 24)
cookieFDC:  move.l    #'_FPU',(a0)+             ; Setup FPU cookie: Type of the FPU
            moveq     #0,d7                     ; 0 = no FPU
            suba.w    #$24,sp
            move.l    (lineFexception).w,(sp)
            move.l    (coprocexception).w,4(sp)
            move.l    #cookieFPU,(lineFexception).w
            move.l    #cookieFPU,(coprocexception).w
            lea       8(sp),a1
            movea.w   #ffcp_unorderedcond,a2
            move.l    #cookieFPU2,d0
            move.l    (a2),(a1)+
            move.l    d0,(a2)+
            move.l    (a2),(a1)+
            move.l    d0,(a2)+
            move.l    (a2),(a1)+
            move.l    d0,(a2)+
            move.l    (a2),(a1)+
            move.l    d0,(a2)+
            move.l    (a2),(a1)+
            move.l    d0,(a2)+
            move.l    (a2),(a1)+
            move.l    d0,(a2)+
            move.l    (a2),(a1)+
            move.l    d0,(a2)+
            clr.l     -(sp)
            movea.l   sp,a2
            frestore  (sp)
cookieFPU2: move.l    #$20000,d7                ; 0x20000 = 68881 or 68882 as co-processor (Exact type unknown)
cookieFPU:  movea.l   a2,sp
            addq.w    #4,sp
            move.l    (sp)+,(lineFexception).w
            move.l    (sp)+,(coprocexception).w
            move.l    (sp)+,(ffcp_unorderedcond).w
            move.l    (sp)+,(ffcp_inexactresult).w
            move.l    (sp)+,(ffcp_divzero).w
            move.l    (sp)+,(ffcp_underflow).w
            move.l    (sp)+,(ffcp_operanderror).w
            move.l    (sp)+,(ffcp_inexactresult).w
            move.l    (sp)+,(ffcp_divzero).w
            move.l    d7,(a0)+
            movea.l   (busexception).w,a1
            movea.l   sp,a2
            move.l    #cookieFPU3,(busexception).w
            move.w    (FPStat).w,d0
            bset      #0,-3(a0)                 ; SFP004 present
cookieFPU3: move.l    a1,(busexception).w
            movea.l   a2,sp
ELSE

IF ROM_TOS206
            tst.b     (STEFlag).l               ; no STE hardware available?
            beq.s     cookieMCH                 ; (correct)
            move.l    #'_VDO',(a0)+
            clr.l     (a0)+                     ; 0,0 = Atari ST (260 ST, 520 ST, 1040 ST, Mega ST, ...)
            move.l    #'_MCH',(a0)+
            clr.l     (a0)+                     ; 0,0 = Atari ST
            bra.s     cookieSWI

cookieVDO:  move.l    #'_VDO',(a0)+
            move.l    #$10000,(a0)+             ; 1,0 = STE Shifter

            move.l    #$10000,d0                ; x = $00 = regular STE
            movea.l   (busexception).w,a1
            movea.l   sp,a2
            move.l    #cookieMCH1,(busexception).w
            tst.b     (scu_gp1).w
            move.w    #$10,d0                   ; x = $10 = Mega STE (with SCSI)
            bra.s     cookieMCH2
cookieMCH1: clr.w     d0
            movea.l   a2,sp
            move.l    #cookieMCH2,(busexception).w
            tst.b     (ide_stat2).l
            move.w    #8,d0                     ; x = $08 = STE with IDE (unknown machine)
cookieMCH2: move.l    a1,(busexception).w
            movea.l   a2,sp
            move.l    #'_MCH',(a0)+
            move.l    d0,(a0)+                  ; 1,x = STE (520, 1040, 2080, 4160, Mega STE, ST Book)

ELIF ROM_TOS306
            move.l    #'_VDO',(a0)+
            move.l    #$20000,(a0)+             ; 2,0 = TT Shifter
            move.l    #'_MCH',(a0)+
            move.l    #$20000,(a0)+             ; 2,0 = TT
ENDIF

cookieSWI:  move.b    #$7f,d0
            tst.b     (STEFlag).l               ; no STE hardware available?
            bne.s     cookieSND2                ; (correct)
            move.l    #'_SWI',(a0)+             ; setup SWI cookie: DIP configuration switches
            moveq     #0,d0
            move.w    (STConfig).w,d0
            lsr.w     #8,d0
            move.l    d0,(a0)+

cookieSND2: moveq     #3,d1                     ; bit 0: PSG, bit 1: 8-bit DMA
            move.l    #'_SND',(a0)+             ; setup SND cookie: Sound hardware
            btst      #7,d0                     ; DIP switch 7 on?
            bne.s     cookieSND3                ; (punt)
            bclr      #1,d1                     ; no 8-bit DMA sound
cookieSND3: move.l    d1,(a0)+

            btst      #6,d0                     ; DIP switch 6 on?
            bne.s     cookieFDC2                ; (punt - no HD floppy)
            move.b    #8,(dsb0).l               ; select HD density for drive A
            move.l    #'_FDC',(a0)+             ; setup FDC cookie: Floppy disk controller
            move.l    #$1415443,(a0)+           ; 'FDC' | (1 << 24)

cookieFDC2:
            move.l    #'_FPU',(a0)+             ; Setup FPU cookie: Type of the FPU
            movea.l   (lineFexception).w,a1
            movea.l   (coprocexception).w,a2
            movea.l   sp,a3
            move.l    #cookieFPUex,(lineFexception).w
            move.l    #cookieFPUex,(coprocexception).w
            fmove.l   d0,fp0
            move.l    #$20000,(a0)+             ; 6888x present
            bra.s     cookieFPUe2
cookieFPUex:clr.l     (a0)+
cookieFPUe2:move.l    a1,(lineFexception).w
            move.l    a2,(coprocexception).w
            movea.l   a3,sp
            movea.l   (busexception).w,a1
            movea.l   sp,a2
            move.l    #cookieFPUe3,(busexception).w
            move.w    (FPStat).w,d0
            bset      #0,-3(a0)                 ; SFP004 present
cookieFPUe3:move.l    a1,(busexception).w
            movea.l   a2,sp

IF ROM_TOS306
            tst.l     (ramtop).w                ; fast mem installed?
            beq.s     nfrbcookie                ; (no)
            move.l    #'_FRB',(a0)+             ; allocate FRB cookie for DMA buffer
            move.l    (_membot).w,d0
            move.l    d0,(a0)+                  ; buffer adress
            add.l     #$10000,d0                ; 64kb size
            move.l    d0,(_membot).w            ; adjust memory base
            move.l    d0,(end_os).w
nfrbcookie:
ENDIF

ENDIF
            clr.l     (a0)+                     ; terminate cookie list
            move.l    #$10,(a0)+                ; 16 available slots in the cookie store

IF !ROM_STBOOK
*+
*  Initialize interrupt vectors
*
*  The exception vectors are pointed to a cold boot (_coldboot)
*  during startup.
*
*  Trap 2 and Divide-by-zero are pointed at an rte
*
*  The HBLANK, VBLANK, line 1001 [someday: line 1111), trap 13, trap 14,
*  and "extended" trap vectors are initialized appropriately.
*
*-
            lea       _rte(pc),a3               ; a3 -> handy RTE
            lea       _rts(pc),a4               ; a4 -> handy RTS

*--- setup 62 vectors:
            lea       _term(pc),a1              ; _term -> draw number of bombs
IF !ROM_TOS306
            adda.l    #$2000000,a1              ; exception number in top 8 bits of the address
ENDIF
            lea       (busexception).w,a0       ; a0 -> interrupt RAM
            move.w    #$3d,d0                   ; d0 = count
sei1:       move.l    a1,(a0)+                  ; write vector
IF !ROM_TOS306
            adda.l    #$1000000,a1              ; increment the exception number
ENDIF
            dbra      d0,sei1                   ; (loop to write more vectors)
            move.l    a3,(divzeroexception).w   ; divide-by-zero vector -> rte

            move.l    a3,(Level7IRQ).w          ; level #7 interrupt -> rte (power exception in a ST Book)
            moveq     #6,d0
            lea       (Level1IRQ_TTVME).w,a1
sei2:       move.l    #_rte,(a1)+               ; level #1 ... level #6 to RTE
            dbra      d0,sei2

*--- install OS interrupt vectors:
            move.l    #vbl,(Level4IRQ_VBL).w    ; vblank handler
            move.l    #hbl,(Level2IRQ_HBL).w    ; hblank handler
            move.l    a3,(trap_aesvdi).w        ; (empty) trap#2 handler
            move.l    #trp13h,(trap_bios).w     ; trap #13 handler
            move.l    #trp14h,(trap_xbios).w    ; trap #14 handler
            move.l    #line1010,(lineAexception).w ;line 1010 handler
            move.l    a4,(etv_timer).w          ; default timer-tick vector -> rts
            move.l    #_critich,(etv_critic).w  ; default critical error handler
            move.l    a4,(etv_term).w           ; default terminal vector -> rts

*+
*  Setup the vblank deferred vector list.
*  (This data structor is ugly,
*   but we seem to be stuck with it).
*
*-
            lea       (_vbl_list).w,a0          ; a0 -> default list of vbl locs
            move.l    a0,(_vblqueue).w          ; install ptr to them
            move.w    #7,d0                     ; clear vbl vectors
avbl:       clr.l     (a0)+                     ; one at a time
            dbra      d0,avbl

            lea       (tconstat).l,a0
            movea.w   #xconstat,a1
            moveq     #31,d0
tconl:      move.l    (a0)+,(a1)+
            dbra      d0,tconl

            movea.l   (busexception).w,a0
            movea.l   sp,a1
            move.l    #vmeinit,(busexception).w
            move.b    #$40,(vme_mask).w         ; Enable IRQ6 from VMEBUS/MFP
            move.b    #$14,(sys_mask).w         ; VSYNC & HSYNC enable in the VME Bus System Control Unit
vmeinit:    move.l    a0,(busexception).w
            movea.l   a1,sp
ENDIF

*+
*  "The other half" of the BIOS handles character I/O,
*  call its initialization hoook.
*  (It can "never fail". This will get interesting
*  if we ever to a detachable keyboard ....)
*-
            bsr       initmfp

            move.w    #$400,d0                  ; Delay Mode, /50 Prescale, data = 0 (about 10us delay)
            bsr       mfpdelay
            move.l    #setikbd,-(sp)
            move.w    #1,-(sp)
            jsr       (ikbdws).l                ; reset ikbd
            addq.l    #6,sp
            move.w    #$700,d0                  ; Delay Mode, /200 Prescale, data = 0 (about 40us delay)
            move.w    #14,d1                    ; 15 * 40us = 600us delay
ikbddelay:  bsr       mfpdelay
            dbra      d1,ikbddelay

*+
*  Fire up %%2 cartridges
*
*-
            moveq     #2,d0                     ; bit# = 2
            bsr       cartscan                  ; execute cartridge aps
IF ROM_STBOOK
            bsr       cartscan_STBOOK_EXTROM

            move.b    #$80,(lcdPowerControl).w  ; LCD display on
            move.b    #$80,(lcdPowerControlShadow).w ; LCD display shadow register
            moveq     #2,d1                     ; Switch to 640x400x1
            lea       setvb1(pc),a6
            bra       waitvbl
setvb1:     move.b    d1,(v_shf_mod).w          ; set rez hardware register

ELIF ROM_TOS206
            moveq     #0,d1                     ; Switch to 320x200x4
            btst      #7,($fffffa01).w          ; monochrome monitor connected?
            bne.s     nomonomon                 ; (no)
            moveq     #2,d1                     ; Switch to 640x400x1
nomonomon:
            lea       setvb1(pc),a6
            bra       waitvbl
setvb1:     move.b    d1,(v_shf_mod).w          ; set rez hardware register

ELIF ROM_TOS306
            moveq     #4,d1                     ; Switch to 640x480x4
            btst      #7,($fffffa01).w          ; monochrome monitor connected?
            bne.s     nomonomon                 ; (no)
            moveq     #6,d1                     ; Switch to 1280x960x1
nomonomon:
            move.b    d1,(shift_tt).w
ENDIF
            move.b    d1,(sshiftmd).w           ; set rez shadow

            bsr       blittest
            jsr       ($e072aa).l               ; linaA blitter/no-blitter table init
            jsr       (esc_init).l              ; clear screen, initialize cursor
            move.l    #reseth,(swv_vec).w       ; RESET system on monitor change
            move.w    #1,(vblsem).w             ; enable vblank processing

*+
*  [1] Fire up %%0 cartridges
*  [2] Enable interrupts
*  [3] Fire up %%1 cartridges
*
*-
            clr.w     d0                        ; magic bit# = 0
            bsr       cartscan                  ; execute cartridge aps
IF ROM_STBOOK
            bsr       cartscan_STBOOK_EXTROM
ENDIF
            move      #$2300,sr                 ; Enable interrupts, go to IPL 3

            moveq     #1,d0                     ; magic bit# = 1
            bsr       cartscan                  ; execute cartridge aps
IF ROM_STBOOK
            bsr       cartscan_STBOOK_EXTROM

            move.l    (_hz_200).w,d0
            addq.l    #3,d0
resDelayL:  cmp.l     (_hz_200).w,d0            ; a short delay of 15-20ms
            bhi.s     resDelayL
            clr.b     (kb_shift).l              ; reset keyboard shift state
ENDIF

            move.l    #privinstr_exception,(privexception).w ;Emulate the MOVE SR so it works in usermode on a 68020+
            bra       resetcont

*+
* Flush instruction and data cache (68020+)
*-
flushCaches:move      sr,-(sp)
            ori       #$700,sr
            movec     cacr,d0
            or.l      #$808,d0                  ; CI (Clear Instruction Cache), DI (Clear Data Cache)
            movec     d0,cacr
            move      (sp)+,sr
            rts

*+
* Emulate the MOVE SR,<ea> so it works in usermode on 68010+
*-
privinstr_exception:movem.l d0-d2,-(sp)
            move.l    a1,-(sp)
            move.l    a0,-(sp)
            movea.l   $16(sp),a0                ; a0 -> pc
            move.w    (a0),d0                   ; d0 -> current opcode
            move.w    d0,d1                     ; d1 -> save current opcode
            and.w     #$ffc0,d0
            cmp.w     #$40c0,d0                 ; move SR,<ea>?
            bne       privinstr_exception_term  ; (no -- punt)

            move.l    #$30004e71,(endosbss).w   ; move.w d0,d0, nop
            move.l    #'NqNu',(endosbss+2*2).w  ; nop, rts
            move.w    d1,d0
            and.w     #7,d0                     ; <ea> register (bit 0..2)
            lsl.w     #8,d0
            lsl.w     #1,d0                     ; move into bit 9..11 of the destination <ea>
            or.w      d0,(endosbss).w
            move.w    d1,d0
            and.w     #$38,d0                   ; <ea> mode (bit 3..5)
            lsl.w     #3,d0                     ; move into bit 6..8 of the destination <ea>
            or.w      d0,(endosbss).w           ; generate a MOVE D0,<ea> from the MOVE SR,<ea> opcode

            moveq     #2,d2                     ; opcode size = 2
            cmp.w     #$180,d0                  ; <ea> == d8(An,Xn)?
            beq       privinstr_exception_term  ; not supported -> _term
            tst.w     d0                        ; <ea> == Dn?
            beq.s     privinstr_exception_dn
            cmp.w     #$140,d0                  ; <ea> == d16(An)?
            beq.s     privinstr_exception_d16
            cmp.w     #$1c0,d0                  ; <ea> == (xxx).w or (xxx).l?
            bne.s     privinstr_exception_other

* <ea> = (xxx).l or (xxx).w
            and.w     #7,d1                     ; register == 0 => (xxx).w
            beq.s     privinstr_exception_abs16
            addq.w    #2,d2                     ; it is (xxx).l -> opcode size += 2
            move.w    4(a0),(endosbss+2*2).w    ; move lower word of the absolute address
privinstr_exception_abs16:addq.w #2,d2          ; opcode size += 2
            move.w    2(a0),(endosbss+2).w      ; move upper word of the absolute address
            bra.s     privinstr_exception_ea    ; -> regular destination ea

* <ea> = d16(An)
privinstr_exception_d16:addq.w #2,d2            ; opcode size += 2
            move.w    2(a0),(endosbss+2).w      ; move d16 offset
privinstr_exception_other:and.w #7,d1           ; d1 = An from the destination <ea>
            cmp.w     #7,d1                     ; relative to A7?
            bne.s     privinstr_exception_ea    ; no -> regular destination ea

* <ea> = d16(A7) or d8(A7,Dn)
* Special case because A7 has to be the USP, instead of the current SSP (in A7)
            move      usp,a1                    ; have to use the USP, otherwise we wouldn't have gotten the exception
            andi.w    #$f3ff,(endosbss).w       ; convert A7-relative into A1-relative destination <ea>
            add.l     d2,$16(sp)                ; pc += opcode size
IF ROM_TOS306
            bsr       flushCaches
ENDIF
            move.w    $14(sp),d0                ; d0 = SR
            jsr       (endosbss).w              ; execute: MOVE D0,d(A1,Dn); NOP; RTS or MOVE D0,d(A1); NOP; RTS
            move      a1,usp                    ; restore USP, but shouldn't have changed anyway
            movea.l   (sp)+,a0                  ; restore registers
            movea.l   (sp)+,a1
            movem.l   (sp)+,d0-d2
            rte                                 ; continue execution

* <ea> = Dn
privinstr_exception_dn:add.l d2,$16(sp)         ; pc += opcode size
            ori.w     #$10,(endosbss).w         ; source ea = (A0)
IF ROM_TOS306
            bsr       flushCaches
ENDIF
            lea       20(sp),a0                 ; point to SR register to SSP
            movem.l   8(sp),d0-d2               ; restore d0-d2
            jsr       (endosbss).w              ; execute: MOVE (A0),Dn; NOP; NOP; RTS
            movea.l   (sp)+,a0                  ; restore registers
            movea.l   (sp)+,a1
            adda.w    #$c,sp                    ; skip d0-d2, because they have already been restored
            rte                                 ; continue execution

privinstr_exception_ea:add.l d2,$16(sp)         ; pc += opcode size
IF ROM_TOS306
            bsr       flushCaches
ENDIF
            movea.l   (sp)+,a0
            movea.l   (sp)+,a1
            move.w    12(sp),d0                 ; d0 = SR
            jsr       (endosbss).w              ; execute: MOVE D0,<ea>; ...; RTS
            movem.l   (sp)+,d0-d2               ; restore registers
            rte                                 ; continue execution

privinstr_exception_term:movea.l (sp)+,a0       ; restore registers
            movea.l   (sp)+,a1
            movem.l   (sp)+,d0-d2
            jmp       (_term).l                 ; illegal instruction => _term

*+
* Load shell (if _cmdload is nonzero)
* or execute GEM in ROM
*-
resetcont:  jsr       (osi).l                   ; initialize DOS

*--- set the current system time and date
            move.w    (osroml+30).l,(systemDate).l ;use BIOS time as current time
            jsr       (readCurrentTime).l       ; set current time to RTC time
            beq.s     notimechip                ; (no RTC)
            bsr       readIKBDTime              ; read time from the keyboard controller
            swap      d0
            tst.b     d0
            beq.s     notimechip                ; (not valid either)
            move.w    d0,(systemDate).l         ; update system time
            swap      d0
            move.w    d0,(systemTime).l

notimechip: clr.b     (tacr).w
            bclr      #5,(iera).w               ; disable Timer A interrupts

IF ROM_TOS306
            move.l    #$3111,d0
            movec     d0,cacr
ENDIF

            movea.l   #atari_image,a0           ; draw Atari Logo on the screen
            movea.l   (_v_bas_adr).w,a1
            move.b    (sshiftmd).w,d0
            cmp.b     #2,d0                     ; 640 x 400 (bw)
            beq.s     drawLogobw                ; (yes)
            cmp.b     #6,d0                     ; 1280 x 960 (bw)
            beq.s     drawLogobw                ; (yes)
IF ROM_TOS306
            adda.w    #4*320,a1
ELSE
            adda.w    #4*160,a1
ENDIF
            move.w    #86-1,d0                  ; 86 lines
drawLogoc1: moveq     #6-1,d1                   ; 6 words per raster line
drawLogoc2: move.w    (a0)+,d2                  ; draw 4 plane Atari Logo
            move.w    d2,(a1)+
            move.w    d2,(a1)+
            move.w    d2,(a1)+
            move.w    d2,(a1)+
            dbra      d1,drawLogoc2
IF ROM_TOS306
            adda.w    #320-6*8,a1
ELSE
            adda.w    #160-6*8,a1
ENDIF
            dbra      d0,drawLogoc1
            bra.s     drawLogo3b
drawLogobw:
IF ROM_TOS306
            adda.w    #4*160,a1
ELSE
            adda.w    #4*80,a1
ENDIF
            move.w    #86-1,d0                  ; 86 lines
drawLogobw2:moveq     #12-1,d1                  ; 12 bytes per raster line
drawLogobw3:move.b    (a0)+,(a1)+               ; draw 1 plane Atari Logo
            dbra      d1,drawLogobw3
IF ROM_TOS306
            adda.w    #160-12,a1
ELSE
            adda.w    #80-12,a1
ENDIF
            dbra      d0,drawLogobw2

drawLogo3b: moveq     #32+7,d7                   ; y = 7
            tst.b     (sshiftmd).w
            bne.s     drawLogo4
            moveq     #32+12,d7                 ; y = 12
drawLogo4:  move.l    #$30002,d6
            move.w    #$1b,-(sp)
            move.l    d6,-(sp)
            trap      #$d                       ; Bconout(2, ESC)
            move.w    #$59,4(sp)
            move.l    d6,(sp)
            trap      #$d                       ; Bconout(2, 'Y')
            move.w    d7,4(sp)
            move.l    d6,(sp)
            trap      #$d                       ; Bconout(2, ' '+0)
            move.w    #$20,4(sp)
            move.l    d6,(sp)
            trap      #$d                       ; Bconout(2, ' '+y)
            addq.w    #6,sp

IF !ROM_TOS306
            cmpi.l    #$3e80,(_hz_200).l        ; system running for >80s?
            bcc       ptch_term                 ; (then no ROM CRC check)
ENDIF

*--- Check the ROM CRC
checkROM:
IF ROM_STBOOK
            move.l    #$3fffe,d7                ; offset to ROM CRC (last 2 bytes)
            move.w    #0,d6                     ; number of banks = 1
ELIF ROM_TOS206
            move.l    #$1fffe,d7                ; offset to ROM CRC (last 2 bytes)
            move.w    #1,d6                     ; number of banks = 2
ELIF ROM_TOS306
            move.l    #$1fffe,d7                ; offset to ROM CRC (last 2 bytes)
            move.w    #3,d6                     ; number of banks = 4
ENDIF
            movea.l   #osroml,a5                ; ROM base address
checkROMl:
IF ROM_STBOOK
            move.w    #1,-(sp)                  ; checksum over every byte
ELIF ROM_TOS206
            move.w    #2,-(sp)                  ; checksum over every other byte
ELIF ROM_TOS306
            move.w    #4,-(sp)                  ; checksum over every 4th byte
ENDIF
            move.l    d7,-(sp)                  ; number of bytes
            move.l    a5,-(sp)                  ; buffer address
            bsr       calccrc
            adda.w    #10,sp
            movea.l   a5,a0
IF ROM_STBOOK
            adda.l    d7,a0
ELIF ROM_TOS206
            adda.l    #$3fffc,a0
ELIF ROM_TOS306
            adda.l    #$7fff8,a0
ENDIF
            move.b    (a0),d1                   ; high byte of CRC
            lsl.w     #8,d1
IF ROM_STBOOK
            move.b    1(a0),d1                  ; low byte of CRC
ELIF ROM_TOS206
            move.b    2(a0),d1                  ; low byte of CRC
ELIF ROM_TOS306
            move.b    4(a0),d1                  ; low byte of CRC
ENDIF
            cmp.w     d1,d0                     ; CRC identical?
            bne.s     checkROMbad               ; (no)
            addq.l    #1,a5                     ; next bank
            dbra      d6,checkROMl
            bra.s     ptch_term

badCRCtext: DC.B      'WARNING: BAD ROM CRC IN CHIP ',0
badCRCtextB:DC.B      '.\r\n',0

checkROMbad:move.l    a5,d5
            pea       (badCRCtext).l
            move.w    #9,-(sp)
            trap      #1
            move.b    #'E',d0                   ; 'E' - even
            btst      #0,d5
            beq.s     checkROMbad2
            move.b    #'O',d0                   ; 'O' - odd
checkROMbad2:move.w   d0,2(sp)
            move.w    #2,(sp)
            trap      #1
IF ROM_TOS306
            move.b    #'E',d0                   ; 'E' - even
            btst      #1,d5
            beq.s     checkROMbad3
            move.b    #'O',d0                   ; 'O' - odd
checkROMbad3:move.w   d0,2(sp)
            move.w    #2,(sp)
            trap      #1
ENDIF
            move.l    #badCRCtextB,2(sp)
            move.w    #9,(sp)
            trap      #1
            addq.w    #6,sp
            addq.l    #1,a5
            dbra      d6,checkROMl

* if no monochrome is active, holding down the alternate key forces
* to 320x200x4 instead of 640x480x4 for ST compatibility
IF ROM_TOS306
            cmpi.b    #6,(sshiftmd).w           ; TT high?
            beq.s     ttlowreschk               ; (yes)
            move.l    #$bffff,-(sp)
            trap      #$d                       ; Kbshift()
            addq.w    #4,sp
            btst      #3,d0                     ; alternate pressed?
            beq.s     ttlowreschk               ; (no)
            clr.w     -(sp)
            pea       ($ffffffff).w
            pea       ($ffffffff).w
            move.w    #5,-(sp)
            trap      #$e                       ; switch to low-res
            adda.w    #$c,sp
            move.l    #$808,d0
            movec     d0,cacr
ttlowreschk:
ENDIF

* During boot till this point any exception triggers a _coldboot, which
* erases the first MB and resets. From now own we point the exceptions
* to _term, which draws the bombs and terminates the currently running app
ptch_term:

IF ROM_STBOOK
            move.l    #$1000000,d1              ; d1 -> exception number
            lea       _term(pc),a0              ; new exception vector
            adda.l    d1,a0                     ; add the exception number into the upper 8 bits (2 = bus error)
            adda.l    d1,a0
            lea       (busexception).w,a1       ; start with the bus error exception
            move.w    #$3f,d0
            move.l    #_coldboot,d2             ; old exception vector
ptch_term1: cmp.l     (a1)+,d2                  ; is it pointing to _coldboot?
            bne.s     ptch_term2                ; no -> ignore It
            move.l    a0,$fffc(a1)              ; point it to _term
ptch_term2: adda.l    d1,a0                     ; increment the exception number in the upper 8 bits
            dbra      d0,ptch_term1             ; next vector ->
ENDIF

            bsr       _dskboot                  ; attempt to boot from disk
            bsr       mtest                     ; memory test and attempt to boot from DMA
            bsr       runresapps                ; execute resident applications in memory

            tst.w     (_cmdload).l              ; load shell from disk?
            beq.s     st_1                      ; (no -- execute GEM in ROM)
            bsr       _auto
            move.l    #osroml,(_sysbase).l      ; -> base of OS
            pea       nullenv(pc)               ; null environment string
            pea       nullenv(pc)               ; null argument string
            pea       cmdname(pc)               ; push shell filename
            clr.w     -(sp)                     ; load-and-go flavor of execute
            bra.s     st_x                      ; exec shell ("never return")

*--- bring up GEM:
st_1:       bsr       _auto                     ; do auto-exec
IF ROM_STBOOK
            bsr       _auto_ROMDISK
ENDIF
            move.l    #osroml,(_sysbase).l      ; -> base of OS

*--- kludge up an enviroment string
            lea       orig_env(pc),a0           ; kludge up an environment string, a0 -> original environment string
            movea.l   #the_env,a1               ; a1 -> place to put it
st_2:       cmpi.b    #'#',(a0)                 ; look for drive# character
            bne.s     st_3                      ; (not it)
            movea.l   a1,a2                     ; a2 -> place to put drive#
st_3:       move.b    (a0)+,(a1)+               ; copy a byte
            bpl.s     st_2                      ; copy while not end-of-string
            move.b    (_bootdev).l,d0           ; compute drive#, and shove it
            add.b     #$41,d0                   ; into the env string at the
            move.b    d0,(a2)                   ; appropriate spot

*--- kludge up an enviroment string:
            pea       (the_env).l               ; push address of environment string
            pea       (nullenv).l               ; no arguments
            pea       nullenv(pc)               ; null shell name (in ROM, after all)
            move.w    #5,-(sp)                  ; createPSP flavor of exec
            move.w    #$4b,-(sp)                ; exec function#
            trap      #1                        ; get pointer to PSP
            adda.w    #14,sp                    ; (clean up cruft)
            movea.l   d0,a0                     ; a0 -> PSP
            move.l    (exec_os).l,8(a0)         ; stuff saddr of GEM in PSP
            pea       (the_env).l               ; our environment string
            move.l    a0,-(sp)                  ; push addr of PSP
            pea       nullenv(pc)               ; null filename
            move.w    #4,-(sp)                  ; just-go
st_x:       move.w    #$4b,-(sp)                ; function = exec
            trap      #1                        ; do it
            adda.w    #14,sp                    ; cleanup stack

*+
* When startup fails (or if the exec returns,
* which "cannot happen") fake a system reset:
*-
            jmp       (reseth).l                ; back to the beginning...

*+
* Default enviroment string
* Cannot be more than 20 chars long without modifying
* the declaration for the_env,
* Any char >= $80 terminates the string (and is included in it)
* The last '#' character is replaced by the boot drive's name (A, B, ...)
*-
orig_env:   DC.B      'PATH=',0                 ; default pathname
            DC.B      '#:\',0                   ; is the boot device
            DC.B      $00                       ; terminate env string
            DC.B      $ff                       ; end of the env string (for our copy)

cmdname:    DC.B      'COMMAND.PRG',0           ; shell name
gemname:    DC.B      'GEM.PRG',0               ; desktop name
            DC.B      0
            DC.B      0

setikbd:    DC.B      $80,$01

*+
* _dskboot - boot (or return diagnostics)
* Passed:       nothing
* Returns:      D0.W = error number (if nonzero)
*-
_dskboot:   moveq     #3,d0                     ; %%3 ap cart
            bsr       cartscan
IF ROM_STBOOK
            bsr       cartscan_STBOOK_EXTROM
ENDIF
            movea.l   (hdv_boot).w,a0           ; go through boot vector
            jsr       (a0)
            tst.w     d0                        ; any errors?
            bne.s     dskb1                     ; (yes -- punt)
            movea.l   (_dskbufp).w,a0           ; a0 -> disk buffer
            jsr       (a0)                      ; execute boot sector (it might return)
dskb1:      rts                                 ; return status

mtest:      move.l    #80*200,d7                ; 80s
            cmp.l     (_hz_200).l,d7            ; is the system running for > 80s?
            bcs       dmaBoot                   ; (no memory test)
            movea.w   #0,a5                     ; done status = false
mtestloop:  cmpa.w    #0,a5                     ; memtest done?
            bne       mtest4                    ; (yes)
            bsr       memtest
            movea.w   d0,a5                     ; memtest done?
            beq       mtest5                    ; (no)
            move.w    #$1b,-(sp)
            move.l    #$30002,-(sp)
            trap      #$d                       ; Bconout(2, ESC)
            move.w    #'p',4(sp)
            move.l    #$30002,(sp)
            trap      #$d                       ; Bconout(2, 'p') Reverse video
            move.w    #$1b,4(sp)
            move.l    #$30002,(sp)
            trap      #$d                       ; Bconout(2, ESC)
            move.w    #'w',4(sp)
            move.l    #$30002,(sp)
            trap      #$d                       ; Bconout(2, 'w') Discard end of line
            move.l    #200,d5                   ; 200 = 1s
            move.w    #79,d4                    ; 79
            tst.b     (sshiftmd).l
            bne.s     mtest2
            move.l    #2*200,d5                 ; 2s
            moveq     #39,d4                    ; 39
mtest2:     move.l    d5,d6
            move.l    d4,d3
mtestsp:    move.w    #' ',4(sp)                ; d3 + 1 spaces
            move.l    #$30002,(sp)
            trap      #$d                       ; Bconout(2, ' ')
            dbra      d4,mtestsp
            move.w    #$d,4(sp)
            move.l    #$30002,(sp)
            trap      #$d                       ; Bconout(2, 13) CR
            subq.l    #1,d3
mtestsp2:   move.w    #' ',4(sp)                ; d3 spaces
            move.l    #$30002,(sp)
            trap      #$d                       ; Bconout(2, ' ')
            dbra      d3,mtestsp2
            move.w    #$1b,4(sp)
            move.l    #$30002,(sp)
            trap      #$d                       ; Bconout(2, ESC)
            move.w    #'q',4(sp)
            move.l    #$30002,(sp)
            trap      #$d                       ; Bconout(2, 'q') Normal video
            addq.l    #6,sp
mtest4:     cmp.l     (_hz_200).l,d6
            bhi.s     mtest5
            move.w    #$1b,-(sp)
            move.l    #$30002,-(sp)
            trap      #$d                       ; Bconout(2, ESC)
            move.w    #'K',4(sp)
            move.l    #$30002,(sp)
            trap      #$d                       ; Bconout(2, 'K') Clear to eol
            move.w    #8,4(sp)
            move.l    #$30002,(sp)
            trap      #$d                       ; Bconout(2, 8) Backspace
            addq.w    #6,sp
            add.l     d5,d6
mtest5:     cmpa.w    #0,a5                     ; memtest done?
            beq.s     mtest6                    ; (no)
            cmp.l     (_hz_200).l,d7
            bls.s     mtest8
mtest6:
IF ROM_STBOOK
            move.w    (tt_mcu+4).l,d0
            not.w     d0
            and.w     #$c,d0                    ; check bit 2 & 3
            bne.s     mtest7
ENDIF
            move.l    #$10002,-(sp)
            trap      #$d                       ; Bconstat(2) - key pressed?
            addq.l    #4,sp
            tst.l     d0
            beq       mtestloop                 ; (no - continue)
            move.l    #$20002,-(sp)
            trap      #$d                       ; Bconin(2) - read key
            addq.l    #4,sp
mtest7:     cmpa.w    #0,a5                     ; memtest done?
            bne.s     mtest8                    ; (yes)
            bsr       memtestabort
            move.l    d7,(_hz_200).l
            bra.s     dmaBoot
mtest8:     move.l    d7,(_hz_200).l
            move.w    #$d,-(sp)
            move.l    #$30002,-(sp)
            trap      #$d                       ; Bconout(2, 13) CR
            move.w    #$1b,4(sp)
            move.l    #$30002,(sp)
            trap      #$d                       ; Bconout(2, ESC)
            move.w    #'K',4(sp)
            move.l    #$30002,(sp)
            trap      #$d                       ; Bconout(2, 'K') Clear to eol
            addq.l    #6,sp

*--- boot from DMA device
dmaBoot:
IF ROM_STBOOK
            clr.w     (the_env).l               ; index to the dmaDevList
ELIF ROM_TOS206
            moveq     #$10,d4
ELIF ROM_TOS306
            jsr       (_scsisubb).l

            moveq     #8,d4
            move.b    (scu_gp1).w,d0
            and.w     #$f8,d0
            move.w    d0,(defaultBootDevice).w
            bne.s     dmaBoot2
            pea       (defaultBootDevice).w
            move.w    #2,-(sp)
            clr.l     -(sp)
            jsr       (_NVMaccess).l
            adda.w    #$a,sp
            tst.w     d0
            beq.s     dmaBoot2
ENDIF
            clr.w     (defaultBootDevice).w
dmaBoot2:   move.w    #1,d1                     ; d1 -> 2 tries per device
dmaBoot3:   move.w    d1,-(sp)
IF ROM_STBOOK
            move.w    (the_env).l,d4
            move.b    dmaDevList(pc,d4.w),d4
ENDIF
            move.w    d4,-(sp)                  ; pdev
            move.l    (_dskbufp).w,-(sp)        ; buf = _dskbufp
            move.w    #1,-(sp)                  ; count = 1
            clr.l     -(sp)                     ; sectnum = 0
            jsr       (_dmaread).l              ; read first sector of this device
            adda.w    #12,sp
            move.w    (sp)+,d1
            tst.l     d0                        ; read successful?
            beq.s     dmaBoot4                  ; yes ->
            addq.l    #1,d0                     ; error == time out?
            dbeq      d1,dmaBoot3               ; timeout or another try left? ->
            bra.s     dmaexecl                  ; try next device
dmaBoot4:   movea.l   (_dskbufp).w,a0
            move.w    #$ff,d0                   ; 256 word checksum over the boot sector
            moveq     #0,d1
dmaBoot5:   add.w     (a0)+,d1
            dbra      d0,dmaBoot5
            cmp.w     #$1234,d1                 ; checksum == 0x1234?
            beq.s     dmaexec                   ; execute this valid boot sector ->
dmaexecl:
IF ROM_STBOOK
            move.w    (the_env).l,d4
            addq.w    #1,d4                     ; increment next device
            move.w    d4,(the_env).l
            cmpi.b    #$ff,dmaDevList(pc,d4.w)  ; end of the device list?
            bne.s     dmaBoot2                  ; no -> continue with the next one
ELSE
            addq.w    #1,d4
            move.w    d4,d0
            and.w     #7,d0
            bne.s     dmaBoot2
            cmp.w     #8,d4
            beq.s     dmaBootx
            moveq     #0,d4
            bra.s     dmaBoot2
dmaBootx:
ENDIF
            rts                                 ; no valid boot sector found -> return

IF ROM_STBOOK
dmaDevList: DC.B      $10,$11,$00,$01,$02,$03,$04,$05 ;boot order of DMA devices, $ff terminates the list
            DC.B      $06,$07,$ff
ENDIF

dmaexec:    movea.l   (_dskbufp).w,a0           ; pointer to the boot sector buffer
            move.l    #'DMAr',d3                ; d3 -> 'DMAr' magic
            move.w    d4,d7                     ; d4 -> pdev
            asl.w     #5,d7                     ; d7 = 0
            move.w    (defaultBootDevice).w,d5  ; defaultBootDevice = 0
IF !ROM_STBOOK
            move.l    d4,-(sp)
ENDIF
            move.l    (hdv_rw).l,-(sp)          ; save read sector function pointer
            jsr       (a0)                      ; execute boot sector
            move.l    (sp)+,d0
IF !ROM_STBOOK
            move.l    (sp)+,d4
ENDIF
            cmp.l     (hdv_rw).w,d0             ; did the read sector function change?
            beq.s     dmaexecl                  ; no -> no device driver was loaded -> continue to load boot sectors
            rts                                 ; a new device driver was loaded -> return


*+
* cartscan - scan cartridge memory for runable applications
* Passed:       d0 = bit# to test in application's initialization vector
* Returns:      after all applications have been examined
* Uses:         a0,d0
*-
IF ROM_STBOOK
cartscan_STBOOK_EXTROM:lea (STBOOK_EXTROM).l,a0
            bra.s     cartscan2
ENDIF

cartscan:   lea       (cartbase).l,a0           ; scan cartridge memory for runable applications, a0 -> cartridge memory
cartscan2:  cmpi.l    #$abcdef42,(a0)+          ; correct magic number?
            bne.s     ca_r                      ; (no, so return)
ca_1:       btst      d0,4(a0)                  ; test bit in MSB of INIT address
            beq.s     ca_2                      ; (not set, so don't execute)
            movem.l   d0-d7/a0-a6,-(sp)         ; save everything
            move.l    4(a0),d0                  ; d0 -> initialization address
            and.l     #$ffffff,d0               ; mask out bit# in the top 8 bits of the address
            movea.l   d0,a0
            jsr       (a0)                      ; call cartridge application
            movem.l   (sp)+,d0-d7/a0-a6         ; restore everything
ca_2:       tst.l     (a0)                      ; test link address
            movea.l   (a0),a0                   ; a0 -> next header (or NULL)
            bne.s     ca_1                      ; loop on next header
ca_r:       rts

_rts:       rts


*+
* memchk - check pattern written to memory
*       Passed:         d1.l = offset
*                       a0 = base of pattern ($1f8 bytes long)
*                       a5 -> return address
*
*       Returns:        EQ: the pattern matched
*                       NE: the pattern didn't match
*
*       Uses:           d0.w, a1
*       Called-by:      Coldstart memory-sizing routine.
*-
memchk:     adda.l    d1,a0                     ; a0 -> memory to check
            clr.w     d0                        ; zap pattern seed
            lea       $1f8(a0),a1               ; a1 -> ending address
memchk1:    cmp.w     (a0)+,d0                  ; match?
            bne.s     memchkr                   ; (no -- return NE)
            add.w     #$fa54,d0                 ; yes -- bump pattern
            cmpa.l    a0,a1                     ; matched entire pattern?
            bne.s     memchk1                   ; (no)
memchkr:    jmp       (a4)                      ; "return" to caller


*+
* val_memval - test memory configuration validation
*  Passed:      a6 -> return addressd
*  Returns:     a5 -> 0 (quick zeropage)
*               EQ: memory setup OK
*               NE: memory never configured succesfully
*
*-
val_memval: cmpi.l    #$752019f3,(memvalid).w   ; test memory configuration validation, check first magic number
            bne.s     val_mr                    ; (mismatched -- return NE)
            cmpi.l    #$237698aa,(memval2).w    ; check one more (for paranoia)
            bne.s     val_mr                    ; (mismatched -- return NE)
            cmpi.l    #$5555aaaa,(memval3).w    ; check a third time (for more paranoia)
val_mr:     jmp       (a6)                      ; return EQ/NE


*+
* Default palette assignments.
*  Sort of corresponding to the GSX spec.
*-
colors:     DC.W      $0fff                     ; 0 white
            DC.W      $0f00                     ; 1 red
            DC.W      $00f0                     ; 2 green
            DC.W      $0ff0                     ; 3 yellow
            DC.W      $000f                     ; 4 blue
            DC.W      $0f0f                     ; 5 magenta
            DC.W      $00ff                     ; 6 cyan
            DC.W      $0555                     ; 7 "low white"
            DC.W      $0333                     ; 8 grey
            DC.W      $0f33                     ; 9 light red
            DC.W      $03f3                     ; 10 light green
            DC.W      $0ff3                     ; 11 light yellow
            DC.W      $033f                     ; 12 light blue
            DC.W      $0f3f                     ; 13 light magenta
            DC.W      $03ff                     ; 14 light cyan
            DC.W      $0000                     ; 15 black

*+
* hbl - force caller to IPL
* Oh-well:      "Yeah, it sucks, but it works" (--lt)
*
* Note:         Hacks caller's IPL to 3 (if it was 0). This is
*               a kludge against fascist programs and certain
*               debuggers that insist on starting processes up
*               at IPL 0.
*
*-
hbl:        move.w    d0,-(sp)                  ; save d0
            move.w    2(sp),d0                  ; get pushed SR
            and.w     #$700,d0                  ; strip crufty bits
            bne.s     hbl_r                     ; not IPL 0, so punt
            ori.w     #$300,2(sp)               ; force caller to IPL 3
hbl_r:      move.w    (sp)+,d0                  ; restore d0, back to victim
            rte

*+
* vbl - vertical black interrupt handler
*
*-
vbl:        addq.l    #1,(_frclock).l           ; bump frame clock
            subq.w    #1,(vblsem).l             ; P(vblsem) -- vblank locked?
            bmi       vblret

            movem.l   d0-d7/a0-a6,-(sp)         ; save registers
            addq.l    #1,(_vbclock).l           ; bump unblocked-frame Clock

*------ Call deferred interrupt vectors
            move.w    (nvbls).l,d7              ; d7 = # of deferred vblank vectors
IF ROM_STBOOK
            beq.s     vbl12                     ; (punt if no vectors)
ELSE
            beq       vbl12                     ; (punt if no vectors)
ENDIF
            subq.l    #1,d7                     ; turn into DBRA count
            movea.l   (_vblqueue).w,a0          ; a0 -> vectors
vbl10:      movea.l   (a0)+,a1                  ; a1 -> deferred vector
            cmpa.w    #0,a1                     ; if(a1 == NULL) continue;
            beq.s     vbl11
            movem.l   d7-d7/a0,-(sp)            ; save registers
            jsr       (a1)                      ; call routine
            movem.l   (sp)+,d7-d7/a0            ; restore registers
vbl11:      dbra      d7,vbl10                  ; loop for more vectors

IF ROM_TOS206 || ROM_TOS306
            move.b    (gpip).w,d1
            tst.b     ($a02).l                  ; microwire interface available?
            beq.s     vbl11b                    ; (no)
            move      sr,-(sp)
            ori       #$700,sr
vbl11a:     move.b    (sndmactl+1).w,d0
            move.b    (gpip).w,d1
            btst      #7,d1
            sne       d1
            move.b    (gpip).w,d2
            btst      #7,d2
            sne       d2
            cmp.b     d1,d2
            bne.s     vbl11a
            cmp.b     (sndmactl+1).w,d0
            bne.s     vbl11a
            move      (sp)+,sr
            btst      #0,d0
            beq.s     vbl11b
            not.b     d1
vbl11b:

IF ROM_TOS206
            move.b    (v_shf_mod).w,d0
            and.b     #3,d0
            cmp.b     #2,d0
            bge.s     vbl11c
ELIF ROM_TOS306
            move.b    (shift_tt).w,d0
            and.b     #7,d0
            cmp.b     #6,d0
            beq.s     vbl11c
ENDIF
            btst      #7,d1
            bne.s     vbl11e
            bsr       _wvbl
IF ROM_TOS206
            move.b    #2,d0
ELIF ROM_TOS306
            move.b    #6,d0
ENDIF
            bra.s     vbl11d
vbl11c:     btst      #7,d1
            beq.s     vbl11e
            move.b    (defshiftmd).w,d0
IF ROM_TOS206
            cmp.b     #2,d0
            blt.s     vbl11d
ELIF ROM_TOS306
            cmp.b     #6,d0
            bne.s     vbl11d
ENDIF
            clr.b     d0
vbl11d:
            move.b    d0,(sshiftmd).w
IF ROM_TOS206
            move.b    d0,(v_shf_mod).w
ELIF ROM_TOS306
            move.b    (shift_tt).w,d1
            and.b     #$f8,d1
            or.b      d0,d1
            move.b    d1,(shift_tt).w
ENDIF

            movea.l   (swv_vec).w,a0
            jsr       (a0)
vbl11e:
ENDIF
            jsr       (blink).l                 ; blink cursor

*--- reload color palettes
            tst.l     (colorptr).w              ; reload color palettes, if(colorptr != NULL)....
            beq.s     vbl1
            movea.l   (colorptr).w,a0           ; a0 -> user's color base
            lea       (palette).w,a1            ; a1 -> hardware palette base
            move.w    #7,d1                     ; d0 = count
vbl2:       move.l    (a0)+,(a1)+               ; load a palette
            dbra      d1,vbl2                   ; ...and repeat
            clr.l     (colorptr).w              ; zap colorptr

*--- reload display base register
vbl1:       tst.l     (screenpt).w              ; reload display base register, if(screenpt == NULL) don't
            beq.s     vbl5
            move.l    (screenpt).w,(_v_bas_adr).w ;set OS variable
            move.b    (_v_bas_adr+2).w,(v_bas_m).w ;load "middle" pointer
            move.b    (_v_bas_adr+1).w,(v_bas_h).w ;load "high" pointer
            move.b    (_v_bas_adr+3).w,(v_bas_l).w ;load "low" pointer

*------ Floppy drive-select timeout
vbl5:       bsr       _flopvbl                  ; (no args)

vbl12:      tst.w     (_prtcnt).w               ; monitor screen dump flag - printscreen active?
            bne.s     no_print                  ; no

*+
* printScreen
*
* We re-enable vblanks here, until the printScreen finishes -
*
*-
            bsr       _dumpit                   ; dump screen

no_print:   movem.l   (sp)+,d0-d7/a0-a6         ; restore registers & return (and a handy RTE)
vblret:     addq.w    #1,(vblsem).l             ; V(vblsem) [release vblank]
_rte:       rte


*+
* wvbl - wait for next vblank
* Passed:       nothing
* Returns:      at beginning of next vblank
* Uses:         D0
*-
wvbl:       move      sr,-(sp)                  ; save psw
            andi      #$fbff,sr                 ; enable vbl interrupts
            move.l    (_frclock).l,d0           ; d0 = frame clock
wvbl1:      cmp.l     (_frclock).l,d0           ; wait for clock to change
            beq.s     wvbl1
            move      (sp)+,sr                  ; then restore psw & return
            rts


*+
* _critic - critical error handler binding for C
* Falls-into:   _critich
* (screwy way to save two bytes....)
*
*-
crit_err:   move.l    (etv_critic).l,-(sp)      ; jump through critic vector

*+
* _critich - default critical error handler
* Loads -1 into D0 and returns
*
*-
_critich:   moveq     #-1,d0                    ; default return value = ERROR
            rts                                 ; return to trap invoker


*+
* trp13h - GEMDOS BIOS trap handler (trap 13)
* trp14h - Atari BIOS extensions (trap 14)
* traph  - trap handler
*
* On the stack:
*       From super-             From user
*       visor mode:             mode:
*       -----------             ------------
*       N(sp) args              N(usp) args
*       6(sp) func#             6(usp) func#
*       2(sp) ret               2(ssp) ret
*        (sp) SR                 (ssp) SR
*
* Returns:      anything in D0
* Uses:         d0-d2/a0-a2
* Keeps:        C registers
*
* Notes         BIOS traps are re-entrant to 'nlevels' (declared near the
*               beginning of this file).  Attempts to recurse more than
*               'nlevels' will probably result in a crash.
*
*               BIOS calls may be made from user mode.  (This differs from
*               the current GEMDOS spec, which states that BIOS traps are
*               available from supervisor mode only).
*
*-
trp14h:     lea       trp14tab(pc),a0           ; a0 -> trap14 jump table
            bra.s     traph
trp13h:     lea       trp13tab(pc),a0           ; a0 -> trap13 jump table
traph:      movea.l   (savptr).l,a1             ; a1 - registers save area
            move.w    (sp)+,d0                  ; pop SR and save it
            move.w    d0,-(a1)                  ; (need in D0 for user-mode test)
            move.l    (sp)+,-(a1)               ; save return addr
IF !ROM_TOS306
            tst.w     (_longframe).w
            beq.s     traph2
ENDIF
            tst.w     (sp)+
traph2:     movem.l   d3-d7/a3-a7,-(a1)         ; save C registers + super stack
            move.l    a1,(savptr).l             ; update save-area pointer

* make sure we have the right stack, call function:
            btst      #13,d0                    ; was in user mode?
            bne.s     b_supr                    ; (was in super: use super stack)
            move      usp,sp                    ; use user stack
b_supr:     move.w    (sp)+,d0                  ; get function#
            cmp.w     (a0)+,d0                  ; out of range?
            bge.s     b_exit                    ; (yes, so punt)
            move.w    d0,d1
            lsl.w     #2,d1                     ; turn D0 into longword index
            move.l    (a0,d1.w),d1              ; get pointer to function handler
IF ROM_TOS306
            bclr      #0,d1                     ; clear the indirect but set the old status in Z
            movea.l   d1,a0
            beq.s     b_1                       ; points to code
ELSE
            movea.l   d1,a0                     ; (quick and dirty test-for-negative)
            bpl.s     b_1                       ; points to code
ENDIF
            movea.l   (a0),a0                   ; indirect through RAM...
b_1:        suba.l    a5,a5                     ; a5 -> zero page
            jsr       (a0)                      ; call BIOS function

* restore registers, cleanup stack and return:
b_exit:     movea.l   (savptr).l,a1             ; a1 -> register save area
            movem.l   (a1)+,d3-d7/a3-a7         ; restore C registers + super stack
IF !ROM_TOS306
            tst.w     (_longframe).w
            beq.s     trph3
ENDIF
            clr.w     -(sp)
trph3:      move.l    (a1)+,-(sp)               ; push return address
            move.w    (a1)+,-(sp)               ; push old SR
            move.l    a1,(savptr).l             ; update save-pointer
            rte                                 ; return to caller


*------ jump table for GEMDOS functions:
trp13tab:   DC.W      $000c                     ; number of entries in jump table
            DC.L      _get_mpb
            DC.L      bconstat
            DC.L      bconin
            DC.L      bconout
IF ROM_TOS306
            DC.L      hdv_rw | 1
ELSE
            DC.L      hdv_rw
ENDIF
            DC.L      _setexc
            DC.L      _tickcal
IF ROM_TOS306
            DC.L      hdv_bpb | 1
ELSE
            DC.L      hdv_bpb
ENDIF
            DC.L      bcostat
IF ROM_TOS306
            DC.L      hdv_mediach | 1
ELSE
            DC.L      hdv_mediach
ENDIF
            DC.L      _drvmap
            DC.L      _shift

*------ jump table for Atari BIOS extensions:
trp14tab:
IF ROM_TOS306
            DC.W      $0060                     ; number of entry points
ELSE
            DC.W      $0041                     ; number of entry points
ENDIF
            DC.L      initmous
            DC.L      _rts
            DC.L      _physbase
            DC.L      _logbase
            DC.L      _getrez
            DC.L      _setscreen
            DC.L      _setpalette
            DC.L      _setcolor
            DC.L      _floprd
            DC.L      _flopwr
            DC.L      _flopfmt
            DC.L      _getdsb
            DC.L      midiws
            DC.L      mfpint
            DC.L      iorec
            DC.L      rsconf
            DC.L      keytrans
            DC.L      _rand
            DC.L      _proto_bt
            DC.L      _flopver
            DC.L      _dumpit
            DC.L      _cursconf
            DC.L      settime
            DC.L      gettime
            DC.L      bioskeys
            DC.L      ikbdws
            DC.L      jdisint
            DC.L      jenabint
            DC.L      giaccess
            DC.L      offgibit
            DC.L      ongibit
            DC.L      xbtimer
            DC.L      dosound
            DC.L      setprt
            DC.L      ikbdvecs
            DC.L      kbrate
            DC.L      _prtblk
            DC.L      wvbl
            DC.L      supexec
            DC.L      puntaes
            DC.L      _rts
            DC.L      floprate
            DC.L      _dmaread
            DC.L      _dmawrite
            DC.L      _bconmap
            DC.L      _rts
IF ROM_TOS306
            DC.L      _NVMaccess
ELSE
            DC.L      _rts
ENDIF
IF ROM_STBOOK || ROM_TOS206
            DC.L      _Waketime
ELSE
            DC.L      _rts
ENDIF
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      blitmode
IF ROM_TOS306
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts

            DC.L      _EsetShift
            DC.L      _EgetShift
            DC.L      _EsetBank
            DC.L      _EsetColor
            DC.L      _EsetPalette
            DC.L      _EgetPalette
            DC.L      _EsetGrey
            DC.L      _EsetSmear

            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts

* this routine seems to be unused?
_e00fae:    btst      #0,1(sp)                  ; read?
            bne.s     _hdv_rw_ptc               ; (no) => no patch necessary, write doesn't need to clear the cache
            move.l    #_hdv_rw_ptt,(sp)         ; patch return address
_hdv_rw_ptc:movea.l   (hdv_rw).w,a0
            jmp       (a0)

_hdv_rw_ptt:move.l    d0,-(sp)
            bsr       _flush_cache              ; flush the CPU cache
            move.l    (sp)+,d0
            jmp       (b_exit).l                ; exit trap
ENDIF

*+
* supexec - execute some code in supervisor mode
*
*-
supexec:    movea.l   4(sp),a0                  ; a0 -> code
            jmp       (a0)                      ; execute it


*+
* Character device I/O
*
* No check is made for "bogus" device numbers.  A wierd device
* number will result in a crash.
*
*-
bconstat:   lea       (xconstat).w,a0           ; a0 -> stat table
            moveq     #0,d1
            bra.s     chsw
bconin:     lea       (xconin).w,a0             ; a0 -> input table
            moveq     #4,d1
            bra.s     chsw
bcostat:    lea       (xcostat).w,a0            ; a0 -> ostat table
            moveq     #8,d1
            bra.s     chsw
bconout:    lea       (xconout).w,a0            ; a0 -> output table
            moveq     #$c,d1
chsw:       move.w    4(sp),d0                  ; get device number
            cmp.w     #5,d0
            bls.s     chsw2
            subq.l    #6,d0
            cmp.w     (bdevcount).w,d0
            bcc.s     chsw1
            movea.l   (bdevtabptr).w,a0
            asl.w     #3,d0
            adda.w    d0,a0
            add.w     d0,d0
            adda.w    d0,a0
            movea.l   (a0,d1.w),a0
            jmp       (a0)

chsw1:      moveq     #0,d0
            rts

chsw2:      lsl.w     #2,d0                     ; turn into longword index
            movea.l   (a0,d0.w),a0              ; get address of handler
            jmp       (a0)                      ; jump to it

*+
* Jump tables for
*       0 - lst: (printer)
*       1 - aux: (rs232)
*       2 - con: (screen)
*       3 - Atari midi
*       4 - Atari keyboard (output only)
*       5 - raw console output (bypass vt52 pressure cooker)
*
* No range checking is performed.  If a bogus device number
* is passed to the BIOS' character I/O handler, the system
* will crash to become funky duex.
*
*-
tconstat:   DC.L      _rts
            DC.L      auxistat
            DC.L      constat
            DC.L      midstat
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
tconin:     DC.L      _lstin
            DC.L      auxin
            DC.L      conin
            DC.L      midin
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
tcostat:    DC.L      _lstostat
            DC.L      _auxostat
            DC.L      conoutst
            DC.L      ikbdost
            DC.L      midiost
            DC.L      _rts
            DC.L      _rts
            DC.L      _rts
tconout:    DC.L      _lstout
            DC.L      _auxout
            DC.L      conout
            DC.L      midiwc
            DC.L      ikbdwc
            DC.L      _asc_out
            DC.L      _rts
            DC.L      _rts

*+
* _drvmap - return "active drive" bit vector
* Passed:       nothing
* Returns:      D0.L = a bit vector of live (rwabs'able) block device
*
*-
_drvmap:    move.l    (_drvbits).w,d0
            rts


*+
* _shift - get/set keyboard shift state
* Synopsis:     LONG _shift(bits)
*               WORD bits
*
* Returns:      D0.B = shift/alt/ctl/shift' bits
*
* Note:         Since the shift bits are changed at interrupt
*               level, any set from a get of the shift state
*               must be done as a critical section.
*
*-
_shift:     moveq     #0,d0
            move.b    (kb_shift).w,d0
            move.w    4(sp),d1
            bmi.s     shifr
            move.b    d1,(kb_shift).w
shifr:      rts


*+
* _get_mpb - return initial memory parameter block
* Synopsis:     _get_mpb(mpb)
*               MPB *mpb;
*
* Returns:      The properly initialized MPB.
*               The MPB points to an MD somewhere in BSS. The MD /must/
*               be in RAM since DOS will modify it.
*-
_get_mpb:   movea.l   4(sp),a0                  ; a0 -> MPB
            lea       (themd).w,a1              ; a1 -> MD

*--- initialize MPB:
            move.l    a1,(a0)                   ; mp_mfl = &themd;
            clr.l     4(a0)                     ; mp_mal = NULL;
            clr.l     8(a0)                     ; mp_rover = NULL;

*---- initialize MD:
            clr.l     (a1)                      ; m_link = NULL;
            move.l    (_membot).w,4(a1)         ; m_start = _membot;
            move.l    (_memtop).w,d0            ; m_length = _memtop - membot;
            sub.l     (_membot).w,d0
            move.l    d0,8(a1)
            clr.l     $c(a1)                    ; m_own = NULL;

IF ROM_TOS306
            cmpi.l    #$1357bd13,(ramvalid).l   ; fast mem available?
            bne.s     _get_mpbret               ; (no)
            cmpi.l    #$1000000,(ramtop).l      ; fast mem installed?
            bls.s     _get_mpbret               ; (no)
            lea       ($0a02).w,a2              ; themd_tt
            move.l    a2,(a1)                   ; themd.m_link = &themd_tt
            clr.l     (a2)                      ; m_link = NULL
            move.l    #$1000001,4(a2)           ; m_start = base address of fast mem | 1
            move.l    (ramtop).l,d0
            sub.l     #$1000000,d0
            move.l    d0,8(a2)                  ; m_length = size of fast mem
            clr.l     $c(a2)                    ; m_own = NULL
_get_mpbret:
ENDIF
            rts

*+
* _setexc - set exception vector
* Synopsis:     setexc(vecno, addr)
*               If 'addr' < 0, the vector is not set.
*
*               Extended vectors ($100 though $107) are located in the
*               first eight longwords of BSS, at $400. This is for
*               convienience -- they could really be located anywhere.
*
* Returns:      D0.L = original vector value
*
*-
_setexc:    move.w    4(sp),d0                  ; d0 = vector #
            lsl.w     #2,d0                     ; turn into longword index
            suba.l    a0,a0
            lea       (a0,d0.w),a0              ; a0 -> vector
            move.l    (a0),d0                   ; d0 = current vector address
            move.l    6(sp),d1                  ; d1 = what_to_change_it_to
            bmi.s     setex1                    ; punt if (d1 < 0)
            move.l    d1,(a0)                   ; set vector address
setex1:     rts

*+
* _tickcal - return system timer calibration value (in ms)
*
*-
_tickcal:   moveq     #0,d0                     ; cast to unsigned longword
            move.w    (_timr_ms).w,d0           ; get calibration
            rts

*+
* _physbase - get physical display base
*
*-
_physbase:  moveq     #0,d0                     ; cleanup pointer-to-be
            move.b    (v_bas_h).w,d0            ; load and shift bits 16..23
            lsl.w     #8,d0
            move.b    (v_bas_m).w,d0            ; load and shift bits 8..15
            lsl.l     #8,d0
            tst.b     (STEFlag).l
            bne.s     _physbase2
            move.b    (v_bas_l).w,d0
_physbase2: rts                                 ; return pointer in d0

*+
* _logbase -get logical display base
*
*-
_logbase:   move.l    (_v_bas_adr).w,d0         ; set software shadow
            rts

*+
* _getrez - get current screen rez
*-
_getrez:    moveq     #0,d0                     ; cleanup dirty bits
IF ROM_STBOOK
            move.b    (sshiftmd).w,d0           ; get screen rezolution
            and.b     #3,d0                     ; strip garbage bits
ELIF ROM_TOS206
            move.b    (v_shf_mod).w,d0          ; get screen rezolution
            and.b     #3,d0                     ; strip garbage bits
ELIF ROM_TOS306
            move.b    (shift_tt).w,d0
            and.b     #7,d0
ENDIF
            rts                                 ; return rez

*+
* _setscreen - set screen location(s), rez
*       _setscreen(logicalLoc, physicalLoc, rez)
*       LONG logicalLoc, physicalLoc;
*       WORD rez;
*
*-

*--- set logical location:
_setscreen: tst.l     4(sp)                     ; if(logloc < 0) then ignore it
            bmi.s     f5a
            move.l    4(sp),(_v_bas_adr).w      ; set software pointer from logloc

*--- set physical location:
f5a:        tst.l     8(sp)                     ; if(physloc < 0) then ignore it
            bmi.s     f5b
            move.b    9(sp),(v_bas_h).w         ; set bits 16..23 of hardware pointer
            move.b    $a(sp),(v_bas_m).w        ; set bits 8..15 of hardware pointer
            move.b    $b(sp),(v_bas_l).w        ; set bits 0..7 of hardware pointer

*--- set screen resolution (clears the screen, clobbers the cursor):
f5b:        tst.w     $c(sp)                    ; if(rez < 0) then ignore it
            bmi.s     f5r
            bsr       _wvbl                     ; wait for start of vertical-blank
            move.b    $d(sp),(sshiftmd).w       ; set software shadow
IF ROM_TOS306
            move.b    (shift_tt).w,d0
            and.b     #$f8,d0
            or.b      $d(sp),d0
            move.b    d0,(shift_tt).w
ELSE
            move.b    (sshiftmd).w,(v_shf_mod).w ;set hardware location
ENDIF
            clr.w     (vblsem).w                ; disable vblank processing
            jsr       (esc_init).l              ; re-initialize glass tty routines
            move.w    #1,(vblsem).w             ; re-enable vblanks
f5r:        rts

*+
* _setpalette - set palette (on next vblank)
*       _setpalette(LONG palettePtr)
*
*-
_setpalette:move.l    4(sp),(colorptr).w        ; set software pointer
            rts                                 ; (updated by vbl handler)

*+
* _setcolor - set single color, return old color
*       _setcolor(WORD colorNum, WORD colorValue)
*
*-
_setcolor:  move.w    4(sp),d1                  ; get color number
            add.w     d1,d1                     ; turn into word index
            and.w     #$1f,d1                   ; force color range (prevent buserr)
            lea       (palette).w,a0            ; a0 -> base of palette memory
            move.w    (a0,d1.w),d0              ; return old color
            tst.b     (STEFlag).l
            beq.s     _setc0a
            and.w     #$777,d0                  ; mask dirty bits
            bra.s     _setc0b
_setc0a:    and.w     #$fff,d0
_setc0b:    tst.w     6(sp)                     ; if new color is <0, don't set it
            bmi.s     _setc1                    ; (punt)
            move.w    6(sp),(a0,d1.w)           ; set new color
_setc1:     rts

*+
* puntaes - throw-away AES, restart the system
*  Passed:      nothing
*  Uses:        everything
*  Returns:     if AES was already thrown away
*
*-
puntaes:    movea.l   osroml+20(pc),a0          ; get pointer to magic
            cmpi.l    #$87654321,(a0)           ; is the magic still there?
            bne.s     paes1                     ; no -- just return
            cmpa.l    (phystop).w,a0            ; is it in ROM?
            bge.s     paes1                     ; yes -- we can't do anything about it
            clr.l     (a0)                      ; clobber AES!
            bra       reseth                    ; restart the system
paes1:      rts

*+
* _term - terminate current process
* Called-by:    Uncaught traps (bus errors, and so on)
* Saves:        processor state (in a bailout area)
*
*-
_term:
IF !ROM_TOS306
            jsr       (savp_2).l                ; stack PC
savp_2:
ENDIF
            move.l    (sp)+,(proc_pc).w         ; save bogus PC + exception number
            movem.l   d0-d7/a0-a7,(proc_regs).w ; common registers
IF ROM_TOS306
            move.l    2(sp),(proc_pc).w
            move.w    6(sp),d0
            and.w     #$fff,d0
            asr.w     #2,d0
            move.b    d0,(proc_pc).w
ENDIF
            move      usp,a0                    ; save USP
            move.l    a0,(proc_usp).w
            moveq     #15,d0                    ; save 16 words off top of
            lea       (proc_stk).w,a0           ; the stack (enough for
            movea.l   sp,a1                     ; any possible 680000 exception)
savp_1:     move.w    (a1)+,(a0)+               ; save a word
            dbra      d0,savp_1
            move.l    #$12345678,(proc_lives).w ; set magic number (procdump lives)

*--- draw an appropriate number of 'shrooms on the screen:
            moveq     #0,d1
            move.b    (proc_pc).w,d1
            subq.w    #1,d1                     ; 2 for bus error, 3 for address, etc.
            bsr.s     do_shroom
            move.l    #savend,(savptr).w        ; clobber BIOS top level
            move.w    #-1,-(sp)                 ; "error" return condition
            move.w    #$4c,-(sp)                ; GEMDOS function #$4C
            trap      #1                        ; "terminate process"
            bra       reseth                    ; on return, reset system
*+
* do_shroom - draw little mushroom clouds on the screen
*  Passed:      d1.w = #shrooms to draw (DBRA count)
*  Returns:     some shrooms on display
*  Uses:        d0-d7/a0-a2
*
*  Discussion:  The graphics ain't all that great.   And this is silly.
*
*-
do_shroom:  move.b    (sshiftmd).w,d7
IF ROM_TOS306
            and.w     #7,d7
ELSE
            and.w     #3,d7
ENDIF
            add.w     d7,d7                     ; d7 = rez index
            moveq     #0,d0
            move.b    (v_bas_h).w,d0
            lsl.w     #8,d0
            move.b    (v_bas_m).w,d0
            lsl.l     #8,d0
            tst.b     (STEFlag).l
            bne.s     dmste
            move.b    (v_bas_l).w,d0
dmste:      movea.l   d0,a0                     ; a0 -> base of mem to draw at
            cmp.w     #6,d7
            blt.s     dm00
            adda.l    #768*100,a0
            bra.s     dm01
dm00:       adda.w    #160*100,a0
dm01:       lea       (mushroom).l,a1           ; a1 -> source from
            move.w    #15,d6                    ; d6 = scanline count
dm0:        move.w    d1,d2                     ; d3 = # to draw on this line
            movea.l   a0,a2                     ; safe ptr to beg of line
dm1:        move.w    mcount(pc,d7.w),d5        ; d5 = #words to replicate
dm2:        move.w    (a1),(a0)+                ; draw a word
            dbra      d5,dm2                    ; (complete single shroom)
            dbra      d2,dm1                    ; another, on the same line
            addq.w    #2,a1                     ; next source word
            adda.w    mwidth(pc,d7.w),a2        ; next dest line
            movea.l   a2,a0
            dbra      d6,dm0                    ; (loop for next line)
            moveq     #30-1,d7
dm3:        bsr       wvbl
            dbra      d7,dm3
            rts                                 ; byebye

mcount:     DC.W      $0003,$0001,$0000,$0000
            DC.W      $0003,$0000,$0000,$0007
mwidth:     DC.W      $00a0,$00a0,$0050,$0000
            DC.W      $0140,$0000,$00a0,$0140

*+
* _fastcpy - "fast" 512-byte copy
* Synopsis:     fastcpy(src, dest)
*
*               Used by _rwabs to fake disk DMA to odd addresses.  Therefore,
*               disk I/O on odd addresses is very slow.  Lose, lose.
*
*-
_fastcpy:   movea.l   4(sp),a0                  ; a0 -> src
            movea.l   8(sp),a1                  ; a1 -> dest
            move.w    #$3f,d0                   ; d0 = move count (64*8 = 512)
fast1:      move.b    (a0)+,(a1)+               ; copy 8 bytes at a time
            move.b    (a0)+,(a1)+               ; to minimize loop overhead
            move.b    (a0)+,(a1)+
            move.b    (a0)+,(a1)+
            move.b    (a0)+,(a1)+
            move.b    (a0)+,(a1)+
            move.b    (a0)+,(a1)+
            move.b    (a0)+,(a1)+
            dbra      d0,fast1
            rts

*+
* Go through hard-disk initialization vector
*
*-
_hinit:     move.l    (hdv_init).l,-(sp)
            rts

autopath:   DC.B      '\AUTO\*.PRG',0
            DC.W      $1234,$5678,$9abc,$def0

*+
* _auto - exec auto-startup files in the appropriate subdirectory
* _auto1 - exec (with filename args)
* Passed:       a0 -> full filespec (pathname)
*               a1 -> filename part of filespec
*               _drvbits: bit vector of active drives
*               _bootdev: contains device to exec from
*
* Returns:      nothing
*
* Note:         If _drvbits%%_bootdev is zero, _auto simply quits (since
*               the device isn't active....)
*
* Uses:         everything
*-
_auto:      move.l    #$bffff,-(sp)
            trap      #$d
            addq.l    #4,sp
            btst      #2,d0                     ; control key pressed?
            bne.s     autoq                     ; (then ignore the AUTO folders)
IF !ROM_TOS306
            move.l    (_drvbits).w,d0           ; d0 - active dev vector
            move.w    (_bootdev).l,d1           ; d1 - dev# to exec from
            btst      d1,d0                     ; is the dev alive?
            beq.s     autoq                     ; (no -- so punt)
ENDIF
            lea       autopath(pc),a0           ; -> path
            lea       autopath+6(pc),a1         ; -> filename
_auto1:     move.l    (sp)+,(autoret).l         ; return addr (used by execlr)
            move.l    a0,(pathname).w           ; setup filename/pathname ptrs
            move.l    a1,(filename).w
IF ROM_TOS306
            move.l    (_drvbits).w,d0           ; d0 - active dev vector
            move.w    (_bootdev).l,d1           ; d1 - dev# to exec from
            btst      d1,d0                     ; is the dev alive?
            beq.s     autoq                     ; (no -- so punt)
ENDIF
            lea       nullenv(pc),a0            ; a0 -> \0\0
            move.l    a0,-(sp)                  ; null enviorment
            move.l    a0,-(sp)                  ; null command tail
            move.l    a0,-(sp)                  ; null shell name
            move.w    #5,-(sp)                  ; Create-PSP subfunction
            move.w    #$4b,-(sp)                ; exec function#
            trap      #1                        ; do DOS CALL
            adda.w    #16,sp
            movea.l   d0,a0                     ; a0 -> PSP
            move.l    #fauto,8(a0)              ; initial PC -> autoexec prog
            move.l    a3,-(sp)                  ; null enviroment
            move.l    d0,-(sp)                  ; -> PSP
            move.l    a3,-(sp)                  ; null shell name
            move.w    #4,-(sp)                  ; just-go
            move.w    #$4b,-(sp)                ; function = exec
            trap      #1                        ; do it
            adda.w    #16,sp                    ; cleanup stack & goodbye
            move.l    (autoret).l,-(sp)
autoq:      rts


*+
* ST Book has another ROMDISK in the 2nd 256kb of the ROM
* which is mapped as drive 'P'.
*-
IF ROM_STBOOK
_auto_ROMDISK:lea     autopathROM(pc),a0        ; -> path
            lea       autopathROM+8(pc),a1      ; -> filename
            bra.s     _auto1

autopathROM:DC.B      'P:\AUTO\*.PRG',0
            DC.W      $1234,$5678,$9abc,$def0
ENDIF

*+
* fauto - exec'd by _auto to do autostartup
*
* Passed:       pathname -> path part of filespec
*               filename -> file path of filespec
*
*-
fauto:      clr.l     -(sp)                     ; get into super mode
            move.w    #$20,-(sp)
            trap      #1
            addq.w    #6,sp                     ; cleanup
            movea.l   d0,a4                     ; a4 -> saved super stack

*---- free up some memory
            movea.l   4(sp),a6                  ; a6 -> base page
            lea       $100(a6),sp               ; sp -> new, safer addr
            move.l    #$100,-(sp)               ; keep $100 (just the basepage)
            move.l    a6,-(sp)                  ; -> start of mem to keep
            clr.w     -(sp)                     ; junk word
            move.w    #$4a,-(sp)                ; setblock(...)
            trap      #1
            addq.w    #6,sp
            tst.w     d0
            bne.s     au_dn                     ; punt on error

            move.w    #7,-(sp)                  ; find r/o+hidden+system files
            move.l    (pathname).l,-(sp)        ; -> filename (on input)
            move.w    #$4e,-(sp)                ; searchFirst
            moveq     #8,d7                     ; d7 = cleanup amount
au1:        pea       (autodma).l               ; setup DTA (for search)
            move.w    #$1a,-(sp)
            trap      #1                        ; search first/search next
            addq.w    #6,sp
            trap      #1
            adda.w    d7,sp                     ; cleanup stack
            tst.w     d0                        ; test for match
            bne.s     au_dn                     ; (no match -- quit)

*--- construct filename from path and the name we just found:
            movea.l   (pathname).l,a0           ; copy pathname
            movea.l   (filename).l,a2           ; a2 -> end+1 of pathname
            lea       (autoname).l,a1
au3:        move.b    (a0)+,(a1)+               ; copy path part of name
            cmpa.l    a0,a2                     ; finished?
            bne.s     au3                       ; (no)
            lea       (autodma+30).l,a0         ; copy fname to end of pathname
au2:        move.b    (a0)+,(a1)+
            bne.s     au2
            pea       nullenv(pc)               ; null enviroment
            pea       nullenv(pc)               ; no command tail
            pea       (autoname).l              ; -> file to exec
            clr.w     -(sp)                     ; load-and-go
            move.w    #$4b,-(sp)                ; exec(...)
            trap      #1
            adda.w    #16,sp
            moveq     #2,d7                     ; reset cleanup amount
            move.w    #$4f,-(sp)                ; searchNext
            bra.s     au1

*+
* The first GEMDOS process can never terminate.
* This is not a good feature.
* Kludge around it - re-initialize the stack
* and return to the guy who called us to begin with.
*
*-
au_dn:      lea       (_supstk+2048).l,sp       ; setup supervisor stack
            move.l    (autoret).l,-(sp)         ; get return addr
            rts                                 ; just jump there ...


*+
* _dumpit: dump screen
*
*-
_dumpit:    movea.l   (scr_dump).l,a0
            jsr       (a0)
            move.w    #-1,(_prtcnt).l
            rts


*+
* _scrdmp - printScreen(), font-end to _prtblk()
*  Passed:      nothing
*  Returns:     nothing
*  Uses:        everything
*-
_scrdmp:    move.l    (_v_bas_adr).w,(p_blkptr).w ;-> screen mem
            clr.w     (p_offset).w              ; offset = 0
            clr.w     d0
            move.b    (sshiftmd).w,d0           ; get w & h
            move.w    d0,(p_srcres).w
            add.w     d0,d0
            lea       reztab(pc),a0
            move.w    (a0,d0.w),(p_width).w     ; set display width, height
            move.w    6(a0,d0.w),(p_height).w
            clr.w     (p_left).w                ; left = right = 0
            clr.w     (p_right).w
            move.l    #$ffff8240,(p_colpal).w   ; -> hardware palettes
            clr.w     (p_masks).w               ; default mask ptr

* draft or final mode
            move.w    (pconfig).w,d1            ; p_dstres = pconfig%%3
            lsr.w     #3,d1
            and.w     #1,d1
            move.w    d1,(p_dstres).w

* printer or rs232 port
            move.w    (pconfig).w,d1            ; p_port = pconfig%%4
            move.w    d1,d0
            lsr.w     #4,d0
            and.w     #1,d0
            move.w    d0,(p_port).w

* select printer flavor
            and.w     #7,d1                     ; p_type = ptype[pconfig & 7]
            move.b    ptype(pc,d1.w),d0
            move.w    d0,(p_type).l

* do it
            pea       (p_blkptr).w              ; -> beginning of parameter area
            move.w    #1,(_prtcnt).w
            bsr       _prtblk                   ; print it (finally)
            move.w    #-1,(_prtcnt).l
            addq.w    #4,sp                     ; cleanup stack
            rts                                 ; and return


*---- screen resolution table (pixel) for printScreen
reztab:     DC.W      $0140,$0280,$0280,$00c8
            DC.W      $00c8,$0190

*--- printer flavors (based on low 3 bits of pconfig)
ptype:      DC.B      $00,$02,$01,$ff,$03,$ff,$ff,$ff

*--- what it is:
mushroom:   DC.W      $0600,$2900,$0080,$4840
            DC.W      $11f0,$01f0,$07fc,$0ffe
            DC.W      $0dfe,$1fff,$1fef,$0fee
            DC.W      $0fde,$07fc,$03f8,$00e0

*+
* waitvbl - wait for the beam inside the vertical blank area
*-
IF ROM_TOS306
waitvbl:    clr.b     (tbcr).w
            clr.b     (tbdr).w
            move.b    #8,(tbcr).w
waitvbl2:   tst.b     (tbdr).w
            beq.s     waitvbl2
            jmp       (a6)
ELSE
waitvbl:    lea       (tbdr).w,a0               ; a0 -> timer B data register
            lea       (tbcr).w,a1               ; a1 -> timer B control register
            bclr      #0,(iera).w               ; disable IRQ of timer B
            moveq     #1,d4                     ; wait for the timer to expire
            clr.b     (a1)                      ; stop timer B
            move.b    #$f0,(a0)                 ; event every 240 scan lines
            move.b    #8,(a1)                   ; timer b: event count mode (HBL)
waitvbl2:   cmp.b     (a0),d4                   ; wait for HBL 239 scan lines to pass
            bne.s     waitvbl2
waitvbl3:   move.b    (a0),d4
            move.w    #615,d3                   ; wait till we are inside the vbl area
waitvbl4:   cmp.b     (a0),d4
            bne.s     waitvbl3
            dbra      d3,waitvbl4
            move.b    #$10,(a1)                 ; timer b: reset
            jmp       (a6)
ENDIF

_wvbl:      bra       wvbl

*+
* runresapps - search address space for 512-byte resident code chunks
*
*-
runresapps: movea.l   (phystop).l,a0            ; start at the top of the address space
runresappsl:suba.w    #$200,a0
            cmpa.w    #$400,a0                  ; reach the lower bottom?
            bls.s     runresappsx               ; (bail out)
            cmpi.l    #$12123456,(a0)           ; check for magic
            bne.s     runresappsl               ; (no magic -> next block)
            cmpa.l    4(a0),a0                  ; second long is equal the base address of the block?
            bne.s     runresappsl               ; (no -> next block)
            clr.w     d0
            movea.l   a0,a1
            move.w    #$ff,d1                   ; checksum over 256 words
runresappsc:add.w     (a1)+,d0                  ; checksum over the block
            dbra      d1,runresappsc
            cmp.w     #$5678,d0                 ; magic checksum?
            bne.s     runresappsl               ; (no -> next block)
            move.l    a0,-(sp)                  ; save current address
            jsr       8(a0)                     ; call code block
            movea.l   (sp)+,a0                  ; continue with next block
            bra.s     runresappsl
runresappsx:rts


gettime:    lea       (readRTCTime).l,a3
            lea       (readIKBDTime).l,a4
            bra.s     timecode
settime:    move.w    4(sp),(systemDate).l
            move.w    6(sp),(systemTime).l
            lea       (writeRTCTime).l,a3
            lea       (writeIKBDTime).l,a4
timecode:   bsr       checkRTC
            bcc.s     timecodertc
            movea.l   a4,a3
timecodertc:jmp       (a3)


*+
* sysbase2ram - copy sysbase into memory and patch it
*-
sysbase2ram:lea       osroml(pc),a0
            lea       (ramsysbase).l,a1
            moveq     #$2f,d0
sysbase2raml:move.b   (a0,d0.w),(a1,d0.w)       ; copy 48 bytes into memory
            dbra      d0,sysbase2raml
            move.w    sysbase2ramjmp(pc),$fffa(a1) ;copy JMP instruction just before
            move.l    4(a1),$fffc(a1)           ; copy ROM reseth into this JMP
            move.w    sysbase2rambra(pc),(a1)   ; patch BRA at the beginning of sysbase copy to hit the JMP
            move.w    $1e(a1),$1c(a1)           ; copy os_date into os_conf
            move.l    a1,(_sysbase).l           ; set new sysbase pointer
            rts

sysbase2ramjmp:jmp    ($0000).w                 ; destination address is patched to reseth address

sysbase2rambra:bra.s  sysbase2ramjmp            ; branch onto the JMP

*+
* blitmode - enabled/disable/test the blitter
*
* Returns: d0.w - bit 0 - blitter enabled
*                 bit 1 - blitter available
*-
blitmode:   bsr.s     blittest                  ; test if blitter is installed
            move.w    d0,d4                     ; d4 = blitter status
            move.w    d0,d5
            lsr.w     #1,d5
            or.w      #$fffe,d5                 ; d5 = blitter enable mask
            jsr       ($e0732a).l
            move.w    d0,d3                     ; d3 = blitter active
            move.w    4(sp),d0                  ; new blitter status
            bmi.s     blitmodex                 ; <0 just return the status
            and.w     d5,d0                     ; mask blitter status out
            or.w      d4,d0                     ; or'd blitter available status
            jsr       ($e07302).l               ; disable/enable blitter
blitmodex:  move.w    d3,d0
            rts

*+
* blittest - test if the blitter is installed
*-
blittest:   move      sr,d1
            move.w    #0,d0                     ; d0 = 0 -> blitter not installed
            suba.l    a0,a0
            movea.l   sp,a2
            ori       #$700,sr
            movea.l   8(a0),a1
            move.l    #blittestx,8(a0)
            tst.w     $8a00(a0)
            moveq     #2,d0                     ; d0 = 2 -> blitter installed
blittestx:  move.l    a1,8(a0)
            move      d1,sr
            movea.l   a2,sp
            rts

*+
* delay for a little bit via timer a in the mfp
*-
mfpdelay:   bsr.s     mfpdelaysetup
IF !ROM_TOS306
mfpdelayl:  btst      #5,(ipra).w               ; did timer a fire?
            beq.s     mfpdelayl                 ; wait more if not
            clr.b     (tacr).w                  ; stop timer a
ELSE
mfpdelayl:  btst      #5,(iprb_TT).w            ; did timer a fire?
            beq.s     mfpdelayl                 ; wait more if not
ENDIF
            rts

*+
* setup timer a in the mfp
*-
mfpdelaysetup:
IF !ROM_TOS306
            move    sr,-(sp)
            ori       #$700,sr
            clr.b     (tacr).w                  ; timer A control register
            bclr      #5,(iera).w               ; disable timer A
            move.b    #$df,(ipra).w             ; clear a pending timer A interrupt
            bclr      #5,(imra).w               ; mask the timer A interrupt
            bset      #5,(iera).w               ; enable timer A
            move      (sp)+,sr
            move.b    d0,(tadr).w               ; set timer a data register
            ror.w     #8,d0
            move.b    d0,(tacr).w               ; set timer a control register
            rol.w     #8,d0
ELSE
            movem.w   d0-d1,-(sp)
            move      sr,-(sp)
            ori       #$700,sr
            move.b    (tcdcr_TT).w,d1
            and.b     #$f,d1
            move.b    d1,(tcdcr_TT).w
            bclr      #5,(ierb_TT).w
            move.b    #$df,(iprb_TT).w
            bclr      #5,(imrb_TT).w
            bset      #5,(ierb_TT).w
            move.b    d0,(tcdr_TT).w
            lsr.w     #4,d0
            and.b     #$f0,d0
            or.b      d0,d1
            move.b    d1,(tcdcr_TT).w
            move      (sp)+,sr
            movem.w   (sp)+,d0-d1
            rts
ENDIF
            rts

memclear:   movea.l   4(sp),a0                  ; a0 -> start address of the block
            movea.l   8(sp),a1                  ; a1 -> end address of the block
            movem.l   d3-d7/a3,-(sp)
            moveq     #0,d1
            moveq     #0,d2
            moveq     #0,d3
            moveq     #0,d4
            moveq     #0,d5
            moveq     #0,d6
            moveq     #0,d7
            movea.w   d7,a3
            move.l    a0,d0
            btst      #0,d0
            beq.s     memclear1
            move.b    d1,(a0)+                  ; clear a single byte to even-align
memclear1:  move.l    a1,d0
            sub.l     a0,d0
            and.l     #$ffffff00,d0
            beq.s     memclearb
            lea       (a0,d0.l),a0
            movea.l   a0,a2
            lsr.l     #8,d0
memclear256:movem.l   d1-d7/a3,-(a2)            ; clear 256 bytes
            movem.l   d1-d7/a3,-(a2)
            movem.l   d1-d7/a3,-(a2)
            movem.l   d1-d7/a3,-(a2)
            movem.l   d1-d7/a3,-(a2)
            movem.l   d1-d7/a3,-(a2)
            movem.l   d1-d7/a3,-(a2)
            movem.l   d1-d7/a3,-(a2)
            subq.l    #1,d0
            bne.s     memclear256
memclearb:  cmpa.l    a0,a1                     ; end address reached?
            beq.s     memcleare
            move.b    d1,(a0)+                  ; clear a single byte
            bra.s     memclearb
memcleare:  movem.l   (sp)+,d3-d7/a3
            rts

*+
* setup PMMU translation table
*-
IF ROM_TOS306
setupPMMU:  lea       ($700).l,a0
            lea       ($e363f0).l,a1            ; translation table
            move.w    #$3f,d0                   ; 64 descriptors
setupPMMUl: move.l    (a1)+,(a0)+
            dbra      d0,setupPMMUl

* PMMU Table:
* $x.......
*   $700: $00000742,$10000001,$20000001,$30000001,$40000001,$50000001,$60000001,$70000001,
*   $720: $80000041,$90000041,$A0000041,$B0000041,$C0000041,$D0000041,$E0000041,$00000782,

* $0.......
*   $740: $000007C2,$01000001,$02000001,$03000001,$04000001,$05000001,$06000001,$07000001,
*   $760: $08000001,$09000001,$0A000001,$0B000001,$0C000001,$0D000001,$0E000001,$0F000001,

* $Fx......
*   $780: $00000041,$01000041,$02000041,$03000041,$04000041,$05000041,$06000041,$07000041,
*   $7a0: $08000041,$09000041,$0A000041,$0B000041,$0C000041,$0D000041,$0E000041,$000007C2,

* $00...... or $FF......
*   $7c0: $00000001,$00100001,$00200001,$00300001,$00400001,$00500001,$00600001,$00700001,
*   $7e0: $00800001,$00900001,$00A00001,$00B00001,$00C00001,$00D00001,$00E00001,$00F00041,

* The purpose of the PMMU is to disable caching in the area of the address space that doesn't contain ram:
* $00F00000..$00FFFFFF
* $80000000..$EFFFFFFF
* $F0000000..$FEFFFFFF
* $FFF00000..$FFFFFFFF

            pmove     ($e364f0).l,crp           ; CPU Root Pointer: $80000002,$00000700: 4 byte descriptor, at $700, lower limit: $00000
            pmove     ($e364f8).l,tc            ; Translation Control Register: $80F04445: MMU enabled, pagesize = 15, bits: 4,4,4,5
            pmove     ($e364fc).l,tt0           ; Transparent Translation Register 0: $017E8107
            pmove     ($e36500).l,tt1           ; Transparent Translation Register 1: $807E8507
            rts
ENDIF

*+
* calccrc - calc a crc
*-
calccrc:    movea.l   4(sp),a0                  ; a0 -> buffer start
            move.l    8(sp),d2                  ; d2 -> number of bytes
            movea.w   $c(sp),a1                 ; a1 -> step offset through the buffer
            clr.w     d0
            clr.w     d1
            clr.w     d3
            lea       (crcTable).l,a2
calccrcl:   move.w    d0,d1
            lsl.w     #8,d0
            lsr.w     #8,d1
            move.b    (a0),d3
            adda.l    a1,a0
            eor.b     d3,d1
            add.w     d1,d1
            move.w    (a2,d1.w),d4
            eor.w     d4,d0
            subq.l    #1,d2
            bne.s     calccrcl
            rts

IF ROM_TOS306
_EsetShift: bsr       _wvbl
            moveq     #0,d0
            move.w    (shift_tt).w,-(sp)
            move.w    6(sp),(shift_tt).w
            move.w    (shift_tt).w,d0
            and.w     #7,d0
            move.b    d0,(sshiftmd).w
            clr.w     (vblsem).w
            jsr       (esc_init).l
            move.w    #1,(vblsem).w
            move.w    (sp)+,d0
            rts

_EgetShift: moveq     #0,d0
            move.w    (shift_tt).w,d0
            rts

_EsetBank:  moveq     #0,d0
            move.w    (shift_tt).w,d0
            and.w     #$f,d0
            tst.w     4(sp)
            bmi.s     _EsetBankx
            move.b    5(sp),(shift_tt+1).w
_EsetBankx: rts

_EsetColor: moveq     #0,d0
            lea       (TT_col).w,a0
            move.w    4(sp),d0
            and.w     #$ff,d0
            add.w     d0,d0
            adda.w    d0,a0
            move.w    (a0),d0
            and.w     #$fff,d0
            move.w    6(sp),d1
            bmi.s     _EsetColorx
            move.w    d1,(a0)
_EsetColorx:rts

_EsetPalette:move.w   4(sp),d0
            and.w     #$ff,d0
            movea.w   d0,a0
            adda.w    a0,a0
            sub.w     #$100,d0
            neg.w     d0
            move.w    6(sp),d1
            cmp.w     d0,d1
            ble.s     _EsetPal2
            move.w    d0,d1
_EsetPal2:  movea.l   8(sp),a1
            lea       $8400(a0),a0
            bra.s     _EsetPal4
_EsetPal3:  move.w    (a1)+,(a0)+
_EsetPal4:  dbra      d1,_EsetPal3
            rts

_EgetPalette:move.w   4(sp),d0
            and.w     #$ff,d0
            movea.w   d0,a0
            adda.w    a0,a0
            sub.w     #$100,d0
            neg.w     d0
            move.w    6(sp),d1
            cmp.w     d0,d1
            ble.s     _EgetPal2
            move.w    d0,d1
_EgetPal2:  movea.l   8(sp),a1
            lea       $8400(a0),a0
            bra.s     _EgetPal4
_EgetPal3:  move.w    (a0)+,(a1)+
_EgetPal4:  dbra      d1,_EgetPal3
            rts

_EsetGrey:  moveq     #0,d0
            move.b    (shift_tt).w,d1
            move.b    d1,d0
            lsr.b     #4,d0
            and.b     #1,d0
            bclr      #4,d1
            tst.w     4(sp)
            beq.s     _EsetGrey2
            bmi.s     _EsetGreyr
            bset      #4,d1
_EsetGrey2: move.b    d1,(shift_tt).w
_EsetGreyr: rts

_EsetSmear: moveq     #0,d0
            move.b    (shift_tt).w,d1
            move.b    d1,d0
            add.b     d0,d0
            subx.w    d0,d0
            neg.w     d0
            bclr      #7,d1
            tst.w     4(sp)
            beq.s     _EsetSmear2
            bmi.s     _EsetSmearr
            bset      #7,d1
_EsetSmear2:move.b    d1,(shift_tt).w
_EsetSmearr:rts
ENDIF

**************************************************************************
* DMAREAD.S from the AHDI source                                         *
**************************************************************************
*+
* dmaread() - read from a DMA device.
* dmawrite() - write to a DMA device.
*
* LONG  sectnum     $4(sp).l
* WORD  count       $8(sp).w
* BYTE  *buf;       $a(sp).l    $b(sp)=high $c(sp)=mid $d(sp)=low
* WORD  pdev;       $e(sp).w
*
* Returns 0 if successful.
* Returns a negative number if failure:
*   EUNDEV (-15L) if unknown device (bad dev number)
*   EREADF (-11L) if read failure
*   EWRITF (-10L) if write failure
*          (-1L)  if timed-out
*
* Comments:
*   ACSI, SCSI and IDE-AT drives are supported.
*-
_dmaread:   move.b    #0,(rwflag).l             ; rwflag = 0 => a read
            move.w    #$08,d1                   ; d1 = opcode for READ
            bra.s     dmarw
_dmawrite:  move.b    #1,(rwflag).l             ; rwflag = 1 => a write
            move.w    #$0a,d1                   ; d1 = opcode for WRITE
dmarw:      cmpi.w    #$10,$e(sp)               ; unit# > IDE-AT unit# 16?
            bhi       undev                     ; if so, return unknow device
            cmpi.w    #$f,$e(sp)                ; an IDE-AT unit?
            bhi.s     ide0                      ; if so, talk IDE-AT
* else talk ACSI or SCSI
            lea       (_cmdblk).l,a0            ; a0 -> beginning of command block
            move.b    d1,(a0)+                  ; byte 0 = opcode
            move.b    5(sp),(a0)+               ; byte 1 = msb of logical block addr
            move.b    6(sp),(a0)+               ; byte 2 = logical block addr
            move.b    7(sp),(a0)+               ; byte 3 = lsb of logical block addr
            move.b    9(sp),(a0)+               ; byte 4 = transfer length (in blocks)
            clr.b     (a0)                      ; byte 5 = control byte
            move.w    $e(sp),d0                 ; d0 = physical unit number
IF ROM_TOS306
            moveq     #0,d1
            move.w    8(sp),d1
            lsl.l     #8,d1
            add.l     d1,d1
ENDIF
            moveq     #6,d2                     ; d2 = length of command
            movea.l   $a(sp),a0                 ; a0 = buffer
            tst.b     (rwflag).l                ; read or write?
            bne.s     rw0                       ; it's a write
            bsr       _dorcmd                   ; send a receive data command
            bra       rw1
rw0:        bsr       wracsi                    ; send a write data command
            bra       rw1                       ; branch will take place for non-IDE drives
*
* Before doing anything with the IDE bus, make sure it's there.
* (The label noide is before at just because it fits better.)
*
noide:      movea.l   a1,sp                     ; got the bus error on IDEASR;
            move.l    a0,(busexception).w       ; restore vector & sp, return EUNDEV
            bra       undev

ide0:       movea.l   (busexception).w,a0
            movea.l   sp,a1
            move.l    #noide,(busexception).w
            tst.b     (ide_stat2).l             ; read a register to probe for IDE bus
            movea.l   a1,sp                     ; hey! no bus error!
            move.l    a0,(busexception).w       ; restore vector & sp and continue.
            move.b    $f(sp),d0                 ; IDE unit#
            bsr       _iderdy                   ; wait for drive to be ready
IF ROM_TOS306
            beq       undev                     ; if drive never gets ready, return with unknown device else
ELSE
            beq.s     undev                     ; if drive never gets ready, return with unknown device else
ENDIF
            moveq     #2,d0                     ; D_IDENTIFY delay required by Conner drives
            add.l     (_hz_200).w,d0            ; between power on and identify()
ide1:       cmp.l     (_hz_200).w,d0
            bcc.s     ide1

            pea       (sbuf).l                  ; scratch buffer for drive parameters
            move.w    $12(sp),-(sp)             ; IDE unit#
            bsr       _identify                 ; identify()
            addq.w    #6,sp                     ; clean up stack
            tst.w     d0                        ; successful?
IF ROM_TOS306
            bmi       rwend                     ; if timed-out, return
ELSE
            bmi.s     rwend                     ; if timed-out, return
ENDIF
            bne.s     rw2                       ; if error, return with error code else can do read or write
            moveq     #2,d0                     ; D_IDENTIFY delay required by Conner drives
            add.l     (_hz_200).w,d0            ; between identify() and r/w
ide4:       cmp.l     (_hz_200).w,d0
            bcc.s     ide4

IF ROM_TOS306
            lea       (sbuf).l,a0
            move.w    $c(a0),-(sp)              ; # of sectors per track
            move.w    6(a0),-(sp)               ; # of heads
            move.w    $12(sp),-(sp)             ; physical unit #
            bsr       _initdevpar
            addq.w    #6,sp
            tst.w     d0
            bmi.s     rwend
            bne.s     rw2
            lea       (sbuf).l,a0
ELSE
            pea       (sbuf).l                  ; beginning of _identify() data
            bsr       _gcparm                   ; get drive current parameters
            addq.w    #4,sp                     ; clean up stack
ENDIF
            move.w    $e(sp),-(sp)              ; physical unit #
            move.l    $c(sp),-(sp)              ; buffer
            move.w    $e(sp),-(sp)              ; count
            move.l    $c(sp),-(sp)              ; logical block address
IF ROM_TOS306
            move.w    $c(a0),-(sp)              ; # of sectors per track
            move.w    6(a0),-(sp)               ; # of heads
ELSE
            move.w    d2,-(sp)                  ; # sectors per track
            move.w    d1,-(sp)                  ; # data heads
ENDIF
            tst.b     (rwflag).l                ; read or write?
            bne.s     ide2                      ; (write)
            bsr       _ideread                  ; read sectors
            bra.s     ide3
ide2:       bsr       _idewrite                 ; write sectors
ide3:       adda.w    #16,sp                    ; clean up stack
rw1:
IF ROM_TOS306
            move      sr,-(sp)
            ori       #$700,sr
            movec     cacr,d1
            ori.w     #$808,d1
            movec     d1,cacr
            move      (sp)+,sr
ENDIF
            tst.w     d0                        ; successful?
            ble.s     rwend                     ; if no error or timed-out, return else
rw2:        moveq     #-11,d0                   ; assume it's a read error EREADF
            tst.b     (rwflag).l                ; read or write?
            beq.s     rwend                     ; if read, done
            moveq     #-10,d0                   ; else, it's a write error EWRITF
rwend:      rts

undev:      moveq     #-15,d0                   ; EUNDEV
            bra.s     rwend

*+
* dorcmd() - send a command which will receive data from the target
*
* Passed:
*   d0.w = physical unit number
*   d2.w = command length (NCMD or LCMD)
*   a0.l = buffer address
*-
_dorcmd:
IF ROM_TOS306
            cmp.w     #7,d0
            bls.s     _dorcmd2
            bsr       _rcvscsi
            rts
_dorcmd2:   bsr       _rcvacsi                  ; it's an ACSI device
ELSE
            bsr.s     _rcvacsi                  ; it's an ACSI device
ENDIF
            rts

*+
* dowcmd() - send a command which will write data to the target
*
* Passed:
*   d0.w = physical unit number
*   d2.w = command length (NCMD or LCMD)
*   a0.l = buffer address
*-
wracsi:
IF ROM_TOS306
            cmp.w     #7,d0
            bls.s     wracsi2
            bsr       _wrtscsi
            rts
wracsi2:
ENDIF
            bsr       _wrtacsi                  ; it's an ACSI device
            rts

*+
* LONG _qdone() - Wait for command byte handshake
* LONG _fdone() - Wait for operation complete
* Passed:   nothing
*
* Returns:  EQ: no time-out
*       MI: time-out condition
*
* Uses:     D0
*
*-
_fdone:     move.l    #600,d0                   ; ACLTMOUT
            bra.s     qd0

_qdone:     moveq     #20,d0                    ; ACSTMOUT

qd0:
IF ROM_TOS306
            move.l    d0,-(sp)                  ; save timeout value
            moveq     #2,d0                     ; busy-wait delay for slow ACSI
            add.l     (_hz_200).w,d0            ; minimum 20 microsec.
sdelay:     cmp.l     (_hz_200).w,d0
            bge.s     sdelay
            move.l    (sp)+,d0                  ; restore timeout value
ENDIF
            add.l     (_hz_200).w,d0
qd1:        cmp.l     (_hz_200).w,d0            ; time-out?
            bcs.s     qdq                       ; (i give up, return NE)
            btst      #5,(gpip).w               ; interrupt?
            bne.s     qd1                       ; (not yet)
            moveq     #0,d0                     ; return EQ (no time-out)
            rts

qdq:        moveq     #-1,d0
            rts

*+
* Wait for end of SASI command
*
* Passed:   d1 value to be written to wdl
*
* Returns:  EQ: success (error code in D0.W)
*       MI: time-out (-1 in D0.W)
*       NE: failure (SASI error code in D0.W)
*
* Uses:     d0
*-
_endcmd:    bsr.s     _fdone                    ; wait for operation complete
            bmi.s     endce                     ; (timed-out, so complain)
IF ROM_TOS306
            move      sr,-(sp)
            ori       #$700,sr
            movec     cacr,d0
            ori.w     #$800,d0
            movec     d0,cacr
            move      (sp)+,sr
ENDIF
            move.w    d1,(fifo).w
            move.w    (diskctl).w,d0            ; get the result
            and.w     #$ff,d0                   ; (clean it up) if non-0 should do a RequestSense command to learn more
endce:      move.l    (_hz_200).w,(lastacstm).l ; update controller last accessed time
            addq.l    #2,(lastacstm).l          ; lastacstm = _hz_200 + 2;
            rts

*+
*  Handle command time-out;
*  Unlock DMA chip and return completion status;
*-
_hdone:     move.w    #$80,(fifo).w             ; Landon's code seems to presume we put $80 there
IF ROM_TOS306
            clr.w     (flock).w                 ; NOW, signal that we are done
ELSE
            sf        (flock).w                 ; NOW, signal that we are done
ENDIF
            rts

*+
* delay()
*   5 - 10ms kludge delay for message byte sent back by controller.
*-
_delay:     move.l    d0,-(sp)                  ; preserve d0
            move.l    (lastacstm).l,d0          ; d0 = controller last accessed time
wait:       cmp.l     (_hz_200).w,d0            ; while (_hz_200 <= lastacstm)
            bcc.s     wait                      ; wait()
            move.l    (sp)+,d0                  ; restore d0
            rts

*+
* rcvacsi() - send a ACSI command which receives data from target.
*
* Passed:
*   d0.w = physical unit number
*   d2.w = command length (NCMD or LCMD)
*   a0.l = buffer address
*-
_rcvacsi:   movea.l   (busexception).w,a1       ; check to see if the ACSI bus actually exists! Needed for prototype PADs
            movea.l   sp,a2
            move.l    #noacsi,(busexception).w
            tst.w     (fifo).w                  ; harmless or bus erorr
            move.l    a1,(busexception).w       ; no bus error - restore & continue
            movea.l   a2,sp
            st        (flock).w                 ; lock FIFO
            bsr.s     _delay                    ; delay if necessary
            movea.w   #diskctl,a1               ; a1 = pointer to DMA chip
            bsr       setadma                   ; set DMA pointer
            move.w    #$190,2(a1)               ; WDL   ; toggle DMA chip to direction
            bsr       rstdelay                  ; delay
            move.w    #$90,2(a1)                ; WDL   ;  for receiving data
            bsr       rstdelay                  ; delay
            bsr       setacnt                   ; set DMA count
            lea       (_cmdblk).l,a0            ; a0 = address of command block
            moveq     #0,d1                     ; direction of DMA is IN
            bsr.s     sblkacsi                  ; send the command block
raend:      bra.s     _hdone                    ; cleanup after IRQ
*+
* wrtacsi() - send an ACSI command which will write data to the target
*
* Passed:
*   d0.w = physical unit number
*   d2.w = command length (NCMD or LCMD)
*   a0.l = buffer address
*-
*
_wrtacsi:   movea.l   (busexception).w,a1       ; check to see if the ACSI bus actually exists! Needed for prototype PADs
            movea.l   sp,a2
            move.l    #noacsi,(busexception).w
            tst.w     (diskctl).w               ; harmless or bus erorr
            move.l    a1,(busexception).w       ; no bus error - restore & continue
            movea.l   a2,sp
            st        (flock).w                 ; lock FIFO
            bsr.s     _delay
            movea.w   #diskctl,a1               ; a1 = pointer to DMA chip
            bsr.s     setadma                   ; set DMA pointer
            move.w    #$90,2(a1)                ; WDL   ; toggle DMA chip for "send"
            bsr       rstdelay                  ; delay
            move.w    #$190,2(a1)               ; WDL
IF ROM_TOS306
            bsr       rstdelay                  ; delay
ELSE
            bsr.s     rstdelay                  ; delay
ENDIF
            bsr.s     setacnt                   ; set DMA count
            move.l    #$100,d1                  ; d1 = direction of DMA is OUT
            bsr.s     sblkacsi                  ; send the command block
waend:      bra       _hdone                    ; cleanup after IRQ
* You get to this label if there is a bus error when probing for
* the ACSI bus: it's meant for prototype PADs, which don't
* have an ACSI DMA chip.
noacsi:     moveq     #-15,d0                   ; return EUNDEV
            movea.l   a2,sp
            move.l    a1,(busexception).w
            rts

*+
* sblkacsi() - send command block
*
* Passed:
*   d0.w = physical unit number
*   d1.l = direction of DMA ($0000 for IN or $0100 for OUT)
*   d2.w = command length (NCMD or LCMD)
*   a1.l = pointer to DMA chip
*
* Returns:
*   d0.l =  0 if successful
*   d0.l = -1 if time-out
*
* Trashes:
*   d0, d1, d2, a2
*-
sblkacsi:   move.b    #$88,d1                   ; next byte is the opcode
            move.w    d1,2(a1)                  ; WDL
            move.b    #$8a,d1                   ; following bytes are operands
            lea       (_cmdblk).l,a2            ; a2 = address of command block
* integrate unit # into cmd blk
            lsl.b     #5,d0                     ; shift unit number into place
            or.b      d0,(a2)                   ; first command byte = unit # | opcode
* control byte is sent seperately
            subq.w    #2,d2                     ; and dbra likes one less
sa1:        swap      d1                        ; d1.hw = operand
            move.b    (a2)+,d1                  ; d1.lw = tells controller next byte
            swap      d1                        ; is an operand
            move.l    d1,(a1)                   ; WDCWDL
            bsr       _qdone
            bmi.s     sbaend                    ; if time-out, returns
            dbra      d2,sa1                    ; else send rest of command block
            move.w    d1,2(a1)                  ; WDL - get ready to send control byte
            move.b    #0,d1                     ; signal sending control byte
            swap      d1                        ; d1.hw = control byte
            move.b    (a2),d1                   ; d1.lw = tells controller it's end
            swap      d1                        ; of command
            move.l    d1,(a1)                   ; send it
            move.b    #$8a,d1                   ; d1 = wdl value
            bsr       _endcmd                   ; wait for command completion
sbaend:     rts                                 ; heading home

*+
* setadma() - set the ACSI DMA pointer
*
* Passed:
*   a0.l = buffer address
*-
setadma:    move.l    a0,-(sp)                  ; move it on stack
            move.b    3(sp),(dmalow).w          ; set low-byte of address
            move.b    2(sp),(dmamid).w          ; set mid-byte of address
            move.b    1(sp),(dmahigh).w         ; set high-byte of address
            addq.l    #4,sp                     ; clean up stack
            rts


*+
* setacnt() - set the ACSI DMA counter
*
* Passed:
*   a1.l = pointer to DMA chip
*-
setacnt:
IF ROM_TOS306
            move.l    d1,-(sp)
            lsr.l     #8,d1
            add.l     d1,d1
            move.w    d1,(a1)
            move.l    (sp)+,d1
ELSE
            move.w    #$ff,(a1)                 ; set the ACSI DMA counter to 255 512-byte-blocks
ENDIF
            rts


*   After talking to the DMA chip in a way that may reset it,
* we need a 8 8Mhz clocks (ie. 1 microsec) delay, before we can
* talk to the chip again.
rstdelay:   tst.b     (gpip).w                  ; delay for 1 microsec
            tst.b     (gpip).w                  ; this amounts to 16 16Mhz clocks
            tst.b     (gpip).w
            tst.b     (gpip).w
            rts

IF ROM_TOS306
_rcvscsi:   move.l    a0,-(sp)
            andi.w    #7,d0
            bsr.s     _scsicmd
            movea.l   (sp)+,a0
            tst.w     d0
            bmi.s     _rcvscsir
            movea.l   a0,a1
            movea.w   #s_data,a2
            move.b    #1,(s_tcr).w
            move.b    (s_inircv).w,d0
_rcvscsil:  bsr       _scsidelay250ms
            bsr       _scsiwait
            bmi.s     _rcvscsir
            btst      #3,$a(a2)
            beq       _scsiaction
            move.b    (a2),(a1)+
            bsr       _scsidone
            bra.s     _rcvscsil
_rcvscsir:  rts

_wrtscsi:   andi.w    #7,d0
            move.l    a0,-(sp)
            move.w    d2,-(sp)
            bsr.s     _scsicmd
            move.w    (sp)+,d2
            movea.l   (sp)+,a0
            tst.w     d0
            bmi.s     _wrtscsir
            movea.l   a0,a1
            movea.w   #s_data,a2
            move.b    #0,(s_tcr).w
            move.b    (s_inircv).w,d0
_wrtscsil:  bsr       _scsidelay250ms
            bsr       _scsiwait
            bmi.s     _wrtscsir
            btst      #3,$a(a2)
            beq       _scsiaction
            move.b    (a1)+,(a2)
            bsr       _scsidone
            bra.s     _wrtscsil
_wrtscsir:  rts

_scsicmd:   move.l    d2,-(sp)
            move.w    d0,-(sp)
            bsr.s     _scsido
            addq.w    #2,sp
            move.l    (sp)+,d2
            tst.w     d0
            bmi.s     _scsicmdr
            move.b    #2,(s_tcr).w
            move.b    #1,(s_icr).w
            lea       ($a96).l,a1
            subq.w    #1,d2
            bsr       _scsidelay250ms
_scsicmdl:  move.b    (a1)+,d0
            bsr       _scsisuba
            tst.w     d0
            bmi.s     _scsicmdr
            dbra      d2,_scsicmdl
            moveq     #0,d0
_scsicmdr:  rts

_scsido:    bsr       _scsidelay250ms
_scsidow:   btst      #6,(s_idstat).w
            beq.s     _scsido2
            cmp.l     (a0),d1
            bhi.s     _scsidow
            bra.s     _scsidoerr
_scsido2:   move.b    #0,(s_tcr).w
            move.b    #0,(s_idstat).w
            move.b    #$c,(s_icr).w
            clr.w     d0
            move.w    4(sp),d1
            bset      d1,d0
            move.b    d0,(s_data).w
            move.b    #$d,(s_icr).w
            andi.b    #$fe,(s_mode).w
            andi.b    #$f7,(s_icr).w
            bsr       _scsidelay250ms
_scsidow2:  btst      #6,(s_idstat).w
            bne.s     _scsidook
            cmp.l     (a0),d1
            bhi.s     _scsidow2
_scsidoerr: moveq     #-1,d0
            bra.s     _scsidowr
_scsidook:  clr.w     d0
_scsidowr:  move.b    #0,(s_icr).w
            rts

_scsiaction:bsr       _scsidelay250ms
            move.b    #3,(s_tcr).w
            move.b    (s_inircv).w,d0
            bsr.s     _scsiwait
            bmi.s     _scsiactionr
            moveq     #0,d0
            move.b    (s_data).w,d0
            bsr       _scsidelay250ms
            move.l    d0,-(sp)
            bsr.s     _scsidone
            tst.w     d0
            beq.s     _scsiaction3
_scsiaction2:addq.l    #4,sp
            bra.s     _scsiactionr
_scsiaction3:bsr.s     _scsidelay250ms
            bsr.s     _scsiwait
            bmi.s     _scsiaction2
            move.b    (s_data).w,d0
            bsr.s     _scsidone
            tst.w     d0
            bmi.s     _scsiaction2
            move.l    (sp)+,d0
_scsiactionr:rts

_scsiwait:  btst      #5,(s_idstat).w
            bne.s     _scsiwaitok
            cmp.l     (a0),d1
            bhi.s     _scsiwait
            moveq     #-1,d0
            bra.s     _scsiwaitr
_scsiwaitok:moveq     #0,d0
_scsiwaitr: rts

_scsidone:  ori.b     #$11,(s_icr).w
_scsidonel: btst      #5,(s_idstat).w
            beq.s     _scsidoneok
            cmp.l     (a0),d1
            bhi.s     _scsidonel
            moveq     #-1,d0
            bra.s     _scsidoner
_scsidoneok:moveq     #0,d0
_scsidoner: andi.b    #$ef,(s_icr).w
            rts

_scsisuba:  move.w    d0,-(sp)
            bsr.s     _scsiwait
            bmi.s     _scsisuba2
            move.b    1(sp),(s_data).w
            bsr.s     _scsidone
_scsisuba2: addq.l    #2,sp
            rts

_scsisubb:  move.b    #$80,(s_icr).w
            bsr.s     _scsidelay250ms
_scsisubbl: cmp.l     (a0),d1
            bhi.s     _scsisubbl
            move.b    #0,(s_icr).w
            bsr.s     _scsidelay1s
_scsisubbl2:cmp.l     (a0),d1
            bhi.s     _scsisubbl2
            rts

_scsidelay250ms:
            movea.w   #_hz_200,a0
            moveq     #51,d1
            add.l     (a0),d1
            rts

_scsidelay1s:
            movea.w   #_hz_200,a0
            move.l    #201,d1
            add.l     (a0),d1
            rts
ENDIF

*+
* Wait for status to come back
*-
w4int:      move.l    #10*200,d0                ; d0 = time-out limit (D_WORST)
            add.l     (_hz_200).w,d0            ; d0 = expiration time
wi0:        btst      #5,(gpip).w               ; interrupt?
            beq.s     wi0                       ; if so, out of the loop
            cmp.l     (_hz_200).w,d0            ; time-out?
            bhi.s     wi0                       ; if not, wait some more
            moveq     #-1,d0                    ; else, return time-out
            bra.s     wi3
wi0:        moveq     #0,d0                     ; clear d0
            move.b    (ide_comst).l,d0          ; d0.b = status returned
            btst      #0,d0                     ; any error?
            bne.s     wi2                       ; if yes, return error code
            btst      #3,d0                     ; else DRQ?
            bne.s     wi3                       ; if so, just return
            moveq     #0,d0                     ; else return OK
            bra.s     wi3
wi2:        move.b    (ide_param).l,d0          ; else d0.b = error bits
wi3:        rts                                 ; return status or error code

*+
* ideread() - reads from 1 to 256 sectors as specified in the Task File,
*       beginning at the specified sector.
*      - sector count equal to 0 requests 256 sectors.
*
* ideread(nhd, nspt, sectnum, count, buf, pdev)
* WORD  nhd;        4(sp).w     ; # of data heads on pdev
* WORD  nspt;       6(sp).w     ; # of physical sectors per track
* LONG  sectnum;    8(sp).l     ; logical block address
* WORD  count;      $c(sp).w    ; # of sectors to read
* BYTE  *buf;       $e(sp).l    ; $f(sp)=high $10(sp)=mid $11(sp)=low
* WORD  pdev;       $12(sp).w   ; physical device number
*-
_ideread:   bsr       set_dhcs                  ; set physical address
            movea.l   $e(sp),a0                 ; a0 -> buffer to read into
            move.b    $d(sp),(ide_seccn).l      ; set sector count
            move.w    $c(sp),d1                 ; d1.w = # of sectors to read
            subq.w    #1,d1                     ; dbra likes one less
            move.b    #0,(ide_stat2).l          ; enable interrupt
            move.b    #$20,(ide_comst).l        ; set command code IDEREAD
ider0:      bsr.s     w4int                     ; wait for interrupt
            tst.w     d0                        ; successful?
            bmi.s     ider1                     ; if timed-out, return
            btst      #3,d0                     ; DRQ?
            beq.s     ider1                     ; if not, return
            bsr       readbuf                   ; fill sector buffer
            dbra      d1,ider0                  ; more to read?
            moveq     #0,d0                     ; everything's fine
ider1:      rts

*+
* idewrite() - writes from 1 to 256 sectors as specified in the Task File,
*       beginning at the specified sector.
*       - sector count equal to 0 requests 256 sectors.
*
* idewrite(nhd, nspt, sectnum, count, buf, pdev)
* WORD  nhd;        4(sp).w     ; # of data heads on pdev
* WORD  nspt;       6(sp).w     ; # of physical sectors per track
* LONG  sectnum;    8(sp).l     ; logical block address
* WORD  count;      $c(sp).w    ; # sectors to read
* BYTE  *buf;       $e(sp).l    ; $f(sp)=high $10(sp)=mid $11(sp)=low
* WORD  pdev;       $12(sp).w   ; physical device number
*-
_idewrite:  bsr.s     set_dhcs                  ; set physical address
            movea.l   $e(sp),a0                 ; a0 -> buffer to write from
            move.b    $d(sp),(ide_seccn).l      ; set sector count
            move.w    $c(sp),d1                 ; d1.w = # of sectors to read
            subq.w    #1,d1                     ; dbra likes one less
            move.b    #0,(ide_stat2).l          ; enable interrupt
            move.b    #$30,(ide_comst).l        ; set command code IDEWRITE
idew0:      btst      #3,(ide_stat2).l          ; DRQ?
            beq.s     idew0                     ; if not, wait longer
idew1:      bsr       wrtbuf                    ; fill sector buffer
            bsr       w4int                     ; wait for interrupt
            tst.w     d0                        ; successful?
            bmi.s     idew2                     ; if timed-out, return
            btst      #3,d0                     ; DRQ?
            beq.s     idew2                     ; if not, return
            dbra      d1,idew1                  ; else go transfer data
            moveq     #0,d0                     ; everything's fine
idew2:      rts

*+
* set_dhcs() - convert a logical block address into a physical address.
*        - set drive #, head #, cylinder # and sector # in task file.
*
* Passed:
*   8(sp).w = nhd = # of data heads
*   $a(sp).w = nspt = # of physical sectors per track
*   $c(sp).l = logical block address
*   $16(sp).w = physical unit #
*-
set_dhcs:   move.l    $c(sp),d1                 ; d1.l = logical block address
            move.w    8(sp),d2                  ; d2.w = # of data heads
            move.w    $a(sp),d0                 ; d0.w = # of physical sectors per track
            mulu.w    d0,d2                     ; d2.l = # of sectors per cylinder = # heads * # of sectors per track
            divu.w    d2,d1                     ; d1.w = cylinder # = log block addr / #spc
            move.b    d1,(ide_zylow).l          ; set cylinder low
            lsr.l     #8,d1                     ; d1.b = cylinder high
            move.b    d1,(ide_zyhig).l          ; set cylinder high
            lsr.l     #8,d1                     ; d1.l = sector # within the cyl
            divu.w    d0,d1                     ; d1.w = head # = sector # within cyl / #spt
            move.w    $16(sp),d0                ; d0.w = physical unit #
            andi.b    #7,d0                     ; mask off flags from physical unit #
            lsl.b     #4,d0                     ; shift unit # to place
            or.b      d0,d1                     ; or in drive #
            move.b    d1,(ide_headn).l          ; set drive and head #
            swap      d1                        ; d1.w = sector # (base 0)
            addq.w    #1,d1                     ; = sector # + 1 (base 1)
            move.b    d1,(ide_stars).l          ; set sector #
            rts

*+
* identify() - allows the Host to receive parameter information from
*          the drive.
*
* identify(pdev, buf)
* WORD  pdev;   4(sp).w     ; physical unit #
* BYTE  *buf;   6(sp).l     ; buffer to put data
*-
IF ROM_TOS306
_initdevpar:move.w    4(sp),d0                  ; d0 = physical unit #
            andi.b    #7,d0                     ; mask off flags (if any)
            lsl.w     #4,d0                     ; shift unit # to place
            move.b    d0,(ide_headn).l          ; set drive #
            move.w    6(sp),d0
            subq.b    #1,d0
            or.b      d0,(ide_headn).l          ; # of heads - 1
            move.b    9(sp),(ide_seccn).l       ; # of sectors per track
            move.b    #0,(ide_stat2).l          ; enable interrupt
            move.b    #$91,(ide_comst).l        ; set command code INITIALIZE DEVICE PARAMETERS
            bra       w4int
ENDIF

*+
* identify() - allows the Host to receive parameter information from
*          the drive.
*
* identify(pdev, buf)
* WORD  pdev;   4(sp).w     ; physical unit #
* BYTE  *buf;   6(sp).l     ; buffer to put data
*-
_identify:  move.w    4(sp),d0                  ; d0 = physical unit #
            andi.b    #7,d0                     ; mask off flags (if any)
            lsl.b     #4,d0                     ; shift unit # to place
            move.b    d0,(ide_headn).l          ; set drive #
            movea.l   6(sp),a0                  ; a0 -> buffer
            move.b    #0,(ide_stat2).l          ; enable interrupt
            move.b    #$ec,(ide_comst).l        ; set command code IDENTIFY
            bsr       w4int                     ; wait for interrupt
            tst.w     d0                        ; successful?
            bmi.s     id0                       ; if timed-out, return
            btst      #3,d0                     ; DRQ?
            beq.s     id0                       ; if not, return with error
            bsr.s     readbuf                   ; read data
            moveq     #0,d0                     ; everything's fine
id0:        rts

*+
* readbuf() - reads 512 bytes (128 longs) of data into sector buffer.
*
* Passed:
*   a0.l = buffer to store data read from sector buffer
*-
readbuf:
IF ROM_TOS306
            moveq     #$7f,d0                   ; d0 = (# of words of data to read) - 1
            lea       (ide_datar).l,a1          ; a1 -> data bus
rb0:        move.l    (a1),(a0)+                ; read data from bus
            dbra      d0,rb0                    ; repeat until all done
ELSE
            moveq     #$1f,d0                   ; d0 = (# of words of data to read / 8) - 1
            lea       (ide_datar).l,a1          ; a1 -> data bus
rb0:        move.w    (a1),(a0)+                ; read data from bus
            move.w    (a1),(a0)+                ; read data from bus
            move.w    (a1),(a0)+                ; read data from bus
            move.w    (a1),(a0)+                ; read data from bus
            move.w    (a1),(a0)+                ; read data from bus
            move.w    (a1),(a0)+                ; read data from bus
            move.w    (a1),(a0)+                ; read data from bus
            move.w    (a1),(a0)+                ; read data from bus
            dbra      d0,rb0                    ; repeat until all done
ENDIF
            rts

*+
* wrtbuf() - writes 512 bytes (128 longs) of data to sector buffer.
*
* Passed:
*   a0.l = buffer with data to write to sector buffer
*-
wrtbuf:
IF ROM_TOS306
            moveq     #$7f,d0                   ; d0 = (# of words of data to read) - 1
            lea       (ide_datar).l,a1          ; a1 -> data bus
wb0:        move.l    (a0)+,(a1)                ; write data to bus
            dbra      d0,wb0                    ; repeat until all done
ELSE
            moveq     #$1f,d0                   ; d0 = (# of words of data to read / 8) - 1
            lea       (ide_datar).l,a1          ; a1 -> data bus
wb0:        move.w    (a0)+,(a1)                ; write data to bus
            move.w    (a0)+,(a1)                ; write data to bus
            move.w    (a0)+,(a1)                ; write data to bus
            move.w    (a0)+,(a1)                ; write data to bus
            move.w    (a0)+,(a1)                ; write data to bus
            move.w    (a0)+,(a1)                ; write data to bus
            move.w    (a0)+,(a1)                ; write data to bus
            move.w    (a0)+,(a1)                ; write data to bus
            dbra      d0,wb0                    ; repeat until all done
ENDIF
            rts

*+
* _iderdy() - test if the IDE drive is ready
*
* Passed:
*   d0.b = IDE drive unit #
*
* Returns: 0 - if drive is NOT ready
*      1 - if drive is ready
*-
_iderdy:    andi.b    #7,d0                     ; mask off flags (if any)
            lsl.b     #4,d0                     ; shift unit # to place
            move.b    d0,(ide_headn).l          ; set drive #
IF ROM_TOS306
            move.l    #5*200,d0                 ; set up timer IDERDY
            add.l     (_hz_200).w,d0
ir0:        btst      #6,(ide_stat2).l
            bne.s     ir1
ELSE
            move.b    #$50,d1                   ; ready status
            move.l    #5*200,d0                 ; set up timer IDERDY
            add.l     (_hz_200).w,d0
ir0:        cmp.b     (ide_stat2).l,d1          ; is drive ready and not busy?
            beq.s     ir1                       ; if so, return with drive ready
ENDIF
            cmp.l     (_hz_200).w,d0            ; time-out yet?
            bcc.s     ir0                       ; if not, wait longer
            moveq     #0,d0                     ; else return drive NOT ready
            rts

ir1:        moveq     #1,d0                     ; else, drive is ready
            rts

*+
* gcparm() - get current drive parameters
*
* gcparm(buf)
* char  *buf;   $4(sp).l    /* -> data returned by identify() */
*
* Returns:
*   d0.w = # of default cylinders
*   d1.w = # of default heads
*   d2.w = # of default sectors per track
*-
IF !ROM_TOS306
_gcparm:    movea.l   4(sp),a0                  ; a0 -> data buffer
            adda.w    #$50,a0                   ; a0 -> where Conner model number is (CONMDL)
            move.l    a0,-(sp)
            pea       (cp2024).l
            move.w    #6,-(sp)
            bsr.s     strcmp                    ; compare model# with "CP2024"
            adda.w    #$a,sp                    ; clean up stack
            tst.w     d0                        ; is unit the CP2024 (Kato 20Mb)?
            bne.s     gcp0                      ; if not, handle the normal way else return default values of CP2024
            move.w    #$267,d0                  ; d0.w = # of cylinders (CP20NCYL)
            move.w    #4,d1                     ; d1.w = # of heads (CP20NHEAD)
            move.w    #$11,d2                   ; d2.w = # of spt (CP20NSPT)
            bra.s     gcpend
gcp0:       movea.l   4(sp),a0
            move.w    2(a0),d0                  ; d0.w = # of cylinders
            move.w    6(a0),d1                  ; d1.w = # of heads
            move.w    $c(a0),d2                 ; d2.w = # of sectors per track
gcpend:     rts

conner:     DC.B      'Conner',0
            DC.B      $00
cp2024:     DC.B      'CP2024',0
            DC.B      $00

*+
* strcmp() - compare two strings
*
* Passed:
*   4(sp).w  = n (# of bytes to compare)
*   6(sp).l  = address of first string
*   10(sp).l = address of second string
*
* Returns:
*   d0.w = 0    if first n bytes of the 2 strings are the same
*        = non-0    otherwise
*-
strcmp:     movem.l   d1/a0-a1,-(sp)            ; save registers d1, a0 and a1
            move.w    $10(sp),d1                ; d1 = byte count
            subq.w    #1,d1                     ; dbra likes one less
            movea.l   $12(sp),a0                ; a0 -> string 1
            movea.l   $16(sp),a1                ; a1 -> string 2
            moveq     #1,d0                     ; assume strings are not the same
str0:       cmpm.b    (a0)+,(a1)+               ; characters the same?
            bne.s     str1                      ; if not, return
            dbra      d1,str0                   ; else compare next character
            moveq     #0,d0                     ; the strings are the same
str1:       movem.l   (sp)+,d1/a0-a1            ; restore registers d1, a0 and a1
            rts
ENDIF

**************************************************************************
*                                                                        *
**************************************************************************
IF ROM_TOS306
readCurrentTime:
            bsr       checkRTC
            bcs.s     readCurrentTime2
            move.b    #$d,(rtcadd).w
            move.b    (rtcdat).w,d0
            btst      #7,d0
            bne.s     readCurrentTime2
            move.l    #$12c80000,-(sp)          ; default date: July 4th, 1989, 12:00am
            bsr       writeRTCTime
            addq.w    #4,sp
readCurrentTime:
            bsr.s     readRTCTime
            cmp.l     #-1,d0
            beq.s     readCurrentTime2
            moveq     #0,d0
readCurrentTime2:
            rts

readRTCTime:bsr       checkRTC
            bcs.s     readCurrentTime2
            move.b    #$d,(rtcadd).w
            btst      #7,(rtcdat).w
            beq       readRTCTimeErr
            move      sr,d2
            move.w    d2,d0
            or.w      #$700,d0
readRTCTime3:move.b    #$a,(rtcadd).w
            btst      #7,(rtcdat).w
            bne.s     readRTCTime3
            moveq     #0,d0
            move.l    d0,d1
            move.b    #0,(rtcadd).w
            move.b    (rtcdat).w,d0
            asr.w     #1,d0
            move.b    #2,(rtcadd).w
            move.b    (rtcdat).w,d1
            bfins     d1,d0{21:6}
            move.b    #4,(rtcadd).w
            move.b    (rtcdat).w,d1
            bfins     d1,d0{16:5}
            move.b    #7,(rtcadd).w
            move.b    (rtcdat).w,d1
            bfins     d1,d0{11:5}
            move.b    #8,(rtcadd).w
            move.b    (rtcdat).w,d1
            bfins     d1,d0{7:4}
            move.b    #9,(rtcadd).w
            move.b    (rtcdat).w,d1
            sub.b     #12,d1
            bfins     d1,d0{0:7}
            move      d2,sr
            move      sr,d2
            ori       #$700,sr
            move.w    d0,(systemDate).l
            swap      d0
            move.w    d0,(systemTime).l
            swap      d0
            move      d2,sr
            rts

readRTCTimeErr:
            moveq     #-1,d0
            rts

writeRTCTime:bsr       checkRTC
            bcs       readCurrentTime2
            move.l    4(sp),d0
            move.b    #$b,(rtcadd).w
            move.b    #$80,(rtcdat).w
            move.b    #$a,(rtcadd).w
            move.b    #$2a,(rtcdat).w
            move.b    #$b,(rtcadd).w
            move.b    #$8e,(rtcdat).w
            move.b    #0,(rtcadd).w
            bfextu    d0{27:5},d1
            add.b     d1,d1
            move.b    d1,(rtcdat).w
            move.b    #2,(rtcadd).w
            bfextu    d0{21:6},d1
            move.b    d1,(rtcdat).w
            move.b    #4,(rtcadd).w
            bfextu    d0{16:5},d1
            move.b    d1,(rtcdat).w
            move.b    #7,(rtcadd).w
            bfextu    d0{11:5},d1
            move.b    d1,(rtcdat).w
            move.b    #8,(rtcadd).w
            bfextu    d0{7:4},d1
            move.b    d1,(rtcdat).w
            move.b    #9,(rtcadd).w
            bfextu    d0{0:7},d1
            add.b     #$c,d1
            move.b    d1,(rtcdat).w
            move.b    #$b,(rtcadd).w
            move.b    #$e,(rtcdat).w
            rts

checkRTC:   movea.l   sp,a0
            movea.l   (busexception).w,a1
            move.l    #checkRTC2,(busexception).w
            move.b    #0,(rtcadd).w
            move.b    (rtcdat).w,d0
            move.l    a1,(busexception).w
            andi      #$fe,ccr
            rts
checkRTC2:  movea.l   a0,sp
            move.l    a1,(busexception).w
            ori       #1,ccr
            rts



_NVMaccess: moveq     #-5,d0
            move.w    4(sp),d1                  ; op = 0: read NVRAM
            beq.s     _NVMread
            cmp.w     #2,d1                     ; op = 2: initialize NVRAM and update checksum
            beq.s     _NVMinit
            bhi.s     _NVMaccessr               ; (unknown op)

* write into NVRAM and update the checksum
            bsr.s     _NVMvalid                 ; op = 1: write NVRAM
            tst.w     d0
            bne.s     _NVMaccessr
            movea.l   $a(sp),a0                 ; buffer
            bra.s     _NVMwrt2
_NVMwrtl:   move.b    d1,(a1)                   ; select register
            move.b    (a0)+,(a2)                ; write value
            addq.w    #1,d1                     ; next reg
_NVMwrt2:   dbra      d2,_NVMwrtl
            bsr       _NVMchksum                ; calculate the checksum
            move.b    #$3f,(a1)
            move.b    d0,(a2)                   ; checksum into reg 63
            not.b     d0
            move.b    #$3e,(a1)
            move.b    d0,(a2)                   ; inverted checksum into reg 62
            moveq     #0,d0                     ; (ok)
_NVMaccessr:rts

* read from NVRAM and also validate the checksum
_NVMread:   bsr.s     _NVMvalid
            cmp.w     #-5,d0                    ; argument error?
            beq.s     _NVMaccessr               ; (fail)
            movea.l   $a(sp),a0                 ; buffer
            bra.s     _NVMread2
_NVMreadl:  move.b    d1,(a1)                   ; select register
            move.b    (a2),(a0)+                ; read value
            addq.w    #1,d1                     ; next reg
_NVMread2:  dbra      d2,_NVMreadl
            rts

* erase NVRAM and update the checksum
_NVMinit:   lea       (rtcadd).w,a1
            lea       (rtcdat).w,a2
            moveq     #0,d0                     ; fill value = 0
            moveq     #14,d1
            moveq     #50-1,d2                  ; fill 48 bytes plus checksum
_NVMinitl:  move.b    d1,(a1)                   ; select register
            move.b    d0,(a2)                   ; clear
            addq.w    #1,d1                     ; next reg
            dbra      d2,_NVMinitl
            move.b    #$3e,(a1)                 ; inverted checksum of 0 is 0xFF
            move.b    #$ff,(a2)
            rts

* validate checksum in NVRAM and function parameters
_NVMvalid:  bsr.s     _NVMchksum                ; calculate the checksum
            move.b    d0,d1
            moveq     #-12,d0                   ; default: checksum error
            move.b    #$3f,(a1)                 ; register 63
            cmp.b     (a2),d1                   ; contains the checksum
            bne.s     _NVMvalidr                ; (error)
            not.b     d1
            move.b    #$3e,(a1)                 ; register 62
            cmp.b     (a2),d1                   ; contains the inverted checksum
            bne.s     _NVMvalidr                ; (error)
            moveq     #-5,d0                    ; default: argument error
            move.w    $a(sp),d1                 ; count
            cmp.w     #48,d1                    ; more than 48 bytes?
            bcc.s     _NVMvalidr                ; (error)
            move.w    $c(sp),d2                 ; start
            bmi.s     _NVMvalidr                ; negative => error
            add.w     d1,d2                     ; start + count
            cmp.w     #48,d2                    ; >48
            bhi.s     _NVMvalidr                ; (error)
            moveq     #0,d0                     ; no error
_NVMvalidr: move.w    $c(sp),d2                 ; d2: count
            move.w    $a(sp),d1
            add.w     #14,d1                    ; d1: first register
            rts

_NVMchksum: lea       (rtcadd).w,a1
            lea       (rtcdat).w,a2
            moveq     #0,d0                     ; checksum = 0
            moveq     #14,d1                    ; NVRAM starts at register 14
            moveq     #48-1,d2                  ; 48 bytes
_NVMcheckl: move.b    d1,(a1)                   ; select register
            add.b     (a2),d0                   ; sum values to checksum
            addq.w    #1,d1
            dbra      d2,_NVMcheckl
            rts

ELSE

readCurrentTime:bsr.s checkRTC
            bcs.s     readCurrentTime2
            bsr.s     readRTCTime
            cmp.l     #-1,d0
            beq.s     readCurrentTime2
            moveq     #0,d0
readCurrentTime2:rts

IF ROM_STBOOK
checkRTC:
            movea.w   #rtc_sec,a0
            move.b    #$00,$1d(a0)              ; Clear RTC Test register
            move.b    #$0c,$1f(a0)              ; Reset 1 Hz & 16 Hz alarm pulse
            move.b    #$08,$1b(a0)              ; Clock Start, Alarm off, Bank 0
            moveq     #0,d0                     ; The RTC is always available
            rts
ELSE
checkRTC:   movea.w   #rtc_sec,a0
            move.l    (busexception).w,d2
            movea.l   sp,a2
            move.l    #checkRTC2,(busexception).w
            bset      #0,$1b(a0)
            move.l    d2,(busexception).w
            move.w    #$a05,d0
            movep.w   d0,5(a0)
            movep.w   5(a0),d1
            and.w     #$f0f,d1
            cmp.w     d0,d1
            bne.s     checkRTC3
            move.b    #1,1(a0)
            bclr      #0,$1b(a0)
            move.b    #0,$1d(a0)
            rts
checkRTC2:  movea.l   a2,sp
            move.l    d2,(busexception).w
checkRTC3:  ori       #1,ccr                    ; RTC not available
            rts
ENDIF

readRTCTime:bsr.s     checkRTC
            bcs       readRTCTimeErr
            lea       (rtcbufa).w,a1
            lea       (rtcbufb).w,a2
            bsr       readRTCTimeMask
readRTCTime2:exg      a1,a2
            bsr       readRTCTimeMask
            moveq     #$c,d0
readRTCTime3:move.b   (a1,d0.w),d1
            cmp.b     (a2,d0.w),d1
            bne.s     readRTCTime2
            dbra      d0,readRTCTime3
            moveq     #0,d0
            move.b    $b(a1),d0
            mulu.w    #10,d0
            add.b     $c(a1),d0
            asr.w     #1,d0
            move.w    d0,d1
            moveq     #0,d0
            move.b    9(a1),d0
            mulu.w    #10,d0
            add.b     $a(a1),d0
            asl.w     #5,d0
            add.w     d0,d1
            moveq     #0,d0
            move.b    7(a1),d0
            mulu.w    #10,d0
            add.b     8(a1),d0
            asl.w     #8,d0
            asl.w     #3,d0
            add.w     d0,d1
            swap      d1
            moveq     #0,d0
            move.b    4(a1),d0
            mulu.w    #10,d0
            add.b     5(a1),d0
            move.w    d0,d1
            moveq     #0,d0
            move.b    2(a1),d0
            mulu.w    #10,d0
            add.b     3(a1),d0
            asl.w     #5,d0
            add.w     d0,d1
            moveq     #0,d0
            move.b    (a1),d0
            mulu.w    #10,d0
            add.b     1(a1),d0
            asl.w     #8,d0
            asl.w     #1,d0
            add.w     d0,d1
            move      sr,d2
            ori       #$700,sr
            move.w    d1,(systemDate).l
            swap      d1
            move.w    d1,(systemTime).l
            move      d2,sr
            move.l    d1,d0
            rts

readRTCTimeMask:moveq #$c,d0
            moveq     #1,d1
readRTCTimeMask2:move.b (a0,d1.w),d2
            and.b     #$f,d2
            move.b    d2,(a1,d0.w)
            addq.w    #2,d1
            dbra      d0,readRTCTimeMask2
            rts

readRTCTimeErr:moveq  #-1,d0
            rts

writeRTCTime:bsr      checkRTC
            bcs       no_RTC_found
            lea       (rtcbufa).w,a1
            movea.w   4(sp),a2
            bsr       calcRTCWeekday
            move.b    d0,6(a1)
            move.w    6(sp),d1
            move.w    d1,d0
            and.l     #$1f,d0
            add.w     d0,d0
            divu.w    #10,d0
            move.b    d0,$b(a1)
            swap      d0
            move.b    d0,$c(a1)
            move.w    d1,d0
            lsr.w     #5,d0
            and.l     #$3f,d0
            divu.w    #10,d0
            move.b    d0,9(a1)
            swap      d0
            move.b    d0,$a(a1)
            lsr.w     #8,d1
            lsr.w     #3,d1
            ext.l     d1
            divu.w    #10,d1
            move.b    d1,7(a1)
            swap      d1
            move.b    d1,8(a1)
            move.w    4(sp),d1
            move.w    d1,d0
            and.l     #$1f,d0
            divu.w    #10,d0
            move.b    d0,4(a1)
            swap      d0
            move.b    d0,5(a1)
            move.w    d1,d0
            lsr.w     #5,d0
            and.l     #$f,d0
            divu.w    #10,d0
            move.b    d0,2(a1)
            swap      d0
            move.b    d0,3(a1)
            lsr.w     #1,d1
            lsr.w     #8,d1
            ext.l     d1
            move.l    d1,d2
            divu.w    #10,d1
            move.b    d1,(a1)
            swap      d1
            move.b    d1,1(a1)
            divu.w    #4,d2
            swap      d2
            move.b    #$e,$1f(a0)
            bset      #0,$1b(a0)
            move.b    #1,$15(a0)
            move.b    d2,$17(a0)
            bclr      #0,$1b(a0)
            bclr      #3,$1b(a0)
            moveq     #$c,d0
            moveq     #1,d1
writeRTCTime2:move.b  (a1,d0.w),(a0,d1.w)
            addq.w    #2,d1
            dbra      d0,writeRTCTime2
            bset      #3,$1b(a0)
            moveq     #0,d0
            rts

no_RTC_found:moveq    #-1,d0
            rts

calcRTCWeekday:moveq  #2,d2
            move.w    a2,d0
            lsr.w     #8,d0
            lsr.w     #1,d0
            add.w     d0,d2
            move.w    d0,d1
            lsr.w     #2,d1
            add.w     d1,d2
            move.w    a2,d1
            lsr.w     #5,d1
            and.w     #$f,d1
            and.w     #3,d0
            bne.s     calcRTCWeekday2
            cmp.w     #2,d1
            bhi.s     calcRTCWeekday2
            subq.w    #1,d2
calcRTCWeekday2:subq.w #1,d1
            add.w     d1,d1
            add.w     daysOffsetToMonths(pc,d1.w),d2
            move.w    a2,d1
            and.w     #$1f,d1
            add.w     d1,d2
            divu.w    #7,d2
            swap      d2
            moveq     #0,d0
            move.w    d2,d0
            rts

daysOffsetToMonths:DC.W $0000,$001f,$003b,$005a
            DC.W      $0078,$0097,$00b5,$00d4
            DC.W      $00f3,$0111,$0130,$014e

_Waketime:  move.l    4(sp),d0
            beq       clearRTCWaketime
            cmp.l     #-1,d0
            beq       readRTCWaketime
            cmp.l     #1,d0
            beq       enableRTCWaketime
            bsr       readRTCTime
            cmp.l     #-1,d0
            beq       enableRTCWaketimeDone
            move.l    4(sp),d1
            and.b     #$e0,d0
            and.b     #$e0,d1
            cmp.l     d1,d0                     ; is the new wakeup time already in the past?
            bcc.s     _Waketime_ret1
            move.l    (waketimeAlarm).l,d2
            beq.s     _Waketime2
            cmp.l     d2,d0
            bhi.s     _Waketime2
            cmp.l     d2,d1
            bcc.s     _Waketime_ret2
_Waketime2: movea.l   d0,a0
            move.l    d1,d2
            and.l     #$1fffff,d2
            and.l     #$1fffff,d0
            cmp.l     d2,d0                     ; new wakeup time is _now_?
            beq.s     _Waketime3
            move.l    d1,(waketimeAlarm).l
            moveq     #0,d0
            rts

_Waketime3: and.l     #$1f0000,d2
            cmp.l     #$10000,d2
            beq.s     _Waketime_ret5
            and.l     #$ffe0ffff,d1
            or.l      #$10000,d1
            move.l    d1,(waketimeAlarm).l
            moveq     #4,d0
            rts

_Waketime_ret5:move.l a0,d0
            add.l     #$10000,d0
            move.l    d0,(waketimeAlarm).l
            moveq     #5,d0
            rts

_Waketime_ret1:moveq  #1,d0
            rts

_Waketime_ret2:moveq  #2,d0
            rts

readRTCWaketime:move.l (waketimeAlarm).l,d0
            rts

clearRTCWaketime:bsr  checkRTC
            bcs       no_RTC_found
            bclr      #2,$1b(a0)                ; RTC Alarm off
            move.b    #$d,$1f(a0)               ; Reset 1Hz, 16 Hz and Alarm
            clr.l     (waketimeAlarm).l
            moveq     #3,d0
            rts

enableRTCWaketime:bsr readRTCTime
            cmp.l     #-1,d0
            beq       enableRTCWaketimeDone
            move.l    (waketimeAlarm).l,d1
            beq       enableRTCWaketimeDone
            and.b     #$e0,d1
            move.l    d1,d2
            and.b     #$e0,d0
            cmp.l     d1,d0                     ; is the new wakeup time already in the past?
            bhi.s     enableRTCWaketimeDone
            bne.s     enableRTCWaketime2
            and.b     #$1f,d2
            cmp.b     #7,d2
            bcs.s     enableRTCWaketime3
enableRTCWaketime2:movea.w #rtc_sec,a0
            bset      #0,$1b(a0)                ; RTC select Bank 1
            move.l    (waketimeAlarm).l,d1
            move.w    d1,d0
            lsr.w     #5,d0                     ; Minutes
            and.l     #$3f,d0
            divu.w    #10,d0
            move.b    d0,7(a0)                  ; RTC 1-minute alarm register
            swap      d0
            move.b    d0,5(a0)                  ; RTC 10-minute alarm register
            move.w    d1,d0
            lsr.w     #8,d0
            lsr.w     #3,d0                     ; Hours
            ext.l     d0
            divu.w    #10,d0
            move.b    d0,$b(a0)                 ; RTC 1-hour alarm register
            swap      d0
            move.b    d0,9(a0)                  ; RTC 10-hour alarm register
            move.l    d1,d0
            swap      d0
            and.w     #$1f,d0                   ; Days
            ext.l     d0
            divu.w    #10,d0
            move.b    d0,$11(a0)                ; RTC 1-hour alarm register
            swap      d0
            move.b    d0,$f(a0)                 ; RTC 10-day alarm register
            movea.w   (waketimeAlarm).l,a2
            bsr       calcRTCWeekday
            move.b    d0,$d(a0)                 ; RTC day-of-the-week alarm register
            bset      #2,$1b(a0)                ; RTC Alarm on
            bclr      #0,$1b(a0)                ; RTC select Bank 0
enableRTCWaketimeDone:moveq #0,d0
            rts

enableRTCWaketime3:clr.l (waketimeAlarm).l
            moveq     #-1,d0
            rts
ENDIF

**************************************************************************
*                                                                        *
*                cp/m-68k atari rbp bios                                 *
*                basic input/output subsystem                            *
*                copyright 1984, atari corporation                       *
*                all rights reserved.                                    *
*                atari confidential                                      *
*                                                                        *
**************************************************************************
**************************************************************************
*                                                                        *
*        convert ikbd real-time clock format to jdos format              *
*                                                                        *
**************************************************************************
jdostime:   lea       (clkrec).w,a0
            bsr       bcdbin
            subi.b    #80,d0                    ; adjust so that 1980 => 0 for time base
            move.b    d0,d2
            asl.l     #4,d2

            bsr       bcdbin
            add.b     d0,d2
            asl.l     #5,d2

            bsr       bcdbin
            add.b     d0,d2
            asl.l     #5,d2

            bsr       bcdbin
            add.b     d0,d2
            asl.l     #6,d2

            bsr       bcdbin
            add.b     d0,d2
            asl.l     #5,d2

            bsr       bcdbin

            lsr.b     #1,d0                     ; adjust to provide two second increments...
            add.b     d0,d2                     ; ...another @!#%@#$% kludge, thank you !
            move.l    d2,(datetime).w
            move.b    #0,(newtod).w             ; clear handshaking flag
            rts


readIKBDTime:move.b   #$ff,(newtod).w
            move.b    #$1c,d1
            bsr       ikbdput
            movea.l   (_hz_200).w,a0
            adda.w    #200,a0
            moveq     #0,d0
readIKBDTime2:cmpa.l  (_hz_200).w,a0
            bcs.s     readIKBDTimex
            tst.b     (newtod).w
            bne.s     readIKBDTime2
            move.l    (datetime).w,d0
readIKBDTimex:rts

writeIKBDTime:move.l  4(sp),(newtime).w
            lea       (kmbuf).l,a0
            move.l    (newtime).w,d2
            move.b    d2,d0
            andi.b    #$1f,d0
            asl.b     #1,d0
            bsr.s     binbcd
            lsr.l     #5,d2
            move.b    d2,d0
            andi.b    #$3f,d0
            bsr.s     binbcd
            lsr.l     #6,d2
            move.b    d2,d0
            andi.b    #$1f,d0
            bsr.s     binbcd
            lsr.l     #5,d2
            move.b    d2,d0
            andi.b    #$1f,d0
            bsr.s     binbcd
            lsr.l     #5,d2
            move.b    d2,d0
            andi.b    #$f,d0
            bsr.s     binbcd
            lsr.l     #4,d2
            move.b    d2,d0
            andi.b    #$7f,d0
            bsr.s     binbcd
            addi.b    #$80,(a0)
            move.b    #$1b,d1
            bsr       ikbdput
            moveq     #5,d3
            lea       (oclkrec).l,a2
            bsr       ikbdstr
            move.b    #$1c,d1
            bsr       ikbdput
            rts

**************************************************************************
*                                                                        *
*                  convert a byte from binary to bcd format              *
*                                                                        *
*       entry:     d0.l  - value                                         *
*                                                                        *
**************************************************************************
binbcd:     moveq     #0,d1
            move.b    d0,d1
            divs.w    #10,d1
            asl.w     #4,d1
            move.w    d1,d0
            swap      d1
            add.w     d1,d0
            move.b    d0,-(a0)                  ; transfer to output clock buffer
            rts

**************************************************************************
*                                                                        *
*                  convert a byte from bcd format to binary              *
*                                                                        *
*       entry:     a0.l  - pointer to byte                               *
*                                                                        *
**************************************************************************
bcdbin:     move.b    (a0)+,d0                  ; get bcd byte
            move.b    d0,d1
            and.w     #$0f,d0                   ; dump high nibble
            and.w     #$f0,d1                   ; isolate high nibble
            asr.w     #4,d1                     ; dump low nibble
            mulu.w    #10,d1                    ; high nibble * 10
            add.w     d1,d0                     ; + low nibble
            rts

**************************************************************************
*                                                                        *
*                  midi output status                                    *
*                                                                        *
*       entry:                                                           *
*                                                                        *
*       word       midiost()                                             *
*                                                                        *
*       returns true/okay to send = -1,  false/not ready = 0             *
*                                                                        *
**************************************************************************
midiost:    moveq     #-1,d0                    ; pre-set to true
            move.b    (midictl).w,d2            ; grab midi status
            btst      #1,d2
            bne.s     midiox                    ; status okay to send
            moveq     #0,d0                     ; status not okay
midiox:     rts

**************************************************************************
*                                                                        *
*                  write char to midi port                               *
*                                                                        *
*       entry:                                                           *
*                                                                        *
*       void       midiwc(chr)                                           *
*       word       chr                                                   *
*                                                                        *
**************************************************************************
midiwc:     move.w    6(sp),d1
midiput:    lea       (midictl).w,a1            ; point to midi register base
midput1:    move.b    (a1),d2                   ; grab midi status
            btst      #1,d2
            beq.s     midput1
            move.b    d1,2(a1)
            rts                                 ; done for now

**************************************************************************
*                                                                        *
*                  put string to midi routine                            *
*                                                                        *
*       entry:                                                           *
*                                                                        *
*       void       midiws(size,ptr)                                      *
*       word       size                                                  *
*       long       ptr                                                   *
*                                                                        *
**************************************************************************
midiws:     moveq     #0,d3
            move.w    4(sp),d3                  ; get size of string buffer - 1
            movea.l   6(sp),a2                  ; get string address
midp1:      move.b    (a2)+,d1
            bsr.s     midiput
            dbra      d3,midp1
            rts

**************************************************************************
*                                                                        *
*                  get midi receiver buffer status                       *
*                                                                        *
*       entry:                                                           *
*                                                                        *
*       word       midistat()                                            *
*                                                                        *
*       -1 signifies true/okay  0 - signifies false/no characters        *
*                                                                        *
**************************************************************************
midstat:    lea       (mbufrec).w,a0            ; point to midi i/o bufrec
            lea       (midictl).w,a1            ; point to midi register base
            moveq     #-1,d0                    ; set result to true
            lea       6(a0),a2
            lea       8(a0),a3
            cmpm.w    (a3)+,(a2)+               ; atomic buffer empty test
            bne.s     midist1                   ; branch of not, assume d0 is "clr.w"'ed
            moveq     #0,d0                     ; set result to false
midist1:    rts

**************************************************************************
*                                                                        *
*                  getchar routine for midi port                         *
*                                                                        *
*       this routine transfers characters from a input queue that is     *
*       filled by an automatic interrupt routine.  the interrupt         *
*       routine handles the actual transfer of the character from the    *
*       i/o port.                                                        *
*                                                                        *
*       entry:                                                           *
*                                                                        *
*       long       midiin()                                              *
*                                                                        *
*       long data returned represents upper three bytes of time stramp   *
*       and least significant byte as data                               *
*                                                                        *
**************************************************************************
* assume that a0/a1 are inited by the midstat call for the rest of
* this routine.

midin:      bsr.s     midstat                   ; see if key pressed
            tst.w     d0
            beq.s     midin                     ; wait until byte comes in
            move      sr,-(sp)                  ; protect this upcoming test
            ori       #$700,sr
            move.w    6(a0),d1                  ; get current head pointer offset from buffer
            cmp.w     8(a0),d1                  ; head=tail?
            beq.s     mwi2                      ; yes

* check for wrap of pointer

            addq.w    #1,d1                     ; i=j+1
            cmp.w     4(a0),d1                  ; ? i>= current bufsiz?
            bcs.s     mwi1                      ; no...
            moveq     #0,d1                     ; wrap pointer
mwi1:       movea.l   (a0),a1                   ; get base address of buffer
            and.l     #$ffff,d1
IF !ROM_TOS306
            moveq     #0,d0
ENDIF
            move.b    (a1,d1.l),d0              ; get character
            move.w    d1,6(a0)                  ; store new head pointer to buffer record
mwi2:       move      (sp)+,sr
            rts

**************************************************************************
*                                                                        *
*                  parallel i/o port service routine                     *
*                                                                        *
*       this set of routines is for general parallel i/o                 *
*                                                                        *
*       entry to listout                                                 *
*                                                                        *
*       entry to listin                                                  *
*                                                                        *
*       exit from listin                                                 *
*                                                                        *
**************************************************************************
_lstout:    btst      #4,(pconfig).w
            bne       _auxout

            move.l    (_hz_200).w,d2            ; d2 = hz_200 - prt_to
            sub.l     (prt_to).w,d2             ; (compute time since last timeout)
            cmpi.l    #5*200,d2                 ; do "fake" timeout if we timed out within
            bcs.s     lperr                     ; the last five seconds
            move.l    (_hz_200).w,d2            ; d2 = starting time for this char
pt0:        bsr       _lstostat                 ; go get parallel port status
            tst.w     d0                        ; ...and check for high (busy)
            bne.s     pt1                       ; port is ready -- print the char

            move.l    (_hz_200).w,d3            ; d3 = hz_200 - d2
            sub.l     d2,d3
            cmpi.l    #$1770,d3                 ; check for 30 second delta
            blt.s     pt0                       ; continue of no timeout

lperr:      moveq     #0,d0                     ; return value of 0 indicates timeout
            move.l    (_hz_200).w,(prt_to).w    ; record time of last timeout
            rts

pt1:        move      sr,d3                     ; save status register
            ori       #$700,sr                  ; protect upcoming switching of the port setting
            moveq     #7,d1                     ; get current io enable register contents
            bsr       gientry
            ori.b     #$80,d0                   ; set port b for output
            move.b    #$87,d1                   ; set to write to io enable
            bsr       gientry
            move      d3,sr                     ; restore status register

            move.w    6(sp),d0                  ; retrieve byte to be sent and...
            move.b    #$8f,d1                   ; write out byte to parallel port
            bsr       gientry

            move      sr,-(sp)
            ori       #$700,sr
            bsr.s     strobeon
            bsr.s     strobeon
            bsr.s     strobeoff
            move      (sp)+,sr
            moveq     #-1,d0                    ; set d0=-1 for good transfer status
            rts


strobeoff:  moveq     #$20,d2                   ; set strobe off
            bra       onbit                     ; go set it!!
strobeon:   move.b    #$df,d2                   ; set strobe on
            bra       offbit                    ; set strobe now...


_lstin:     moveq     #7,d1                     ; get current io enable register contents
            bsr       gientry
            andi.b    #$7f,d0                   ; set port b for input
            move.b    #$87,d1                   ; set to write to io enable
            bsr       gientry

            bsr.s     strobeoff                 ; busy off!
lstibusy:   bsr.s     _lstostat                 ; go get parallel port status
            tst.w     d0                        ; ...and check for high (busy)
            bne.s     lstibusy                  ; loop till high...
            bsr.s     strobeon
            moveq     #$f,d1                    ; init to use gientry routine to read
            bra       gientry                   ; now get the byte from the parallel port
* d0.l contains the byte of data from the port
* the 'bra' is implied rts from this routine

**************************************************************************
*                                                                        *
*                  parallel port status routine                          *
*                                                                        *
**************************************************************************
_lstostat:  lea       (gpip).w,a0               ; point to mfp register base
            moveq     #-1,d0                    ; pre-init to true (parallel port ready)
            btst      #0,(a0)
            beq.s     lst1
            moveq     #0,d0                     ; parallel port busy
lst1:       rts


**************************************************************************
*                                                                        *
*                  auxillary port input status routine                   *
*                                                                        *
**************************************************************************
auxistat:   lea       (rbufrec).w,a0            ; point to rs-232 buffer record
_auxistat:  moveq     #-1,d0                    ; set result to true
            lea       6(a0),a1                  ; head
            lea       8(a0),a0                  ; tail
            cmpm.w    (a0)+,(a1)+               ; atomic buffer empty test
            bne.s     auxist1
            moveq     #0,d0                     ; set result to false
auxist1:    rts

**************************************************************************
*                                                                        *
*                  auxillary input routine                               *
*                                                                        *
**************************************************************************
auxin:      lea       (rbufrec).w,a0            ; point to rs-232 buffer record
            lea       (gpip).w,a2
_auxin:     bsr       rs232get
            move.w    d0,-(sp)
            tst.b     $20(a0)                   ; flow control active?
            beq.s     auxinrts                  ; (no)
            move.w    8(a0),d0                  ; tail
            sub.w     6(a0),d0                  ; - head
            bpl.s     auxin2                    ; underflow?
            add.w     4(a0),d0                  ; + size
auxin2:     cmp.w     $a(a0),d0
            bgt.s     auxinrts
            tst.b     $1e(a0)                   ; high-water flag already set?
            beq.s     auxinrts                  ; no...exit...
            bsr.s     auxinclrhw
auxinrts:   move.w    (sp)+,d0
            rts

auxinclrhw: clr.b     $1e(a0)                   ; clear high-water flag
            btst      #0,$20(a0)                ; is the rs232 mode xon/xoff?
            bne.s     auxinclrhw2               ; (yes)
            bra       rtson
auxinclrhw2:move.b    #$11,$21(a0)              ; "xon"
            bra.s     _auxoutwr

**************************************************************************
*                                                                        *
*                  auxillary port output status routine                  *
*                                                                        *
**************************************************************************
_auxostat:  lea       (rbufrec+14).w,a0         ; point to rs-232 buffer record
__auxostat: move.w    8(a0),d1                  ; tail
            bsr       wrapin
            moveq     #-1,d0                    ; set result to true
            cmp.w     6(a0),d1                  ; head
            bne.s     _auxostatrts
            moveq     #0,d0                     ; set result to false
_auxostatrts:rts

**************************************************************************
*                                                                        *
*                  auxillary output routine                              *
*                                                                        *
**************************************************************************
_auxout:    move.w    6(sp),d0                  ; get data
            lea       (rbufrec+14).w,a0
            bsr       rs232put                  ; exit via rs-232 output routine
            lea       (rbufrec).w,a0
            lea       (gpip).w,a2

_auxoutwr:  tst.b     $2c(a2)                   ; transmitter status
            bpl.s     _auxoutrts                ; not ready
            move      sr,-(sp)
            ori       #$700,sr
            bsr       wr_rs232
            move      (sp)+,sr
_auxoutrts: rts

**************************************************************************
*                                                                        *
*               ikbd output status                                       *
*                                                                        *
*       entry:                                                           *
*                                                                        *
*       word    ikbdost()                                                *
*                                                                        *
*       returns true/okay to send = 1,  false/not ready = 0              *
*                                                                        *
**************************************************************************
ikbdost:    moveq     #-1,d0                    ; pre-set to true
            move.b    (keyctl).w,d2             ; grab ikbd status
            btst      #1,d2
            bne.s     ikbdox                    ; status okay to send
            moveq     #0,d0                     ; status not okay
ikbdox:     rts

**************************************************************************
*                                                                        *
*               write char to ikbd port                                  *
*                                                                        *
*       entry:                                                           *
*                                                                        *
*       void    ikbdwc(chr)                                              *
*       word    chr                                                      *
*                                                                        *
**************************************************************************
ikbdwc:     move.w    6(sp),d1
ikbdput:    lea       (keyctl).w,a1             ; point to ikbd register base
ikput1:     move.b    (a1),d2                   ; grab keyboard status
            btst      #1,d2
            beq.s     ikput1
IF ROM_TOS306
            move.w    #$400,d0
            bsr       mfpdelay
ELSE
            lea       (tcdr).w,a0
            move.w    #$bf,d0
ikput2:     move.b    (a0),d2
ikput3:     cmp.b     (a0),d2
            beq.s     ikput3
            dbra      d0,ikput2
ENDIF
            move.b    d1,2(a1)                  ; write char to the ikbd port
            rts                                 ; done for now

**************************************************************************
*                                                                        *
*               put string to to ikbd routine                            *
*                                                                        *
*       entry:                                                           *
*                                                                        *
*       void    ikbdws(size,ptr)                                         *
*       word    size                                                     *
*       long    ptr                                                      *
*                                                                        *
**************************************************************************
ikbdws:
IF ROM_TOS306
            moveq     #0,d3
ENDIF
            move.w    4(sp),d3
            movea.l   6(sp),a2
ikbdstr:    move.b    (a2)+,d1
            bsr.s     ikbdput
            dbra      d3,ikbdstr
            rts


constat:    lea       (kbufrec).w,a0            ; point to ikbd buffer record
            moveq     #-1,d0                    ; set result to true
            lea       6(a0),a2                  ; head
            lea       8(a0),a3                  ; tail
            cmpm.w    (a3)+,(a2)+               ; atomic buffer empty test
            bne.s     const1                    ; branch of not, assume d0 is "clr.w"'ed
            moveq     #0,d0                     ; set result to false
const1:     rts


conin:      bsr.s     constat                   ; see if key pressed
            tst.w     d0
            beq.s     conin                     ; wait until key pressed
            move      sr,-(sp)                  ; protect this upcoming test
            ori       #$700,sr
            move.w    6(a0),d1                  ; get current head pointer offset
            cmp.w     8(a0),d1                  ; head=tail?
            beq.s     cwi2                      ; yes

* check for wrap of pointer

            addq.w    #4,d1                     ; i=h+4
            cmp.w     4(a0),d1
            bcs.s     cwi1                      ; no...
            moveq     #0,d1                     ; wrap pointer
cwi1:       movea.l   (a0),a1                   ; get base address of buffer
            and.l     #$ffff,d1
            move.l    (a1,d1.l),d0              ; get character
            move.w    d1,6(a0)                  ; store new head pointer to buffer
cwi2:       move      (sp)+,sr
            rts


conoutst:   moveq     #-1,d0
            rts                                 ; jdos requirement


***********************************************************************************
*                                                                                 *
*                routine to set up the general interrupt port registers           *
*                        (gpip,are,ddr)                                           *
*                                                                                 *
*                algorithm to set up the port                                     *
*                                                                                 *
*                1.  mask off all interrupts via the imrx register,               *
*                2.  clear all enable and pending bits in the ierx and iprx       *
*                         registers;                                              *
*                3.  check the inerrupt in-service registers and loop till        *
*                         clear;                                                  *
*                4.  init the aer register bits as desired (default = 11111111);  *
*                5.  init the ddr register bits as desired (default = 10000000);  *
*                6.  clear the gpip register;                                     *
*                7.  enable all desired interrupt enable bits;                    *
*                8.  mask on all desired interrupt mask bits;                     *
*                                                                                 *
***********************************************************************************
initmfp:    lea       (gpip).w,a0               ; init mfp address pointer
            moveq     #0,d0                     ; init to zero for clearing mfp
            movep.l   d0,0(a0)                  ; clear gpip thru iera
            movep.l   d0,8(a0)                  ; clear ierb thru isrb
            movep.l   d0,$10(a0)                ; clear isrb thru vr

            move.b    #$48,$16(a0)              ; set mfp autovector to $100 and s-bit
            bset      #2,2(a0)                  ; set cts to low to high transition

IF ROM_TOS306
            lea       (gpip_TT).w,a0            ; init mfp #2 address pointer
            moveq     #0,d0                     ; init to zero for clearing mfp
            movep.l   d0,0(a0)                  ; clear gpip thru iera
            movep.l   d0,8(a0)                  ; clear ierb thru isrb
            movep.l   d0,$10(a0)                ; clear isrb thru vr
            move.b    #$58,$16(a0)              ; set mfp autovector to $140 and s-bit
ENDIF

            clr.b     (privexception).w

* init the "c" timer
            move.w    #$1111,(tc_rot).w         ; setup bitstream for /4 on timer c interrupts
            move.w    #$14,(_timr_ms).w         ; set timer calibration value

            moveq     #2,d0                     ; set to timer C
            moveq     #$50,d1                   ; set to /64 for 200 hz tick
            move.w    #$c0,d2                   ; set to 192
            bsr       settimer                  ; setup timer and init interrupt vector.....

            lea       (timercint).l,a2          ; point to the timer C interrupt routine...
            moveq     #5,d0                     ; point the the timer C interrupt number
            bsr       initint

* init the "d" timer
            moveq     #3,d0                     ; select the d timer
            moveq     #1,d1                     ; init for /4 for 9600 baud
            moveq     #2,d2                     ; init for 9600 baud
            bsr       settimer                  ; branch to out timer initialier...

            move.b    #1,(rbufrec+34).w

* now init the 3 rs232 chip registers
            lea       (gpip).w,a0
            move.l    #$880105,d0
            movep.l   d0,$26(a0)                ; inits scr,ucr,rsr,tsr

IF ROM_TOS306
* now init the 3 rs232 chip registers in mfp #2
            lea       (gpip_TT).w,a0
            move.l    #$880105,d0
            movep.l   d0,$26(a0)                ; inits scr,ucr,rsr,tsr
            move.b    #1,(tcdcr_TT).w           ; timer c+d control register: delay 1:4
            move.b    #2,(tddr_TT).w            ; timer d data register
            move.b    #1,($1380).w              ; ???
ENDIF

* check for SCC chip
            tst.b     (STEFlag).l
            bne.s     initmfp2
            bsr       initscc

* initialize the default rs-232 control line settings
initmfp2:   bsr       dtron
            bsr       rtson

* initialize the rs-232 buffer record structure
            lea       (rbufrec).w,a0
            lea       (rs232init).l,a1
            moveq     #$23,d0
            bsr       lbmove                    ; do block move and return

IF ROM_TOS306
            lea       (ttmfp_rbufrec).w,a0
            lea       (ttrs232init).l,a1
            moveq     #$23,d0
            bsr       lbmove

*           ttrs232init:DC.L      iobuf_ttrs232i            ; ibufptr
*                       DC.W      $0100                     ; ibufsiz
*                       DC.W      $0000                     ; ibufhead
*                       DC.W      $0000                     ; ibuftail
*                       DC.W      $0080                     ; ibuflow
*                       DC.W      $00c0                     ; ibufhigh
*                       DC.L      iobuf_ttrs232o            ; obufptr
*                       DC.W      $0100                     ; obufsiz
*                       DC.W      $0000                     ; obufhead
*                       DC.W      $0000                     ; obuftail
*                       DC.W      $0080                     ; obuflow
*                       DC.W      $00c0                     ; obufhigh
*                       DC.B      $00                       ; rsrbyte
*                       DC.B      $00                       ; tsrbyte
*                       DC.B      $00                       ; rxoff
*                       DC.B      $00                       ; txoff
*                       DC.B      $01                       ; rsmode_xon
*                       DC.B      $00                       ; rsmode_filler
*                       DC.W      $01ff

ENDIF

* initialize the midi buffer record structure
            lea       (mbufrec).w,a0
            lea       (minit).l,a1
            moveq     #$d,d0
            bsr       lbmove                    ; do block move and return

            move.w    #-1,(altdigits).l         ; reset alt-numpad ascii entering
            move.l    #aciaexit,d0              ; init ikbd and midi error handler address
            move.l    d0,(vkbderr).w            ; init keyboard error handler address
            move.l    d0,(vmiderr).w            ; init midi error handler address
            move.l    #sysmidi,(midivec).w      ; point to system midi interrupt vector
            move.l    #astatusmidi,(astatusmidip).w
            move.l    #astatuskeyboard,(astatuskeyp).w
            move.l    #itsakey,(itsakeyp).w

* init the midi acia next
            move.b    #$03,(midictl).w          ; init the midi acia via master reset

* init the acia to divide by 16x clock, 8 bit data, 1 stop bit, no parity,
* rts low, transmitting interrupt disabled, receiving interrupt enabled
            move.b    #$95,(midictl).w

* initialize the keyboard acia interrupt vector exception address
            move.b    #7,(conterm).w            ; enabled keyclick, repeat key, bell functions

            move.l    #jdostime,(clkintvec).w
            move.l    #genrts,d0                ; generalized rts for ikbd subsystems
            move.l    d0,(statintvec).w
            move.l    d0,(msintvec).w           ; init user mouse interrupt adr to rts
            move.l    d0,(joyintvec).w
            bsr       initdevstables
*
* Sound routine initialization - uses the pre-init'ed d0.l=0000 !!
*
            moveq     #0,d0                     ; init 'd0' to clear sound variables
            move.l    d0,(cursnd).w             ; clear sound ptr
            move.b    d0,(timer).w              ; clear delay timer
            move.b    d0,(auxd).w               ; clear temp value
            move.l    d0,(prt_to).w             ; init printer timeout to 0

            bsr       strobeoff                 ; init strobe to off (line high!)
            move.b    #15,(cdelay1).w           ; init system default key repeat values
            move.b    #2,(cdelay2).w

* within the mouse relative routine
* initialize the ikbd buffer record structure
            lea       (kbufrec).w,a0
            lea       (kinit).l,a1
            moveq     #$d,d0
            bsr.s     lbmove                    ; do block move and return

            bsr       bioskeys                  ; point key translation address to
* the rom based translation tables

* init the acia next
            move.b    #$03,(keyctl).w           ; init the acia via master Reset

* now that the vector is initialized, we can allow interrupts to occur!
* init the acia do divice by 64 clock, 8 bit data, 1 stop bit, no parity,
* rts low, transmitting interrupt disabled, receiving interrupt enabled

            move.b    #$96,(keyctl).w

            movea.l   #mfpvectr,a3              ; point to initializing array of exception vec's
            moveq     #3,d1                     ; init branch counter/index
st1:        move.l    d1,d2
            move.l    d1,d0                     ; load in interrupt # to setup
            addi.b    #9,d0                     ; add constand to point to proper mfp interrupt
            asl.l     #2,d2
            movea.l   (a3,d2.w),a2
            bsr       initint                   ; go to service routine
            dbra      d1,st1
            lea       (midikey).l,a2
            moveq     #6,d0                     ; load in interrupt # to setup
            bsr       initint                   ; go to service routine

* initializing code which sets the enable
* and mask bits...
            lea       (ctsint).l,a2             ; point to the CTS interrupt routine...
            moveq     #2,d0                     ; point to the CTS interrrupt number
            bsr       initint

IF ROM_TOS306
            movea.l   #$e36704,a3               ; ttmfp_txerr,ttmfp_txrint,ttmfp_rxerr,ttmfp_rcvrint
            movea.w   #mfpTT_SendError,a0       ; $0164
            move.l    (a3)+,(a0)+               ; mfpTT_SendError = ttmfp_txer
            move.l    (a3)+,(a0)+               ; mfpTT_SendBufferEmpty = ttmfp_txrint
            move.l    (a3)+,(a0)+               ; mfpTT_ReceiveError = ttmfp_rxerr
            move.l    (a3)+,(a0)+               ; mfpTT_ReceiveBufferFull = ttmfp_rcvrint
            ori.b     #$1e,(iera_TT).w
            ori.b     #$1e,(imra_TT).w
ENDIF

genrts:     rts

lbmove:     move.b    (a1)+,(a0)+
            dbra      d0,lbmove
            rts

kinit:      DC.L      iobuf_keyb                ; ibufptr
            DC.W      $0100                     ; ibufsiz
            DC.W      $0000                     ; ibufhead
            DC.W      $0000                     ; ibuftail
            DC.W      $0040                     ; ibuflow
            DC.W      $00c0                     ; ibufhigh
minit:      DC.L      iobuf_midi                ; ibufptr
            DC.W      $0080                     ; ibufsiz
            DC.W      $0000                     ; ibufhead
            DC.W      $0000                     ; ibuftail
            DC.W      $0020                     ; ibuflow
            DC.W      $0060                     ; ibufhigh
rs232init:  DC.L      iobuf_rs232i              ; ibufptr
            DC.W      $0100                     ; ibufsiz
            DC.W      $0000                     ; ibufhead
            DC.W      $0000                     ; ibuftail
            DC.W      $0080                     ; ibuflow
            DC.W      $00c0                     ; ibufhigh
            DC.L      iobuf_rs232o              ; obufptr
            DC.W      $0100                     ; obufsiz
            DC.W      $0000                     ; obufhead
            DC.W      $0000                     ; obuftail
            DC.W      $0080                     ; obuflow
            DC.W      $00c0                     ; obufhigh
            DC.B      $00                       ; rsrbyte
            DC.B      $00                       ; tsrbyte
            DC.B      $00                       ; rxoff
            DC.B      $00                       ; txoff
            DC.B      $01                       ; rsmode_xon
            DC.B      $00                       ; rsmode_filler
            DC.W      $01ff

* array of exception vector addresses for the above interrupts, including
* dummy vectors that point to "rte's".

mfpvectr:   DC.L      txerror
            DC.L      txrint
            DC.L      rxerror
            DC.L      rcvrint

***********************************************************************************
*                                                                                 *
*                routine to set up a timer                                        *
*                                                                                 *
*                algorithm to init a timer                                        *
*                                                                                 *
*                1.  determine which timer and set d0.b = to timer's index value  *
*                    as shown below;                                              *
*                2.  disable the associated interrupt;                            *
*                3.  disable the timer itself via it's timer control register;    *
*                4.  initialize the timer's data register                         *
*                5.  repeat step #4 until the data register's contents are        *
*                    verified, per the errata sheet to the 68901 description;     *
*                6.  turn on the timer by using the value that you previously     *
*                    stored in d1;                                                *
*                                                                                 *
*                note:    the interrupt vector for the associated timer           *
*                         is not set in this routine, so it is the user's         *
*                         responsiblity to set it if so desired!                  *
*                                                                                 *
*                                                                                 *
*                registers used:          d0-d3/a0-a3                             *
*                registers saved:         d0-d3/a0-a3                             *
*                entry:                                                           *
*                        d0.l - timer to be set                                   *
*                                0 - timer a                                      *
*                                1 - timer b                                      *
*                                2 - timer c                                      *
*                                3 - timer d                                      *
*                        d1.b - timer's new control setting                       *
*                        d2.b - timer's data register data                        *
*                                                                                 *
*                exit:   no values to pass                                        *
*                                                                                 *
*                        d3   - used and abused by call to mskreg routine         *
*                        a0.l - set to mfp register base                          *
*                        a1.l - temporary location for a3                         *
*                        a2.l - used to pass table address to mskreg routine      *
*                        a3.l - used to pass table address to mskreg routine      *
*                                                                                 *
***********************************************************************************
settimer:   movem.l   d0-d4/a0-a3,-(sp)         ; save all registers to be messed with!!
            movea.w   #gpip,a0                  ; set mfp chip address pointer

            movea.l   #imrt,a3                  ; mask off the timer's interrupt maskable bit
            movea.l   #imrmt,a2
            bsr.s     mskreg

            movea.l   #iert,a3                  ; mask off the timer's interrupt enable bit
            movea.l   #imrmt,a2
            bsr.s     mskreg

            movea.l   #iprt,a3                  ; mask off the timer's interrupt pending bit
            movea.l   #imrmt,a2
            bsr.s     mskreg

            movea.l   #isrt,a3                  ; mask off the timer's interrupt inservice bit
            movea.l   #imrmt,a2
            bsr.s     mskreg

            movea.l   #tcrtab,a3                ; mask off the timer's control bits
            movea.l   #tcrmsk,a2
            bsr.s     mskreg

            exg       a3,a1                     ; save address pointer for restoring control

            lea       (tdrtab).l,a3             ; initialize the timer data register
            moveq     #0,d3                     ; to prevent false effective address generation
            move.b    (a3,d0.w),d3
verify:     move.b    d2,(a0,d3.w)
            cmp.b     (a0,d3.w),d2
            bne.s     verify

            exg       a3,a1                     ; grab that register address back
            or.b      d1,(a3)                   ; mask the timer control register value

            movem.l   (sp)+,d0-d4/a0-a3         ; restore all registers that were saved
            rts

***********************************************************************************
*                generalize mask register bit(s) routine                          *
*                                                                                 *
*       entry                                                                     *
*       static  d0 - contains the timer #                                         *
*               d3 - used and abused                                              *
*               d4 - used and abused                                              *
*       static  a0 - mfp register base                                            *
*               a3 - points to table of similar timer registers                   *
*       static  a2 - points to table of similar timer data registers              *
***********************************************************************************

mskreg:     bsr.s     getmask
            move.b    (a2),d3                   ; grab mask now
            and.b     d3,(a3)                   ; and have masked off the desired bit(s)
            rts

getmask:    moveq     #0,d3                     ; to prevent false effective address generation
            adda.w    d0,a3                     ; have got pointer to mfp register now
            move.b    (a3),d3                   ; now have the address offset to mfp
            add.l     a0,d3
            movea.l   d3,a3                     ; now have address pointing to desired mfp reg.
* now we get the mask to turn off interrupt
            adda.w    d0,a2                     ; have got pointer to mask now
            rts


iert:       DC.B      $06,$06,$08,$08
iprt:       DC.B      $0a,$0a,$0c,$0c
isrt:       DC.B      $0e,$0e,$10,$10
imrt:       DC.B      $12,$12,$14,$14
imrmt:      DC.B      $df,$fe,$df,$ef
tcrtab:     DC.B      $18,$1a,$1c,$1c
tcrmsk:     DC.B      $00,$00,$8f,$f8
tdrtab:     DC.B      $1e,$20,$22,$24

***********************************************************************************
*                initialize mfp interrupt via GEMDOS                              *
*                                                                                 *
*                entry                                                            *
*                                                                                 *
*                void    mfpint(numint,intvec)                                    *
*                word    numint                                                   *
*                long    intvec                                                   *
*                                                                                 *
***********************************************************************************
mfpint:     move.w    4(sp),d0
            movea.l   6(sp),a2
            andi.l    #$f,d0                    ; to ensure masking of 0-$f

***********************************************************************************
*                                                                                 *
*                routine to init an mfp associated interrupt vector               *
*                                                                                 *
*                algorithm                                                        *
*                                                                                 *
*                1.  block the interrupt via it's mask bit;                       *
*                2.  disable the interrupt's enable and pending bits;             *
*                3.  check the interrupt's in-service register and loop till      *
*                    clear;                                                       *
*                4.  init the interrupt's associated vector;                      *
*                5.  set the interrupt's enable bit;                              *
*                6.  set the interrupt's mask bit;                                *
*                                                                                 *
*                entry                                                            *
*                         d0 - contains interrupt # to aff                        *
*                         a2 - contains new vector address                        *
***********************************************************************************

initint:    movem.l   d0-d2/a0-a2,-(sp)         ; save affected registers
            bsr.s     disint                    ; disable the interrupts
            move.l    d0,d2                     ; get a copy so as to determine where to...
            asl.w     #2,d2                     ; place the a2 address into the int. vector
            addi.l    #$100,d2                  ; interrupt vector addr = (4 * int) + $000100
            movea.l   d2,a1                     ; transfer the calculated address to a register
            move.l    a2,(a1)                   ; ...that can act upon it thus<--vector init'ed
            bsr.s     enabint                   ; enable interrupts
            movem.l   (sp)+,d0-d2/a0-a2         ; restore affected registers
            rts

*************************************************************************
*                disable an mfp interrupt via GEMDOS                    *
*                                                                       *
*                entry                                                  *
*                                                                       *
*                void    jdisint(numint)                                *
*                word    numint                                         *
*                                                                       *
*************************************************************************

jdisint:    move.w    4(sp),d0
            andi.l    #$f,d0                    ; to ensure masking of 0-$f

*************************************************************************
*                interrupt disable routine                              *
*************************************************************************

disint:     movem.l   d0-d1/a0-a1,-(sp)         ; save affected registers
            lea       (gpip).w,a0               ; set mfp chip address pointer
            lea       $12(a0),a1                ; set a1 to the mskoff routine
            bsr.s     bselect                   ; generate the appropriate bit to clear
            bclr      d1,(a1)                   ; and clear the bit...
            lea       6(a0),a1                  ; set a1 for another mskoff call
            bsr.s     bselect
            bclr      d1,(a1)                   ; and clear the bit...
            lea       $e(a0),a1                 ; now set up to check for interrupts in progress
            bsr.s     bselect                   ; get proper a/b version...
            move.b    #$fe,d0
            rol.b     d1,d0
            move.b    d0,(a1)
            movem.l   (sp)+,d0-d1/a0-a1         ; restore affected registers
            rts

*************************************************************************
*                enable/re-enabled an mfp interrupt via GEMDOS          *
*                                                                       *
*                entry                                                  *
*                                                                       *
*                void    jenabint(numint)                               *
*                word    numint                                         *
*                                                                       *
*************************************************************************

jenabint:   move.w    4(sp),d0
            andi.l    #$f,d0                    ; to ensure masking of 0-$f

*************************************************************************
*                enable interrupt routine                               *
*************************************************************************

enabint:    movem.l   d0-d1/a0-a1,-(sp)         ; save affected registers
            lea       (gpip).w,a0               ; set mfp chip address pointer
            lea       6(a0),a1                  ; set up to enable the interrupt enable bit
            bsr.s     bselect
            bset      d1,(a1)                   ; and set the bit...
            lea       $12(a0),a1                ; set up to enable the interrupt mask bit
            bsr.s     bselect
            bset      d1,(a1)                   ; and set the bit...
            movem.l   (sp)+,d0-d1/a0-a1         ; restore affected registers
            rts


*************************************************************************
*                                                                       *
*       the following routine generates the appropriate bset/bclr #     *
*       for the interrupt # specified in d0.    valid interrupt #'s are *
*       0 --> 15 as shown in the 68901 chip specification.  It also     *
*       selects between the ixra and the ixrb version of the register   *
*       as is appropriate.                                              *
*                                                                       *
*       entry   d0 - contains the interrupt number                      *
*               a1 - contains the pointer to the "ixra" version of      *
*                    the interrupt byte to mask                         *
*       exit            d0 - same as upon entry                         *
*                       d1 - contains the number of the bit             *
*************************************************************************
bselect:    move.b    d0,d1                     ; copy d0 to d1 for scratch work
            cmpi.b    #8,d1                     ; see if desired int # >= 8...
            blt.s     skip0                     ; ...and branch if it ain't...
            subq.w    #8,d1                     ; adjust for using ixrb instead
            rts

skip0:      addq.l    #2,a1                     ; adjust for using ixrb instead
            rts


*************************************************************************
*                                                                       *
*               receiver buffer full interrupt routine                  *
*                                                                       *
*               grabs data from the rs-232 receiver port                *
*                                                                       *
*************************************************************************
rcvrint:    movem.l   d0-d1/a0-a2,-(sp)         ; save affected registers
            lea       (rbufrec).w,a0            ; point to current output buffer record
            lea       (gpip).w,a2               ; set mfp chip address pointer
_rcvrint:   move.b    $2a(a2),$1c(a0)           ; do the required rsr read before the udr read!
            move.b    $2e(a2),d0                ; get incoming data byte

            btst      #0,$20(a0)                ; is the rs232 mode xon/xoff?
            beq.s     ri3                       ; no... so process normally

            cmpi.b    #$13,d0                   ; is the data an "xoff" signal?
            bne.s     ri2                       ; no...now check for xon
            move.b    #$ff,$1f(a0)              ; set to halted transmission to host
            bra.s     ri8

ri2:        cmpi.b    #$11,d0                   ; is the data an "xon" signal?
            bne.s     ri3                       ; neither xon/xoff value, must be normal data...
            clr.b     $1f(a0)                   ; set to normal transmission status
            bra.s     ri7                       ; abnormal exit condition!!

ri3:        move.w    8(a0),d1                  ; get current tail pointer offset
            bsr       wrapin                    ; do wrap of input pointer if needed
            cmp.w     6(a0),d1                  ; head=tail?
            beq.s     ri8                       ; yes...exit...
            bsr       rs232put                  ; store data in ring buffer

* now check for highwater mark triggering of flow-control
            tst.b     $20(a0)                   ; flow control active?
            beq.s     ri8                       ; no...exit...
            move.w    8(a0),d0                  ; current tail pointer
            sub.w     6(a0),d0                  ; - current head
            bpl.s     ri4                       ; wrap around?
            add.w     4(a0),d0                  ; + size to correct the wrap around
ri4:        cmp.w     $c(a0),d0                 ; high-water mark not reached?
            blt.s     ri8                       ; correct...exit...
            tst.b     $1e(a0)                   ; high-water flag already set?
            bne.s     ri8                       ; yes...exit...
            move.b    #$ff,$1e(a0)              ; set high-water flag
            btst      #0,$20(a0)                ; are we using xon/xoff flow control?
            bne.s     ri6                       ; yes...
            bsr       rtsoff                    ; we're ready now for more data...yum! yum!
            bra.s     ri8
ri6:        move.b    #$13,$21(a0)              ; xoff
ri7:        tst.b     $2c(a2)                   ; transmitter status?
            bpl.s     ri8                       ; not ready
            bsr       wr_rs232
ri8:        move.b    #$ef,$e(a2)
            movem.l   (sp)+,d0-d1/a0-a2         ; restore affected registers
            rte

*************************************************************************
*                                                                       *
*               transmit buffer empty interrupt routine                 *
*                                                                       *
*************************************************************************
txrint:     movem.l   d0-d1/a0-a2,-(sp)         ; save affected registers
            lea       (gpip).w,a2               ; set mfp chip address pointer
            lea       (rbufrec).w,a0            ; point to current output buffer record
_txrint:    bsr       wr_rs232
            move.b    #$fb,$e(a2)               ; xmit buffer empty
            movem.l   (sp)+,d0-d1/a0-a2         ; restore affected registers
            rte

*************************************************************************
*                                                                       *
*               Clear-To-Send interrupt routine                         *
*                                                                       *
*************************************************************************
ctsint:     movem.l   d0-d1/a0-a2,-(sp)         ; save affected registers
            lea       (rbufrec).w,a0            ; point to current output buffer record
            lea       (gpip).w,a2               ; set mfp chip address pointer

            btst      #1,$20(a0)                ; are we using CTS/RTS flow control?
            beq.s     ctsexit                   ; no...
            btst      #2,(a2)                   ; CTS already active?
            bne.s     ctsoff                    ; yes...
            clr.b     $1f(a0)                   ; xon_flag = 0
            bset      #2,2(a2)                  ; CTS set
            tst.b     $2c(a2)                   ; transmitter status?
            bpl.s     ctsexit                   ; not empty...
            bsr.s     wr_rs232                  ; send a byte
            bra.s     ctsexit
ctsoff:     move.b    #$ff,$1f(a0)              ; xon_flag = -1
            bclr      #2,2(a2)
ctsexit:    move.b    #$fb,$10(a2)              ; clear CTS
            movem.l   (sp)+,d0-d1/a0-a2         ; restore affected registers
            rte


*************************************************************************
*               routines to handle tx or rx errors                      *
*************************************************************************
rxerror:    movem.l   d0/a0,-(sp)               ; save all registers
            lea       (gpip).w,a0               ; set mfp chip address pointer
_rxerror:   move.b    $2a(a0),(rbufrec+28).l    ; receiver status register
            move.b    $2e(a0),d0                ; dummy read of data register
            move.b    #$f7,$e(a0)
            movem.l   (sp)+,d0/a0               ; restore all registers
            rte

txerror:    move.l    a0,-(sp)
            lea       (gpip).w,a0               ; set mfp chip address pointer
_txerror:   move.b    $2c(a0),(rbufrec+29).l    ; transmitter status register
            move.b    #$fd,$e(a0)
            movea.l   (sp)+,a0                  ; restore all registers
            tst.b     (tsr).w
            rte


wr_rs232:   move.l    a0,-(sp)                  ; save all registers
            move.b    $21(a0),d0                ; send handshake byte pending?
            beq.s     wr_rs2322                 ; no...
            clr.b     $21(a0)                   ; clear handshake byte
            bra.s     wr_rs232o                 ; output the handshake byte
wr_rs2322:  move.b    $20(a0),d0                ; flow control active?
            and.b     $1f(a0),d0                ; and xon-flag
            bne.s     wr_rs2324                 ; yes...
            adda.w    #$e,a0                    ; switch to output ring buffer
            move.w    6(a0),d0                  ; head-index
            cmp.w     8(a0),d0                  ; = tail-index?
            beq.s     wr_rs2324                 ; buffer empty? => out
            bsr.s     rs232get                  ; get next character from the buffer
wr_rs232o:  tst.b     $2c(a2)                   ; transmit buffer empty?
            bpl.s     wr_rs232o                 ; no...wait...
            move.b    $2c(a2),(rbufrec+29).l    ; save transmitter status
            move.b    d0,$2e(a2)                ; output the byte
wr_rs2324:  movea.l   (sp)+,a0                  ; restore all registers
            rts

rs232put:   move.w    8(a0),d1                  ; tail-index
            bsr.s     wrapin
rs232putl:  cmp.w     6(a0),d1                  ; = head-index?
            beq.s     rs232putl                 ; buffer full...wait...
            movea.l   (a0),a1                   ; ptr to send buffer
            and.l     #$ffff,d1
            move.b    d0,(a1,d1.l)              ; byte into the send buffer
            move.w    d1,8(a0)                  ; update tail-index
            rts

rs232get:   move.w    6(a0),d1                  ; head-index
            cmp.w     8(a0),d1                  ; = tail-index?
            beq.s     rs232get                  ; empty? wait...
            bsr.s     wrapin
            movea.l   (a0),a1                   ; ptr to receive buffer
            moveq     #0,d0
            and.l     #$ffff,d1
            move.b    (a1,d1.l),d0              ; byte from the receive buffer
            move.w    d1,6(a0)                  ; update head-index
            rts

rtson:      lea       (psgsel).w,a1
            move      sr,-(sp)
            ori       #$700,sr
            move.b    #14,(a1)
            move.b    (a1),d1
            and.b     #$f7,d1                   ; clear bit 3 in port A
            move.b    d1,2(a1)
            move      (sp)+,sr
            rts

rtsoff:     lea       (psgsel).w,a1
            move      sr,-(sp)
            ori       #$700,sr
            move.b    #14,(a1)
            move.b    (a1),d1
            ori.b     #8,d1                     ; set bit 3 in port A
            move.b    d1,2(a1)
            move      (sp)+,sr
            rts

wrapin:     addq.w    #1,d1                     ; i=h+1
            cmp.w     4(a0),d1                  ; > i => current bufsiz?
            bcs.s     wi1                       ; no...
            moveq     #0,d1                     ; wrap pointer
wi1:        rts

iorec:      move.w    4(sp),d1                  ; device number
            beq.s     iorecaux                  ; rs232? =>
            asl.l     #2,d1
            move.l    devtab(pc,d1.w),d0
            rts

iorecaux:   move.l    (bsrecauxp).w,d0
            rts

devtab:     DC.L      rbufrec                   ; RS232
            DC.L      kbufrec                   ; IKBD
            DC.L      mbufrec                   ; MIDI

rsconf:     movea.l   (bsrsconfp).w,a0
            jmp       (a0)

auxrsconf:  lea       (rbufrec).w,a0            ; point to current output buffer record
            lea       (gpip).w,a2               ; set mfp chip address pointer
_auxrsconf: moveq     #0,d0
            cmpi.w    #-2,4(sp)                 ; -2 = return last baudrate
            bne.s     auxrsconf1
            move.b    $22(a0),d0                ; last baudrate
            rts

auxrsconf1: ori       #$700,sr                  ; no interrupts for now
*
*      first we grab the old ucs,rsr,tsr,scr contents
*
            movep.l   $28(a2),d7
*
*      set flow control mode(s)
*
            move.w    6(sp),d0                  ; if -1 then don't change
            cmp.w     #3,d0
            bhi.s     auxc1                     ; no =>
            bne.s     auxrsconf2
            moveq     #1,d0                     ; set flag for handshake
auxrsconf2: cmp.b     $20(a0),d0                ; state unchanged? => continue
            beq.s     auxc1
            move.w    d0,-(sp)                  ; save new handshake state
            tst.b     $1f(a0)                   ; xon flag set?
            beq.s     auxrsconf3
            clr.b     $1f(a0)                   ; clear xon flag
            bsr       wr_rs232                  ; transmit xon to continue
auxrsconf3: tst.b     $1e(a0)                   ; RTS set?
            beq.s     auxrsconf4                ; no...
            bsr       auxinclrhw                ; reenable RTS
auxrsconf4: move.w    (sp)+,d0                  ; new handshake state
            move.b    d0,$20(a0)                ; set new handshake state
*
*      set timer baud rate
*
auxc1:      tst.w     4(sp)                     ; if -1 then don't change
            bmi.s     auxc2
            bclr      #0,$2a(a2)                ; disable receiver
            bclr      #0,$2c(a2)                ; disable transmitter
            move.w    4(sp),d1                  ; new baudrate
            move.b    d1,$22(a0)                ; store for later
            move.b    baudctrl(pc,d1.w),d0      ; get baudrate control register settings mask
            move.b    bauddata(pc,d1.w),d2      ; get baudrate data register value
            andi.b    #$70,$1c(a2)
            move.b    d2,$24(a2)                ; set tuner D to new baud rate
            or.b      d0,$1c(a2)
            bset      #0,$2a(a2)                ; enable receiver
            bset      #0,$2c(a2)                ; enable transmitter

*      set rs-232 registers

auxc2:      tst.w     8(sp)                     ; if -1 then don't change
            bmi.s     auxc3
            move.b    9(sp),$28(a2)             ; set ucr
auxc3:      tst.w     $a(sp)                    ; if -1 then don't change
            bmi.s     auxc4
            move.b    $b(sp),$2a(a2)            ; set rsr
auxc4:      tst.w     $c(sp)                    ; if -1 then don't change
            bmi.s     auxc5
            move.b    $d(sp),$2c(a2)            ; set tsr
auxc5:      tst.w     $e(sp)                    ; if -1 then don't change
            bmi.s     auxc6
            move.b    $f(sp),$26(a2)            ; set scr
auxc6:      move.l    d7,d0                     ; move old contents of rs-232 registers to d0.l
            rts

* baudrate table - control register setting

baudctrl:   DC.B      $01,$01,$01,$01,$01,$01,$01,$01
            DC.B      $01,$01,$01,$01,$01,$01,$02,$02

* baudrate table - data register setting

bauddata:   DC.B      $01,$02,$04,$05,$08,$0a,$0b,$10
            DC.B      $20,$40,$60,$80,$8f,$af,$40,$60

rs232devs:  DC.L      auxistat                  ; Dev 6: "Modem 1" - ST MFP
            DC.L      auxin
            DC.L      _auxostat
            DC.L      _auxout
            DC.L      auxrsconf
            DC.L      rbufrec

            DC.L      sccbistat                 ; Dev 7: "Modem 2" - SCC Channel B
            DC.L      sccbin
            DC.L      sccbostat
            DC.L      sccbout
            DC.L      sccbrsconf
            DC.L      sccbbufrec

IF TOS_ROM306
            DC.L      ttmfp_auxistat            ; Dev 8: "Seriell 1" - TT MFP
            DC.L      ttmfp_auxin
            DC.L      ttmfp__auxostat
            DC.L      ttmfp__auxout
            DC.L      ttmfp_auxrsconf
            DC.L      ttmfp_rbufrec
ENDIF

            DC.L      sccaistat                 ; Dev 8/9: "Seriell 2" - SCC Channel A
            DC.L      sccain
            DC.L      sccaostat
            DC.L      sccaout
            DC.L      sccarsconf
            DC.L      sccabufrec

_bconmap:   moveq     #0,d0
            move.w    4(sp),d1
            move.w    (bdevmappedAux).w,d0
            cmp.w     #-1,d1
            beq.s     _bconmap_rts
            move.l    #bdevtabptr,d0
            cmp.w     #-2,d1
            beq.s     _bconmap_rts
            moveq     #0,d0
            subq.w    #6,d1
            bmi.s     _bconmap_rts
            cmp.w     (bdevcount).w,d1
            bcc.s     _bconmap_rts
            move.w    (bdevmappedAux).w,d1
            subq.w    #6,d1
            asl.w     #3,d1
            move.w    d1,d2
            add.w     d1,d1
            add.w     d1,d2
            movea.l   (bdevtabptr).w,a0
            adda.w    d2,a0
            lea       (xconstat+4).w,a1
            move.l    (a1),(a0)+
            move.l    $20(a1),(a0)+
            move.l    $40(a1),(a0)+
            move.l    $60(a1),(a0)+
            move.l    (bsrsconfp).l,(a0)+
            move.l    (bsrecauxp).w,(a0)+
            move.w    4(sp),d1
            subq.w    #6,d1
            asl.w     #3,d1
            move.w    d1,d2
            add.w     d1,d1
            add.w     d1,d2
            movea.l   (bdevtabptr).w,a0
            adda.w    d2,a0
            move.l    (a0)+,(a1)
            move.l    (a0)+,$20(a1)
            move.l    (a0)+,$40(a1)
            move.l    (a0)+,$60(a1)
            move.l    (a0)+,(bsrsconfp).w
            move.l    (a0)+,(bsrecauxp).w
            move.w    (bdevmappedAux).w,d0
            move.w    4(sp),(bdevmappedAux).w
_bconmap_rts:rts


initdevstables:lea    (bdevtable).w,a0
            move.l    a0,(bdevtabptr).w
IF TOS_ROM306
            move.w    #4,(bdevcount).w
ELSE
            move.w    #3,(bdevcount).w
            moveq     #1,d0                     ; only MFP RS232 is available
            movea.l   sp,a1
            movea.l   (busexception).w,a2
            move.l    #initdevnoscc,(busexception).w
            tst.b     (scu_gp1).w
            move.w    #3,d0                     ; also 2 SCC RS232 are available
initdevnoscc:movea.l  a1,sp
            move.l    a2,(busexception).w
            move.w    d0,(bdevcount).w
ENDIF
            lea       rs232devs(pc),a1
IF TOS_ROM306
            move.w    #$17,d0                   ; 4 devices
ELSE
            move.w    #$11,d0                   ; 3 devices
ENDIF
initdev1:   move.l    (a1)+,(a0)+
            dbra      d0,initdev1
            move.w    #6,(bdevmappedAux).w      ; Device 6 is mapped into device 1 (Aux)
            lea       (rs232devs).l,a1          ; Map MFP RS232 to dev 1
            lea       (xconstat+4).w,a0
            move.l    (a1)+,(a0)                ; Fix tconstat for dev 1
            move.l    (a1)+,$20(a0)             ; Fix tconin for dev 1
            move.l    (a1)+,$40(a0)             ; Fix tcostat for dev 1
            move.l    (a1)+,$60(a0)             ; Fix tconout for dev 1
            move.l    (a1)+,(bsrsconfp).w
            move.l    (a1)+,(bsrecauxp).w
            rts

IF ROM_TOS306
ttmfp_auxistat:
            lea       (ttmfp_rbufrec).w,a0
            bra       _auxistat
ttmfp_auxin:lea       (ttmfp_rbufrec).w,a0
            lea       (gpip_TT).w,a2
            bra       _auxin
ttmfp__auxostat:
            lea       (ttmfp_rbufrec+14).w,a0
            bra       __auxostat
ttmfp__auxout:
            move.w    6(sp),d0
            lea       (ttmfp_rbufrec+14).w,a0
            bsr       rs232put
            lea       (ttmfp_rbufrec).w,a0
            lea       (gpip_TT).w,a2
            bra       _auxoutwr
ttmfp_rcvrint:movem.l   d0-d1/a0-a2,-(sp)
            lea       (ttmfp_rbufrec).w,a0
            lea       (gpip_TT).w,a2
            bra       _rcvrint
ttmfp_txrint:movem.l   d0-d1/a0-a2,-(sp)
            lea       (gpip_TT).w,a2
            lea       (ttmfp_rbufrec).w,a0
            bra       _txrint
ttmfp_rxerr:movem.l   d0/a0,-(sp)
            lea       (gpip_TT).w,a0
            bra       _rxerror
ttmfp_txerr:move.l    a0,-(sp)
            lea       (gpip_TT).w,a0
            bra       _txerror
ttmfp_auxrsconf:lea       (ttmfp_rbufrec).w,a0
            lea       (gpip_TT).w,a2
            bra       _auxrsconf
ENDIF

sccinit:    move.b    (a1)+,d0                  ; SCC register number
            bmi.s     sccina                    ; end of the register list? => exit
            move.b    d0,(a0)                   ; select register
            move.b    (a1)+,(a0)                ; write into register
            bra.s     sccinit
sccina:     rts


* SCC initialization
scctbl:     DC.B      $04,$44                   ; x16, 1 stop, no parity
            DC.B      $01,$04
            DC.B      $02,$60
            DC.B      $03,$c0                   ; rx 8 bit, no crc, rx disabled
            DC.B      $05,$e2                   ; tx 8 bit, no crc, tx disabled, rts,dtr on
            DC.B      $06,$00
            DC.B      $07,$00
            DC.B      $09,$01
            DC.B      $0a,$00                   ; NRZ
            DC.B      $0b,$50                   ; tx & rx clk = brg, trxc=in
            DC.B      $0c,$18                   ; clock divisor for 9600 (pclk)
            DC.B      $0d,$00                   ; divisor high
            DC.B      $0e,$02                   ; BRG = PCLK
            DC.B      $0e,$03                   ; enable BRG
            DC.B      $03,$c1                   ; enable Rx, 8 bits
            DC.B      $05,$ea                   ; enable Tx, 8 bits, rts, dtr on
            DC.B      $0f,$20
            DC.B      $00,$10
            DC.B      $00,$10
            DC.B      $01,$17
            DC.B      $09,$09
            DC.B      $ff                       ; end of the register list
            DC.B      $00

* disconnect LAN from port a
nolan:      move      sr,d1
            ori       #$700,sr
            move.b    #14,(psgsel).w
            move.b    (psgsel).w,d0
            bset      #7,d0
            move.b    #14,(psgsel).w
            move.b    d0,(psgwr).w
            move      d1,sr
            rts

initscc:    movea.l   (busexception).w,a0
            movea.l   sp,a1
            move.l    #initscc_none,(busexception).w
            tst.b     (sccdat_a).w
            move.l    a0,(busexception).w
            lea       (SCC).w,a0
            lea       sccvect(pc),a1
            moveq     #15,d0
initscc2:   move.l    (a1)+,(a0)+
            dbra      d0,initscc2
IF ROM_TOS306
            clr.w     (scdmactl).w
ENDIF
            bsr.s     nolan
            lea       (sccabufrec).w,a0
            lea       sccbufinit(pc),a1
            moveq     #$25,d0
            bsr       lbmove
            lea       (sccbbufrec).w,a0
            lea       sccbufinit(pc),a1
            moveq     #$25,d0
            bsr       lbmove
            move.l    #$1434,(sccbbufrec).w
            move.l    #$1534,(sccbbufrec+14).w
            lea       (sccctl_a).w,a2
            move.b    #9,(a2)
            move.b    #$c0,(a2)
            move.w    #$104,d0                  ; Delay Mode, /4 Prescale, data = 4 (about 6us delay)
            jsr       (mfpdelay).l
            lea       (sccctl_a).w,a0
            lea       scctbl(pc),a1
            bsr       sccinit
            lea       (sccctl_b).w,a0
            lea       scctbl(pc),a1
            bsr       sccinit
            bset      #5,(vme_mask).w           ; Enable IRQ5 from VMEBUS/SCC
            rts

initscc_none:move.l   a0,(busexception).w
            movea.l   a1,sp
            rts


**************************************************************************
*                                                                        *
*                  83C30 SCC interrupt vectors                           *
*                                                                        *
**************************************************************************
sccvec_portbtrnbempty:movem.l d0-d1/a0-a2,-(sp)
            lea       (sccctl_b).w,a2
            lea       (sccbbufrec).w,a0
            bra       scc23
sccvec_portbextstchg:movem.l d0-d1/a0-a2,-(sp)
            lea       (sccctl_b).w,a2
            lea       (sccbbufrec).w,a0
            bra       scc24
sccvec_portbrecchr:movem.l d0-d1/a0-a2,-(sp)
            lea       (sccctl_b).w,a2
            lea       (sccbbufrec).w,a0
            bra.s     scc09
sccvec_portbschrrec:movem.l d0-d1/a0-a2,-(sp)
            lea       (sccctl_b).w,a2
            lea       (sccbbufrec).l,a0
            bra       scc16

sccvec_portatrnbempty:movem.l d0-d1/a0-a2,-(sp)
            lea       (sccctl_a).w,a2
            lea       (sccabufrec).w,a0
            bra       scc23
sccvec_portaextstchg:movem.l d0-d1/a0-a2,-(sp)
            lea       (sccctl_a).w,a2
            lea       (sccabufrec).w,a0
            bra       scc24
sccvec_portarecchr:movem.l d0-d1/a0-a2,-(sp)
            lea       (sccctl_a).w,a2
            lea       (sccabufrec).w,a0
            bra.s     scc09
sccvec_portaschrrec:movem.l d0-d1/a0-a2,-(sp)
            lea       (sccctl_a).w,a2
            lea       (sccabufrec).l,a0
            bra       scc16

scc09:      move.b    #8,(a2)                   ; select scc register #8
            move.b    (a2),d0                   ; read "Receive Data register"
            and.b     $23(a0),d0
            btst      #0,$20(a0)
            beq.s     scc11
            cmp.b     #$13,d0
            bne.s     scc10
            st        $1f(a0)
            bra.s     scc15
scc10:      cmp.b     #$11,d0
            bne.s     scc11
            tst.b     $1f(a0)
            sf        $1f(a0)
            bne.s     scc14
scc11:      move.w    8(a0),d1
            bsr       wrapin
            cmp.w     6(a0),d1
            beq.s     scc15
            bsr       rs232put
            tst.b     $20(a0)
            beq.s     scc15
            tst.b     $1e(a0)
            bne.s     scc15
            move.w    8(a0),d0
            sub.w     6(a0),d0
            bpl.s     scc12
            add.w     4(a0),d0
scc12:      cmp.w     $c(a0),d0
            blt.s     scc15
            st        $1e(a0)
            btst      #0,$20(a0)
            bne.s     scc13
            move.b    $1d(a0),d0
            bclr      #1,d0                     ; clear RTS bit
            move.b    d0,$1d(a0)
            move.b    #5,(a2)                   ; select scc register #5
            move.b    d0,(a2)                   ; write "Transmit Parameters and Controls" register
            bra.s     scc15
scc13:      move.b    #$13,$21(a0)
scc14:      move.b    #0,(a2)                   ; select scc register #0
            move.b    (a2),d0                   ; read register #0
            btst      #2,d0                     ; Tx Buffer Empty?
            beq.s     scc15
            bsr.s     wr_scc
scc15:      move.b    #0,(a2)                   ; select scc register #0
            move.b    #$38,(a2)                 ; write "Reset Highest IUS Command" - clear pending interrupt
            movem.l   (sp)+,d0-d1/a0-a2
            rte

scc16:      move.b    #1,(a2)                   ; select scc register #1
            move.b    (a2),d0                   ; read special receive condition status bits
            move.b    #8,(a2)                   ; select scc register #8
            move.b    (a2),d0                   ; read "Receive Data register"
            move.b    #0,(a2)                   ; select scc register #0
            move.b    #$30,(a2)                 ; write "Error Reset Command" - clear error condition
            bra.s     scc15

wr_scc:     move.l    a0,-(sp)
            tst.b     $21(a0)
            bne.s     scc19
IF ROM_STBOOK
            btst      #0,$20(a0)
            beq.s     scc18
            tst.b     $1f(a0)
            bne.s     scc22
scc18:      btst      #1,$20(a0)
            beq.s     scc19
            move.b    #0,(a2)                   ; select scc register #0
            move.b    (a2),d0                   ; read register #0
            btst      #5,d0                     ; CTS?
            beq.s     scc22
ELSE
            move.b    $20(a0),d0
            and.b     $1f(a0),d0
            bne.s     scc2
ENDIF
scc19:      move.b    #0,(a2)                   ; select scc register #0
            move.b    (a2),d0                   ; read register #0
            btst      #2,d0                     ; Tx Buffer Empty?
            beq.s     scc22
            move.b    $21(a0),d0
            beq.s     scc20
            clr.b     $21(a0)
            bra.s     scc21
scc20:      adda.w    #$e,a0
            move.w    6(a0),d0
            cmp.w     8(a0),d0
            beq.s     scc22
            bsr       rs232get
scc21:      move.b    #8,(a2)                   ; select scc register #8
            move.b    d0,(a2)                   ; write transmit buffer
scc22:      movea.l   (sp)+,a0
            rts

scc23:      move.b    #0,(a2)                   ; select scc register #0
            move.b    #$28,(a2)                 ; write "Reset Tx Int Pending"
            move.b    #0,(a2)                   ; select scc register #0
            move.b    #$38,(a2)                 ; write "Reset Highest IUS Command" - clear pending interrupt
            bsr.s     wr_scc
            movem.l   (sp)+,d0-d1/a0-a2
            rte

scc24:      btst      #1,$20(a0)
            beq.s     scc25
            move.b    #0,(a2)                   ; select scc register #0
            move.b    (a2),d0                   ; read register #0
            btst      #5,d0                     ; CTS?
            seq       $1f(a0)
            beq.s     scc25
IF ROM_STBOOK
            bsr       wr_scc
ELSE
            bsr.s     wr_scc
ENDIF
scc25:      move.b    #0,(a2)                   ; select scc register #0
            move.b    #$10,(a2)                 ; write "Reset Ext/Status Interrupts"
            move.b    #0,(a2)                   ; select scc register #0
            move.b    #$38,(a2)                 ; write "Reset Highest IUS Command" - clear pending interrupt
            movem.l   (sp)+,d0-d1/a0-a2
            rte


**************************************************************************
*                                                                        *
*                  scc BIOS callbacks                                    *
*                                                                        *
**************************************************************************
sccbistat:  lea       (sccbbufrec).w,a0
            lea       (sccctl_b).w,a2
            bra.s     sccistat
sccbin:     lea       (sccbbufrec).w,a0
            lea       (sccctl_b).w,a2
            bra.s     sccin
sccbostat:  lea       (sccbbufrec).w,a0
            lea       (sccctl_b).w,a2
            bra.s     _sccostat
sccbout:    lea       (sccbbufrec).w,a0
            lea       (sccctl_b).w,a2
            bra       _sccout
sccaistat:  lea       (sccabufrec).w,a0
            lea       (sccctl_a).w,a2
            bra.s     sccistat
sccain:     lea       (sccabufrec).w,a0
            lea       (sccctl_a).w,a2
            bra.s     sccin
sccaostat:  lea       (sccabufrec).w,a0
            lea       (sccctl_a).w,a2
            bra.s     _sccostat
sccaout:    lea       (sccabufrec).w,a0
            lea       (sccctl_a).w,a2
            bra.s     _sccout

**************************************************************************
*                                                                        *
*                  scc port input status routine                         *
*                                                                        *
**************************************************************************
sccistat:   moveq     #0,d0                     ; set result to false
            lea       6(a0),a1                  ; head
            lea       8(a0),a0                  ; tail
            cmpm.w    (a0)+,(a1)+               ; atomic buffer empty test
            beq.s     sccist1
            moveq     #-1,d0                    ; set result to true
sccist1:    rts


**************************************************************************
*                                                                        *
*                  scc port output status routine                        *
*                                                                        *
**************************************************************************
_sccostat:  move.w    8(a0),d1                  ; tail
            bsr       wrapin
            cmp.w     6(a0),d1                  ; head
            beq.s     _sccost1
            moveq     #-1,d0                    ; set result to true
            rts

_sccost1:   moveq     #0,d0                     ; set result to false
            rts


**************************************************************************
*                                                                        *
*                  scc input routine                                     *
*                                                                        *
**************************************************************************
sccin:      bsr       rs232get
            move.w    d0,-(sp)
            tst.b     $20(a0)                   ; flow control active?
            beq.s     sccinrts                  ; (no)
            tst.b     $1e(a0)                   ; high-water flag already set?
            beq.s     sccinrts                  ; no...exit...
            move.w    8(a0),d0                  ; tail
            sub.w     6(a0),d0                  ; - head
            bpl.s     sccin2                    ; underflow?
            add.w     4(a0),d0                  ; + size
sccin2:     cmp.w     $a(a0),d0
            bgt.s     sccinrts
            bsr.s     sccinclrhw
sccinrts:   move.w    (sp)+,d0
            rts

sccinclrhw: clr.b     $1e(a0)                   ; clear high-water flag
            btst      #0,$20(a0)                ; is the rs232 mode xon/xoff?
            beq.s     sccinclrhw2
            move.b    #$11,$21(a0)              ; "xon"
            bra.s     _sccoutwr
sccinclrhw2:move.b    $1d(a0),d0
            bset      #1,d0                     ; set RTS
            move.b    d0,$1d(a0)
            move.b    #5,(a2)                   ; select scc register #5
            move.b    d0,(a2)                   ; write "Transmit Parameters and Controls" register
            rts

**************************************************************************
*                                                                        *
*                  scc output routine                                    *
*                                                                        *
**************************************************************************
_sccout:    move.w    6(sp),d0                  ; get data
            adda.w    #$e,a0
            bsr       rs232put                  ; exit via rs-232 output routine
            suba.w    #$e,a0
_sccoutwr:  move.b    #0,(a2)                   ; select scc register #0
            move.b    (a2),d0                   ; read register #0
            btst      #2,d0                     ; Tx Buffer Empty?
            beq.s     _sccoutrts                ; not ready
            move      sr,-(sp)
            ori       #$700,sr
            bsr       wr_scc
            move      (sp)+,sr
_sccoutrts: rts

**************************************************************************
*                                                                        *
*                  scc rsconf() routine                                  *
*                                                                        *
**************************************************************************
sccarsconf: lea       (sccabufrec).w,a0         ; point to current output buffer record
            lea       (sccctl_a).w,a2
            bra.s     sccrsconf
sccbrsconf: lea       (sccbbufrec).w,a0         ; point to current output buffer record
            lea       (sccctl_b).w,a2

sccrsconf:  moveq     #0,d0
            cmpi.w    #-2,4(sp)                 ; -2 = return last baudrate
            bne.s     sccrsconf2
            move.b    $22(a0),d0                ; last baudrate
            rts

sccrsconf2: ori       #$700,sr                  ; no interrupts for now
*
*      first we grab the old ucs,rsr,tsr,scr contents
*
            moveq     #0,d7
            move.b    $1c(a0),d7
            asl.w     #8,d7
            swap      d7
            move.b    $1d(a0),d7
            lsr.b     #1,d7
            and.b     #4,d7
            asl.w     #8,d7
*
*      set flow control mode(s)
*
            move.w    6(sp),d0                  ; if -1 then don't change
            cmp.w     #3,d0
            bhi.s     sccrsconf6
            bne.s     sccrsconf3
            moveq     #1,d0                     ; set flag for handshake
sccrsconf3: cmp.b     $20(a0),d0                ; state unchanged? => continue
            beq.s     sccrsconf6
            tst.b     $1f(a0)                   ; xon flag set?
            beq.s     sccrsconf4
            clr.b     $1f(a0)                   ; clear xon flag
            bsr       wr_scc
sccrsconf4: tst.b     $1e(a0)                   ; RTS set?
            beq.s     sccrsconf5                ; no...
            move.w    d0,-(sp)                  ; save new handshake state
            bsr       sccinclrhw                ; reenable RTS
            move.w    (sp)+,d0                  ; new handshake state
sccrsconf5: move.b    d0,$20(a0)                ; set new handshake state
*
*      set timer baud rate
*
sccrsconf6: move.w    4(sp),d0                  ; new baudrate
            cmp.w     #$f,d0                    ; if -1 (out of range 0..15) then don't change
            bhi.s     sccrsconf7
            move.b    d0,$22(a0)                ; new baudrate
            asl.w     #1,d0
            lea       (sccbaudtab).l,a1
            move.w    (a1,d0.w),d0
            move.b    #$c,(a2)                  ; select scc register #12
            move.b    d0,(a2)                   ; write "Lower Byte of Baud Rate Generator Time Constant" register
            lsr.w     #8,d0
            move.b    #$d,(a2)                  ; select scc register #13
            move.b    d0,(a2)                   ; write "Upper Byte of Baud Rate Generator Time Constant" register
*      set rs-232 registers

sccrsconf7: move.w    8(sp),d0                  ; if -1 then don't change
            bmi.s     sccrsconf12
            move.b    d0,$1c(a0)
            move.b    d0,d1
            and.b     #$60,d1
            lsr.b     #5,d1
            moveq     #$ff,d2
            lsr.b     d1,d2
            move.b    d2,$23(a0)
            move.b    d0,d1
            and.b     #$60,d1
            beq.s     sccrsconf8
            cmp.b     #$60,d1
            bne.s     sccrsconf9
sccrsconf8: eori.b    #$60,d1
sccrsconf9: move.b    $1d(a0),d2
            and.b     #$9f,d2
            or.b      d1,d2
            move.b    d2,$1d(a0)
            move.b    #5,(a2)                   ; select scc register #5
            move.b    d2,(a2)                   ; write "Transmit Parameters and Controls" register
            asl.b     #1,d1
            or.b      #1,d1                     ; "Rx Enable"
            move.b    #3,(a2)                   ; select scc register #3
            move.b    d1,(a2)                   ; write "Receive Parameters and Control" register
            move.b    d0,d1
            and.b     #$1e,d1
            lsr.b     #1,d1                     ; mask stop bits (bit 2..3) and parity (bit 0..1)
            bclr      #1,d1
            sne       d2
            bclr      #0,d1                     ; disable parity
            bne.s     sccrsconf10
            bclr      #1,d2                     ; "odd parity"
            bra.s     sccrsconf11
sccrsconf10:bset      #1,d2                     ; "even parity"
sccrsconf11:and.b     #3,d2                     ; mask parity bits
            or.b      d2,d1
            or.b      #$40,d1                   ; "X16 Clock Mode"
            move.b    #4,(a2)                   ; select scc register #4
            move.b    d1,(a2)                   ; write "Transmit/Receive Mis- cellaneous Parameters and Modes" register

sccrsconf12:
IF ROM_TOS306
            move.w    $a(sp),d0                 ; if -1 then don't change
ELSE
            move.w    $c(sp),d0                 ; if -1 then don't change
ENDIF
            bmi.s     sccrsconf14
            btst      #3,d0
            beq.s     sccrsconf13
            bset      #4,$1d(a0)                ; set "Tx Enable"
            bne.s     sccrsconf14
            move.b    #5,(a2)                   ; select scc register #5
            move.b    $1d(a0),(a2)              ; write "Transmit Parameters and Controls" register
            bra.s     sccrsconf14
sccrsconf13:bclr      #4,$1d(a0)                ; clear "Tx Enable"
            beq.s     sccrsconf14
            move.b    #5,(a2)                   ; select scc register #5
            move.b    $1d(a0),(a2)              ; write "Transmit Parameters and Controls" register
sccrsconf14:move.l    d7,d0                     ; move old contents of rs-232 registers to d0.l
            rts

sccbaudtab: DC.W      $000b,$0018,$0032,$0044
            DC.W      $0067,$007c,$008a,$00d0
            DC.W      $01a1,$0345,$04e8,$068c
            DC.W      $074d,$08ee,$0d1a,$13a8
sccvect:    DC.L      sccvec_portbtrnbempty
            DC.L      $000000
            DC.L      sccvec_portbextstchg
            DC.L      $000000
            DC.L      sccvec_portbrecchr
            DC.L      $000000
            DC.L      sccvec_portbrecchr
            DC.L      $000000
            DC.L      sccvec_portatrnbempty
            DC.L      $000000
            DC.L      sccvec_portaextstchg
            DC.L      $000000
            DC.L      sccvec_portarecchr
            DC.L      $000000
            DC.L      sccvec_portarecchr
            DC.L      $000000
sccbufinit: DC.L      $001210                   ; ibufptr
            DC.W      $0100                     ; ibufsiz
            DC.W      $0000                     ; ibufhead
            DC.W      $0000                     ; ibuftail
            DC.W      $0080                     ; ibuflow
            DC.W      $00c0                     ; ibufhigh
            DC.L      $001310                   ; obufptr
            DC.W      $0100                     ; obufsiz
            DC.W      $0000                     ; obufhead
            DC.W      $0000                     ; obuftail
            DC.W      $0080                     ; obuflow
            DC.W      $00c0                     ; obufhigh
            DC.B      $08                       ; rsrbyte
            DC.B      $ea                       ; tsrbyte
            DC.B      $00                       ; rxoff
            DC.B      $00                       ; txoff
            DC.B      $01                       ; rsmode_xon
            DC.B      $00                       ; rsmode_filler
            DC.W      $01ff

*************************************************************************
*       this code handles the midi/keyboard interrupt exception         *
*************************************************************************
midikey:    movem.l   d0-d3/a0-a3,-(sp)         ; save all registers
keymidi:    movea.l   (astatusmidip).w,a2
            jsr       (a2)
            movea.l   (astatuskeyp).w,a2
            jsr       (a2)
            btst      #4,(gpip).w               ; check for pending interrupt occurance
            beq.s     keymidi                   ; repeat this interrupt processing
            move.b    #$bf,(isrb).w             ; clear in-service bit
            movem.l   (sp)+,d0-d3/a0-a3         ; restore all registers
            rte                                 ; go back to what was happening!

astatusmidi:lea       (mbufrec).w,a0
            lea       (midictl).w,a1
            movea.l   (vmiderr).w,a2
            bra.s     astatus2
astatuskeyboard:lea   (kbufrec).w,a0
            lea       (keyctl).w,a1
            movea.l   (vkbderr).w,a2
astatus2:   move.b    (a1),d2                   ; grab device status
            btst      #7,d2                     ; make sure it was an interrupt request
            beq.s     aciaexit                  ; nope..it's empty
            btst      #0,d2                     ; see if receiver buffer is full
            beq.s     mk1                       ; nope..it's empty
            movem.l   d2/a0-a2,-(sp)
            bsr.s     arcvrint                  ; yes...get byte
            movem.l   (sp)+,d2/a0-a2
mk1:        andi.b    #$20,d2                   ; mask off bits already tested
            beq.s     aciaexit                  ; see if any other status bits are on
            move.b    2(a1),d0                  ; grab data byte from acia data register
            jmp       (a2)                      ; yes to branch to pre-inited error subroutine

aciaexit:   rts

*************************************************************************
*       acia receiver buffer full interrupt routine                     *
*************************************************************************

arcvrint:   move.b    2(a1),d0                  ; grab data byte from acia data register
            cmpa.l    #kbufrec,a0
            bne       midibyte                  ; don't treat midi acia data as anything other than as pure data...
            tst.b     (kstate).w
            bne.s     ML3
            cmpi.b    #$f6,d0
            bcc.s     itsnotakey                ; is it a ikbd header?
            move.l    (itsakeyp).w,-(sp)        ; jump into ikbd processing function
            rts

itsnotakey: subi.b    #$f6,d0                   ; generate true index into tables now
            andi.l    #$ff,d0                   ; clear high 3 bytes for indexing
            lea       (ikbdev).l,a3             ; point to ikbd device state codes
            move.b    (a3,d0.w),(kstate).w      ; set ikbd state
            lea       (ikbdlen).l,a3            ; point to ikbd device buffer length table
            move.b    (a3,d0.w),(kindex).w      ; set ikbd device index counter
            addi.w    #$f6,d0                   ; re-constitute original value
            cmpi.b    #$f8,d0                   ; mouse position record?
            blt.s     ML8                       ; no
            cmpi.b    #$fb,d0                   ; mouse position record?
            bgt.s     ML8                       ; no
            move.b    d0,(mousebuf).w           ; store mouse byte
            rts

ML8:        cmpi.b    #$fd,d0                   ; joystick record?
            blt.s     ML8b                      ; no
            move.b    d0,(joyrec).w             ; store joystick byte
ML8b:       rts

ikbdev:     DC.B      $01,$02,$03,$03,$03,$03,$04,$05
            DC.B      $06,$07
ikbdlen:    DC.B      $07,$05,$02,$02,$02,$02,$06,$02
            DC.B      $01,$01

ML3:        cmpi.b    #6,(kstate).w
            bcc       ML35                      ; a joystick 0/1 record byte, not both!
            lea       (ikbdparams).l,a2         ; point to ikbd subsystem parameters table
            moveq     #0,d2
            move.b    (kstate).w,d2             ; load to generate longword offset
            subq.b    #1,d2                     ; d2 = (kstate-1) * 12
            asl.w     #1,d2
            add.b     (kstate).w,d2
            subq.b    #1,d2
            asl.w     #2,d2
            movea.l   (a2,d2.w),a0              ; load in subsystem's record pointer
            movea.l   4(a2,d2.w),a1             ; load in subsystem's index base+record pointer
            movea.l   8(a2,d2.w),a2             ; load in subsystem's pointer variable that contains the pointer to the subsystem's interrupt routine
            movea.l   (a2),a2                   ; get the address of the interrupt routine
            moveq     #0,d2                     ; clear out 'd2' for address manipulation
            move.b    (kindex).w,d2
            suba.l    d2,a1
            move.b    d0,(a1)                   ; store byte into the subsystems buffer
            subq.b    #1,(kindex).w             ; decrement the number of bytes in the package
            tst.b     (kindex).w                ; package received?
            bne.s     ML1                       ; not yet...
ikserve:    move.l    a0,-(sp)                  ; stuff buffer pointer to stack
            jsr       (a2)                      ; go service the subsystem interrupt routine
            addq.w    #4,sp                     ; re-adjust stack
            clr.b     (kstate).w                ; reset ikbd state
ML1:        rts

ikbdparams: DC.L      statrec
            DC.L      amrec
            DC.L      statintvec

            DC.L      amrec
            DC.L      mousebuf
            DC.L      msintvec

            DC.L      mousebuf
            DC.L      clkrec
            DC.L      msintvec

            DC.L      clkrec
            DC.L      joyrec
            DC.L      clkintvec

            DC.L      joyrec
            DC.L      joyrec+2
            DC.L      joyintvec

ML35:       move.l    #joyrec+1,d1
            add.b     (kstate).w,d1             ; kstate reflects joy0 or joy1 state
            subq.b    #6,d1
            movea.l   d1,a2                     ; create index to joyrec table for record byte
            move.b    d0,(a2)
            movea.l   (joyintvec).w,a2          ; get user's joystick interrupt routine adr
            lea       (joyrec).w,a0             ; send along address of joystick data
            bra.s     ikserve

itsakey:    move.b    (kb_shift).w,d1           ; load in kbshift for manipulation
* check the special keys
            cmpi.b    #$2a,d0                   ; left shift?
            bne.s     ari2
            bset      #1,d1
            bra       ari10
ari2:       cmpi.b    #$aa,d0
            bne.s     ari3
            bclr      #1,d1
            bra       ari10
ari3:       cmpi.b    #$36,d0                   ; right shift?
            bne.s     ari4
            bset      #0,d1
            bra.s     ari10
ari4:       cmpi.b    #$b6,d0
            bne.s     ari5
            bclr      #0,d1
            bra.s     ari10
ari5:       cmpi.b    #$1d,d0                   ; CTRL
            bne.s     ari6
            bset      #2,d1
            bra.s     ari10
ari6:       cmpi.b    #$9d,d0
            bne.s     ari7
            bclr      #2,d1
            bra.s     ari10
ari7:       cmpi.b    #$38,d0                   ; ALT
            bne.s     ari8
            bset      #3,d1
            bra.s     ari10
ari8:       cmpi.b    #$b8,d0
            bne.s     ari9
            bclr      #3,d1

            tst.w     (altdigits).l             ; ascii code via numpad active?
            bmi.s     ari10                     ; no...
            move.b    d1,(kb_shift).w           ; store shift status
            move.l    a0,-(sp)
            moveq     #0,d1                     ; no scancode
            move.w    d1,d0                     ; clear ascii code reg
            move.b    (altdigits+1).l,d0        ; take entered ascii code
            move.w    #-1,(altdigits).l         ; reset alt-numpad ascii entering
            bra       conin25

ari9:       cmpi.b    #$3a,d0                   ; CAPS LOCK
            bne.s     ari11
            btst      #0,(conterm).w
            beq.s     ari9a                     ; no click please!
            movem.l   d0-d2/a0-a2,-(sp)
            movea.l   (kcl_hook).w,a0
            jsr       (a0)
            movem.l   (sp)+,d0-d2/a0-a2
ari9a:      bchg      #4,d1                     ; toggle CAPS LOCK state
ari10:      move.b    d1,(kb_shift).w           ; restore new kbshift value
            rts                                 ; ignore CAPS LOCK break


ari11:      btst      #7,d0                     ; is it a break code?
            bne.s     ari12                     ; no... a break code!
            move.b    d0,(keyrep).w             ; save for repeat purposes
            move.b    (cdelay1).l,(keydelay1).w
            move.b    (cdelay2).l,(keydelay2).w
            bra.s     ari16
ari12:      move.b    d0,d1
            bclr      #7,d1
            cmp.b     (keyrep).w,d1
            bne.s     ari15
            moveq     #0,d1
            move.b    d1,(keyrep).w
            move.b    d1,(keydelay1).w
            move.b    d1,(keydelay2).w

ari15:      cmpi.b    #$c7,d0                   ; is it a "home" break-code?
            beq.s     ari18a                    ; yes...allow it to pass
            cmpi.b    #$d2,d0                   ; is it an "insert" break-code?
            bne       ari14                     ; no...regular break junk...
ari18a:     btst      #3,(kb_shift).w           ; early "ALT" test to prevent double "nulls"
            beq       ari14                     ; no ALT...so exit now...
ari16:      btst      #0,(conterm).w
            beq.s     ari16a                    ; no click please!
            movem.l   d0-d2/a0-a2,-(sp)
            movea.l   (kcl_hook).w,a1
            jsr       (a1)
            movem.l   (sp)+,d0-d2/a0-a2
ari16a:     move.l    a0,-(sp)                  ; store kbufrec pointer

            moveq     #0,d1
            move.b    d0,d1

default: use regular ascii table:movea.l (skeytran).w,a0
            andi.w    #$7f,d0
            btst      #4,(kb_shift).w           ; caps-lock pressed?
            beq.s     conin21
            movea.l   (skeycl).w,a0             ; use caps-lock table
conin21:    btst      #0,(kb_shift).w
            bne.s     conin22
            btst      #1,(kb_shift).w
            beq.s     conin23
conin22:    cmpi.b    #$3b,d0                   ; see if a possible function key
            bcs.s     conin22a                  ; unsigned less than lowest function scancode
            cmpi.b    #$44,d0                   ; see if a possible function key
            bhi.s     conin22a                  ; unsigned greater than highest function scan
            addi.w    #$19,d1                   ; add to change to GSX standard
            moveq     #0,d0                     ; change to GSX standard
            bra       conin25

conin22a:   movea.l   (skeyshif).w,a0           ; use the shift table
conin23:    move.b    (a0,d0.w),d0
            btst      #2,(kb_shift).w           ; is the control key down?
            beq.s     conin24a
            cmpi.b    #$d,d0                    ; is it a carriage return?
            bne.s     conin23a
            moveq     #$a,d0                    ; change to a linefeed according to GSX spec...
            beq.s     conin24x
conin23a:   cmpi.b    #$47,d1                   ; convert CONTROL-home to gsx standard
            bne.s     conin23b
            addi.w    #$30,d1                   ; by adding #$30...
            bra       conin25
conin23b:   cmpi.b    #$4b,d1                   ; convert CONTROL-left arrow to gsx standard
            bne.s     conin23c
            moveq     #$73,d1                   ; change according to gsx spec
            moveq     #0,d0
            bra       conin25
conin23c:   cmpi.b    #$4d,d1                   ; convert CONTROL-right arrow to gsx standard
            bne.s     conin24x
            moveq     #$74,d1                   ; change according to gsx spec
            moveq     #0,d0
            bra       conin25

conin24x:   cmpi.b    #$32,d0                   ; Control-M?
            bne.s     conin240
            moveq     #0,d0                     ; ascii code 0
            bra       conin25
conin240:   cmpi.b    #$36,d0                   ; Control-Shift-Right?
            bne.s     conin241
            moveq     #$1e,d0                   ; ascii code RS
            bra       conin25
conin241:   cmpi.b    #$2d,d0                   ; Control-X?
            bne.s     conin24a
            moveq     #$1f,d0                   ; ascii code US
            bra       conin25

* this routine allows entering any ascii code by using the numpad
* and holding down ALT during it.
conin24a:   btst      #3,(kb_shift).w           ; is the alt key down?
            beq       conin2x
            cmp.b     #$67,d1                   ; key 0 ... 9 on the number pad?
            bcs.s     outside                   ; no...
            cmp.b     #$70,d1
            bhi.s     outside                   ; no...
            move.w    (altdigits).l,d0          ; already entering an ascii code?
            bpl.s     conin24b                  ; yes
            moveq     #0,d0                     ; start with 0
conin24b:   mulu.w    #10,d0                    ; previous digit * 10
            ext.w     d1
            move.b    (a0,d1.w),d1              ; ascii code of current digit
            sub.b     #'0',d1                   ; convert into number
            add.b     d1,d0                     ; plus old number
            move.w    d0,(altdigits).l          ; store as new number code
            movea.l   (sp)+,a0
            rts

outside:    cmpi.b    #$62,d1                   ; is it an "alt help" signal to dump the screen?
            bne.s     alt15a                    ; no...
            addq.w    #1,(_prtcnt).w            ; yes...switch the signal flag on!...
            movea.l   (sp)+,a0                  ; restore kbufrec pointer
            bra       ari14                     ; ...and exit
*
*       check the alt-insert/alt-home key make/break combinations, first
*
alt15a:     lea       (mousekey1).l,a2          ; get pointer to first alt. mouse scancode table
            moveq     #3,d2                     ; create countdown
mkloop1:    cmp.b     (a2,d2.w),d1              ; is table's scancode value = current value?
            beq       keymaus1                  ; yes...go preprocess it...
            dbra      d2,mkloop1                ; no...loop back to check next table value

            cmpi.b    #$48,d1                   ; is it an up arrow?
            bne.s     alt11
            move.b    #0,d1                     ; x value for up arrow
            move.b    #$f8,d2                   ; y value for up arrow
            move.b    (kb_shift).w,d0           ; grab current setting
            andi.b    #3,d0                     ; KBRSH+KBLSH bits
            beq       keymaus
            move.b    #$ff,d2                   ; y value for up arrow
            bra       keymaus
alt11:      cmpi.b    #$4b,d1                   ; is it an left arrow?
            bne.s     alt12
            move.b    #0,d2                     ; x value for left arrow
            move.b    #$f8,d1                   ; y value for left arrow
            move.b    (kb_shift).w,d0           ; grab current setting
            andi.b    #3,d0                     ; KBRSH+KBLSH bits
            beq       keymaus
            move.b    #$ff,d1                   ; x value for left arrow
            bra       keymaus
alt12:      cmpi.b    #$4d,d1                   ; is it an right arrow?
            bne.s     alt13
            move.b    #8,d1                     ; x value for right arrow
            move.b    #0,d2                     ; y value for right arrow
            move.b    (kb_shift).w,d0           ; grab current setting
            andi.b    #3,d0                     ; KBRSH+KBLSH bits
            beq       keymaus
            move.b    #1,d1                     ; x value for right arrow
            bra       keymaus
alt13:      cmpi.b    #$50,d1                   ; is it an down arrow?
            bne.s     alt14
            move.b    #0,d1                     ; x value for down arrow
            move.b    #8,d2                     ; y value for down arrow
            move.b    (kb_shift).w,d0           ; grab current setting
            andi.b    #3,d0                     ; KBRSH+KBLSH bits
            beq       keymaus
            move.b    #1,d2                     ; y value for down arrow
            bra       keymaus
alt14:      btst      #2,(kb_shift).w           ; CTRL key?
            bne.s     conin24                   ; yes
            cmpi.b    #2,d1
            bcs.s     alt1                      ; not >= the '1' key scancode
            cmpi.b    #$d,d1
            bhi.s     alt1                      ; not <= the '=' key scancode
            addi.b    #$76,d1                   ; scancode is a key between '1' key and '=' key
            bra.s     alt2
alt1:       cmpi.b    #$41,d0                   ; is the key an ascii 'A' or greater?
            bcs.s     alt3                      ; no skip to check if 'a'-'z'...
            cmpi.b    #$5a,d0                   ; is the key an ascii 'Z' or less?
            bhi.s     alt3                      ; no skip to check if 'a'-'z'...
alt2:       moveq     #0,d0
            bra.s     conin25
alt3:       cmpi.b    #$61,d0                   ; is the key an ascii 'a' or greater?
            bcs.s     conin25                   ; not...skip to finish normal processing
            cmpi.b    #$7a,d0                   ; is the key an ascii 'z' or less?
            bhi.s     conin25                   ; not...skip to finish normal processing
            bra.s     alt2

conin2x:    btst      #2,(kb_shift).w           ; CTRL key?
            beq.s     conin25                   ; no
conin24:    andi.w    #$1f,d0                   ; yep, so CTRLize the key
conin25:    asl.w     #8,d1                     ; shift the scan code to the word's high byte
            add.w     d1,d0                     ; form the outgoing word

            movea.l   (sp)+,a0                  ; restore kbufrec pointer

            move.w    8(a0),d1                  ; get current tail pointer offset
            addq.w    #4,d1                     ; index = tail + 4
            cmp.w     4(a0),d1                  ; check to see if buffer should wrap
            bcs.s     ari13                     ; no...
            moveq     #0,d1                     ; wrap pointer
ari13:      cmp.w     6(a0),d1                  ; head=tail?
            beq.s     ari14                     ; yes
            movea.l   (a0),a2                   ; get buffer pointer

* move kb_shift into the upper 8 bits of the keycode
            swap      d0
            clr.w     d0
            move.b    (kb_shift).w,d0
            swap      d0
            lsl.l     #8,d0
            lsr.w     #8,d0

            move.l    d0,d2                     ; save keycode for our reset key commands
            bclr      #$1c,d2                   ; clear capslock state
            swap      d2                        ; isolate kb_shift & scancode
            cmp.w     #$c53,d2                  ; control+alt+delete?
            beq       reseth                    ; => reset
            cmp.w     #$d53,d2                  ; control+alt+rshift+delete?
            beq       _coldboot                 ; => cold boot by erasing all memory

            btst      #3,(conterm).w            ; should Bconin() to return the shift status?
            bne.s     ari13b                    ; yes...
            andi.l    #$ffffff,d0               ; no, filter it out (default case)
ari13b:     and.l     #$ffff,d1
            move.l    d0,(a2,d1.l)              ; store the data
            move.w    d1,8(a0)                  ; store the new buftail pointer
ari14:      rts

keyclicksnd:move.l    #gkeyclicksnd,(cursnd).w  ; sound data for key click
            move.b    #0,(timer).w              ; enable sound timer
            rts


midibyte:   movea.l   (midivec).w,a2            ; get contents of midivec for indirect branch
            jmp       (a2)                      ; jump to midi interrupt handler


sysmidi:    move.w    8(a0),d1                  ; get current tail pointer offset
            addq.w    #1,d1                     ; index = tail + 1
            cmp.w     4(a0),d1                  ; check to see if buffer should wrap
            bcs.s     mi13                      ; no...
            moveq     #0,d1                     ; wrap pointer
mi13:       cmp.w     6(a0),d1                  ; head=tail?
            beq.s     mi14                      ; yes
            movea.l   (a0),a2                   ; get buffer pointer
            and.l     #$ffff,d1
            move.b    d0,(a2,d1.l)              ; store the data
            move.w    d1,8(a0)                  ; store the new buftail pointer
mi14:       rts


keymaus1:   moveq     #5,d3                     ; pre-init to "keyboard" right mouse button
            btst      #4,d1                     ; see if it is a left or right button...
            beq.s     keym1                     ; it's a right button ($47/$c7)
            moveq     #6,d3                     ; it's a left button ($52/$d2)
keym1:      btst      #7,d1                     ; see if it is a make or break action
            beq.s     keym2                     ; it's a set button action (make code)
            bclr      d3,(kb_shift).w           ; it's a clear button action (break code)
            bra.s     keym3                     ; go to further pre-init action...
keym2:      bset      d3,(kb_shift).w           ; it's a set button action (set code)
keym3:      moveq     #0,d1
            moveq     #0,d2
*
* finish up at the actual pseudo mouse routine
*

keymaus:    lea       (kmbuf).w,a0              ; point to key-emulating mouse buffer
            movea.l   (msintvec).w,a2           ; grab mouse interrupt vector
            clr.l     d0
            move.b    (kb_shift).w,d0           ; get current button status
            lsr.b     #5,d0                     ; shift right button bit to 'd0'
            addi.b    #$f8,d0                   ; add relative mouse header
            move.b    d0,(a0)                   ; store in first byte of record header
            move.b    d1,1(a0)                  ; store x value in second byte of record buffer
            move.b    d2,2(a0)                  ; store y value in third byte of record buffer
            jsr       (a2)
            movea.l   (sp)+,a0                  ; restore kbufrec pointer
            rts


mousekey1:  DC.B      $47,$c7,$52,$d2

*************************************************************************
*                                                                       *
*************************************************************************
_coldboot:  move      #$2700,sr                 ; disable all IRQSR
IF ROM_STBOOK
            move.l    ($0004).w,(busexception).w ;a bus error triggers a reset
            movea.w   #addrexception,a0         ; start erasing from $c on
            moveq     #0,d0
            move.l    #$3fffc,d1                ; erase 1MB
_coldboot2: move.l    d0,(a0)+
            dbra      d1,_coldboot2
            movea.l   ($0004).w,a0              ; jump into the ROM to reset the system
            jmp       (a0)

ELIF ROM_TOS206
            move.w    #5,d0
            lea       _coldbootr(pc),a0
            movea.w   #addrexception,a1
_coldboot1: move.l    (a0)+,(a1)+               ; copy erase routine to $000c...
            dbra      d0,_coldboot1
            jmp       (addrexception).w

_coldbootr: move.l    ($0004).w,(busexception).w
            lea       _coldbootre(pc),a0
            moveq     #0,d0
_coldbootl: move.l    d0,(a0)+
            move.l    d0,(a0)+
            move.l    d0,(a0)+
            move.l    d0,(a0)+
            bra.s     _coldbootl
_coldbootre:nop

ELIF ROM_TOS306
            move.l    #$808,d0
            movec     d0,cacr

            moveq     #0,d0
            movec     d0,vbr                    ; set vector base to address 0
            pmove     ($e36738).l,tc            ; 0L - disable PMMU
            pmove     ($e36738).l,tt0           ; 0L
            pmove     ($e36738).l,tt1           ; 0L

            move.w    #11,d0
            lea       _coldbootr(pc),a0
            movea.w   #addrexception,a1
_coldboot1: move.l    (a0)+,(a1)+               ; copy erase routine to $000c...
            dbra      d0,_coldboot1
            jmp       (addrexception).w

_coldbootr: lea       _coldboott(pc),a0
            move.l    a0,(busexception).w
            lea       _coldbootre(pc),a0
            moveq     #0,d0
_coldbootl: move.l    d0,(a0)+                  ; erase ST RAM
            move.l    d0,(a0)+
            move.l    d0,(a0)+
            move.l    d0,(a0)+
            bra.s     _coldbootl
_coldboott: move.l    ($0004).w,(busexception).w
            lea       ($1000000).l,a0
_coldbootfl:move.l    d0,(a0)+                  ; erase fast RAM
            move.l    d0,(a0)+
            move.l    d0,(a0)+
            move.l    d0,(a0)+
            bra.s     _coldbootfl
_coldbootre:nop

ENDIF

*************************************************************************
*                                                                       *
*       protocol for accessing a gi sound chip register                 *
*                                                                       *
*       this bios call must be accessed in supervisor state             *
*       because it affects the 'sr' register                            *
*                                                                       *
*       entry                                                           *
*                                                                       *
*       word    giaccess(data,register)                                 *
*       word    data,register                                           *
*                                                                       *
*               data -- data register read/write date                   *
*               register -- chip register to select                     *
*               d1 = #$0000     ;selects read operation of the register *
*               d1 = #$80 .or .xx       ;selects write xx to register   *
*               example write to portb - $80 .or. $0f = $8f             *
*                                                                       *
*       exit                                                            *
*       read operations                                                 *
*       d0.b -- data register contains byte of date                     *
*       write operations                                                *
*       d0.b -- data register contains a verification of written data   *
*                                                                       *
*************************************************************************
giaccess:   move.w    4(sp),d0
            move.w    6(sp),d1
gientry:    move      sr,-(sp)
            ori       #$700,sr
            movem.l   d1-d2/a0,-(sp)            ; save affected registers
            lea       (psgsel).w,a0             ; init desired gi register addr
            move.b    d1,d2                     ; make a copy to test for read or write
            andi.b    #$f,d1                    ; turn off any extraneous bits
            move.b    d1,(a0)                   ; select register
            asl.b     #1,d2                     ; shift once for carry bit detection
            bcc.s     giread                    ; carry clear, so do a read operation
giwrit:     move.b    d0,2(a0)                  ; init the memory location
giread:     moveq     #0,d0                     ; clear our register
            move.b    (a0),d0                   ; grab the data from the gi register
            movem.l   (sp)+,d1-d2/a0            ; restore affected registers
            move      (sp)+,sr
            rts


*********************************************************************
*       routine to turn on the dtr signal                           *
*********************************************************************

dtron:      move.b    #$ef,d2
            bra.s     offbit

*********************************************************************
*                                                                   *
*       routine to set any bit in the gi port a area                *
*                                                                   *
*       entry                                                       *
*                                                                   *
*       word    ongibit(bitnum)                                     *
*       word    bitnum                                              *
*                                                                   *
*               bitnum - byte size mask with desired bit set to "1" *
*                                                                   *
*********************************************************************

ongibit:    moveq     #0,d2
            move.w    4(sp),d2
onbit:      movem.l   d0-d2,-(sp)
            move      sr,-(sp)
            ori       #$700,sr
            moveq     #14,d1                    ; get ready to read in the port a contents
            move.l    d2,-(sp)
            bsr.s     gientry                   ; go get it...
            move.l    (sp)+,d2
            or.b      d2,d0                     ; set bit(s) on
            move.b    #$8e,d1                   ; setup to write to port a
            bsr.s     gientry                   ; go set it and return
            move      (sp)+,sr
            movem.l   (sp)+,d0-d2
            rts


*********************************************************************
*                                                                   *
*       routine to clear any bit in the gi port a area              *
*                                                                   *
*       entry                                                       *
*                                                                   *
*       word    offgibit(bitnum)                                    *
*       word    bitnum                                              *
*                                                                   *
*               bitnum - byte size mask with desired bit set to "0" *
*                                                                   *
*********************************************************************

offgibit:   moveq     #0,d2
            move.w    4(sp),d2
offbit:     movem.l   d0-d2,-(sp)
            move      sr,-(sp)
            ori       #$700,sr
            moveq     #14,d1                    ; get ready to read in the port a contents
            move.l    d2,-(sp)
            bsr.s     gientry                   ; go get it...
            move.l    (sp)+,d2
            and.b     d2,d0                     ; turn bit(s) off
            move.b    #$8e,d1                   ; setup to write to port a
            bsr.s     gientry                   ; go set it and return
            move      (sp)+,sr
            movem.l   (sp)+,d0-d2
            rts


*************************************************************************
*                                                                       *
*               EXTENDED RBP BIOS TIMER INIT CALL                       *
*                                                                       *
*       entry:                                                          *
*                                                                       *
*       word    initmous(type,param,intvec)                             *
*       word    type                                                    *
*       long    param,initvec                                           *
*                                                                       *
*               type - key/abs/rel/off  mouse function requested        *
*                       4/  2/  1/  0   value                           *
*               param - address of parameter block                      *
*               intvec - mouse interrupt vector                         *
*                                                                       *
*                                                                       *
*       parameter block definition:                                     *
*                                                                       *
*       byte 0 - y=0 at top/bottom; if non-zero then y=0 at bottom      *
*               otherwise y=0 at top                                    *
*       byte 1 - parameter for set mouse buttons command                *
*       byte 2 - x threshold/scale/delta parameter                      *
*       byte 3 - y threshold/scale/delta parameter                      *
*                                                                       *
*       the following bytes are required for the absolute mode only     *
*                                                                       *
*       byte 4 - xmsb for absolute mouse maximum position               *
*       byte 5 - xlsb for absolute mouse maximum position               *
*       byte 6 - ymsb for absolute mouse maximum position               *
*       byte 7 - ylsb for absolute mouse maximum position               *
*       byte 8 - xmsb for absolute mouse initial position               *
*       byte 9 - xlsb for absolute mouse initial position               *
*       byte a - ymsb for absolute mouse initial position               *
*       byte b - ylsb for absolute mouse initial position               *
*                                                                       *
*************************************************************************

* first we determine if the init is for a absolute, relative or keycode
* mouse action.

initmous:   tst.w     4(sp)                     ; turn mouse off?
            beq.s     im1                       ; yes...disable mouse
            move.l    $a(sp),(msintvec).w       ; init the mouse interrupt vector
            movea.l   6(sp),a3
            cmpi.w    #1,4(sp)                  ; relative mouse request?
            beq.s     im2                       ; yes...
            cmpi.w    #2,4(sp)                  ; absolute mouse request?
            beq.s     im3                       ; yes...
            cmpi.w    #4,4(sp)                  ; keycode mouse request?
            beq.s     im4                       ; yes...
            moveq     #0,d0                     ; error condition returned -- improper request
            rts

im1:        moveq     #$12,d1                   ; disable mouse
            bsr       ikbdput
            move.l    #xbtexit,(msintvec).w     ; re-init the mouse interrupt vector
            bra.s     imexit

im2:        lea       (transbuf).w,a2           ; set transfer buffer pointer
            move.b    #8,(a2)+                  ; set to relative mouse
            move.b    #$b,(a2)+                 ; set relative mouse threshold x,y
            bsr.s     setmouse
            moveq     #6,d3                     ; set length of the string -1 to transfer
            lea       (transbuf).w,a2           ; set transfer buffer pointer
            bsr       ikbdstr                   ; do transfer to ikbd
            bra.s     imexit

im3:        lea       (transbuf).w,a2           ; set transfer buffer pointer
            move.b    #9,(a2)+                  ; set to absolute mouse
            move.b    4(a3),(a2)+               ; set xmsb max
            move.b    5(a3),(a2)+               ; set xlsb max
            move.b    6(a3),(a2)+               ; set ymsb max
            move.b    7(a3),(a2)+               ; set ylsb max
            move.b    #$c,(a2)+                 ; set absolute mouse scale
            bsr.s     setmouse
            move.b    #$e,(a2)+                 ; load initial absolute mouse position
            move.b    #0,(a2)+                  ; filler load
            move.b    8(a3),(a2)+               ; initial xmsb absolute mouse position
            move.b    9(a3),(a2)+               ; initial xlsb absolute mouse position
            move.b    $a(a3),(a2)+              ; initial ymsb absolute mouse position
            move.b    $b(a3),(a2)+              ; initial ylsb absolute mouse position
            moveq     #$10,d3                   ; set length of string -1 to transfer
            lea       (transbuf).w,a2           ; set transfer buffer pointer
            bsr       ikbdstr                   ; do transfer to ikbd
            bra.s     imexit

im4:        lea       (transbuf).w,a2           ; set transfer buffer pointer
            move.b    #$a,(a2)+                 ; set to mouse keycode mode
            bsr.s     setmouse
            moveq     #5,d3                     ; set length of string -1 to transfer
            lea       (transbuf).w,a2           ; set transfer buffer pointer
            bsr       ikbdstr                   ; do transfer to ikbd
imexit:     moveq     #-1,d0                    ; set to true to indicate good init
            rts


setmouse:   move.b    2(a3),(a2)+               ; set x threshold/scale/delta
            move.b    3(a3),(a2)+               ; set y threshold/scale/delta
            moveq     #$10,d1                   ; setup to determine if top/bottom
            sub.b     (a3),d1                   ; set y=0 at ?
            move.b    d1,(a2)+
            move.b    #7,(a2)+                  ; set mouse button action
            move.b    1(a3),(a2)+               ; mouse button parameter
            rts


*************************************************************************
*                                                                       *
*               EXTENDED RBP BIOS TIMER INIT CALL                       *
*                                                                       *
*       entry:                                                          *
*                                                                       *
*       void    xbtimer(id,control,data,intvec)                         *
*       word    id,control,data                                         *
*       long    intvec                                                  *
*                                                                       *
*               intvec - timer interrupt vector                         *
*               control - timer's control setting                       *
*               data - timer's data register setting                    *
*               id - timer id   a-0, b-1, c-2, d-3                      *
*                                                                       *
*       Special Note:                                                   *
*                                                                       *
*       In the interest of preserving as many features for the user     *
*       in the future, timer A should be reserved for the end-user      *
*       or independent software vendor's application program.  System   *
*       software or those application needing just a "tick" should      *
*       constrain themselves to timer C, which is adequate for delay    *
*       and other timing uses.  Future hardware may or may not bring    *
*       out the timer A input line out...giving software developers     *
*       another useful aspect of the machine to utilize.                *
*                                                                       *
*       The recommended usage of the timers is as follows:              *
*                                                                       *
*       Timer A - Reserved for end-users and stand-alone applications.  *
*       Timer B - Reserved for screen graphics, primarily.              *
*       Timer C - Reserved for system timing (GSX,GEM,DESKTOP,ET.AL).   *
*       Timer D - Reserved for baud rate control of RS-232 port,        *
*                the interrupt vector is available to anyone.           *
*                                                                       *
*************************************************************************

xbtimer:    moveq     #0,d0
            moveq     #0,d1
            moveq     #0,d2
            move.w    4(sp),d0
            move.w    6(sp),d1
            move.w    8(sp),d2
            bsr       settimer                  ; setup the timer
            tst.l     $a(sp)                    ; if >$7fffffff then skip and exit
            bmi.s     xbtexit
            movea.l   $a(sp),a2                 ; setup for initint call
            moveq     #0,d1                     ; clear long
            lea       xbtim(pc),a1              ; point to timer -> interrupt # translation tab
            andi.l    #$ff,d0                   ; mask off the highest three bytes in register
            move.b    (a1,d0.w),d0              ; setup for initint call
            bsr       initint
xbtexit:    rts

xbtim:      DC.B      $0d,$08,$05,$04

*************************************************************************
*                                                                       *
*               KEYBOARD TRANSLATION TABLE CHANGE CALL                  *
*                                                                       *
*       entry:                                                          *
*                                                                       *
*       long    keytrans(unshift,shift,capslock)                        *
*       long    unshift,shift,capslock                                  *
*                                                                       *
*               -1 signifies no change to vector                        *
*                                                                       *
*       exit:                                                           *
*               d0.l - returns pointer to beginning of                  *
*                       key translation address pointers                *
*               order of pointers is:                                   *
*               unshifted,shifted,caps-locked                           *
*               Note:  buffer space for each table should $80!!         *
*                                                                       *
*************************************************************************

keytrans:   tst.l     4(sp)
            bmi.s     kt1
            move.l    4(sp),(skeytran).w
kt1:        tst.l     8(sp)
            bmi.s     kt2
            move.l    8(sp),(skeyshif).w
kt2:        tst.l     $c(sp)
            bmi.s     kt3
            move.l    $c(sp),(skeycl).w
kt3:        move.l    #skeytran,d0
            rts


*************************************************************************
*                                                                       *
*               RESTORE BIOS KEYBOARD TRANSLATION TABLE                 *
*                                                                       *
*       entry:                                                          *
*                                                                       *
*       void    bioskeys()                                              *
*                                                                       *
*                                                                       *
*************************************************************************

bioskeys:   move.l    #gskeytran,(skeytran).w
            move.l    #gskeyshif,(skeyshif).w
            move.l    #gskeycl,(skeycl).w
IF !ROM_TOS306
            clr.b     (keyrep).w
ENDIF
            rts

*************************************************************************
*                                                                       *
*               RETURN IKBD SUBSYSTEM INTERRUPT TABLE POINTER           *
*                                                                       *
*       entry:                                                          *
*                                                                       *
*       void    dosound(ptr)                                            *
*       long    ptr     ;points to start of sound interpreter table     *
*                                                                       *
*                                                                       *
*************************************************************************

dosound:    move.l    (cursnd).w,d0             ; return current status in D0.L
            move.l    4(sp),d1                  ; if new ptr < 0, then just return
            bmi.s     ds_r                      ; (invalid ptr, so return)
            move.l    d1,(cursnd).w             ; setup new sound ptr
            clr.b     (timer).w                 ; zap sound timer register
ds_r:       rts

*************************************************************************
*                                                                       *
*               SET/RETURN PRINTER CONFIGURATION WORD                   *
*                                                                       *
*       entry:                                                          *
*                                                                       *
*       word    setptr(pconfig)                                         *
*       word    pconfig ;sets/gets printer information word             *
*                                                                       *
*                                                                       *
*************************************************************************

setprt:     move.w    (pconfig).w,d0            ; get current config word before we change it
            tst.w     4(sp)                     ; see if we don't change the word
            bmi.s     nosetp                    ; don't set printer word
            move.w    4(sp),(pconfig).w         ; set printer config word
nosetp:     rts

*************************************************************************
*                                                                       *
*               SET/RETURN KEY REPEAT VALUES                            *
*                                                                       *
*       entry:                                                          *
*                                                                       *
*       word    kbrate(initial,repeat)                                  *
*       word    initial,repeat                                          *
*                                                                       *
*       initial determines the number of 50 hz cycles to wait before    *
*       a keyrepeat is to commence.  repeat determines the interval     *
*       between keyrepeats after the initial pause.                     *
*                                                                       *
*************************************************************************

kbrate:     move.w    (cdelay1).w,d0            ; get current initial/repeat values
            tst.w     4(sp)                     ; see if we don't change the word
            bmi.s     kbrate1                   ; don't set key repeat values
            move.w    4(sp),d1                  ; set key repeat values
            move.b    d1,(cdelay1).w            ; set initial delay
            tst.w     6(sp)                     ; see if we don't change the word
            bmi.s     kbrate1                   ; don't set key repeat values
            move.w    6(sp),d1                  ; set key repeat values
            move.b    d1,(cdelay2).w            ; set subsequent delay
kbrate1:    rts


*************************************************************************
*                                                                       *
*               RETURN POINTER TO IKBD/MIDI INTERRUPT VECTORS           *
*                                                                       *
*       entry:                                                          *
*                                                                       *
*       long    ikbdvecs()                                              *
*               returns a pointer to the midi interrupt vector and      *
*               ikbd subsystem interrupt vector table.  the table       *
*               structure is as follows:                                *
*                                                                       *
*       midivec         ds.l    1       ;midi interrupt handler vector  *
*       vkbderr         ds.l    1       ;keyboard error handler address *
*       vmiderr         ds.l    1       ;midi error handler address     *
*       statintvec      ds.l    1       ;ikbd status interrupt vector   *
*       msintvec        ds.l    1       ;mouse interrupt vector         *
*       clkintvec       ds.l    1       ;realtime clk interrupt vector  *
*       joyintvec       ds.l    1       ;joystick interrupt vector      *
*                                                                       *
*       note:   msintvec is modified via the initmouse system function  *
*               call.  since gem uses this vector, modifying it can be  *
*               fatal while running under gem.  clkintvec is used by    *
*               gemdos.  its pre-inited vector must be restored for     *
*               proper gemdos operation.  Caveat hacker!                *
*                                                                       *
*                                                                       *
*************************************************************************

ikbdvecs:   move.l    #midivec,d0
            rts

*************************************************************************
*                                                                       *
*       C Timer interrupt routine to process the PSG sound table        *
*                                                                       *
*************************************************************************
*+ (lmd)
* timercint - timer c interrupt handler
* divide 200 Hz interrupt frequency to 50 hz, and do:
*       sound handler processing
*       key-repeat processing;
*       control-g bell and keyclick if enabled via sound handler
*       system timer-tick handoff
*       updates:        tc_rot (every tick)
*
*       imports:        etv_timer (timer handoff vector)
*                       _timr_ms (timer calibration value)
*
*-
timercint:  addq.l    #1,(_hz_200).l            ; increment raw tick counter
            rol       (tc_rot).l                ; rotate divisor bits
            bpl.s     t_punt                    ; if not 4th interrupt, then return
            movem.l   d0-d7/a0-a6,-(sp)

            bsr.s     sndirq                    ; process sounds

            btst      #1,(conterm).w            ; check for key repeat enabled
            beq.s     krexit                    ; not enabled

* process for repeat key function first because it can affect the sound
* table if enabled and the user is 'using'...

            tst.b     (keyrep).w
            beq.s     krexit
            tst.b     (keydelay1).w
            beq.s     kr1
            subq.b    #1,(keydelay1).w
            bne.s     krexit
kr1:        subq.b    #1,(keydelay2).w
            bne.s     krexit
            move.b    (cdelay2).w,(keydelay2).w
            move.b    (keyrep).w,d0
            lea       (kbufrec).w,a0
            bsr       ari16                     ; repeat key stroke and stuff into buffer

*+ (lmd)
* Call system timer vector
* (first guy in the system daisy-chain)
*
*-
krexit:     move.w    (_timr_ms).w,-(sp)        ; push #ms/tick
            movea.l   (etv_timer).w,a0          ; get vector
            jsr       (a0)                      ; call it
            addq.w    #2,sp                     ; cleanup stack
tick1:      movem.l   (sp)+,d0-d7/a0-a6
t_punt:     move.b    #$df,(isrb).w             ; clear the interrupt channel
            rte

*********************************************************
*
*  Quick & dirty sound stuff
*
*
*  Programmed by Dave Staugas
*                14 Mar 1985
*
*
*
*
*********************************************************
*
*
*
*
*  To start a sound, load the 32-bit address of the
*                       byte stream for that sound in 32-bit
*                       "cursnd", & zero the 8-bit "timer"
*
*
*
*
*   Sound interrupt routine
*   Called from timer C irq
*
sndirq:     movem.l   d0-d1/a0,-(sp)
            move.l    (cursnd).w,d0             ; get current sound ptr
            beq.s     snd1                      ; br to exit if zero, inactive
            movea.l   d0,a0                     ; ptr to a0
            move.b    (timer).w,d0              ; check delay timer
            beq.s     snd3                      ; br over delay timer update if not on
*
            subq.b    #1,d0                     ; tick off delay timer
            move.b    d0,(timer).w              ; save new
            bra.s     snd1                      ; skip sound update this time

snd3:       move.b    (a0)+,d0                  ; pick up next sound command
            bmi.s     snd2                      ; if minus, go do special
*
            move.b    d0,(psgsel).w             ; else, register load command--select this
            cmpi.b    #7,d0                     ; reg. 7 selected?
            bne.s     sn1                       ; br if no
*
            move.b    (a0)+,d1                  ; get data to write to reg 7
            andi.b    #$3f,d1                   ; always leave i/o port settings alone
            move.b    (psgsel).w,d0             ; get mixer contents
            andi.b    #$c0,d0                   ; mask off non-useful info...
            or.b      d1,d0                     ; generate new setting
            move.b    d0,(psgwr).w              ; write data
            bra.s     snd3                      ; go for next command

sn1:        move.b    (a0)+,(psgwr).w           ; write next byte as data directly to reg
            bra.s     snd3                      ; go for next command
*
*  special case command
*
snd2:       addq.b    #1,d0                     ; was command 255?
            bpl.s     snd5                      ; br if yes--set delay timer
*
            cmpi.b    #$81,d0                   ; was command 128 (before increment)
            bne.s     snd6                      ; br if not
*
*  command 128
*
            move.b    (a0)+,(auxd).w            ; 128--set aux data from next byte in stream
            bra.s     snd3                      ; go for next command
*
*  command > 128
*
snd6:       cmpi.b    #$82,d0                   ; command greater than 129
            bne.s     snd5                      ; br if yes--must be set timer
*
*  command 129
*
            move.b    (a0)+,(psgsel).w          ; 129--select register
            move.b    (a0)+,d0                  ; get increment step (signed)
            add.b     d0,(auxd).w               ; add to aux data
            move.b    (a0)+,d0                  ; terminating value
            move.b    (auxd).w,(psgwr).w        ; load reg from data in auxd
            cmp.b     (auxd).w,d0               ; reached end of cycle?
            beq.s     snd4                      ; br if so
*
*  still within loop, reset sound pointer to iterate for next irq
*
            subq.w    #4,a0                     ; back up sound ptr to repeat this command
            bra.s     snd4                      ; update ptr & exit
*
*  set delay timer
*
snd5:       move.b    (a0)+,(timer).w           ; set delay timer from next byter in stream
            bne.s     snd4                      ; if non-zero real delay here
            movea.w   #0,a0                     ; else, sound terminator--set ptr to null

snd4:       move.l    a0,(cursnd).w             ; update sound ptr
snd1:       movem.l   (sp)+,d0-d1/a0            ; pop stack & exit
            rts


*
* sound data...
*
*
* format:
*
*      sound data usually is found in byte pairs, the first of which is the command
*      and the second is the argument.  However, some commands take on more than
*      1 argument
*
*      cmd     function        argument(s)
*      00      load reg0       data0
*      01      load reg1       data0
*      02      load reg2       data0
*      03      load reg3       data0
*      04      load reg4       data0
*      05      load reg5       data0
*      06      load reg6       data0
*      07      load reg7       data0   note: b7 & b6 forced set for all data to r...
*      08      load reg8       data0
*      09      load reg9       data0
*      0A      load reg10      data0
*      0B      load reg11      data0
*      0C      load reg12      data0
*      0D      load reg13      data0
*
*
*      80      init temp w/    data0
*
*      81      loop defined
*              by 3 args       data0 as register to load using temp
*                              data1 as increment/decrement (signed) of temp
*                              data2 as loop terminator value of temp
*
*      82-FF   set delay
*              timer           data0 is # of counts till next update
*                                      note: if data0 = 0, sound is terminated
*
*
*
*
*********************************************************
* VT52 emulator calls for the bell sound
*********************************************************
ringbel:    btst      #2,(conterm).w            ; console bell enabled?
            beq.s     rgbel                     ; (no sound)
            movea.l   (bell_hook).l,a0
            jsr       (a0)                      ; go through the bell vector
            rts


keybellsnd: move.l    #bellsnd,(cursnd).w       ; sound data for console bell
            move.b    #0,(timer).w              ; enable sound timer
rgbel:      rts


*+
* flopini - initialize floppies
* Passed (on the stack):
*        $c(sp) devno
*        $8(sp) ->DSB
*        $4(sp) ->buffer (unused)
*        $0(sp) return address
*
* Returns:       EQ if initialization succeeded (drive attached).
*                NE if initialization failed (no drive attached).
*-
_flopini:   clr.l     (frbufcookie).l
            lea       (dsb0).l,a1               ; get ptr to correct DSB
            tst.w     $c(sp)
            beq.s     fi_1
            lea       (dsb1).l,a1
fi_1:       move.w    (seekrate).l,6(a1)        ; setup default seek rate
            move.w    #3,4(a1)                  ; default: HD mode
            moveq     #-1,d0                    ; (default error)
            clr.w     2(a1)                     ; fake clean drive
            clr.w     (frbufscnt).l
IF !ROM_TOS306
            move.w    #$ff00,2(a1)              ; default = recal drive (it's dirty)
ENDIF
IF ROM_STBOOK
            bsr.s     checkfdc
            beq.s     fi_ndrv
ENDIF
            bsr       floplock                  ; setup parameters
            bsr       select                    ; select drive an side
IF ROM_TOS306
            move.w    #$ff00,2(a1)              ; default = recal drive (it's dirty)
ENDIF
            bsr       restore                   ; attempt restore
            beq.s     fi_ok                     ; (quick exit if that won)
            moveq     #10,d7                    ; attempt seek to track 10
            bsr       hseek1                    ; (hard seek to 'd7')
            bne.s     fi_nok                    ; (failed: drive unusable)
            bsr       restore                   ; attempt restore after seek
fi_ok:      beq       flopok                    ; return OK (on win)
fi_nok:     bra       flopfail                  ; return failure
IF ROM_STBOOK
fi_ndrv:    move.w    #$0202,(diskmode).w       ; both drives are "unsure"
            moveq     #0,d0
            tst.w     (_nflops).w               ; any floppy drives installed?
            beq.s     fi_ret                    ; (no, return NO_ERROR)
            moveq     #-15,d0                   ; return "unknown device"
fi_ret:     rts

drvnrdy:    moveq     #-2,d0                    ; return DRIVE_NOT_READY
            rts
ENDIF

IF ROM_TOS306
            move.l    a0,(busexception).w
            movea.l   a1,sp
            movem.l   (sp)+,d7/a6
drvnrdy:    moveq     #-15,d0
            rts
ENDIF

*+
* checkfdc - check if the FDC track/sector register are available
* Returns:      EQ, if the FDC is not available
*               NE, if the FDC is available
*-
IF ROM_STBOOK
checkfdc:   addq.w    #1,(flock).w              ; lock floppies (allow 'recursion')
            movem.l   d0-d7/a6,-(sp)
            lea       (fifo).w,a6
            move.w    #$82,(a6)                 ; setup 1770 track register
            moveq     #0,d7
            bsr       wdiskctl
            move.w    #$84,(a6)                 ; load 1770 sector register
            moveq     #-1,d7
            bsr       wdiskctl
            move.w    #$82,(a6)                 ; read 1770 track register
            bsr       rdiskctl
            move.b    d0,d1                     ; not equal to 0?
            bne.s     checkfdc2
            move.w    #$84,(a6)                 ; read 1770 sector register
            bsr       rdiskctl
            subq.w    #1,(flock).w              ; unlock floppies
            tst.b     d1                        ; track register wrong?
            bne.s     checkfdc2
            cmp.b     #$ff,d0                   ; sector register wrong?
checkfdc2:  movem.l   (sp)+,d0-d7/a6
            eori      #4,sr                     ; flip Z bit (return NE if bit is zero)
            rts
ENDIF

*+
* floprd - read sector from floppy
* Passed (on the stack):
*       $14(sp) count
*       $12(sp) sideno
*       $10(sp) trackno
*        $e(sp) sectno
*        $c(sp) devno
*        $8(sp) ->DSB
*        $4(sp) ->buffer
*        $0(sp) return address
*
* Returns       EQ, the read won (on all sectors),
*               NE, the read failed (on some sector).
*-
_floprd:
IF ROM_STBOOK
            bsr.s     checkfdc
            beq.s     drvnrdy
ENDIF
IF ROM_TOS306
            tst.w     (_nflops).w               ; any floppy drives installed?
            beq.s     drvnrdy                   ; (no, return "unknown device")
ENDIF
            bsr       change                    ; test for disk change
            moveq     #-11,d0                   ; set default error#
            bsr       frbcheckrd
            bsr       floplock                  ; lock floppies, setup parameters
frd1:       bsr       select                    ; select drive, setup registers
            bsr       go2track                  ; seek appropriate track
            bcs       flopfail
            bne       frde                      ; retry on seek failure
frd1l:      move.w    #-1,(curr_err).l          ; set general error#
            move.w    #$190,(a6)                ; toggle DMA data direction,
            tst.b     (gpip).w
            tst.b     (gpip).w                  ; delay for 1 microsec
            tst.b     (gpip).w                  ; this amounts to 16 16Mhz clocks
            tst.b     (gpip).w
            move.w    #$90,(a6)                 ; leave hardware in READ state
            tst.b     (gpip).w
            tst.b     (gpip).w                  ; delay for 1 microsec
            tst.b     (gpip).w                  ; this amounts to 16 16Mhz clocks
            tst.b     (gpip).w
            move.w    #1,(diskctl).w            ; set sector count register
            move.w    #$80,(a6)                 ; startup 1770 "read sector" command
            move.w    #$80,d7                   ; (read single)
            bsr       wdiskctl
            move.l    (_hz_200).w,d7
            add.l     #300,d7                   ; set timeout timer

*--- Wait for read completion:
frd2:       btst      #5,(gpip).w               ; 1770 done yet?
            beq.s     frd4                      ; (yes)
            cmp.l     (_hz_200).w,d7            ; timeout reached?
            bhi.s     frd2                      ; (punt on timeout)

*---- check status after read
            move.w    #-2,(curr_err).w          ; set "timeout" error
            bsr       reset1770                 ; (clobber 1770)
            bra.s     frde                      ; (go retry)

*--- check status after read:
frd4:       move.w    #$90,(a6)                 ; examine DMA status register
            move.w    (a6),d0
            btst      #0,d0                     ; bit zero inidcates DMA ERROR
            beq.s     frde                      ; (when its zero -- retry)

            move.w    #$80,(a6)                 ; exeamine 1770 status register
            bsr       rdiskctl
            and.b     #$1c,d0                   ; check for RNF, checksum, lost-data
            bne.s     frd4                      ; (bail on error)
            move.w    #2,(retrycnt).w           ; reset retry count for next sector
            addq.w    #1,(csect).w              ; advance sector #
            addi.l    #$200,(cdma).w            ; advance buffer by 512 bytes
            subq.w    #1,(ccount).w             ; decrement sector count
            beq       flopok                    ; (done)
            bsr       select1
            bra       frd1l

frd4:       bsr.s     err_bits                  ; set error# from 1770 bits
frde:       cmpi.w    #1,(retrycnt).w           ; are we on the "middlemost" retry?
            bne.s     frd5
frde1:      bsr       reseek                    ; yes, home and reseek the head
frd5:       subq.w    #1,(retrycnt).w           ; drop retry count
            bpl       frd1                      ; (continue of any retries left)
            bra       flopfail                  ; fail when we run out of patience

*+
* err_bits - set "curr_err" according to 1770 error status
* Passed:       d0 = 1770 status
*
* Returns:      curr_err, containing current error number
*
* Uses:         d1
*-
err_bits:   moveq     #-13,d1                   ; write protect?
            btst      #6,d0
            bne.s     eb1
            moveq     #-8,d1                    ; record-not-found?
            btst      #4,d0
            bne.s     eb1
            moveq     #-4,d1                    ; CRC error?
            btst      #3,d0
            bne.s     eb1
            move.w    (def_error).w,d1          ; use default error#
eb1:        move.w    d1,(curr_err).w           ; set current error number & return
            rts

*+
* flopwr - write sector to floppy
* Passed (on the stack):
*       $14(sp) count
*       $12(sp) sideno
*       $10(sp) trackno
*        $e(sp) sectno
*        $c(sp) devno
*        $8(sp) ->DSB
*        $4(sp) ->buffer (unused)
*        $0(sp) return address
*
* Returns:      EQ, the write won (on all sectors),
*               NE, the write failed (on some sectors).
*-
_flopwr:
IF ROM_STBOOK
            bsr       checkfdc
            beq       drvnrdy
ENDIF
IF ROM_TOS306
            tst.w     (_nflops).w               ; any floppy drives installed?
            beq       drvnrdy                   ; (no, return "unknown device")
ENDIF
            bsr       change                    ; check for disk swap
            moveq     #-10,d0                   ; set default error number
            bsr       frbcheckwr
            bsr       floplock                  ; lock floppies

*+
* If the boot sector is written to,
* set the media change mode to "unsure".
* (Kludge, kludge, kludge....)
*-
            move.w    (csect).w,d0              ; sector 1
            subq.w    #1,d0
            or.w      (ctrack).w,d0             ; track 0
            or.w      (cside).w,d0              ; side 0
            bne.s     fwr1                      ; if not boot sector, then OK
            moveq     #2,d0                     ; set media change mode to unsure
            bsr       setdmode                  ; (boy, is this /ugly/)

fwr1:       bsr       select                    ; select drive
            bsr       go2track                  ; seek
            bcs       flopfail
            bne       fwre1                     ; (retry on seek failure)
fwr1a:      move.w    #-1,(curr_err).w          ; set general error#
            move.w    #$90,(a6)                 ; toggle DMA chip to clear status
            tst.b     (gpip).w
            tst.b     (gpip).w                  ; delay for 1 microsec
            tst.b     (gpip).w                  ; this amounts to 16 16Mhz clocks
            tst.b     (gpip).w
            move.w    #$190,(a6)                ; leave in WRITE mode
            tst.b     (gpip).w
            tst.b     (gpip).w                  ; delay for 1 microsec
            tst.b     (gpip).w                  ; this amounts to 16 16Mhz clocks
            tst.b     (gpip).w
            move.w    #1,d7                     ; load sector-count register
            bsr       wdiskctl
            move.w    #$180,(a6)                ; load "WRITE SECTOR" command
            move.w    #$a0,d7                   ; into 1770 cmdreg
            bsr       wdiskctl
            move.l    (_hz_200).w,d7
            add.l     #300,d7                   ; d7 = timeout timer

fwr2:       btst      #5,(gpip).w               ; done yet?
            beq.s     fwr4                      ; (yes, check status)
            cmp.l     (_hz_200).w,d7            ; timeout reached?
            bhi.s     fwr2                      ; (still tickin')
            bsr       reset1770                 ; timed out -- reset 1770
            bra.s     fwre                      ; and retry

fwr4:       move.w    #$180,(a6)                ; get 1770 status
            bsr       rdiskctl
            bsr       err_bits                  ; compute 1770 error bits
            btst      #6,d0                     ; if write protected, don't retry
            bne       flopfail                  ; (can't write, so punt)
            and.b     #$5c,d0                   ; check WriteProt+RecNtFnd+CHKSUM+LostD
            bne.s     fwre                      ; retry on error

            move.w    #2,(retrycnt).w           ; reset retry count
            addq.w    #1,(csect).w              ; bump sector number
            addi.l    #$200,(cdma).w            ; add DMA pointer for next sector
            subq.w    #1,(ccount).w             ; if(!--count) return OK;
            beq       flopok
            bsr       select1                   ; setup sector#, DMA pointer
            bra       fwr1a                     ; write next (no seek)

fwre:       cmpi.w    #1,(retrycnt).w           ; re-seek head in "middle" retry
            bne.s     fwr5                      ; (not middle retry)
fwre1:      bsr       reseek                    ; home head and seek
fwr5:       subq.w    #1,(retrycnt).w           ; decrement retry count
            bpl       fwr1                      ; loop if there's still hope
            bra       flopfail                  ; otherwise return error status
*+
* _flopfmt - format a track
* Passed (on the stack):
*       $1a(sp) initial sector data
*       $16(sp) magic number
*       $14(sp) interleave
*       $12(sp) side
*       $10(sp) track
*        $e(sp) spt
*        $c(sp) devno
*        $8(sp) pointer to state block
*        $4(sp) dma address
*        $0(sp) [return]
*
* Returns:      EQ: track successfully written.  Zero.W-terminated list of
*               bad sectors left in buffer (they might /all/ be bad.)
*
*               NE: could not write track (write-protected, drive failure,
*               or something catastrophic happened).
*-
_flopfmt:   cmpi.l    #$87654321,$16(sp)        ; check for magic# on stack
            bne       flopfail                  ; no magic, so we just saved the world
IF ROM_STBOOK
            bsr       checkfdc
            beq       drvnrdy
ENDIF
IF ROM_TOS306
            tst.w     (_nflops).w               ; any floppy drives installed?
            beq       drvnrdy                   ; (no, return "unknown device")
ENDIF
            bsr       change                    ; check for disk flip
            moveq     #-10,d0                   ; set default error number
            bsr       frbcheckfmt
            bsr       floplock
            bsr       select                    ; select drive and side
            move.w    $e(sp),(spt).w            ; save sectors-per-track
            move.w    $14(sp),(interlv).w       ; save interleave factor
            move.w    $1a(sp),(virgin).w        ; save initial sector data
            move.l    8(sp),(fmtstateblk).w

*--- put drive into "changed" mode
            moveq     #2,d0                     ; d0 = "CHANGED"
            bsr       setdmode                  ; set media change mode
            moveq     #3,d0                     ; HD mode
            cmpi.w    #$d,(spt).w               ; 13 sectors or more require HD mode
            bcc.s     flopfmt1
            moveq     #0,d0                     ; SD mode
flopfmt1:
IF ROM_STBOOK
            bsr       hdselect                  ; select SD/HD mode of floppy
ELSE
IF ROM_TOS206
            tst.b     (STEFlag).l               ; no STE hardware available?
            bne.s     flopfmt2                  ; (correct)
ENDIF
            move.w    d0,(fdccs).w
flopfmt2:
ENDIF
            move.w    d0,4(a1)

*--- seek to track (hard seek):
            bsr       hseek                     ; hard seek to 'ctrack'
            bne       flopfail                  ; (return error on seek failure)
            move.w    (ctrack).w,2(a1)          ; record current track#

*--- format track, then verify it:
            move.w    #-1,(curr_err).w          ; vanilla error mode
            bsr.s     fmtrack                   ; format track
            bne       flopfail                  ; (return error on seek failure)
            move.w    (spt).w,(ccount).w        ; set number of sectors to verify
            move.w    #1,(csect).w              ; starting sector# = 1
            bsr       verify1                   ; verify sectors

*--- if there are any bad sectors, return /that/ error...
            movea.l   (cdma).w,a2               ; a2 -> bad sector list
            tst.w     (a2)                      ; any bad sectors?
            beq       flopok                    ; no -- return OK
            move.w    #-16,(curr_err).w         ; set error number
            bra       flopfail                  ; return error

*+
* fmtrack - format a track
* Passed:       variables setup by _flopfmt
* Returns:      NE on failure, EQ on success
* Uses:         almost everything
* Called-by:    _flopfmt
*
*-
fmtrack:    move.w    #-10,(def_error).w        ; set default error number
            movea.l   (cdma).w,a2               ; a2 -> prototyping area
            movea.l   (fmtstateblk).w,a3
            moveq     #120-1,d1                 ; 120 x $4e (track leadin)
            cmpi.w    #$d,(spt).w
            bcc.s     fmtrack1
            moveq     #60-1,d1                  ; 60 x $4e (track leadin)
fmtrack1:   moveq     #$4e,d0
            bsr       wmult

            clr.w     d3                        ; interleave index = table start
            tst.w     (interlv).w               ; interleave < 0
            bmi       fmtrack2                  ; use custom interleave table ->
            moveq     #1,d3                     ; first sector = 1

*---- address mark
ot3:        move.w    d3,d4                     ; d4 = starting sector (this pass)
ot1:        moveq     #12-1,d1                  ; 12 x $00
            clr.b     d0
            bsr       wmult
            moveq     #3-1,d1                   ; 3 x $f5
            moveq     #$f5,d0
            bsr       wmult
            move.b    #$fe,(a2)+                ; $fe -- address mark intro
            move.b    (ctrack+1).w,(a2)+        ; track#
            move.b    (cside+1).w,(a2)+         ; side#
            move.b    d4,(a2)+                  ; sector#
            move.b    #2,(a2)+                  ; sector size (512)
            move.b    #$f7,(a2)+                ; write checksum

*--- gap between AM and data:
            moveq     #22-1,d1                  ; 22 x $4e
            moveq     #$4e,d0
            bsr       wmult
            moveq     #12-1,d1                  ; 12 x $00
            clr.b     d0
            bsr       wmult
            moveq     #3-1,d1                   ; 3 x $f5
            moveq     #$f5,d0
            bsr       wmult

*--- data block:
            move.b    #$fb,(a2)+                ; $fb -- data intro
            move.w    #256-1,d1                 ; 256 x virgin.W (initial sector data)
ot2:        move.b    (virgin).w,(a2)+          ; copy high byte
            move.b    (virgin+1).w,(a2)+        ; copy low byte
            dbra      d1,ot2                    ; fill 512 bytes
            move.b    #$f7,(a2)+                ; $f7 -- write checksum
            moveq     #40-1,d1                  ; 40 x $4e
            moveq     #$4e,d0
            bsr       wmult

            tst.w     (interlv).w               ; interleave < 0
            bmi       fmtrack2                  ; use custom interleave table ->
            add.w     (interlv).w,d4            ; bump sector#
            cmp.w     (spt).w,d4                ; if(d4 <= spt) then_continue;
            ble.s     ot1                       ; proto more sectors this pass
            addq.w    #1,d3                     ; bump pass start count
            cmp.w     (interlv).w,d3            ; if(d3 <= interlv) then_continue;
            ble.s     ot3

*--- end-of-track
ot5:        move.w    #2800,d1                  ; 2801 x $4e -- end of track trailer
            cmpi.w    #$d,(spt).w
            bcc.s     ot4
            move.w    #1400,d1                  ; 1401 x $4e -- end of track trailer
ot4:        moveq     #$4e,d0
            bsr       wmult

*--- setup to write the track:
            move.b    (cdma+3).w,(dmalow).w     ; load dma pointer
            move.b    (cdma+2).w,(dmamid).w
            move.b    (cdma+1).w,(dmahigh).w
            move.w    #$90,(a6)                 ; toggle R/W flag and
            tst.b     (gpip).w
            tst.b     (gpip).w                  ; delay for 1 microsec
            tst.b     (gpip).w                  ; this amounts to 16 16Mhz clocks
            tst.b     (gpip).w
            move.w    #$190,(a6)                ; select sector-count register
            tst.b     (gpip).w
            tst.b     (gpip).w                  ; delay for 1 microsec
            tst.b     (gpip).w                  ; this amounts to 16 16Mhz clocks
            tst.b     (gpip).w
            moveq     #$60,d7                   ; (absurd sector count)
            bsr       wdiskctl
            move.w    #$180,(a6)                ; select 1770 cmd register
            move.w    #$f0,d7                   ; write format_track command
            bsr       wdiskctl
            move.l    (_hz_200).w,d7
            add.l     #300,d7                   ; d7 = timeout timer

*--- wait for 1770 complete:
otw1:       btst      #5,(gpip).w               ; is 1770 done?
            beq.s     otw2                      ; (yes)
            cmp.l     (_hz_200).w,d7            ; timeout reached?
            bhi.s     otw1                      ; (still tickin')
            bsr       reset1770                 ; timed out -- reset 1770
oterr:      moveq     #1,d7                     ; return NE (error status)
            rts

fmtrack2:   cmp.w     (spt).w,d3                ; last sector reached?
            beq       ot5                       ; yes -> end of track
            move.w    d3,d6
            add.w     d6,d6
            move.w    (a3,d6.w),d4              ; pick new sector number from the table
            addq.w    #1,d3                     ; increment interleave index
            bra       ot1

*--- see if the write-track won:
otw2:       move.w    #$190,(a6)                ; check DMA status bit
            move.w    (a6),d0
            btst      #0,d0                     ; if its zero, there was a DMA error
            beq.s     oterr                     ; (so return NE)
            move.w    #$180,(a6)                ; get 1770 status
            bsr       rdiskctl
            bsr       err_bits                  ; set 1770 error bits
            and.b     #$44,d0                   ; check for writeProtect & lostData
            rts                                 ; return NE on 1770 error

*----- write 'D1+1' copies of D0.B into A2, A2+1, ...
wmult:      move.b    d0,(a2)+                  ; record byte in proto buffer
            dbra      d1,wmult                  ; (do it again)
            rts


*+
* _flopver - verify sectors on a track
*       $14(sp) count
*       $12(sp) sideno
*       $10(sp) trackno
*        $e(sp) sectno
*        $c(sp) devno
*        $8(sp) ->DSB
*        $4(sp) ->buffer (at least 1K long)
*        $0(sp) return address
*
* Returns:      NULL.W-terminated list of bad sectors in the buffer if D0 == 0,
*               OR some kind of error (D0 < 0).
*
*-
_flopver:
IF ROM_STBOOK
            bsr       checkfdc
            beq       drvnrdy
ENDIF
IF ROM_TOS306
            tst.w     (_nflops).w               ; any floppy drives installed?
            beq       drvnrdy                   ; (no, return "unknown device")
ENDIF
            bsr       change                    ; hack disk change
            moveq     #-11,d0                   ; set default error#
            bsr       frbcheckfmt
            bsr       floplock                  ; lock floppies, setup parameters
            bsr       select                    ; select floppy
            bsr       go2track                  ; go to track
            bne       flopfail                  ; (punt if that fails)
            bsr.s     verify1                   ; verify some sectors
            bra       flopok                    ; return "OK"

*+
* verify1 - verify sectors on a single track
* Passed:       csect = starting sector#
*               ccount = number of sectors to verify
*               cdma -> 1K buffer (at least)
*
* Returns:      NULL.W-terminated list of bad sectors (in the buffer)
*               (buffer+$200..buffer+$3ff used as DMA buffer)
*
* Enviroment:   Head seeked to correct track;
*               Drive and side already selected;
*               Motor should be spinning (go2track and fmttrack do this).
*
* Uses:         Almost everything.
*
* Called-by:    _flopfmt, _flopver
*
*-
verify1:    move.w    #-11,(def_error).w        ; set default error number
            movea.l   (cdma).w,a2               ; a2 -> start of bad sector list
            addi.l    #$200,(cdma).w            ; bump buffer up 512 bytes

*--- setup for (next) sector
tvrlp:      move.w    #2,(retrycnt).w           ; init sector-retry count
            move.w    #$84,(a6)                 ; load 1770 sector register
            move.w    (csect).w,d7              ; with 'csect'
            bsr       wdiskctl

*--- setup for sector read
tvr1:       move.b    (cdma+3).w,(dmalow).w     ; load dma pointer
            move.b    (cdma+2).w,(dmamid).w
            move.b    (cdma+1).w,(dmahigh).w
            move.w    #$190,(a6)                ; toggle R/W (leave in W state)
            tst.b     (gpip).w
            tst.b     (gpip).w                  ; delay for 1 microsec
            tst.b     (gpip).w                  ; this amounts to 16 16Mhz clocks
            tst.b     (gpip).w
            move.w    #$90,(a6)
            tst.b     (gpip).w
            tst.b     (gpip).w                  ; delay for 1 microsec
            tst.b     (gpip).w                  ; this amounts to 16 16Mhz clocks
            tst.b     (gpip).w
            move.w    #1,d7                     ; set DMA sector count to 1
            bsr       wdiskctl
            move.w    #$80,(a6)                 ; load 1770 command register
            move.w    #$80,d7                   ; with ReadSector command
            bsr       wdiskctl
            move.l    (_hz_200).w,d7
            add.l     #300,d7                   ; d7 = timeout timer

*--- wait for command completion
tvr2:       btst      #5,(gpip).w               ; test for 1770 done
            beq.s     tvr4                      ; (yes, it completed)
            cmp.l     (_hz_200).w,d7            ; timeout reached?
            bhi.s     tvr2                      ; (still tickin')
            bsr       reset1770                 ; reset controller and return error
            bra.s     tvre

*--- got "done" interrupt, check DMA status:
tvr4:       move.w    #$90,(a6)                 ; read DMA error status
            move.w    (a6),d0
            btst      #0,d0                     ; if DMA_ERROR is zero, then retry
            beq.s     tvre

*--- check 1770 completion status (see if it's happy):
            move.w    #$80,(a6)                 ; read 1770 status register
            bsr       rdiskctl
            bsr       err_bits                  ; set error# from 1770 register
            and.b     #$1c,d0                   ; check for record-not-found, crc-error,
            bne.s     tvre                      ; and lost data; return on error

*--- read next sector (or return if done)
tvr6:       addq.w    #1,(csect).w              ; bump sector count
            subq.w    #1,(ccount).w             ; while(--count) read_another;
            bne       tvrlp
            subi.l    #$200,(cdma).w            ; readjust DMA pointer
            clr.w     (a2)                      ; terminate bad sector list
            rts                                 ; and return EQ


*--- read failure: retry or record bad sector
tvre:       cmpi.w    #1,(retrycnt).w           ; re-seek head?
            bne.s     tvr5                      ; (no)
            bsr       reseek                    ; yes: back to home and then back
tvr5:       subq.w    #1,(retrycnt).w           ; to the current track...
            bpl       tvr1
            move.w    (csect).w,(a2)+           ; record bad sector
            bra.s     tvr6                      ; do next sector

*+
* _flopvbl - floppy vblank handler
* Deselects floppies after the motor stops
*-
_flopvbl:
IF ROM_TOS306
            tst.w     (_nflops).w               ; any floppy drives installed?
            beq       fvblr                     ; (no, return "unknown device")
ENDIF
            lea       (fifo).w,a6               ; a6 -> fifo
            st        (_motoron).w              ; assume motor is on
            tst.w     (flock).w                 ; floppies locked?
            bne.s     fvblr                     ; (yes, so don't touch them)
IF ROM_STBOOK
            bsr       checkfdc
            beq.s     fvblr
ENDIF

*--- write-protect monitor:
            move.l    (_frclock).w,d0           ; check a drive every 8 jiffies
            move.b    d0,d1                     ; (save jiffy count)
            and.b     #7,d1                     ; time yet?
            bne.s     fvbl1                     ; (no)
            move.w    #$80,(a6)                 ; select 1770 command/status register

*--- select drive, record it's WP status:
            lsr.b     #3,d0                     ; use bit 4 as drive# to check
            and.w     #1,d0
            lea       (wpstatus).w,a0
            adda.w    d0,a0
            cmp.w     (_nflops).w,d0
            bne.s     fvbl2
            clr.w     d0
fvbl2:      addq.b    #1,d0
            lsl.b     #1,d0                     ; (magic shift left)
            eori.b    #7,d0                     ; invert select bits, select side 0
            bsr       setporta                  ; set port A (d2 = old bits)
            bsr       rdiskctl                  ; get 1770 status
            move.w    d0,d1
            btst      #6,d1                     ; test Write-Protect status bit
            sne       (a0)                      ; set WP status to $00 or $FF.
            move.b    d2,d0                     ; restore old drive-select bits
            bsr       setporta

fvbl1:      move.w    (wpstatus).w,d0           ; or _wpstatus into _wplatch
            or.w      d0,(wplatch).w            ; (catch any WP transitions)

*--- floppy deselect test
            tst.w     (deselflg).w              ; floppies already deselected?
            bne.s     fvblr1                    ; (yes, so don't do it again)
            move.l    (_hz_200).w,d0
            cmp.l     (fltimeout).l,d0          ; did we hit our 5s timeout?
            bcc.s     fvbl2

            bsr       rdiskctl                  ; read 1770 status register
            btst      #7,d0                     ; is the motor still on?
            bne.s     fvblr                     ; (yes, so don't deselect)
fvbl2:      move.b    #7,d0                     ; deselect both drives
            bsr       setporta                  ; (set bits 0..3 in portA of PSG)
            move.w    #1,(deselflg).w           ; indicate floppies deselected
fvblr1:     clr.w     (_motoron).w              ; indicate motor is OFF
fvblr:      rts                                 ; back to vbl


*+
* floplock - lock floppies and setup floppy parameters
*
* Passed (on the stack):
*       $18(sp) - count.W (sector count)
*       $16(sp) - side.W (side#)
*       $14(sp) - track.W (track#)
*       $12(sp) - sect.W (sector#)
*       $10(sp) - dev.W (device#)
*        $c(sp) - obsolte.L
*         8(sp) - dma.L (dma pointer)
*         4(sp) - ret1.L (caller's return address)
*         0(sp) - ret.L (floplock's return address)
*
* Passed:       D0.W = default error number
*-
floplock:   movem.l   d3-d7/a3-a6,($168a).l     ; save C registers

            lea       (fifo).w,a6               ; a6 -> fifo
            st        (_motoron).w              ; kludge motor state = ON
            move.w    d0,(def_error).w          ; set default error number
            move.w    d0,(curr_err).w           ; set current error number
            move.w    #1,(flock).w              ; tell vbl not to touch floppies
            move.l    8(sp),(cdma).w            ; cdma -> /even/ DMA address
            move.w    $10(sp),(cdev).w          ; save devide# (0 . 1)
            move.w    $12(sp),(csect).w         ; save sector# (1 . 9, usually)
            move.w    $14(sp),(ctrack).w        ; save track# (0 . 39 .  79   )
            move.w    $16(sp),(cside).w         ; save side# (0 . 1)
            move.w    $18(sp),(ccount).w        ; save sector count (1..spt)
            move.w    #2,(retrycnt).w           ; setup retry count

*--- pick a DSB
            lea       (dsb0).w,a1
            tst.w     (cdev).w
            beq.s     flock2
            lea       (dsb1).w,a1

*--- recalibrate drive (if it needs it)
flock2:     tst.w     2(a1)                     ; if (curtrack < 0) recalibrate()
            bpl.s     flockr

            bsr       select                    ; select drive & side
            clr.w     2(a1)                     ; we're optimistic -- assume winnage
            bsr       restore                   ; attempt restore
            beq.s     flockr                    ; (it won)
            moveq     #10,d7                    ; attempt seek to track 10
            bsr       hseek1
            bne.s     flock1                    ; (failed)
            bsr       restore                   ; attempt restore again
            beq.s     flockr                    ; (it won)
flock1:     move.w    #$ff00,2(a1)              ; complete failure (what can we do?)
flockr:     rts

*+
* flopfail - unlock floppies and return error.
*
*-
flopfail:   moveq     #1,d0                     ; disk change mode = unsure
            bsr       setdmode                  ; set media change mode
            move.w    (curr_err).w,d0           ; get current error number
            ext.l     d0                        ; extent to long
            bra.s     unlok1                    ; clobber floppy lock & return

*+
* flopok - unlock floppies and return success status:
*
*-
flopok:     clr.l     d0                        ; return 0 (success)
unlok1:     move.l    d0,-(sp)                  ; (save return value)
IF ROM_TOS306
            bsr       flushCaches
ENDIF
            move.w    #$86,(a6)                 ; force WP to real-time mode
            move.w    2(a1),d7                  ; dest-track = current track
            bsr       wdiskctl
            move.w    #$10,d6                   ; cmd = seek w/o verify
            bsr       flopcmds                  ; do it

            move.l    (_hz_200).l,d0
            add.l     #5*200,d0                 ; 5s floppy deselect timeout
            move.l    d0,(fltimeout).l

            move.w    (cdev).w,d0               ; set last-access time for 'cdev'
            lsl.w     #2,d0
            lea       (_acctim).w,a0
            move.l    (_frclock).w,(a0,d0.w)
            cmpi.w    #1,(_nflops).w            ; if (nflops == 1) set other time, too
            bne.s     unlok2
            move.l    (_frclock).w,4(a0)        ; set last-access time for floppy 1

unlok2:     move.l    (sp)+,d0                  ; restore return value
            movem.l   ($168a).w,d3-d7/a3-a6     ; restore C registers
            clr.w     (flock).l                 ; unlock floppies
            bsr       frbbackcopy               ; if necessary copy FRB buffer back
            rts


*+
* hseek  - seek to 'ctrack' without verify
* hseek1 - seek to 'd7' without verify
* hseek2 - seek to 'd7' without verify, keep current error number
*
* Returns:      NE on seek failure ("cannot happen"?)
*               EQ if seek wins
*
* Uses:         d7, d6, ...
* Jumps-to:     flopcmds
* Called-by:    _flopfmt, _flopinit
*
*-
hseek:      move.w    (ctrack).l,d7             ; dest track = 'ctrack'
hseek1:     move.w    #-6,(curr_err).l          ; possible error = "seek error"
            move.w    #$86,(a6)                 ; write destination track# to data reg
            bsr       wdiskctl
            move.w    #$10,d6                   ; execute "seek" command
            bra       flopcmds                  ; (without verify...)

*+
* reseek - home head, then reseek track
* Returns:      EQ/NE on success/failure
* Falls-into:   go2track
*
*-
reseek:     move.w    #-6,(curr_err).l          ; set "seek error"
IF ROM_STBOOK
            bsr.s     restore                   ; restore head
            bne.s     go2trr                    ; (punt if home fails)
ELSE
            bsr       restore                   ; restore head
            bne       go2trr                    ; (punt if home fails)
ENDIF

            clr.w     2(a1)                     ; current track = 0
            move.w    #$82,(a6)                 ; set "current track" reg on 1770
            clr.w     d7
            bsr       wdiskctl

            move.w    #$86,(a6)                 ; seek out to track five
            move.w    #5,d7
            bsr       wdiskctl                  ; dest track = 5
            move.w    #$10,d6
            bsr.s     flopcmds                  ; seek
            bne.s     go2trr                    ; return error on seek failure
            move.w    #5,2(a1)                  ; set current track#

*+
* go2track - seek proper track
* Passed:       Current floppy parameters (ctrack, et al.)
* Returns:      EQ/NE on success/failure
* Calls:        flopcmds
*-
go2track:   move.w    #1,(fseekrtr).l           ; set seek retry count
go2track1:  move.w    #-6,(curr_err).l          ; set "seek error"
            move.w    #$86,(a6)                 ; set destination track# in
            move.w    (ctrack).w,d7             ; 1770's data register
            bsr       wdiskctl                  ; (write track#)
            moveq     #$14,d6                   ; execute 1770 "seek_with_verify"
            bsr.s     flopcmds                  ; (include seek-rate bits)
            bcs.s     go2trr
            bne.s     go2track2                 ; return error on seek failure
            and.b     #$18,d7                   ; check for RNF, CRC_error, lost_data
            beq.s     go2track3                 ; (exit on no error)
go2track2:  move.w    4(a1),d0
            and.w     #3,d0
            eori.w    #3,d0                     ; toggle between SD/HD mode for a second seek try
IF ROM_STBOOK
            move.w    d0,4(a1)
            bsr       hdselect                  ; select SD/HD mode of floppy
ELIF ROM_TOS206
            move.w    d0,4(a1)
            tst.b     (STEFlag).l               ; no STE hardware available?
            bne.s     go2track2b                ; (correct)
            move.w    d0,(fdccs).w
go2track2b:
ELIF ROM_TOS306
            move.w    d0,(fdccs).w
            move.w    d0,4(a1)
ENDIF
            subq.w    #1,(fseekrtr).l           ; --seek retry count
            bne.s     go2trr                    ; (out of retries -- punt)
            bsr.s     restore                   ; move head to track 00 to reset it
            bra.s     go2track1                 ; try again
go2track3:  move.w    (ctrack).w,2(a1)          ; update current track number
            clr.w     d7                        ; set EQ
go2trr:     rts


*+
* restore - home head
* Passed:       nothing
* Returns:      EQ/NE on success/failure
* Falls-into:   flopcmds
*-
restore:    clr.w     d6                        ; $00 = 1770 "restore" command
            bsr.s     flopcmds                  ; do restore
            bne.s     res_r                     ; punt on timeout
            btst      #2,d7                     ; test TRK00 bit
            eori      #4,ccr                    ; flip Z bit (return NE if bit is zero)
            bne.s     res_r                     ; (punt if didn't win)
            clr.w     2(a1)                     ; set current track#
res_r:      rts


*--- seek rate conversion table for HD mode
* HD is using twice the clock speed (16MHz instead of 8MHz) for the FDC,
* so the FDC has to seek "slower"
dseekrt:    DC.B      $01,$01,$00,$00           ; 6ms and 12ms -> 12ms,  2ms and 3ms -> 6ms


*+
* flopcmds - floppy command (on-in seek speed bits from database)
* Passed:       d6.w = 1770 command
* Sets-up:      seek bits (bits 0 and 1) in d6.w
* Falls-into:   flopcmds
* Returns:      EQ/NE on success/failure
*-
flopcmds:   move.w    6(a1),d0                  ; get floppy's seek rate bits
            and.w     #3,d0                     ; OR into command
            tst.w     4(a1)                     ; SD mode?
            beq.s     flopcmds1                 ; (yes)
            lea       (dseekrt).l,a0            ; use a HD-compatible seek rate table
            move.b    (a0,d0.w),d0
flopcmds1:  or.b      d0,d6
            or.b      (a1),d6                   ; (a1) == 8 for HD, with sets the verify bit in the command
IF ROM_STBOOK
            move.w    4(a1),d0                  ; get SD/HD mode
            bsr.s     hdselect                  ; select SD/HD mode of floppy
ELSE
IF ROM_TOS206
            tst.b     (STEFlag).l               ; no STE hardware available?
            bne.s     flopcmds2                 ; (correct)
ENDIF
            move.w    4(a1),(fdccs).w
flopcmds2:
ENDIF

*+
* flopcmd - execute 1770 command (with timeout)
* Passed:       d6.w = 1770 command
*
* Returns:      EQ/NE on success/failure
*               d7 = 1770 status bits
*
*-
flopcmd:    move.l    (_hz_200).w,d7            ; setup timeout (assume short)
            add.l     #300,d7
            move.w    #$80,(a6)                 ; select 1770 command register
            bsr       rdiskctl                  ; read it to clobber READY status
            btst      #7,d0                     ; is motor on?
            bne.s     flopcm                    ; (yes, keep short timeout)
            move.l    (_hz_200).w,d7            ; extra timeout for motor startup
            add.l     #600,d7
flopcm:     bsr       wdiskct6                  ; write command (in d6)
flopc1:     cmp.l     (_hz_200).w,d7            ; timeout?
            bcs.s     flopcto                   ; (yes, reset and return failure)
            btst      #5,(gpip).w               ; 1770 completion?
            bne.s     flopc1                    ; (not yet, so wait some more)
            bsr       rdiskct7                  ; return EQ + 1770 status in d7
            clr.w     d6
            rts

flopcto:    bsr.s     reset1770                 ; bash controller
            moveq     #0,d6
            subq.w    #1,d6                     ; and return NE
            rts


*+
* hdselect - select HD mode for 1.4MB floppies and SD for 720kd floppies
* Passed:          d0.w SD/HD mode
*-
IF ROM_STBOOK
hdselect:   tst.w     d0                        ; if != 0 then select HD
            bne.s     hdselect2
            clr.b     d0                        ; SD mode
            bra.s     hdselect3
hdselect2:  move.b    #$80,d0                   ; HD mode is bit 7 in PORT A on the sound chip
hdselect3:  move      sr,d2                     ; save status register
            move.b    #14,(psgsel).w            ; select port on GI chip
            move.b    (psgsel).w,d1             ; get current bits
            bclr      #7,d1                     ; clear HD bit
            or.b      d0,d1                     ; or-in our new bit
            move.b    #14,(psgsel).w            ; select port on GI chip
            move.b    d1,(psgwr).w              ; and write 'em back out there
            move      d2,sr                     ; restore status register
            rts
ENDIF

*+
* reset1770 - reset disk controller after a catastrophe
* Passed:       nothing
* Returns:      nothing
* Uses:         d7
*-
reset1770:  move.w    #$80,(a6)                 ; execute 1770 "reset" command
            move.w    #$d0,d7
            bsr       wdiskctl
IF ROM_TOS306
            move.w    d0,-(sp)
            move.w    #$114,d0
            bsr       mfpdelay
            move.w    (sp)+,d0
ELSE
            move.w    #2,d0
r1770a:     move.b    (tcdr).w,d1               ; wait for 1770 to stop convulsing
r1770b:     cmp.b     (tcdr).w,d1
            beq.s     r1770b
            dbra      d0,r1770a
ENDIF
            bsr.s     rdiskct7                  ; return 1770 status in d7
            rts


*+
* select - setup drive select, 1770 and DMA registers
* Passed:       cside, cdev
* Returns:      appropriate drive and side select
*-
select:     clr.w     (deselflg).w              ; floppies NOT deselected
            move.w    (cdev).w,d0               ; get device number
            addq.b    #1,d0                     ; add and shift to get select bits
            lsl.b     #1,d0                     ; into bits 1 and 2
            or.w      (cside).w,d0              ; or-in side number (bit 0)
            eori.b    #7,d0                     ; negate bits for funky hardware select
            and.b     #7,d0                     ; strip anything else out there
            bsr.s     setporta                  ; do drive select

            move.w    #$82,(a6)                 ; setup 1770 track register
            move.w    2(a1),d7                  ; for current track number
            bsr.s     wdiskctl

select1:    move.w    #$84,(a6)                 ; setup requested sector_number from
            move.w    (csect).w,d7              ; caller's parameters
            bsr.s     wdiskctl
            move.b    (cdma+3).w,(dmalow).w     ; setup DMA chip's DMA pointer
            move.b    (cdma+2).w,(dmamid).w
            move.b    (cdma+1).w,(dmahigh).w
            rts


*+
* setporta - set floppy select bits in PORT A on the sound chip
* Passed:       d0.b (low three bits)
* Returns:      d1 = value written to port A
*               d2 = old value read from port A
* Uses:         d1
*-
setporta:   move      sr,-(sp)                  ; save our IPL
            ori       #$700,sr                  ; start critical section
            move.b    #14,(psgsel).w            ; select port on GI chip
            move.b    (psgsel).w,d1             ; get current bits
            move.b    d1,d2                     ; save old bits for caller
            and.b     #$f8,d1                   ; strip low three bits there
            or.b      d0,d1                     ; or-in our new bits
            move.b    d1,(psgwr).w              ; and write 'em back out there
            move      (sp)+,sr                  ; restore IPL to terminate CS, return
            rts


*+
* Primitives to read/write 1770 controller chip (DISKCTL register).
*
* The 1770 can't keep up with full-tilt CPU access, so
* we have to surround read and writes with delay loops.
* This is not really as slow as it sounds.
*
*-
wdiskct6:   bsr.s     rwdelay                   ; delay
            move.w    d6,(diskctl).w            ; write d6 to diskctl
            rts

wdiskctl:   bsr.s     rwdelay                   ; delay
            move.w    d7,(diskctl).w            ; write d7 to diskctl
            rts

rdiskct7:   bsr.s     rwdelay                   ; delay
            move.w    (diskctl).w,d7            ; read diskctl into d7
            rts

rdiskctl:   bsr.s     rwdelay                   ; delay
            move.w    (diskctl).w,d0            ; read diskctl into d0
            rts


rwdelay:
IF ROM_TOS306
            move.w    d0,-(sp)
            move.w    #$119,d0
            bsr       mfpdelay
            move.w    (sp)+,d0
ELSE
            movem.l   d0-d1,-(sp)               ; save registers
            move.w    #2,d0
rwdly1:     move.b    (tcdr).w,d1               ; busy-loop: give 1770 time to settle
rwdly2:     cmp.b     (tcdr).w,d1
            beq.s     rwdly2
            dbra      d0,rwdly1
            movem.l   (sp)+,d0-d1               ; restore registers
ENDIF
            rts


*+
* change - check to see if the "right" floppy bas been inserted
* On the stack:
*       $10(sp) - dev.W (device#)
*        $c(sp) - dsb.L (pointer to Device State Block)
*         8(sp) - dma.L (dma pointer)
*         4(sp) - ret1.L (caller's return address)
*         0(sp) - ret.L (change's return address)
*
* Returns:      both media "might have changed" condition
*
* Uses:         C registers
*
*-
change:     cmpi.w    #1,(_nflops).l            ; if there are zero or two floppies
            bne.s     ch_r                      ; then do nothing (return OK)
            move.w    $10(sp),d0                ; if cdev == _curflop
            cmp.w     (_curflop).l,d0           ; (...current disk == current drive?)
            beq.s     ch_ok1                    ; then return OK (but use drive #0)

*--- ask the user to stick in the other floppy (via critical error handler)
push disk# we want inserted:move.w d0,-(sp)
            move.w    #-17,-(sp)                ; push "INSERT_A_DISK" error number
            bsr       crit_err                  ; use critical error handler and
            addq.w    #4,sp                     ; hope somebody handles it
            move.w    #$ffff,(wplatch).l        ; set "might have changed" on both drvs
            lea       (_acctim).l,a0
            clr.l     (a0)+
            clr.l     (a0)
            move.w    $10(sp),(_curflop).l      ; set current disk#
ch_ok1:     clr.w     $10(sp)                   ; use drive 0
ch_r:       rts

*+
* setdmode - set drive-change mode
* Passed:         d0.b = mode to put current drive in (0, 1, 2)
* Uses:           a0
*
*-
setdmode:   lea       (diskmode).w,a0           ; a0 -> disk mode table
            move.b    d0,-(sp)                  ; (save mode)
            move.w    (cdev).w,d0               ; d0.w = drive# (index into table)
            cmpi.w    #1,(_nflops).l
            bne.s     setdmode2
            move.w    (_curflop).l,d0
setdmode2:  move.b    (sp)+,(a0,d0.w)           ; set drive's mode
            rts


*+
* floprate - sets the seek rate of the specified floppy drive
* On the stack:
*         6(sp) - rate.w (0: 6ms, 1: 12ms, 2:2ms, 3:3ms)
*         4(sp) - dev.W (device#)
*         0(sp) - return address
*
* Returns:      prior seek rate for the specified drive
*
*-

*--- pick a DSB
floprate:   lea       (dsb0).l,a1
            tst.w     4(sp)
            beq.s     floprate2
            lea       (dsb1).l,a1
floprate2:  move.w    6(a1),d0                  ; current rate
            move.w    6(sp),d1                  ; new seek rate
            cmp.w     #-1,d1                    ; new seek rate == -1
            beq.s     floprateret               ; (just return the current rate)
            cmp.w     #-2,d1                    ; new seek rate == -2
            beq.s     flopratehd                ; (select HD floppy density)
            cmp.w     #-3,d1                    ; new seek rate == -3
            beq.s     flopratesd                ; (select SD floppy density)
            cmp.w     #-4,d1                    ; new seek rate == -4
            beq.s     floprateretdens           ; (return current density)
            move.w    d1,6(a1)                  ; set new seek rate in dsb
floprateret:ext.l     d0
            rts

flopratehd: move.b    #8,(a1)                   ; set density to HD
            moveq     #0,d0                     ; return no error
            rts

flopratesd: clr.b     (a1)                      ; set density to SD
            moveq     #0,d0                     ; return no error
            rts

floprateretdens:tst.b (a1)                      ; return current density setting
            sne       d0                        ; -1:HD, 0:SD
            ext.w     d0
            ext.l     d0
            rts


*+
* Fast RAM doesn't support DMA transfers, we use a temporary buffer
* for loads/stores from and into Fast RAM. The buffer is in _FRB cookie.
*-
frbbackcopy:move.w    (frbufscnt).l,d1          ; sectors to copy back into memory?
            beq.s     frbcheckrts               ; (no, punt)
IF ROM_TOS306
            move.l    d0,-(sp)
            bsr       flushCaches
            move.l    (sp)+,d0
ENDIF
            clr.w     (frbufscnt).l             ; reset sector flag
            movea.l   (frbufaddress).l,a0       ; requested buffer address in memory
            movea.l   (frbufcookie).l,a1        ; used DMA buffer address
frbcopya1_a0:asl.w    #5,d1
            subq.w    #1,d1
frbcopyl:   move.l    (a1)+,(a0)+
            move.l    (a1)+,(a0)+
            move.l    (a1)+,(a0)+
            move.l    (a1)+,(a0)+
            dbra      d1,frbcopyl
frbcheckrts:rts

frbcheckrd:
IF ROM_TOS306
            cmpi.l    #$a00000,8(sp)            ; beyond the 10MB limit?
ELSE
            cmpi.l    #$400000,8(sp)            ; beyond the 4MB limit?
ENDIF
            bcs.s     frbcheckrts               ; (no, punt)
            bsr.s     findFRBc
            move.l    8(sp),(frbufaddress).l    ; save buffer address
            move.l    (frbufcookie).l,8(sp)     ; inject FRB buffer into the parameter
            move.w    $18(sp),(frbufscnt).l     ; save number of sectors
            rts

frbcheckwr:
IF ROM_TOS306
            cmpi.l    #$a00000,8(sp)            ; beyond the 10MB limit?
ELSE
            cmpi.l    #$400000,8(sp)            ; beyond the 4MB limit?
ENDIF
            bcs.s     frbcheckrts               ; (no, punt)
            bsr.s     findFRBc
            movea.l   8(sp),a1                  ; get the source buffer address
            movea.l   (frbufcookie).l,a0
            move.l    a0,8(sp)                  ; inject FRB buffer into the parameter
            move.w    $18(sp),d1                ; number of sectors
            bra.s     frbcopya1_a0              ; copy D1 sectors from A1 to A0

frbcheckfmt:
IF ROM_TOS306
            cmpi.l    #$a00000,8(sp)            ; beyond the 10MB limit?
ELSE
            cmpi.l    #$400000,8(sp)            ; beyond the 4MB limit?
ENDIF
            bcs.s     frbcheckrts               ; (no, punt)
            bsr.s     findFRBc
            move.l    8(sp),(frbufaddress).l    ; save buffer address
            move.l    (frbufcookie).l,8(sp)     ; inject FRB buffer into the parameter
            move.w    #1,(frbufscnt).l          ; 1 sector
            rts

* get FRB buffer pointer from the cookies
findFRBc:   tst.l     (frbufcookie).l           ; variable already set?
            bne.s     findFRBcrts               ; (yes, return)
            movea.l   (_p_cookies).w,a0
            cmpa.w    #0,a0                     ; no cookie jar?
            beq.s     findFRBcx
findFRBcl:  tst.l     (a0)                      ; end of the cookie jar?
            beq.s     findFRBcx
            cmpi.l    #'_FRB',(a0)+
            beq.s     findFRBcf
            addq.l    #4,a0
            bra.s     findFRBcl
findFRBcf:  move.l    (a0)+,(frbufcookie).l
findFRBcrts:rts

findFRBcx:  addq.l    #8,sp                     ; go 2 return levels up
            moveq     #-12,d0                   ; return -12 if no _FRB cookie
            rts