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

Unit Calculator in Objective C

An piece of code I wrote in 2006, which allows you to do mathematical calculations with units from Objective-C. I’ve updated the code to Objective-C with properties and generics. It is not depended on macOS and should work fine on iOS as well.

By default the result will be in SI compatible units, but all units and prefixes (like kg or ms) can be freely defined in a configuration file units.dat. You can also force a specific unit and add variables to the term. It is also trivial to add new functions, even functions which accept a list of values.

A few examples:

((123.45e-1 + sqrt(+4) -4-.69/2)*2^1/5)^2 => 16
 avg(1,2,3,4) => 2.5
 (9*m^2)^0.5 => 3 m
 60 m + 2 mm => 60.002 m
 sqrt(9 m^2) => 3 m
 20 lb requestedUnit: kg => 9.07185 kg
 60 km/h + 12 m/s requestedUnit: km/h => 103.2 km/h
 1 Torr*760 requestedUnit: atm => 1 atm
 68 F requestedUnit: C => 20 C
 0 C + 0 C requestedUnit: C => 273.15 C --- internally temperatures are converted to Kelvin

The project is an Xcode project with a simple NSLog in the app delegate for testing. It also has a bunch of unit tests to make sure the math works out in all cases.

The code can be found on GitHub.

Label Placement

Implementation of a little algorithm to draw labels/rectangles on a background while trying to avoid overlapping. This is useful for maps, drawing labels on graphs, etc. The sources are – as always – available on GitHub.

In the screenshots you can see the planned area in light grey, while the repositioned ones are in black. The size of the rectangles is arbitrary.

The implementation is specifically simple and not optimized for performance at all. The goal is to be able to understand it.

Raspbian Jessie with a Pi-Raq LCD

The Pi-Raq came with an old version of Raspbian. An upgrade to e.g. Jessie will render the display non-functioning. The manufacturer sadly never got back to me, so I fixed it myself.

Download the archive with all needed files from GitHub.

There are two main things to do:

  1. Provide a custom dt-blob.bin for the pin configuration. This has changed with later version of Raspbian and is the reason why the old file no longer works.
  2. Update a stock config.txt to switch to the LCD.

How to modify the dt-blob.dts

There is very few documenation for this, which is expected, considering that hooking up a custom LCD to a Raspberry is not a very common thing to do.

I used the stock dt-blob.dts as a new base. First I stripped it down, because the Pi-Raq ships with a “Pi 2 Model B rev 1.1” only, which means all other pin configurations are not used anyway and I can’t test it. If you try to use it with a different Raspberry Pi (which seems possible), you have to change the pins_2b2 to match your Raspberry Pi. Use the stock version as an example and transfer the changes over.

By looking at the “Pi-RAQ Hardware Schematic.pdf” you can find out all custom pin mappings:

  • Pin 0 => PCLK-Out (pixel clock)
  • Pin 1 => DE-Out (display enable)
  • Pin 4–8 => B3-B7-Out (5 bits for blue)
  • Pin 9–14 => G2-G7-Out (6 bits for blue, our eyes are more sensitive to green)
  • Pin 15–19 => R3-R7-Out (5 bits for blue)
  • Pin 20–26 are for the jog shuttle controls. They don’t need a custom config, because they are already covered by the pin@default case.
    • Pin 20 = Right button
    • Pin 21 = Left button
    • Pin 22 = Rot1
    • Pin 23 = Center button
    • Pin 24 = Rot2
    • Pin 25 = Down button
    • Pin 26 = Left button
  • Pin 27 is the output for the backlight of the LCD. It has to be an output and should have a startup_state of “active”, otherwise the display stays off after boot. It can still be turned off at any time via a script.

Normal buttons are detected on a falling edge with 100ms debounce. Rotation is doing an edge-detection (both, 10ms debounce). If Rot1 changes and the Rot1 and Rot2 bits are the same, the wheel was rotated left. If both bits have a different value, it was rotated right.

Everything beyond pin 28 is not modified.

How to modify the config.txt

The config.txt has to be modified to switch from HDMI to the LCD. Besides basic changes, like turning overscan off (the LCD doesn’t have a overscan area) and set the framebuffer size to 1024×100 pixel, it is also necessary to enable dpi support (via enable_dpi_lcd), switch the default over and reconfigure the timing and output format for the dpi. Check the comments for a bit more details. The timings and the output format are specific to the LCD panel, which is connected.

Installation Step-by-Step

  1. Install Raspbian on an SD Card
  2. Use ApplePi-Baker to install Raspbian Jessie Lite (the normal version is not needed for this tiny display)
  3. Boot Raspberry with this card. You need to have a HDMI display connected and a USB keyboard
  4. sudo raspi-config => fix the keyboard layout, set a password, changed the hostname, reboot
  5. Remove the card and copy the content of this folder into /home/pi
  6. Put the card back into the Raspberry and boot the Pi-Raq again.
  7. It is typically faster to SSH into the Pi-Raq to configure it, but not necessary.
  8. Now finish the installation:

sudo nano /boot/config.txt

Uncomment/Modify the following lines, which should already be in the config.txt

disable_overscan=1

framebuffer_width=1024
framebuffer_height=100

and add this to the bottom of the file:

#----------------------------------------------------------------------------------------------------
#Generated on Thu Feb 19 13:20:39 2015 by Segler-HP
#config file for None
#Output Format -> DPI_OUTPUT_FORMAT_16BIT_565_CFG1
#RGB Order -> DPI_RGB_ORDER_RGB
#Output Enable Mode ->DPI_OUTPUT_ENABLE_MODE_DATA_VALID
#Invert Pixel Clock ->RGB Data changes on falling edge and is stable at rising edge
#Hsync Disable ->False
#Vsync Disable ->False
#Output Enable ->False
#Hsync Polarity ->Inverted
#Vsync Polarity ->Inverted
#Output Enable Polarity ->default for HDMI mode
#Hsync Phase ->DPI_PHASE_POSEDGE
#Vsync Phase ->DPI_PHASE_NEGEDGE
#Output Enable Phase ->DPI_PHASE_NEGEDGE
#----------------------------------------------------------------------------------------------------
hdmi_timings=1024 0 50 100 50  100 0 2 10 2 0 0 0  60 0 25000000 7
enable_dpi_lcd=1
display_default_lcd=1
dpi_output_format=4194306 #6488594
dpi_group=2
dpi_mode=87

The pin configuration of the device tree can be compiled from source:

sudo dtc -I dts -O dtb -o /boot/dt-blob.bin dt-blob.dts

As an alternative to you copy the dt-blob.bin

sudo cp dt-blob.bin /boot/dt-blob.bin

The Raspberry icons occupy a large amount of the vertical space during boot. They can be disabled by adding an option to the cmdline.txt:

sudo nano /boot/cmdline.txt

Add logo.nologo to the parameters. It should look like this after it:

dwc_otg.lpm_enable=0 logo.nologo console=ttyAMA0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline rootwait

We should also upgrade and update our installation:

sudo apt-get upgrade && sudo apt-get update

Now we can reboot:

sudo reboot

The HDMI should now be off (and can it be disconnected) and the LCD is used

Launching the demos

To get the demos to work, we need to install a few more python specific pieces:

For AlarmTest we need pygame:

sudo apt-get install python-pygame

This demo can be launched by cd AlarmTest and then python main.py

We need pip to install more Python packages

sudo apt-get install build-essential python-dev python-pip

This is only used for rotTest:

sudo pip install psutil

This demo can be launched by cd rotTest and then python main.py. The demo can be quit via Control-C, but the display stays blank. I haven’t fixed this. sudo reboot solves it…

The full dt-blob.dts for EarthLCD Pi-Raq

/dts-v1/;

/ {
   videocore {
      clock_routing {
        vco@PLLD { freq = <2000000000>; };
        chan@DPER { div = <8>; }; // APER will be 500MHz
      }; // clock routing

      pins_2b2 { // Pi 2 Model B rev 1.1
         pin_config {
            pin@default {
               polarity = "active_high";
               termination = "pull_down";
               startup_state = "inactive";
               function = "input";
            }; // pin

            pin@p0  { function = "dpi";    termination = "no_pulling"; drive_strength_mA = < 8 >; };    // PCLK-OUT
            pin@p1  { function = "dpi";    termination = "no_pulling"; drive_strength_mA = < 8 >; };    // DE-OUT

            pin@p2  { function = "i2c1";   termination = "pull_up";    }; // I2C 1 SDA
            pin@p3  { function = "i2c1";   termination = "pull_up";    }; // I2C 1 SCL

            pin@p4  { function = "dpi";    termination = "no_pulling"; };   // B3-Out
            pin@p5  { function = "dpi";    termination = "no_pulling"; };   // B4-Out
            pin@p6  { function = "dpi";    termination = "no_pulling"; };   // B5-Out
            pin@p7  { function = "dpi";    termination = "no_pulling"; };   // B6-Out
            pin@p8  { function = "dpi";    termination = "no_pulling"; };   // B7-Out

            pin@p9  { function = "dpi";    termination = "no_pulling"; };   // G2-Out
            pin@p10 { function = "dpi";    termination = "no_pulling"; };   // G3-Out
            pin@p11 { function = "dpi";    termination = "no_pulling"; };   // G4-Out
            pin@p12 { function = "dpi";    termination = "no_pulling"; };   // G5-Out
            pin@p13 { function = "dpi";    termination = "no_pulling"; };   // G6-Out
            pin@p14 { function = "dpi";    termination = "no_pulling"; };   // G7-Out

            pin@p15 { function = "dpi";    termination = "no_pulling"; };   // R3-Out
            pin@p16 { function = "dpi";    termination = "no_pulling"; };   // R4-Out
            pin@p17 { function = "dpi";    termination = "no_pulling"; };   // R5-Out
            pin@p18 { function = "dpi";    termination = "no_pulling"; };   // R6-Out
            pin@p19 { function = "dpi";    termination = "no_pulling"; };   // R7-Out

            // Pin 20..26 are the Jog Shuttle controls, they are all inputs, which is covered by the pin@default case
            // p20 = Right button
            // p21 = Left button
            // p22 = Rot1           (Rot1 ^ Rot2) != 0 => Right, (Rot1 ^ Rot2) == 0 => Left if an edge was detected
            // p23 = Center button
            // p24 = Rot2
            // p25 = Down button
            // p26 = Left button

            pin@p27 { function = "output"; termination = "pull_up"; startup_state = "active"; };    // LCD Backlight enable, active at boot

            // From here on it is identical to Jessie's defaults:

            // The firmware changes I2C pin functions on the fly, returning them to inputs when done. But pins 28&29 are
            // not used on a 1.1 Pi2, so the I2C0 function ends up multiply mapped (bad). therefore don't statically map.
            // pin@p28 { function = "i2c0";   termination = "pull_up";    }; // I2C 0 SDA
            // pin@p29 { function = "i2c0";   termination = "pull_up";    }; // I2C 0 SCL
            pin@p31 { function = "output"; termination = "pull_down"; }; // LAN_RUN
            pin@p32 { function = "output"; termination = "pull_down"; }; // Camera LED
            pin@p35 { function = "input";  termination = "no_pulling"; polarity = "active_low"; }; // Power low
            pin@p38 { function = "output"; termination = "no_pulling";    }; // USB current limit (0=600mA, 1=1200mA)
            pin@p40 { function = "pwm";    termination = "no_pulling"; drive_strength_mA = < 16 >; }; // Right audio
            pin@p41 { function = "output"; termination = "no_pulling";    }; // Camera shutdown
            // Communicate with the SMPS by "bit-bashing" the I2C protocol on GPIOs 42 and 43
            pin@p42 { function = "output"; termination = "pull_up";    }; // SMPS_SCL
            pin@p43 { function = "input";  termination = "no_pulling";    }; // SMPS_SDA
            pin@p44 { function = "gp_clk"; termination = "pull_down"; }; // ETH_CLK - Ethernet 25MHz output
            pin@p45 { function = "pwm";    termination = "no_pulling"; drive_strength_mA = < 16 >; }; // Left audio

            pin@p46 { function = "input";  termination = "no_pulling"; polarity = "active_low"; }; // HDMI hotplug detect (goes to pin 6 of IC1)
            pin@p47 { function = "output"; termination = "pull_down"; }; // activity LED
            pin@p48 { function = "sdcard"; termination = "pull_up";    drive_strength_mA = < 8 >; }; // SD CLK
            pin@p49 { function = "sdcard"; termination = "pull_up";    drive_strength_mA = < 8 >; }; // SD CMD
            pin@p50 { function = "sdcard"; termination = "pull_up";    drive_strength_mA = < 8 >; }; // SD D0
            pin@p51 { function = "sdcard"; termination = "pull_up";    drive_strength_mA = < 8 >; }; // SD D1
            pin@p52 { function = "sdcard"; termination = "pull_up";    drive_strength_mA = < 8 >; }; // SD D2
            pin@p53 { function = "sdcard"; termination = "pull_up";    drive_strength_mA = < 8 >; }; // SD D3
         }; // pin_config

         pin_defines {
            pin_define@HDMI_CONTROL_ATTACHED { type = "internal"; number = <46>; };

            pin_define@NUM_CAMERAS { type = "internal"; number = <1>; };
            pin_define@CAMERA_0_I2C_PORT { type = "internal"; number = <0>; };
            pin_define@CAMERA_0_SDA_PIN { type = "internal"; number = <28>; };
            pin_define@CAMERA_0_SCL_PIN { type = "internal"; number = <29>; };
            pin_define@CAMERA_0_SHUTDOWN { type = "internal"; number = <41>; };
            pin_define@CAMERA_0_UNICAM_PORT { type = "internal"; number = <1>; };
            pin_define@CAMERA_0_LED { type = "internal"; number = <32>; };

            pin_define@FLASH_0_ENABLE { type = "absent"; };
            pin_define@FLASH_0_INDICATOR { type = "absent"; };
            pin_define@FLASH_1_ENABLE { type = "absent"; };
            pin_define@FLASH_1_INDICATOR { type = "absent"; };

            pin_define@POWER_LOW { type = "internal"; number = <35>; };
            pin_define@LEDS_DISK_ACTIVITY { type = "internal"; number = <47>; };
            pin_define@LAN_RUN { type = "internal"; number = <31>; };
            pin_define@SMPS_SDA { type = "internal"; number = <43>; };
            pin_define@SMPS_SCL { type = "internal"; number = <42>; };
            pin_define@ETH_CLK { type = "internal"; number = <44>; };
            pin_define@USB_LIMIT_1A2 { type = "internal"; number = <38>; };
            pin_define@SIO_1V8_SEL { type = "absent"; };
            pin_define@PWML { type = "internal"; number = <45>; };
            pin_define@PWMR { type = "internal"; number = <40>; };
            pin_define@SAFE_MODE { type = "internal"; number = <3>; };
            pin_define@SD_CARD_DETECT { type = "absent"; };
            pin_define@ID_SDA { type = "internal"; number = <0>; };
            pin_define@ID_SCL { type = "internal"; number = <1>; };
            pin_define@DISPLAY_I2C_PORT { type = "internal"; number = <0>; };
            pin_define@DISPLAY_SDA { type = "internal"; number = <28>; };
            pin_define@DISPLAY_SCL { type = "internal"; number = <29>; };
         }; // pin_defines
      }; // pins

   };
};

The full config.txt

# For more options and information see
# http://www.raspberrypi.org/documentation/configuration/config-txt.md
# Some settings may impact device functionality. See link above for details

# uncomment if you get no picture on HDMI for a default "safe" mode
#hdmi_safe=1

# uncomment this if your display has a black border of unused pixels visible
# and your display can output without overscan
disable_overscan=1

# uncomment the following to adjust overscan. Use positive numbers if console
# goes off screen, and negative if there is too much border
#overscan_left=16
#overscan_right=16
#overscan_top=16
#overscan_bottom=16

# uncomment to force a console size. By default it will be display's size minus
# overscan.
framebuffer_width=1024
framebuffer_height=100

# uncomment if hdmi display is not detected and composite is being output
#hdmi_force_hotplug=1

# uncomment to force a specific HDMI mode (this will force VGA)
#hdmi_group=1
#hdmi_mode=1

# uncomment to force a HDMI mode rather than DVI. This can make audio work in
# DMT (computer monitor) modes
#hdmi_drive=2

# uncomment to increase signal to HDMI, if you have interference, blanking, or
# no display
#config_hdmi_boost=4

# uncomment for composite PAL
#sdtv_mode=2

#uncomment to overclock the arm. 700 MHz is the default.
#arm_freq=800

# Uncomment some or all of these to enable the optional hardware interfaces
#dtparam=i2c_arm=on
#dtparam=i2s=on
#dtparam=spi=on

# Uncomment this to enable the lirc-rpi module
#dtoverlay=lirc-rpi

# Additional overlays and parameters are documented /boot/overlays/README

#----------------------------------------------------------------------------------------------------
#Generated on Thu Feb 19 13:20:39 2015 by Segler-HP
#config file for None
#Output Format -> DPI_OUTPUT_FORMAT_16BIT_565_CFG1
#RGB Order -> DPI_RGB_ORDER_RGB
#Output Enable Mode ->DPI_OUTPUT_ENABLE_MODE_DATA_VALID
#Invert Pixel Clock ->RGB Data changes on falling edge and is stable at rising edge
#Hsync Disable ->False
#Vsync Disable ->False
#Output Enable ->False
#Hsync Polarity ->Inverted
#Vsync Polarity ->Inverted
#Output Enable Polarity ->default for HDMI mode
#Hsync Phase ->DPI_PHASE_POSEDGE
#Vsync Phase ->DPI_PHASE_NEGEDGE
#Output Enable Phase ->DPI_PHASE_NEGEDGE
#----------------------------------------------------------------------------------------------------
hdmi_timings=1024 0 50 100 50  100 0 2 10 2 0 0 0  60 0 25000000 7
enable_dpi_lcd=1
display_default_lcd=1
dpi_output_format=4194306 #6488594
dpi_group=2
dpi_mode=87

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

Deutsche im Valley

Was muss man wissen, wenn man in die USA nach Silicon Valley zieht?

Auf den folgenden Seiten habe ich Informationen gesammelt, die hoffentlich auch für andere Deutsche in Silicon Valley nützlich sind. Viel Spaß!

All diese Informationen sind natürlich ohne Gewähr!

Silicon Valley

Silicon Valley hat seinen Namen vom chemischen Element Silizium (nicht etwa Silikon, dafür ist Los Angeles bekannt…) in den ICs der High-Tech Industrie, die sich in den letzten 30 Jahren in der Südbay mehr und mehr angesiedelt hat. Folgenden Orte befinden sich in dem Gebiet, das man allgemein Silicon Valley nennt:

Wie finde ich was

Natürlich denkt man ans Internet… wobei in der Bay Area ist der Nutzen, den man aus dem Internet ziehen kann, erheblich größer, als irgendwo sonst in der Welt. Als Stichwort muss man für die Gegend nur “Bay Area” eingeben. Nur in extremen Ausnahmefällen wird damit nicht die SF Bay Area verknüpft.

Die Zeitung im Valley ist übrigens The Mercury News – international bekannt für ihre Infos über die Firmen im Valley, im Valley für ihre Fry’s Anzeigen jeden Freitag 🙂

Locale Infodienste

Diesen Absatz habe ich vor 12 Jahren geschrieben… Wichtig ist jedoch zu wissen, das das Internet deutlich mehr genutzt wird als in Deutschland. Lokale Informationen bekommt man stets online: wann ist ein Restaurant offen? Wo ist die nächste Wäscherei? u.s.w.

Straßenkarten

  • Apple Maps – der 3D View funktioniert fast im ganzen Valley
  • Google Maps – Besonderheit: sehr gutes UI, optionale Satellitenkarten, zudem gute Verkehrsinfos, sowie gutes Traffic-Routing. Street View ist generell verfügbar.
  • Yahoo Maps – Besonderheit: Verkehrsinfo mit Durchschnittsgeschwindigkeiten und Unfällen auf den Highways, zudem kann man Banken, Tankstellen, u.s.w. auf der Karte einblendbar

Reisen und Flüge

Pauschalreisen via TUI, etc. kennt man in den USA nicht. Man bucht Flug, Hotel und ggf. Mietwagen – und trägt quasi auch das alleinige Risiko, dass alles klappt. Versicherungen sind 10% des Reisepreises – ob einem das Wert ist, muss man selbst entscheiden.

Diese 3 sind die bekanntesten Websites:

Sehr empfehlenswerte Meta-Suchmaschinen für Flüge und Hotels

Visa

Um in den USA zu arbeiten, braucht man ein Visum (engl. Visa). Alle Infos dazu kann man online finden. Folgende Kategorien sind besonders üblich, viele davon sind in der Anzahl pro Jahr beschränkt (z.B. das H-Visum)

Visum Beschreibung
F, J Studentenvisa, benötigt um in den USA zu studieren.
H Das übliche “Arbeitsvisum”, welches einem vom Arbeitgeber besorgt wird. Die Quote für 2006 ist seit April/Mai bereits erreicht, so dass man keine Chance hat vor nächstem Jahr ein solchen Visum zu erhalten. Nächster Termin ist April 2007 um einen Job ab September 2007 anzufangen.
L Wenn die Firma eine Niederlassung in Deutschland hat, kann man in die USA versetzt werden. Man muss jedoch vorher bereits min. ein Jahr für die Firma gearbeitet haben.

Achtung: Es gibt diverse Websites, die ähnliches anbieten, aber die wollen nur Geld… Bei VISA Informationen sollte man darauf achten, dass der Servername mit .gov aufhört (siehe oben), dann ist man auf einer offiziellen Website. Einige .org gehören den Städten und sind auch ok.

Greencard

Neben dem normalen VISA kann man in jedem Winter an der Lottery um eine Greencard teilnehmen. Ist kostenlos und inzwischen komplett via Internet. Es gibt IMO keinen Grund das nicht jährlich zu versuchen:

http://dvlottery.state.gov

Doppelstaatsbürgerschaft

Als Besitzer einer Greencard und wenn man 5 Jahre in den USA lebt, kann man die amerikanische Staatsbürgerschaft annehmen. Man muss dazu natürlich einige Voraussetzungen erfüllen und man verliert automatisch seine deutsche Staatsbürgerschaft. Man muss vorher einen speziellen Antrag beim deutschen Konsulat stellen, damit man sie nicht verliert. Dabei muss man allerdings gute Gründe angeben, warum man die Deutsche behalten will (i.d.R. einfach: Kontakte, Konten, Erbschaften, u.s.w), aber man muss auch Gründe angeben, warum man die Amerikanische braucht! Das ist deutlich schwerer. Der beste Grund ist i.d.R. wenn Arbeitsplätze nur für Amerikaner ausgeschrieben sind.

Hilfreich ist auch die Yahoo Gruppe Zweitpäße, die bei Fragen zu Doppelstaatsangehörigkeit sehr hilfreich ist.

Bank Account

Ein Konto ist so ziemlich das erste was man in den USA braucht. Schließlich will man seinen Gehaltsscheck einlösen können.

Es gibt eine große Auswahl an Banken, aber die Keypoint Credit Union (eine Art Genossenschaftsbank) ist für den Einstieg sehr zu empfehlen. Sie hat zwar nur einige wenige Filialen, aber es gibt Geldautomaten z.B. auf dem Apple Campus (natürlich nur für Apple Angestellte) und man kann dort nicht nur Geld abheben (ca. $600 pro Tag), sondern auch Geld einzahlen und Schecks einreichen. Es gibt also kaum Gründe zur Bank selbst zu fahren. Relative neu ist die Möglichkeit einen Scheck mit dem iPhone einzureichen: in der Keypoint App einfach ein Foto vom Scheck machen und fertig. Den Scheck aufheben, falls es Probleme gibt. Es kann übrigens bis zu einer Woche dauern, bis der Scheck gutgeschrieben ist (der Gehaltsscheck ist schneller)

Nutzt man das Konto als Gehaltskonto (gleich bei der Einrichtung angeben), kann man alle Geldautomaten in der Welt kostenlos nutzen – praktisch um Geld von den USA nach Deutschland zu “überweisen” (einfach eine 2. ATM Karte beantragen und zu einer Vertrauensperson nach Deutschland schicken). Man muss die Gebühren sich über ein Webformular gutschreiben

Die Karte wird hier ATM-Card oder auch Debit-Card genannt. Man kann sie mit Geheimzahl auch bei Safeway u.s.w. nutzen (die Geheimzahl kann man bei der Bank ändern). Sie hängt an einem Checkings-Account, was einem Girokonto entspricht. Es gibt auch einen Savings-Account, der unserem Sparbuch entspricht. Die Zinsen dort sind lächerlich ($500 nach über einem Jahr haben immerhin $1 Zinsen eingebracht), aber man sollte den Savings-Account trotzdem einrichten. Warum? Das Stichwort lautet Credit History – siehe unten.

Man sollte versuchen gleich eine zweite Debit-Card zu bekommen – ich habe eine weitere ohne meinen Namen bekommen. Ideal für den Partner oder für die Eltern in Deutschland. Warum für die Eltern? Man kann einfach anrufen und sagen: Mom, kannst Du bitte man vom Geldautomaten $500 abheben und auf mein deutsches Konto einzahlen? Dann läßt man sich die Gebühren gutschreiben und schon hat man Geld ohne jegliche Kosten nach Deutschland verschoben.

Eine weitere Möglichkeit, an eine Kreditkarte und ein gutes Credit History-Rating zu kommen, ist die Secured Visa Card (gibt’s bestimmt auch bei anderen Kreditkarten-Anbietern). Man richtet bei seiner Hausbank einen Savings-Account ein und überweist mindestens $250. Der Kreditrahmen der Karte beläuft sich just auf den Betrag, der als Guthaben auf dem Savings-Account liegt. Auch hier gilt: Brav konsumieren und genug Geld auf den Savings-Account einzahlen hilft, ein gutes Rating zu erlangen.

Die gebräuchlichste Kreditkarte ist von VISA, gefolgt – mir Abstand – von AmEx (American Express) und Mastercard. Diners, u.s.w. gibt es kaum noch.

Credit History

Das ist das wichtigste Ziel, das man sich mittelfristig setzen sollte. Warum ist die Credithistory wichtig? Weil der Vermieter überprüft, ob man zahlungswillig ist, weil die Telefongesellschaft selbiges tut und natürlich für den Auto- oder gar Hauskauf, falls man finanziert oder least. Nur wer in den USA Schulden macht und sie brav abstottert, der ist auch kreditwürdig. Der durchschnittliche Amerikaner hat 5 oder 6 Kreditkarten!

In den USA wird die Zahlungsmoral von allen Personen von 3 Credit-Büros gesammelt (Experian, Transunion und Equifax) – Hallo Big Brother… Als neuer Gast hat man natürlich keine History und gilt somit als nicht kreditwürdig – das muss man ändern.

Die Kreditwürdigkeit wird als Zahl – FICO genannt – zwischen 350 und 850 beschrieben. Der Durchschnitt in den USA liegt bei 720. Hat man einen FICO von 700 oder mehr, dann bekommt man die günstigsten Zinsen. Unter 650 bekommt man nur sehr hohe (20–30%!) oder gar keinen Kredit. Ist man erstmal 3 Jahre in den USA hat man die letzte Schwelle erreicht, denn ab 3 Jahren Credithistory wird alles besser. Näheres kann man unter MyFico finden.

Den FICO kann man selber überprüfen. An sich sind die Auskünfte bei den 3 Büros umsonst, nur kann man das nirgends auf deren Webseiten finden. Ab 1. September 2005 soll das besser werden. Bis dahin hat man 4 Möglichkeiten:

  • Annual Credit Report – einmal pro Jahr ist der Report aller 3 Institute umsonst
  • MyFico um für alle 3 Büros zu wissen wo man steht. Kostet ca. $45.
  • bei den einzelnen Büros das abzutesten.
  • Einen von vielen Diensten, die im TV ständig angeboten werden. Die wollen aber nur Geld verdienen, die Services lohnen IMO nicht.

Man sollte niemals ein Auto oder gar Haus kaufen, wenn man seinen FICO nicht kennt. Die Autohändler hauen einen gnadenlos über das Ohr! Sie fragen nämlich Deinen Score ab (mit Deiner Erlaubnis) und sagen dann: du hast leider nur einen schlechten Score von 620, obwohl er bei 720 liegt. Und schon zahlst Du doppelt so hohe Zinsen. Der Scam (Betrug) läuft sofort ins Leere, wenn man fragt bei welchem Büro (siehe oben) sie nachgefragt haben, denn mein Score ist besser (und man hat den Ausdruck natürlich dabei). Tja, dann können sie nur noch sowas sagen wie: ehm, ja, für Autos ist der Score ein anderer – klaro, und die Erde ist eine Scheibe.

Um mein Kreditrating zu unterstützen, habe ich mein Auto finanziert. Der Zinssatz war im Angebot und marginal, und ich habe das Auto nach ein paar Monaten abgezahlt. Die Kosten für die Zinsen waren es Wert, denn dadurch haben wir ein höheres Kreditrating bekommen, was den Zinssatz beim Hauskauf gesenkt hat.

Wie macht man das?

  • Sofort einen Checking- und auch einen Savings-Account einrichten.
  • Sobald man die SSN Sozialversicherungsnummer hat (dauert ca. 6 Wochen) eine Kreditkarte bei Keypoint beantragen. Diese Kreditkarte wann immer es geht nutzen (man kann online jederzeit sehen, was man ausgibt) und jeden Monat brav bezahlen. Man kann häufig auch Rechnungen für Gas, Wasser, Strom, Telefon, Internet, Fernsehen, etc. via Kreditkarte zahlen – sofort einrichten!
  • Falls man eine Amex in Deutschland hatte: Amex USA anrufen und um eine Karte bitten. In den USA wird fast alles per Telefon geregelt – nicht per Brief oder gar Einschreiben mit Rückschein. Klappt erstaunlich gut, aber es gibt natürlich auch mal Deppen (Unwissende) auf der anderen Seite der Leutung.
  • Nach 6 Monaten versuchen weitere Kreditkarte zu bekommen. Amazon hat z.B. eine, die einem 1% des Umsatzes als Gutschein für http://www.amazon.com gutschreibt. Nicht viel, aber für lau…

Wohnen

Die Mieten im Valley und auch in San Francisco sind extrem hoch. Das ist der Grund, warum die Gehälter so gut sind…

Wer eine Wohnung mieten will, hat prinzipiell zwei Möglichkeiten:

  • Craigslist – dort gibt es Wohnungen und Häuser von Privat zu mieten (mittels Craigslist kann man Wohnungen auch via Google Maps finden). Der Anlaufort.
  • Bei Safeway, etc. am Ausgang gibt es kostenlos Apartment Guide und For Rent. Dort finden sich ausschließlich Apartmentanlagen die von einer Firma betrieben werden und einen Manager vor Ort haben.

Apartmentanlagen bieten i.d.R. Pool, Fitness-Raum, etc. – viele davon sind aber recht alt und die neuen (gut ausgestatteten) sind teuer. Einfach mit dem Wagen rumfahren und angucken – insbesondere den Raumplan. Viele Gebäude haben dünne Wände und schlechtes Management (“Problem? Kenne ich nicht…”) Gute sind schwer zu finden. Und sind sie wirklich gut, besteht die Gefahr, dass in 1–2 Jahren die Anlage in Condos umgewandelt wird (sprich: die Apartments einzeln verkauft werden) und dann muss man als Mieter kaufen oder raus. Mieterschutz über die Vertragsdauer hinaus gibt es in den USA nicht.

Die Lage ist teilweise erbärmlich: direkt am Freeway, neben der Eisenbahn, am Flughafen… Der Lärm kann sehr nervig sein. Um rauszufinden, wie der Fluglärm ist, kann man sich die anfliegenden und landenden Flugzeuge auf einer Karte ansehen

Mitverträge laufen 6, 9 oder 12 Monate. Häufig bekommt man 1/2 Miete bei 6 Monaten und 1 Miete bei 12 Monaten umsonst. Teilweise kann man verhandeln – bei den Guten geht das aber kaum. Bezahlt wird am Monatsanfang via Scheck. Der erste Scheck (eine Monatsmiete + Security Deposit) muss von der Bank abgesichert sein, d.h. zur Bank fahren und einen solchen Scheck anfordern. Der muss auf einen bestimmten Namen ausgestellt sein – sonst fährt man 2x zur Bank.

Will man ausziehen, sonst muss man nach der Vertragsdauer 30 Tage im voraus kündigen (der Vermieter darf entsprechend auch kündigen). Das kann relevant werden, weil der Vermieter gerne die Miete anhebt, wenn der Vertrag zu Ende ist – er weiß ja genau, dass ein Umzug auch Geld kostet… Will man das zumindest teilweise vermeiden, muss man wieder für 12 Monate mieten. Risiko!

Zieht man aus, versucht der Vermieter alles um den Security Deposit zu behalten. Also wird Reinigung, Farbe, u.s.w. in Rechnung gestellt. Das kann man nur vermeiden, wenn man einen professionellen Reinigungsdienst anheuert und die Rechnung vorlegt. Normal Wear-and-tear darf nicht in Rechnung gestellt werden (Teppich-Abnutzung, kleine Löcher in der Wand durch Bilder, u.s.w.), aber es gibt die Regel: die Wohnung muss nach dem Auszug wieder so aussehen, wie beim Einzug (Fotos machen!) und das dehnt der Vermieter über die Schmerzgrenze hinaus. Am Ende muss jeder selbst entscheiden, wieviel Stress es einem Wert ist. Jemand bietet sogar ein Security Deposit Recovery Kit an – entscheidend ist, dass man schon vor der Kündigung der Wohnung daran denkt/

Kaufen

Ohne Credithistory ist das Kaufen einer Wohnung nur mit Cash möglich. Und der Markt im Valley ist teuer, sehr teuer.

Wer mehr darüber wissen will, kommt nicht umhin das Buch “How to Buy a House in California” zu kaufen.

So teuer, dass man Kaufen zur Zeit nur rechtfertigen kann, wenn man erwartet, dass die Preise immer weiter steigen. Die monatliche Belastung nach dem Kauf ist aber heute i.d.R. erheblich höher als die Miete für ein äquivalentes Objekt. Allerdings kann man die Zinsbelastung (welche in den erste Jahren fast ausschließlich zahlt) vom Einkommen abziehen, so dass man teilweise massiv Geld sparen kann. Man sollte einen CPA/Steuerberater hinzuziehen, wenn man über den Hauskauf nachdenkt.

Wichtig: jedes Jahr werden 1.25% des Kaufpreises des Hauses in Steuern fällig. Ein $1,000,000 kostet also ca. $1000 Steuern pro Monat. Ein bisschen hängt das von der genauen Lage ab, aber die Größenordnung stimmt. Es kann sich lohnen ein billigeres Haus in guter Lage zu kaufen und zu renovieren – aber diesen Trick kennt natürlich auch jeder Investor.

Eine gute Quelle für Häuser ist MLS. Zudem kann man auf Zillow den Schätzwert von Häusern grob ermitteln.

Laufende Kosten

Die laufenden Kosten sind ebenfalls recht hoch. Folgende Punkte fallen mir ein:

  • Gas – im Winter kann das leicht über $100 gehen, bei Häusern leicht das doppelte.
  • Strom – je nach Bedarf ($50-$200 pro Monat, je nachdem ob man nur ein Apartment oder ein Haus hat)
  • Müll – $20 pro Monat, einige Apartments kassieren selbst
  • Wasser – teilweise enthalten, ansonsten je nach Benutzung – einige Anlagen berechnen Pauschalen nach Bedrooms.
  • Telefon – $30 – analog mit kostenlosen Ortsgesprächen – überteuert! Viele Leute haben inzwischen nur noch ein Handy.
  • Mobiltelefon – $40-$100 – teilweise bis zu 1000 Minuten pro Monat enthalten und am Wochenende und Abend ganz kostenlos in den USA. Kann sich lohnen. Es gibt auch Prepaid, aber die versteckten Kosten sind da in der geringen Gültigkeit des Guthabens.
  • Internet – DSL (AT&T) oder Kabel (Comcast), teilweise auch Verizon: $20 für 3–12 Monate, dann $50. Unter Androhung der Kündigung kann man eventuell den Tarif wieder reduzieren.
  • Fernsehen – kostenlos, aber nur via Antenne. Kabel Analog (80 Kanäle), Kabel Digital (200 Kanäle), Satellit (200 Kanäle) ab $45 pro Monat, bis $150. Viele Leute haben gar keinen Kabelanschluss mehr. Analog und das Internet sind der Ersatz (Apple TV, Roku, Netflix, Amazon).
  • Gärtner – bei Häusern – häufig in der Miete enthalten.

Alles über Autos in den USA

Das Auto in den USA ist natürlich die wichtigste Methode von A nach B zu gelangen. Meistens sogar die einzige…

Autokauf

Es gibt 3 grundlegende Möglichkeiten an einen Wagen zu kommen.

  • einen Gebrauchten kaufen
  • einen Neuwagen kaufen
  • einen Neuwagen leasen

Alles Optionen, die man durchdenken sollte. Für die meisten Privatleute, die ein Auto länger als 3 Jahre fahren wollen ist leasen zu teuer. Es kann aber eine Option sein, wenn man einen schlechten FICO Score hat, denn die monatlichen Beträge sind natürlich deutlich geringer.

Erste Regel: Alle Autohändler wollen natürlich nur das Beste: Dein Geld – und zwar soviel wie möglich davon! Egal, was er sagt und tut: er wird alles versuchen Dich zu bescheissen. Fakt!

Wie fängt man also an?

  • jemanden fragen, der schon mal ein Auto hier gekauft hat…
  • CarBuyingTips besuchen und lernen. Die Seite hat sehr viele Tips, was man beim Autokauf beachten muss. Die Website enthält auch viele Links zu Online Anbietern (Cars und Carsdirect). Sehr gut für Preisvergleiche.
  • Consumer Reports ist in etwa die Stiftung Warentest der USA. Die haben viele nützliche Tips und man kann auch im Zeitschriftenladen einen Neu- bzw. Gebrauchtwagenführer kaufen – Pflichtlektüre! Für $26 pro Jahr bekommt man jeden Monat eine Zeitschrift.
  • Fighting Chance verkauft für $34.95 eine Sammlung an PDFs, die einem sehr wertvolle Hinweise zum Autokauf – insbesondere Neuwagen – gibt. Ist das Geld wert!
  • El Camino Real und noch größer: Stevens Creek Blvd sind die Automeilen in Silicon Valley. Hier kann man Neuwagen angucken und auch Gebrauchte finden. Achtung: erst obige Webseite durchgehen! Die Händler wollen übrigens immer Deinen Führerschein (klaro, für ’ne Probefahrt) und die Telefonnummer – um Dich dann über Tage und Wochen anzurufen, damit Du ein Auto kaufst.
  • Wheels and Deals – Privatpersonen mieten dort einen Stellplatz (für einen Monat oder so) für ihren Gebrauchten. Den Verkauf wickelt aber Wheels & Deals ab inkl. Anmeldung, u.s.w. Man zahlt dafür zwar etwas, aber es ist ein “rundum-sorglos” Paket. Der Besitzer haut einen nicht über das Ohr – selten im Auto-Sektor. Der Laden liegt am El Camino Real und man kann dort auch Probefahrten machen. Man kann auch weniger bieten, als auf dem Preisschild steht!
  • Will man einen Gebrauchtwagen kaufen, muss man sich die VIN angucken (das entspricht der Fahrgestellnummer) – man kann sie auf der Fahrerseite eines jeden Autos unten hinter der Frontscheibe lesen. Mit der VIN und für $25 pro Monat kann man bei Carfax die Geschichte des Autos überprüfen: Besitzer, Laufleistung, Rückrufe, Versicherungsschäden (sehr beliebt: Wagen abgesoffen in Florida, als Totalschaden abgeschrieben und nach Kalifornien verschoben). Wer einen Gebrauchten kaufen will, muss die VIN überprüfen. Viele Händler bieten das auch schon an, aber immer selbst kontrollieren. Netter Tip am Rande: die Händler drucken den Report immer gleich aus, wenn sie den Wagen in Zahlung nehmen – und dort findet sich das Datum! Sprich: man weiß wie lange die Kiste schon auf dem Hof steht! Mehr als einen Monat? Gleich mal $500 weniger bieten!!!
  • Den Wert eines Gebraucht- oder Neuwagens kann man über verschiedene Seiten in Erfahrung bringen. Der gängigste und gleichzeitig teuerste Vergleichswert kommt von Kelley’s Blue Book. Bei gesuchten Modellen muss man sich drauf einstellen, nur wenig unter den dort ermittelten Preisen bleiben zu können. Bei Modellen, die etwas schwieriger zu verkaufen sind, kommen die Werte von Edmunds realistischer. Edmunds bietet interessante Artikel zum Thema Neu- und Gebrauchtwagenkauf.

Laufende Kosten

  • Gas (Benzin) – Diesel ist teuer und selten. Es gibt 3 Sorten, je nach Wagen muss man eventuell das teure Premium tanken (entspricht unserem Normal Benzin). Die Preise zwischen Tankstellen können massiv schwanken – teilweise direkte Nachbarn haben 40 cent/Gallone Unterschied! Trotzdem ist Gas deutlich billiger als in Deutschland. Hier kann man die günstigsten Benzin Preise finden:
  • Die jährliche Steuer. Die liegt bei 2% des aktuellen Wertes des Autos. Man muss sie jährlich bezahlen und bekommt per Post einen Aufkleber für das hintere Nummernschild geschickt.
  • TÜV gibt es nicht – man sieht es schon an den Autos, die hier so rumfahren. ASU gibt es aber. Läuft zusammen mit der Steuer.
  • Die Versicherung ist in den USA sehr teuer, insbesondere für Fahranfänger – wie bei uns, denn die 20 Jahre in Deutschland mit Führerschein zählen nicht! Abhilfe: z.B. Mitglied bei AIG werden. Die erkennen einen deutschen Führerschein an und man zahlt nur $80-$100 pro Monat. Ohne AIG kann es 3x so viel sein! Die Mitgliedschaft bei AIG bekommt man am Besten über seinen Arbeitgeber – Relocation Abteilung fragen!

Nach 2 Jahren kann man auch bei AAA (dem amerikanischen ADAC) u.s.w. gucken. Die AAA Mitgliedschaft enthält einen Abschleppservice und hilft falls mal sich aus dem Auto ausgeschlossen hat, bzw. die Batterie tot ist. Lohnt sich meiner Meinung nach.

Führerschein

Der CA Führerschein ist nötig, inklusive einer praktischen Fahrprüfung! Auf DMV kann man die Verkehrsregeln als PDF runterladen und lernen. Die Prüfung kostet lächerliche $33. Ein Sehtest, Foto, bis zu 3 theoretische Prüfungen und entsprechende Praktische sind enthalten. Man kann die theoretische Prüfung auch auf Deutsch machen. Kann witzig sein, denn es gibt nur – zumindest war es in 2003 so – 3 deutsche Fragebögen. Sobald man die SSN hat (Nummer reicht, die Karte ist nicht erforderlich), kann man den Führerschein machen. Wichtig: zwischen theoretischer und praktischer Prüfung darf man die USA nicht verlassen, sonst kann sich der Führerschein um 6 Monate verzögern! Man kann die praktische Prüfung aber um Monate nach hinten schieben.

Der Führerschein ist nur ein paar Jahre gültig und muss verlängert werden. Dies kann in aller Regel aber online geschehen. Wichtig ist nur die Adresse anzupassen, wenn man umzieht!

Die praktische Prüfung macht man bevorzugt im eigenen Auto (falls man durchfällt kann man trotzdem weiterfahren bis zur nächsten Prüfung!) Sie dauert ca. 45 Minuten und man muss zeigen, dass man die Beleuchtung, Scheibenwischer, u.s.w. schalten kann, sowie die Verkehrsregeln kennt. Die Fahrt durch Santa Clara war nicht ohne! Einparken ist aber einfach…

Wer die Prüfung im Leihwagen machen will, muss möglicherweise eine Einverständniserklärung (Letter) der Verleihfirma vorlegen, dies ist zumindest bei Hertz der Fall. Außerdem muss Versicherung nachgewiesen werden, diese ist möglicherweise nicht im Mietvertrag enthalten.

Die Prüfungen werden immer vom DMV gemacht. Es gibt eine Stelle in Santa Clara und eine in Los Gatos. In Los Gatos ist die Prüfung etwas einfacher, weil weniger Verkehr als bei El Camino Real – aber das hängt auch von der Tageszeit ab.

Der Führerschein ist auch die ID (sprich: der Paß-Ersatz), wichtig für Identifikationen:

  • hin und wieder bei Kreditkarten-Käufen
  • um als junger Mensch in Bars zu gelangen (ab 21)
  • zum Kaufen von Alkohol zu kaufen (ab 21)
  • und für inner-USA Flüge

Tickets, Polizei

  • Allgemeine Alkoholkontrollen, etc. gibt es in den USA quasi nie (in Hamburg, werden vor Weihnachten ganze Straßen abgesperrt) Ein Polizist darf einen nicht ohne begründeten Verdacht anhalten. Das kann aber schon ein kaputtes Bremslicht sein… Für sowas gibt’s in aller Regel aber eine Verwarnung (kostenlos)
  • Polizisten müssen einen anhalten um einem ein Ticket auszustellen. Allerdings reicht in den USA die Aussage eines Polizisten aus, eine Messung mit Radar-Pistole ist möglich, aber nicht notwendig.
  • Auch bekommt man üblicherweise kein Ticket, wenn alle um einen gleich schnell fahren (Highway 280 mit 80mi/h ist ein gutes Beispiel) – aber legal ist das zu schnell Fahren natürlich nicht.
  • Überfahren eines Stop-Schildes kann aber sehr teuer werden. Ganz generell: sobald man ein Ticket bekommen hat, muss man eine Nachschulung machen (geht via Internet), ansonsten landet das bei der Versicherung und man kann über die nächsten 2–3 Jahre über $3000 mehr an Versicherung zahlen! Ist glücklicherweise nicht mir passiert…
  • Falsch parken ist sehr teuer ($50), ebenso parken in SF, wenn Street Cleaning Day angesagt ist.
  • Obacht: “Schutzmann’s Brust und Rücken, auf die Bremse drücken” gilt in den USA anscheinend nicht. Im Gegenteil, Handsignale, Gestiken und sinnlose Pfeiferei können für den zerstreuten deutschen Immigranten sehr irreführend sein.

Telefon

Festnetz

Festnetz ist in den USA quasi immer analoges Telefon mit ein paar Erweiterungen (welche sich die Telefonfirmen teuer bezahlen lassen), wie Caller ID (Anzeige der Nummer des Anrufers). Im Valley gibt es nur zwei Anbieter quasi mit Monopol: – AT&T (Cupertino, San Jose, Los Altos, u.v.a.m.) – Verizon (z.B. in Los Gatos)

Teilweise kann man auch andere Anbieter bekommen, wie Sonic, die von AT&T die Leitung mieten. Die Kosten liegen zwischen $50-$100 pro Monat inklusive Steuern und DSL-Internet – Gespräche ins deutsche Festnetz sind kostenlos! Insbesondere Sonic ist sehr empfehlenswert, weil ohne langfristigen Vertrag, sowie mit sehr gutem Service und Datenschutz.

Wer ein Mobil-Telefon hat, braucht den Festnetz Anschluß in der Regel gar nicht. Aber durch die Bündelung mit DSL, kann man eventuell doch einen bekommen.

Kosten nach Deutschland

Achtung man kann problemlos mit 3h Telefonat nach Deutschland auf eine $500 Telefonrechnung kommen (via AT&T)! Deswegen: einen Vertrag mit Telefonaten nach Deutschland buchen!

Internet-Telefon

Skype und andere sind eine sehr preisgünstige Alternative zu den analogen Telefonleitungen. Hier sind alle Gespräche innerhalb der USA im Preis enthalten und die Gespräche nach Deutschland sind auch recht günstig! Hat der Partner in Deutschland ebenfalls Skype ist es gar kostenlos! Facetime geht natürlich auch…

Vonage kann bei vorhandenem Internet und Telefonadapter ebenfalls die Telefonleitung ersetzen inkl. lokaler Telefonnummer. Der Preis fängt bei $10 an und hört bei $30 (unbegrenzte Anrufe nach Deutschland) auf.

Der deutsche Anbieter Sipgate vergibt kostenlose deutsche Rufnummern mit beliebiger deutscher Vorwahl, solange man in Deutschland noch in dem Vorwahlbereich im Telefonbuch steht – notfalls von jemand anders die Nummer holen lassen. Hat man einen VoIP-Router (z.B. die sehr gute Fritz!Box aus Deutschland mitbringen), so kann man sich auch Deutschland aus zum Ortstarif anrufen lassen. Kauft man Guthaben bei Sipgate, kann man auch fuer 1.79 Eurocent/min nach Deutschland telefonieren.

Mobil-Telefon

In den USA heißt es “cell phone” oder “mobile phone”, nicht Handy (das ist ein deutsches Wort)

Der Wettbewerb in den USA ist deutlich schwächer als in Deutschland. Wenn man sich die Werbung ansieht, glaubt man, dass es eine große Konkurrenz gibt, aber bei den Preisen macht es sich kaum bemerkbar. $100/Monat für ein Handy ist durchaus nicht ungewöhnlich!

Es gibt diverse Anbieter in den USA: * AT&T Relativ gute Netzabdeckung im Valley. * Verizon Ebenfalls sehr gute Abdeckung. * T-Mobile Im letzten Jahr eine schlechtere Abdeckung als AT&T * Sprint * Metro – sehr günstig ($30-$40), alle Gespräche inkl., keine Vertragsbindung – man muss aber das Handy kaufen! Abdeckung zweifelhaft.

Die Verträge fangen ab $40 an und enthalten 400–1500 Freiminuten pro Monat. D.h. egal wohin man in den USA telefoniert. Gespräche im selben Netz sind meistens kostenlos (zumindest bei AT&T) – ideal für Partner! Am Wochenende und nachts ist es meistens ebenfalls kostenlos. Außer bei Metro und T-Mobile hat man 1–2 Jahre Vertragsbindung. Verizon ist relativ teuer, ist aber bei Tests immer an der Spitze.

Es kommt nicht selten vor, dass mit manchen Providern in manchen Ecken des Valleys keine Verbindung zu kriegen ist. Das ist besonders ärgerlich, wenn das am Wohnort oder Arbeitsplatz ist, und enorm ärgerlich, wenn man schon in einem Zwei-Jahres-Vertrag hängt. Es gilt also die Rückgabefrist (i.d.R. eine Woche) zu nutzen und das Telefon zurückzugeben, wenn die Verbindungen nicht gut sind.

Ein iPad mit 3G gekauft mit T-Mobile SIM hat 200MB/Monat kostenlos Daten. Prima um zumindest mal Email o.ä. abzurufen.

SPAM

Wahrscheinlich einige Stunden nachdem der analoge Telefonanschluß freigeschaltet wurde, wird das Telefon klingeln. Irgendjemand will Geld sammeln (Polizei, Feuerwehr, was auch immer – derjenige kann auch nur behaupten für die Polizei zu sammeln), was verkaufen, u.s.w. Telefon-Spamming…

Deswegen: als erstes Don’t Call aufrufen und seine Telefonnummer auf die “Don’t Call” Liste setzen lassen. Anbietern mit denen man nicht in geschäftlicher Beziehung steht, dürfen einen dann nicht mehr anrufen – sonst setzt es empfindliche Geldstrafen für den Anrufer! Leider dauert es 2–3 Monate ist der Eintrag aktiv wird, ab dann wird es aber deutlich ruhiger!

Shopping

Das Amerikaners liebstes Hobby… Fast alle Läden wollen einem eine Kundenkarte – möglichst gleich mit Kreditkarte (funktioniert häufig nur in dem Laden) – andrehen. Das kann sinnvoll sein (Safeway, DSW), oder auch nicht (Fry’s Kreditkarte: bringt null Vorteile gegenüber einer beliebigen andere, keine Rabatte, nix – und funktioniert nur bei Fry’s). Meistens sind sie kostenlos, bei einigen Läden kostet sie Geld (dafür gibt es aber z.B. Rabatt auf jeden Einkauf im Laden)

Preise sind stets ohne Mehrwertsteuer! Die 9.25% werden am Ende draufgeschlagen. Keine Steuern auf den Lebensmitteleinkauf – deswegen fragen Fast-Food Restaurants, ob man “to-go” will. In dem Fall entfallen die Steuern. Ist man im Restaurant, fallen sie an.

Online

  • Amazon – klaro, Bücher, DVDs kennt man. Aber Amazon führt fast alles! Mit Prime-Membership ($99 pro Jahr) wird fast alles in 2 Tagen geliefert. Teilweise für ein paar Dollar sogar innerhalb von wenigen Stunden. Es ist möglich mehrere Lieferadressen zu haben (z.B. zu Hause und in der Firma)

Rabatte

Und auf fast alles gibt es Rabatte! Man muss nur die Regeln kennen und zur richtigen Zeit am richtigen Ort sein. Häufig ist es nicht der Mühe wert, ab und zu ist es aber Gold wert!

Es gibt diverse Arten von Rabatten: * In-Store – der beste Rabatt: der Betrag wird direkt an der Kasse abgezogen – sprich man zahlt weniger. * Mail-In – bei diesem Rabatt muss man i.d.R. die Quittung, einen Barcode vom Verpackungskarton und ein Rabattformular an eine bestimmte Adresse schicken – nach 6–8 Wochen bekommt man dann einen Scheck per Post. Häufig gibt es noch Sonderregelungen: üblich ist z.B. maximal einen Rabatt pro Haushalt. Bei Software wird man gerne getäuscht: der Rabatt gilt nur für Kunden, die eine alte Programmversion bereits besitzt. Sprich: es ist ein Upgrade-Rabatt. Zudem kann man einen Artikel nicht mehr zurückgeben, wenn man den Barcode von der Verpackung abgetrennt hat – man verliert also sein allgemein übliches Umtauschrecht! * Additional x% off: vom Preisschild wird an der Kasse nochmals x% abgezogen. * 2-for–1: 2 Artikel zum Preis von einen – häufig bei Safeway und Costco. Achtung: unter dem Preisschild steht häufig, dass man gar nicht 2 Artikel kaufen muss – einen einzelnen gibt es zum 1/2 Preis – natürlich nur mit Safeway Club Karte. Bei Costco gilt das nicht, d.h. man bekommt 2 Artikel zum Preis von einem. * buy 2 and get another one 50% off * buy 3 and get another one free – beide gibt es häufig in Schuhgeschäften. Auf das billigste Paar gibt es den Rabatt.

Ebenfalls eventuell hilfreich: * http://www.slickdeals.net * http://www.fatwallet.com * http://www.dealcatcher.com * http://www.couponcabin.com

Umtauschen

Umtausch bzw. das Zurückgeben (mit Geld zurück), ist in den USA in den meisten Läden kein Problem! Daran muss man sich als Deutscher wirklich gewöhnen: die Leute bringen Kleidung, Fernseher, was auch immer zurück – haben häufig nichtmal mehr den Beleg und es klappt trotzdem! Ich habe einzelne Steckdosen, die 2 Jahre in meiner Garage lagen, problemlos bei Home Depot gegen eine Gutschrift zurückgeben können – völlig ohne Beleg! Mit Beleg hätte ich sogar das Geld zurückbekommen.

Es gibt allerdings Ausnahmen: Geöffnete Software, Audio-CDs, DVDs kann man natürlich nicht umtauschen (außer gegen ein identisches Produkt – wobei jemand auch das gemacht hat: er hat eine identische CD bekommen – natürlich verblistert – und dann im nächsten Schritt diese CD zurückgegeben… Der Laden hat zwar Streß gemacht, aber das waren nunmal seine Regeln…)

Einkaufen von Nahrungsmitteln

Viele Läden haben Clubkarten. Die muss man nehmen, sonst zahlt man eindeutig zu viel! Bei Safeway kann man die Karte sofort bekommen und ich empfehle im Formular (Adresse, Telefon) einfach Müll einzutragen – außer dem eigenen Namen. Telefonnummern, die mit 555 anfangen sind z.B. immer ungültig (deswegen nutzt Hollywood die auch)

  • SafewayDie Kette überhaupt, hier bekommt man fast alle. In Stevens Creek (bei der 280/Lawrence Kreuzung) ist einer der größten hier überhaupt. 7 Tage die Woche, 24h geöffnet!
  • Trader Joe’s – Man glaubt es nicht, aber die Kette gehört den Aldi-Brüdern. Gutes Essen zu günstigen Preisen – insbesondere der Wein!
  • Nob Hill Foods
  • Whole Foods Market – Bio-Ladenkette, teuer (Spitzname: “Whole Paycheck”), aber sehr gute Nahrungsmittel!
  • Cost Plus WorldMarket – gute Weine und deutsche Schokolade 🙂
  • BevMo! Beverages&More – etliche deutsche Biersorten, Sonderangebote fuer Wein (mit Kundenkarte, versteht sich)

Achtung: in Cupertino schließen alle Läden um 22 Uhr! Nur BJs, und ein paar andere Restaurants haben danach noch auf.

Fast jeden Tag gibt es irgendwo im Valley Farmersmärkte (“Wochenmarkt”). Am Freitag zum Beispiel in Cupertino bei der Vallco Mall auf dem Parkplatz bei Alexanders Steakhouse. Am Sonntag ein sehr großer im historischen Downtown von Campbell. Eine Übersicht gibt es z.B. auf Yelp: http://www.yelp.com/list/south-bay-farmers-markets-by-day-santa-clara

Einkaufen von ‘deutschen’ Nahrungsmitteln

  • Cost Plus WorldMarket – gute Weine und deutsche Schokolade 🙂
  • Dittmer’s in Mountain View – eine deutsche Fleischerei mit Wüstchen u.v.a.m.
  • Esther’s German Bakery in Mountain View – deutsche Brote, Brötchen, Kuchen. Auch auf diversen Farmersmärkten vertreten!
  • Trader Joe’s – Hat auch diverse deutsche Nahrungsmittel (Bier, u.s.w.)
  • Kalinka – ein winziger russischer Markt mit diversen deutschen Produkten. Lohnt nur, wenn man eh in der Nähe ist.

Nutella gibt es inzwischen in diversen Läden, selbst Safeway.

Costco

Costco ist ebenfalls sowas wie Kult hier. Es entspricht am ehesten der Metro und kostet $45/Jahr Mitgliedschaft. Braucht man 25kg Mehl, so bekommt man es hier. Ist also insbesondere was für Großfamilien. Allerdings gibt es auch Fernseher, Bücher, DVDs, Audio-CDs, u.s.w. Und genau da kann man häufig sehr gut sparen!

Costco ist übrigens die Quelle für Handtücher, Laken, Waschlappen – sehr gute Qualität (flauschig) und günstige Preise! Auch Klamotten kann man teilweise günstig bekommen (Calvin Klein Jeans für $22). Kleidung kann man dort nicht anprobieren, aber man kann sie problemlos wieder zurückbringen.

Achtung: Die Öffnungszeiten sind deutlich kürzer, als bei den normalen Geschäften.

Achtung 2: Costco akzeptiert ausschließlich American Express CC, ATM-Cards und Cash! Keine VISA oder Mastercard! Das wird sich im April 2016 ändern.

Ketten

Folgende Ketten führen diverse Dinge, ähnlich Karstadt u.a. in Deutschland. Um 2–3 dieser Department-Stores sind häufig die Malls aufgebaut mit Food-Court und teilweise zusätzlich mit einem Kinokomplex.

Elektronikläden

  • Fry’s hat einige Filialen in der Umgebung. Fry’s ist eine Kreuzung aus Conrad und Media Markt und Saturn. Man findet fast alles, aber es ist nicht immer günstig! Am Wochenende in der Zeitungsbeilage findet man die Schnäppchen. Vom 30-Tage-Rückgaberecht scheinen bei Fry’s viele Kunden gebrauch zu machen, deswegen ist es ein sehr gutes Anzeichen, ob ein Produkt etwas taugt oder nicht, wieviele der Verpackungen auf dem Stapel offensichtlich schon mal zurückgegeben wurden.
    • Palo Alto
    • Sunnyvale (quasi die Zentrale, sehr groß)
    • Campbell (recht klein, gehe dort nur im Notfall hin)
    • San Jose
    • Fremont
  • Best Buy – Fernseher, Küchengeräte, Telefone, Spielekonsolen, u.s.w.
  • Central Computer Systems – kleinere Läden im Stevens Creek Blvd und El Camino Real. Der Parkplatz ist übrigens hinter dem Gebäude.
  • Weird Stuff Warehouse – Ebenfalls ein Teil der Silicon Valley Geschichte: ein Laden, der gebrauchte Computer verkauft – sucht man einen Apple 2, kann man ihn hier am ehesten finden! Auch uralte Originalsoftware, u.s.w.

Buchläden

  • Barnes & Noble
  • DigitalGuru – nahe dem Sunnyvale Fry’s, der Laden hat ausschließlich Computer und Engineering Bücher!
  • AAA Book Search – Buchsuche und Preisvergleiche
  • Stanford Bookstore – Der Buchladen auf dem Gelände der Stanford Universität
  • Recycle Bookstore (408) 286–6275)
  • abebooks.com – Abebooks is the world’s largest online marketplace for books. Whether it’s new, used, rare, or out-of-print, you can find it here, through our community of over 13,500 independent booksellers selling 80 million books from around the globe.

Malls

Malls sind die großen Einkaufszentren, häufig mit Kinokomplex, wie man sie aus dem Fernsehen kennt. Enthält auch einen großen Bereich mit Fastfood.

  • Valley Fair – teure und reichhaltige Mall (mit dem nächsten Apple Store). Empfehlenswert, wenn man was Gutes sucht.
  • Santana Row – weniger eine Mall, als eine superteure Einkaufsstraße mit Restaurants. Liegt auf der anderen Straßenseite von Valley Fair.
  • Oakridge – ähnlich Valley Fair, aber nicht so schön und kleiner.
  • Great Mall – outlet mall, d.h. bekannte Ketten verkaufen ihre Ladenhüter hier billiger. Ehemalige Autofabrik – riesig!
  • Stanford Shopping Center – vor der bekannten Uni gelegen. Outdoor Mall, ähnliche Läden wie Valley Fair – teuer
  • Sunnyvale Town Center Mall – ist nach der Dot-Com Krise pleite gegangen
  • Vallco Fashion Mall – ebenfalls eine ziemlich traurige Mall – hauptsächlich Sears und JC Penney halten die Fahne hoch. Macys hat ihren Laden dort geschlossen.
  • Gilroy Premium Outlets – outlet mall, hauptsächlich mit Luxusketten – kann sich lohnen!

Möbel

Möbel sind sehr teuer und i.d.R. geschmacklos – zumindest für den durchschnittlichen Deutschen…

Alternativ kann man auf Craigslist nach gebrauchten Möbeln gucken.

Sonstige Läden

  • DSW ist eine gute Quelle für günstige Schuhe! Ich kaufe fast ausschließlich dort (Stevens Creek Blvd nahe Barnes & Noble) – mit Kundenkarte bekommt man alle $250 Umsatz einen 10% Gutschein!
  • Bed Bath & Beyond – alles für das Bad, das Schlafzimmer und die Küche. Besonders bekannt durch die Rabatte, die man per Post bekommt (20% auf einen Artikel, 20% auf den gesamten Einkauf)
  • Tuesday Morning – eine Kette, die Restbestände verkauft. Dazu gehören auch Kaffeemaschinen, ein paar Möbel, Bettbezüge, u.s.w. – sehr sehr günstig!

Craigslist

Craigslist ist die (kostenlose) Avis im Valley. Anzeigen sind ebenfalls kostenlos und man kann prima was auf der Nachbarschaft finden. Pflicht, wenn man eine Mietwohnung sucht!

Essen

Essen ist in den USA recht teuer. Zumal die Portionen sehr groß sind, kann es billiger sein essen zu gehen, als zu kochen – gerade wenn man Single ist.

Restaurant-Tips

Opentable erlaubt die einfache Reservierung in vielen Restaurants der Umgebung

Der Tip ist immer erforderlich und mindestens 15%, bei sehr gutem Service auch mal 20% oder sogar mehr. Nur wenn der Service extrem schlecht ist, kann man ihn reduzieren. Faustformeln gibt es viele: durch 10 + 50%, durch 6, durch 5 und abrunden, Tax (Steuer) mal 2 (macht 16.5% Tip) – je nach Laune. Bei Gruppen ab 6–8 Personen kann der Tip evtl. schon in der Rechnung sein (meistens um die 18%). Achtung: man braucht keinen Tip auf die Steuer bezahlen, also: die Faustformel auf die Summe vor der Steuer anwenden!

Bei Sodas (Cola, Sprite, etc) sind Refills im Preis immer enthalten, wenn das Getränk nicht in der Flasche oder Dose kommt.

Wenn möglich vorher reservieren (zur Not telefonisch) – man bekommt einen besseren Platz und die Wartezeit ist auch nicht so lang, denn bei einigen Restaurants muss man teilweise bis zu 1 Stunde warten. Manresa nimmt keine Reservierungen nach 17 Uhr mehr an und ist fast immer ausgebucht. Bis 15 Minuten Warten ist in den USA normal. Über eine Stunde ist im Valley sehr selten (außer an einigen Feiertagen).

In einigen Restaurants bekommt man einen Pager, der einem anzeigt, wenn man an der Reihe ist. Es wird einem erzählt: die Reichweite reicht bis auf die Straße direkt vor dem Restaurant, aber nicht weiter. Unsinn! Die Hersteller bewerben sie mit 1/4 bis 1/2mi Reichweite (in idealen Bedingungen bis 2mi). Das reicht zumindest einige Läden in einer Mall weit…

In allen Restaurants kann man die Reste mitnehmen (to-go), das ist in vielen Restaurants sogar üblich, weil die Portionen so groß sind. Und schon hat man ein Mittagessen/Abendessen für den nächsten Tag.

Die Frage des Kellners ob alles ok ist, ist übrigens ernst gemeint. Wenn etwas beim Essen nicht gut ist, sollte man das sagen! Restaurants sind üblicherweise sehr bemüht es dem Kunden recht zu machen! Selbst wenn man die halbe Portion schon gegessen hat, wird problemlos ausgetauscht oder aber die Rechnung um den Preis des Gerichts reduziert. Restaurants leben von ihren zufriedenen Kunden (die wiederkommen und das Restaurant empfehlen) und sind somit sehr an Feedback interessiert. Wenn jemand sagt: “Alles ist ok, danke!” und es ist nicht ok, dann ist es sein Problem!

Günstige Restaurants

Hier führe ich Restaurantketten auf, die nicht in die typische Fast-Food Kategorie fallen.

Frühstück und Brunch

  • Brunch im Dolce Hayes Mansion – $39.95 pro Person, aber mit ausgezeichneter Auswahl! Bester Brunch im Valley.
  • Frühstück (nunja, bis 2 Uhr) bei Stacks’ – am Wochenende bis zu 1.5 Stunden Wartezeit! Keine Reservierungen für Gruppen kleiner als 6. Aber mit Abstand das beste Frühstück! Blaubeer Pfannkuchen z.B. Auch die Omletts sind großartig. Am Sonntag ist in Campbell Farmersmarkt, dann ist es besonders voll.
  • IHOP (International House of Pancakes) – einfaches Frühstück den ganzen Tag über
  • Hobee’s leckere Pan Cakes, ungewöhnliche Burger
  • Denny’s – das untere Ende der Diners, aber 24h geöffnet (wenn man nachts um 1 Uhr was braucht und keine Ansprüche stellt)
  • Carrows – ähnlich Denny’s

Fast-Food

Burger, French Fries (nein, Freedom Fries sagt im Valley keiner), Hot-Dogs, Burritos, Pizzas u.s.w

  • San Pedro Square Market Eine große Ansammlung von Fast-Food Restaurants (keine Ketten!) – super Essen, abends häufig mit Live-Musik, zudem großem Sitzbereich draußen. Tip: die (teuren) Konjoe Burger – ohne Frage der beste Burger, den ich je hatte! Lange Wartezeit. Parken im Parkhaus auf der anderen Straßenseite. In der gleichen Straße gibt es zudem diverse Bars und Restaurants.
  • In & Out Burger
  • Carl’s Jr
  • McDonalds, Burger King, u.s.w sind schlechter als in Deutschland – aber auch deutlich billiger!
  • Food Malls – in den großen Malls gibt es sogenannte Food Malls, wo man prima ganz billig essen kann

Ordentliche Restaurants – nicht für jeden Tag

  • California Cafe – ebenfalls in Los Gatos, einfallsreiche gehobene Küche zu günstigen Preisen – im letzten Jahr deutlich abgefallen!
  • Amber India – der beste Inder – Punkt!
  • Kyoto Palace – Japanisches Teppan Dining, d.h. die Herdplatte ist im Tisch und der Kock zaubert vor den Augen. Fried Reis nehmen! Ähnlich Benihana http://www.benihana.com (die sind aber ’ne Kette) – klassischer Ort für Geburtstage mit Ständchen…
  • Habana Cuba – die Nachbarschaft sieht etwas komisch aus, aber das Essen ist authentisch – teilweise mit Live-Musik!
  • Korean Palace – authentische Koreanische Küche mit der Herdplatte im Tisch zum Selberbraten
  • Chef Chu – der wohl bekannteste Chinese im Valley
  • Sino – ein sehr guter moderner Chinese mit Bar-Area in Santana Row gelegen. Mir hat die Peking Ente sehr gefallen.
  • Taiwan Restaurant – in Willow Glen (San Jose) gelegen, sehr gute Küche
  • Opa! – in Willow Glen und Campbell, ein gutes griechisches Restaurant!

Besonderer Anlaß ($50-$xxx pro Person)

  • Manresa – sehr teuer: Menü ab $65-$120 – bestes Restaurant in der Gegend! Zwei Michelin-Sterne.
  • Sent Sovi – französische Küche, sehr gut
  • Alexander’s Steakhouse – ganz neues Steakhaus in Cupertino, sehr teuer, eindeutig das Beste!
  • Forbes Mill Steakhouse – gute Steaks!
  • Sundance The Steakhouse – ähnlich gute Steaks wie in Forbes Mill in Palo Alto gelegen
  • LaForet – abseits in den Hügeln gelegen ist es der Ort im Silicon Valley für einen Heiratsantrag (habe ich nur gelesen), aber das Essen ist auch was besonderes…

“Deutsche” Küche

  • Naschmarkt – Österreichische Küche, bestes Restaurant in diesem Bereich! Reservierung notwendig!
  • Teske’s Germania – erstaunlich gute Küche (die Spätzle sind aber nicht mein Fall). Deutsche Bier!
  • Gunther’s – Gunter – wohl über 80 – steht in der Küche. Einfache deutsche Hausmannskost. Ich mag das Wiener Schnitzel.
  • Speisekammer in Alameda – sehr gut, war ich aber seit 10 Jahren nicht mehr.
  • Harry’s Hofbrau – Fastfood Restaurant mit deutschem Einschlag
  • Suppenküche in San Francisco – Bißchen laut, super Essen und Atmosphaere!
  • Walzwerk in San Francisco – Klein und gemütlich, 1a Bedienung, gute Küche.
  • Tyrolean Inn in Ben Lomond – Im Wald gelegen, wie man sich eine Berghütte so vorstellt. Lohnt sich in Verbindung mit einem Trip zu den Redwoods in Big Basin.

Was kann man unternehmen?

Im direkten Umfeld (Silicon Valley) ist es etwas dünn, aber im weiteren Umfeld sind die Möglichkeiten nahezu grenzenlos! Ich hoffe, dass hier im Laufe der Zeit Ideen gesammelt werden. San Francisco hat sehr sehr viel zu bieten!

San Jose Downtown hat Ihre eigene Website.

Kinos

Kinokarten kosten um die $12 für Erwachsene. Es gibt keine Platzreservierungen und somit ist das Kaufen von Tickets online (für $1 mehr pro Ticket!) totaler Unsinn – trotzdem machen die Kinos massiv Werbung dafür. Achtung: viele Kinos akzeptieren keine Kreditkarten für Tickets! Meistens steht aber ein ATM in der Nähe. Nachmittags sind die Karten teilweise deutlich billiger.

Ich versuche immer 15–20 Minuten vor der Vorstellung da zu sein um einen guten Platz zu bekommen. Popcorn (Salzig! Das süße Popcorn heißt in den USA “Kettle Corn”) und Cola gehören dazu, sind aber ebenfalls teuer. Bei Costco kann man 4 Tickets für eine große Kinokette (AMC) als Gutschein für $34 kaufen.

Konzerte, Theater

Konzerte gibt es reichlich, via Ticketmaster kann man Karten bekommen. Gute Konzerte sind teilweise Monate im Voraus ausverkauft!

Besonderer Tip: Konzerte in der Mountain Winery – hoch in den Hügeln mit Blick über das Valley gelegen ist es der open-air Spot für Konzerte. Man kann auch gleich ein Dinner buchen – sehr nett bei der Aussicht! Durch das Dinner ist man auch sehr früh da und hat einen guten Parkplatz.

Villa Montalvo ist ähnlich – aber ohne die schöne Aussicht. Die Parkplätze sind weit entfernt, es gibt aber einen kostenlosen Busshuttle.

San Jose Downtown hat viele kleine Restaurants, Bars und Clubs mit Live-Musik.

Museen

Natur / Hiking

  • Parks in Kalifornien
    • Año Nuevo SR – bekannt für seine Seeelefanten und Seeotter (zur richtigen Jahreszeit)
    • Pinnacles – ca 1.5h südlich vom Valley, uralte Vulkankegel und das Gebiet wo der kalifornische Kondor wieder angesiedelt wird.
  • Open Space Preserves sind keine echten Parks, aber gute Wandergegenden
  • Auf dem Gelände der Stanford Universität kann man – am Wochenende – sehr schöne Spaziergänge machen.
  • ein Spaziergang zum Radioteleskop von Standford. Sehr beliebt auch bei Joggern.

Sonstiges

  • San Jose Flea Market – teilweise über 2000 Verkäufer, 12km lang! Mittwoch-Sonntag, wobei am Wochenende natürlich am meisten los ist – in der Woche sind es deutlich weniger Stände. Großer Farmers Markt!
  • Renaissance Fair – jeden Herbst bei Gilroy – zurück ins Mittelalter – hauptsächlich kleine Stände, aber sehr unterhaltsam. Toll für Kinder!
  • Gilroy Garlic Festival – jedes Jahr im Sommer an einem Wochenende dreht sich alles ums Knoblauch (nunja, Gilroy riecht fast immer nach Knoblauch – ehrlich!). Ein großer Spaß (Knoblaucheis, Pommes mit Knoblauch, u.s.w.) – aber Achtung: über 40 Grad heiß! Einen großen Hut, Sonnenschutz und viel trinken!!! Parken ist etwas weg und ein Fußmarsch ist angesagt. Trotzdem muss man das mindestens einmal mitmachen.
  • Silicon Valley Electronics Fleamarket – jeden Monat ein Flohmarkt mit Elektronik… Einer der letzten “historischen” Erinnerungen an Silicon Valley der 70er und 80er Jahre.
  • Paramount’s Great America – Nord-Kaliforniens größter Freizeitpark
  • Raging Waters – Der größte Wasserpark in Kalifornien
  • Bonfante Gardens – Familienpark – eher was für die ganz Kleinen
  • Santa Cruz Beach Boardwalk – ebenfalls ein Freizeitpark direkt am Wasser in Santa Cruz gelegen.
  • Monterey Bay Aquarium – Wohl das bekannteste Aquarium der Welt mit einem der größten Becken
  • Aquarium of the Bay – In San Francisco direct am Pier 39 gelegen. Winzig im Vergleich zum Aquarium in Monterey, aber trotzdem nett.
  • Roaring Camp Railroads – Fahrt mit einer authentischen Dampfeisenbahn aus dem 18. Jahrhundert durch die Redwoods. Nicht billig.
  • The Mystery Spot – irgendwie schräg

Kurz-Trips

  • Lake Tahoe – der hoch gelegene fast spiegelklare große See liegt auf der Grenze zwischen Kalifornien und Nevada. Der See ist ein klassisches Wochenend-Ausflugsziel – im Winter das Ski-Gebiet für die Bay Area! Man kann entweder ein Ferienhaus (mit ein paar Freunden mieten) oder in ein Hotel gehen. Sobald man in Nevada ist (in Süd-Tahoe ist die Grenze kaum zu erkennen), gibt es Spielkasinos.
  • Yosemite Nationalpark – Reservierungen für Hotels sollte man sehr sehr rechtzeitig machen. Teuer!
  • Death Valley – am Besten im Frühling, keinesfalls im Sommer!
  • Big Sur – Klassischer Highway–1
  • Las Vegas
  • Los Angeles
  • Hawai

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

Atari ST Book: ROM disk listing

Commented assembly listing for the ROM disk in an Atari ST-Book. Also available on GitHub.

***
*  Half the ST Book ROM contains a ROM disk, which is mapped as drive 'P' into GEMDOS
***
$e40000 : abcd ef42                STBOOK_EXTROM:DC.L    $abcdef42

$e40004 : 00e4 0200                CA_NEXT:    DC.L      CA2_NEXT                   ; pointer to next application
$e40008 : 08e4 0026                CA_INIT:    DC.L      ROMDISK_CODE
$e4000c : 00e4 0026                CA_RUN:     DC.L      ROMDISK_CODE
$e40010 : 1776                     CA_TIME:    DC.W      $1776
$e40012 : 69b6                     CA_DATE:    DC.W      $69b6
$e40014 : 0000 016c                CA_SIZE:    DC.L      $0000016c
$e40018 : 424f 4f4b 524f 4d2e 5052+CA_NAME:    DC.B      'BOOKROM.PRG',0
$e40024 : ffff                                 DC.W      $ffff

$e40026 : 601a                     ROMDISK_CODE:bra.s    ROMDISK_INIT              ; PRG_magic
$e40028 : 0000 014c                            DC.L      $0000014c                 ; PRG_tsize
$e4002c : 0000 0000                            DC.L      $00000000                 ; PRG_dsize
$e40030 : 0000 0000                            DC.L      $00000000                 ; PRG_bsize
$e40034 : 0000 0000                            DC.L      $00000000                 ; PRG_ssize
$e40038 : 0000 0000                            DC.L      $00000000                 ; PRG_res1
$e4003c : 0000 0000                            DC.L      $00000000                 ; PRGFLAGS
$e40040 : 0000                                 DC.W      $0000                     ; ABSFLAG

$e40042 : 303c 00fc                ROMDISK_INIT:move.w   #252,d0                   ; 252kb ROMDISK
$e40046 : 31fc 0200 0800                       move.w    #512,(ROMDISK_BPB).w      ; 512 bytes per sector
$e4004c : 31fc 0001 0802                       move.w    #1,(ROMDISK_BPB+2).w      ; 1 sector per cluster
$e40052 : 31fc 0200 0804                       move.w    #512,(ROMDISK_BPB+4).w    ; 512 bytes per cluster
$e40058 : 31fc 0001 0806                       move.w    #1,(ROMDISK_BPB+6).w      ; sector length of root directory
$e4005e : 31fc 0001 0810                       move.w    #1,(ROMDISK_BPB+16).w     ; 16 bit FAT
$e40064 : e340                                 asl.w     #1,d0
$e40066 : 3200                                 move.w    d0,d1
$e40068 : 31c1 080e                            move.w    d1,(ROMDISK_BPB+14).w     ; 504 clusters per disk
$e4006c : d27c 00ff                            add.w     #$ff,d1
$e40070 : e041                                 asr.w     #8,d1
$e40072 : 31c1 0808                            move.w    d1,(ROMDISK_BPB+8).w      ; 2 sectors per FAT
$e40076 : 31c1 080a                            move.w    d1,(ROMDISK_BPB+10).w     ; starting sector of second FAT = 2
$e4007a : 31c1 080c                            move.w    d1,(ROMDISK_BPB+12).w     ; starting sector of data = 2*2+1 = 5
$e4007e : d378 080c                            add.w     d1,(ROMDISK_BPB+12).w
$e40082 : 0678 0001 080c                       addi.w    #1,(ROMDISK_BPB+12).w

$e40088 : 303c 000f                            move.w    #15,d0                    ; ROMDISK is drive P
$e4008c : 7e01                                 moveq     #1,d7
$e4008e : e1af                                 lsl.l     d0,d7
$e40090 : 8fb8 04c2                            or.l      d7,(_drvbits).w           ; enable drive

$e40094 : 21f8 0472 0816                       move.l    (hdv_bpb).w,(ROMDISK_HDV_BPB).w
$e4009a : 21f8 0476 081a                       move.l    (hdv_rw).w,(ROMDISK_HDV_RW).w
$e400a0 : 21f8 047e 081e                       move.l    (hdv_mediach).w,(ROMDISK_HDV_MEDIACH).w

$e400a6 : 41fa 0024                            lea       ROMDISK_BPB(pc),a0
$e400aa : 21c8 0472                            move.l    a0,(hdv_bpb).w
$e400ae : 41fa 002c                            lea       ROMDISK_RW(pc),a0
$e400b2 : 21c8 0476                            move.l    a0,(hdv_rw).w
$e400b6 : 41fa 0034                            lea       ROMDISK_MEDIACH(pc),a0
$e400ba : 21c8 047e                            move.l    a0,(hdv_mediach).w

$e400be : 487a 00ae                            pea       ROMDISK_insttext(pc)
$e400c2 : 3f3c 0009                            move.w    #9,-(sp)
$e400c6 : 4e41                                 trap      #1
$e400c8 : 5c4f                                 addq.w    #6,sp
$e400ca : 4e75                                 rts

$e400cc : 302f 0004                ROMDISK_BPB:move.w    4(sp),d0
$e400d0 : 2078 0816                            movea.l   (ROMDISK_HDV_BPB).w,a0
$e400d4 : 43fa 002e                            lea       ROMDISK_BPB_IMP(pc),a1
$e400d8 : 6000 001e                            bra       ROMDISK_CHKDRV
$e400dc : 302f 000e                ROMDISK_RW: move.w    $e(sp),d0
$e400e0 : 2078 081a                            movea.l   (ROMDISK_HDV_RW).w,a0
$e400e4 : 43fa 0026                            lea       ROMDISK_RW_IMP(pc),a1
$e400e8 : 6000 000e                            bra       ROMDISK_CHKDRV
$e400ec : 302f 0004                ROMDISK_MEDIACH:move.w 4(sp),d0
$e400f0 : 2078 081e                            movea.l   (ROMDISK_HDV_MEDIACH).w,a0
$e400f4 : 43fa 0074                            lea       ROMDISK_MEDIACH_IMP(pc),a1
$e400f8 : b07c 000f                ROMDISK_CHKDRV:cmp.w  #15,d0                    ; ROMDISK is drive P
$e400fc : 6600 0004                            bne       ROMDISK_CHKDRVx
$e40100 : 2049                                 movea.l   a1,a0
$e40102 : 4ed0                     ROMDISK_CHKDRVx:jmp   (a0)

$e40104 : 203c 0000 0800           ROMDISK_BPB_IMP:move.l #ROMDISK_BPB,d0          ; return ROMDISK_BPB
$e4010a : 4e75                                 rts

$e4010c : 207c 00e4 0222           ROMDISK_RW_IMP:movea.l #ROMDISK_BASE,a0
$e40112 : 7000                                 moveq     #0,d0
$e40114 : 302f 000c                            move.w    $c(sp),d0                 ; start sector number
$e40118 : e188                                 lsl.l     #8,d0
$e4011a : e388                                 lsl.l     #1,d0
$e4011c : d1c0                                 adda.l    d0,a0
$e4011e : 7400                                 moveq     #0,d2
$e40120 : 342f 000a                            move.w    $a(sp),d2                 ; number of sectors
$e40124 : e98a                                 lsl.l     #4,d2
$e40126 : 226f 0006                            movea.l   6(sp),a1                  ; destination buffer
$e4012a : 2009                                 move.l    a1,d0
$e4012c : 082f 0000 0005                       btst      #0,5(sp)                  ; write access? That will fail!
$e40132 : 6704                                 beq.s     ROMDISK_RW_IMP2
$e40134 : 70f3                                 moveq     #-13,d0
$e40136 : 4e75                                 rts

$e40138 : 0800 0000                ROMDISK_RW_IMP2:btst  #0,d0                     ; the destination has to be an even address for fast copying
$e4013c : 6618                                 bne.s     ROMDISK_RW_IMP4
$e4013e : 22d8                     ROMDISK_RW_IMP3:move.l (a0)+,(a1)+
$e40140 : 22d8                                 move.l    (a0)+,(a1)+
$e40142 : 22d8                                 move.l    (a0)+,(a1)+
$e40144 : 22d8                                 move.l    (a0)+,(a1)+               ; copy 32 bytes
$e40146 : 22d8                                 move.l    (a0)+,(a1)+
$e40148 : 22d8                                 move.l    (a0)+,(a1)+
$e4014a : 22d8                                 move.l    (a0)+,(a1)+
$e4014c : 22d8                                 move.l    (a0)+,(a1)+
$e4014e : 5382                                 subq.l    #1,d2
$e40150 : 6b14                                 bmi.s     ROMDISK_RW_IMP6
$e40152 : 66ea                                 bne.s     ROMDISK_RW_IMP3
$e40154 : 6010                                 bra.s     ROMDISK_RW_IMP6
$e40156 : e74a                     ROMDISK_RW_IMP4:lsl.w #3,d2
$e40158 : 12d8                     ROMDISK_RW_IMP5:move.b (a0)+,(a1)+
$e4015a : 12d8                                 move.b    (a0)+,(a1)+
$e4015c : 12d8                                 move.b    (a0)+,(a1)+
$e4015e : 12d8                                 move.b    (a0)+,(a1)+
$e40160 : 5382                                 subq.l    #1,d2
$e40162 : 6b02                                 bmi.s     ROMDISK_RW_IMP6
$e40164 : 66f2                                 bne.s     ROMDISK_RW_IMP5
$e40166 : 4280                     ROMDISK_RW_IMP6:clr.l d0                     ; no error
$e40168 : 4e75                                 rts

$e4016a : 4280                     ROMDISK_MEDIACH_IMP:clr.l d0                 ; no error
$e4016c : 4e75                                 rts

$e4016e : 3032 3533 4b20 524f 4d44+ROMDISK_insttext:DC.B '0253K ROMDISK installed as P:\r\n',0
$e4018e : 0000 0000 ffff ffff                  DC.L      0          ; no relocation table
$e40196 : ffff ffff ffff ffff                  DCB.B     110,$ff    ; fill the 512-page to the end with $FF

$e40200 : 0000 0000                CA2_NEXT:   DC.L      $000000
$e40204 : 00e4 0222                CA2_INIT:   DC.L      ROMDISK_BASE
$e40208 : 00e4 0222                CA2_RUN:    DC.L      ROMDISK_BASE
$e4020c : 1869                     CA2_TIME:   DC.W      $1869
$e4020e : 99c6                     CA2_DATE:   DC.W      $99c6
$e40210 : 0003 fa00                CA2_SIZE:   DC.L      509 * 512
$e40214 : 4341 5254 524f 4d2e 494d+CA2_NAME:   DC.B      'CARTROM.IMG',0
$e40220 : ffff                                 DC.W      $ffff

$e40222 : 0000 0000 ffff 0400      ROMDISK_BASE:DC.B     $00,$00,$00,$00,$ff,$ff,$04,$00 ;509 * 512-byte sectors
$e4022a : 0500 0600 0700 0800                  DC.B      $05,$00,$06,$00,$07,$00,$08,$00
$e40232 : 0900 0a00 0b00 ffff                  DC.B      $09,$00,$0a,$00,$0b,$00,$ff,$ff
$e4023a : 0d00 0e00 0f00 1000                  DC.B      $0d,$00,$0e,$00,$0f,$00,$10,$00
$e40242 : 1100 1200 1300 1400                  DC.B      $11,$00,$12,$00,$13,$00,$14,$00
$e4024a : 1500 1600 1700 1800                  DC.B      $15,$00,$16,$00,$17,$00,$18,$00
$e40252 : 1900 1a00 1b00 1c00                  DC.B      $19,$00,$1a,$00,$1b,$00,$1c,$00
$e4025a : 1d00 1e00 1f00 2000                  DC.B      $1d,$00,$1e,$00,$1f,$00,$20,$00
$e40262 : 2100 2200 2300 2400                  DC.B      $21,$00,$22,$00,$23,$00,$24,$00
$e4026a : 2500 2600 2700 2800                  DC.B      $25,$00,$26,$00,$27,$00,$28,$00
...

Atari ST Book: Internal Registers

By looking at the ROM code, available software and the hardware layout I can now document most specialty registers in the ST Book from Atari. I hope it is useful to you. Also available at GitHub.

The main ROM for the ST Book starts at $e0000 and ends at $e3ffff (256kb large). At $e40000 starts the ROM-Disk (also 256kb large), which is automatically activated as drive P.

YM Port A

Bit Description
0 Side Select (S0SEL)
1 Disk 0 Select (D0SEL)
2 Disk 1 Select (D1SEL)
3 MAX241 Pin 20 (TI3) – RS232 RTS (/MFPRTS)
4 MAX241 Pin 6 (TI2) – RS232 DTR (/MFPDTR)
5 Centronics Strobe (STROBE)
6 IDE Reset (IDERESET)
7 FDD Dense Selection (FDD_DENSE_SEL)

Autovector Interrupt Level 7 /POWER FAIL (NMI)

MFP Input Pins

Bit Description
7 Power Alarms (Power Alarms) (POWER_ALARMS-) = (/SRCLOW /RTC_ALARM /POWERON)
6 MAX241 Pin 22 (RD4) – RS232 CD (/MFPRI)
5 XDISKINT
4 KTDK (VBL) (KTOK)
3 XBLTINT
2 MAX241 Pin 26 (RD3) – RS232 CTS (/MFPCTS)
1 MAX241 Pin 5 (RD2) – RS232 DCD (/MFPCD)
0 Centronics Busy (BUSY)

$D0000 – Unknown $D0004 – Unknown

$FF820B

Bit Description
0 0: enable combo -> shadow controller video transfer, 1: disable combo -> shadow controller video transfer

$FF827E

Bit Description
0 0: Shadow Chip OFF
1 0: /SHIFTER_OFF output (Unused in STBook)
2 0: POWER_OFF (Turns off main VCC when high)
3 0: /22ON output (turns off LCD Bias when HIGH)
4 0: REFRESH_MACHINE output (turns on refresh controller)
5 0: RS–232_OFF output (turns off +/- 10 generator)
6 0: (Unused in STBook)
7 0: MTR_POWER_ON (turns on IDE drive motor supply)

$FF9200 (Configuration/Signal register)

Bit Description
0 0: Power Button pressed /(POWER_SWITCH)
1 0: Top is closed /(TOP_CLOSED)
2 0: RTC Alarm triggered /(RTC_ALARM)
3 0: “Common Source” voltage level below 7.2V /(SOURCE_DEAD) – triggers an NMI
4 0: “Common Source” voltage level below 8.8V /(SOURCE_LOW)
5 0: External Modem (Pin 10 on J204) /(MODEM_WAKE)
6 Reserved (always 1)
7 0: Triggered from the Expansion Port /(EXPANSION_WAKE)
8 Reserved
9 Reserved
10 Reserved
11 Reserved
12 Reserved
13 SELF TEST (?)
14 LOW SPEED FLOPPY
15 DMA AVAILABLE

$FF9210 8-bit Common Power Voltage Level in 100mV steps

$FF9214 8-bit Reference Voltage Level in 100mV steps