LaserWriter IISC SCSI protocol reversed (for emulator implementation)

demik

6502
Hello everyone,

Got sidetracked since a few month, ended up doing this... Decoded most of the LaserWriter IISC SCSI protocol and figured I should post it here, it's probably interesting for the Snow or PiSCSI team (poke @slipperygrey)

I do have a complete Snow implementation as well, please give me 2-3 weeks and there will be a Pull Request on GitHub for a reference implementation
If you know people interested in this, please forward this thread to them, I will gladly help other people implementing this in SCSI emulators (also thanks to everyone in the IRC channel for helping me during the process).

If you have questions don't hesitate to ask or comment
Note: this is 90% done, there is probably corners cases to be found

Introduction​

This has been reversed from both the controller board and the System 6 driver (6.0.5)
There is strong evidence that this also applies to the Personal LaserWriter SC, but as I had only access to IISC resources, the work is focused on the IISC only.

The IISC hardware can handle 4MB of DRAM but the driver doesn't make use of it, so it's useless to go above 1MB.
Probably provisioning for future printers that never happened

Vendor specific names are the standard ones from the SCSI-2 spec, except 0x06 which is my own

Overall architecture​

The printer has a 1MB framebuffer, almost all of it is used to buffer the next printed page
The printer work usually at 300 dpi, although it can work at 288 dpi. It implies some hardware trickery (most likely bigger margins with the engine still at 300 dpi).
More information in the 0x04 command below. For emulator implementation, it doesn't really matter.

The driver composite the page into the printer framebuffer by sending SCSI commands to the printer, and then start the Canon raster engine
Some of them are QuickDraw-like commands but not really. The drivers make the translation from the QuickDraw objects into its own stuff which is passed to the printer.

The scaling at 25%, 50%, 75% is done by the driver. At 288 dpi, that's a pixel perfect scale of 1x, 2x, and 3x respectively from what you see in MacOS.

The printer cannot draw objects. The driver convert QuickDraw objets to custom black and white bitmap masks, into multiple bands if needed.
The thing has part of a QuickDraw stack! Dithering is handled by the driver, not the printer.

Overall the implementation is not really hard in a emulator, the driver does the heavy lifting.

SCSI commands​

The IISC uses only group 0 SCSI commands (six-byte CDBs)

0x00 TEST UNIT READY (Standard & Mandatory)

You likely want to return SCSI_GOOD (0x00) here
There is a few places during the printing sequence where the driver will poll the printer so the printer has time to do work on its own.
- after each FORMAT command
- during the printer search (after the two INQUIRY)
- before each READ BLOCK LIMITS command
- after a command PRINT

If needed, you may want to return STATUS_BUSY here (0x08).

Suggested emulator implementation: return STATUS_GOOD everywhere

0x03 REQUEST SENSE (Standard & Mandatory)

This is likely where the hardware error shows up (overtemp, paper jam etc).
Didn't manage to trigger anything or decode the message. The driver is reading 12 bytes

Suggested emulator implementation: return 12 null bytes, anything with more or less zeroes seems to works fine (driver seems to check mostly the first and fourth byte if that matter)

0x04 FORMAT (Standard for printers. Optional in SCSI spec)

This one is on the opaque side. Information below is for documentation, as implementation is not really needed unless you want to go for a faithful one.
It is believed that this command is used to set the timing of the raster engine

The output "beam" of the raster engine is a 8 bit FIFO that goes through a 74LS166, hence why some "8" values or multiple of it in the formulas below.
As the printer is black and white only, 1 byte represent 8 pixels. The FIFO has a depth of 512 bytes, which is sufficient for a scan line.

Driver will send two FORMAT commands in a row. CDB structure for printers described here:
https://www.staff.uni-mainz.de/tacke/scsi/SCSI2-11.html

IISC uses only "Set form" (0x00) and "Vendor-specific" (0x02) format types
Description below are guesses. Variable names:
  • IW = Image Width
  • IH = Image Height
  • PH = Page Height. Calculated within by the driver. Not visible in the FORMAT payload
  • SS = Scanline size in bytes. Calculated as (pixel width + 7 / 8) within the driver
For A4 the Service Manual specifies a printable area of 8.0" x 11.27"
In the two sequences below, the page format was set to A4

A4 at 300 dpi:
FORMAT [04 00 00 00 04 00] <- [00 7B 00 11]
FORMAT [04 02 00 00 04 00] <- [01 2C 0D 2F]

Code:
+=======+===============+================================+
| Field |    Value      |          Meaning               |
|=======+===============+================================|
|   B   | 0x007B = 123  | (PH − IH) / 2 + 60             |
|-------+---------------+--------------------------------|
|   A   | 0x0011 = 17   | (2550 − (SS * 8)) / 16 + 8     |
|-------+---------------+--------------------------------|
|   D   | 0x012C = 300  | SS (Scanline size)             |
|-------+---------------+--------------------------------|
|   C   | 0x0D2F = 3375 | IH (scan lines per page)       |
+=======+===============+================================+
  • B = Vertical margins. (in lines). ~ 10 mm margins
  • A = Horizontal margins (in bytes). 2550 = max printable pixels @ 300 dpi, including margins. ~ 6mm margins.
  • D = 300 bytes * 8 bits = 2400 pixels wide
  • C = 3375 scan lines = 11.25" @ 300 DPI (slightly less than the 11.27" specified in the service manual for A4)
In human words this is the driver telling the printer "expect 3375 raster lines of 300 bytes each"

A4 at 288dpi (4% shrink but pixel perfect scale):
FORMAT [04 00 00 00 04 00] <- [00 BE 00 17]
FORMAT [04 02 00 00 04 00] <- [01 20 0C A8]

With the same decoding as above it gives you this comparaison table

Code:
+====================+=========+=========+
|      Field         | 300 DPI | 288 DPI |
|====================+=========+=========|
| IW (pixels)        | 2400    | 2304    |
|--------------------+---------+---------|
| IH (pixels)        | 3375    | 3240    |
|--------------------+---------+---------|
| SS (bytes)         | 300     | 288     |
|--------------------+---------+---------|
| IW (inches)        | 8.0"    | 8.0"    |
|--------------------+---------+---------|
| IH (inches)        | 11.25"  | 11.25"  |
|--------------------+---------+---------|
| Horizontal margins | 17      | 23      |
|--------------------+---------+---------|
| Vertical margins   | 123     | 190     |
+====================+=========+=========+

Suggested emulator implementation: read 4 bytes if transfer size is specified, then discard, otherwise return STATUS_GOOD. The 0x06 CDB (SETUP) is more useful.

0x05 READ BLOCK LIMITS (Non standard, Vendor specific)

This one is tricky. It's a two stage data transfer in three SCSI phases:
  • phase 1, 0x05 CDB command with 0x0A of transfer size
  • phase 2, read 0x0A bytes. It's an header for the pixel mask following. The driver will do a normal SCSI write for this phase
  • phase 3, read <n> amount of bytes. <n> is calculated with the values read from the header. The driver will do a blind SCSI write for this phase

Do not reselect the device during phase 2 and 3.
The driver cut the document to be printed into multible bands. Each band is likely QuickDraw object or part of a bitmap MacOS side.
You will receive a 0x05 CDB for every band that the driver creates. Complex documents can easily go over 10000 bands.

The header is 10 bytes. It contains a set of coordinates which is the band viewport location

Code:
+=====+========+========+========+========+========+========+========+========+
|  Bit|   7    |   6    |   5    |   4    |   3    |   2    |   1    |   0    |
|Byte |        |        |        |        |        |        |        |        |
|=====+========+========+========+========+========+========+========+========|
| 0   |                    x1 top coordinate (MSB)                            |
|-----+-----------------------------------------------------------------------|
| 1   |                    x1 top coordinate (LSB)                            |
|-----+-----------------------------------------------------------------------|
| 2   |                    y1 top coordinate (MSB)                            |
|-----+-----------------------------------------------------------------------|
| 3   |                    y1 top coordinate (LSB)                            |
|-----+-----------------------------------------------------------------------|
| 4   |                    x2 bottom coordinate (MSB)                         |
|-----+-----------------------------------------------------------------------|
| 5   |                    x2 bottom coordinate (LSB)                         |
|-----+-----------------------------------------------------------------------|
| 6   |                    y2 bottom coordinate (MSB)                         |
|-----+-----------------------------------------------------------------------|
| 7   |                    y2 bottom coordinate (LSB)                         |
|-----+-----------------------------------------------------------------------|
| 8   |                    Padding (always 0x00)                              |
|-----+-----------------------------------------------------+--------+--------|
| 9   |                    Padding (always 0x00)            | Mask*  |    1   |
+=====+=====================================================+========+========|

* Mask type (set in bit 1). A mask type of 0 is for black pixels, a mask type of 1 is for white pixels (erase). Bit 0 is always set

Suggested emulator implementation:
- You may want to return CHECK CONDITION (0x02) if the header is not 10 bytes long and skip to the next command, or TASK ABORTED (0x40) and abort the printing
- Limit amount of work between phase 2 and 3 so you can grab the blind SCSI write (phase 3). Do not reselect or the write probably is lost

0x06 SETUP (Non standard, No name in spec, Vendor specific)

Kind of a page setup command

Code:
+==============+========+========+========+========+========+========+========+
|  Bit|   7    |   6    |   5    |   4    |   3    |   2    |   1    |   0    |
|Byte |        |        |        |        |        |        |        |        |
|=====+========+========+========+========+========+========+========+========+
| 0   |                          Y_OFFSET (MSB)                               |
|-----+-----------------------------------------------------------------------+
| 1   |                          Y_OFFSET (LSB)                               |
|-----+-----------------------------------------------------------------------+
| 2   |                          X_OFFSET (MSB)                               |
|-----+-----------------------------------------------------------------------+
| 3   |                          X_OFFSET (LSB)                               |
|-----+-----------------------------------------------------------------------+
| 4   |                           WIDTH (MSB)                                 |
|-----+-----------------------------------------------------------------------+
| 5   |                           WIDTH (LSB)                                 |
|-----+-----------------------------------------------------------------------+
| 6   |                          HEIGHT (MSB)                                 |
|-----+-----------------------------------------------------------------------+
| 7   |                          HEIGHT (LSB)                                 |
+=============================================================================+

The driver will always send you this before any 0x05 CDB commands.

When all of the offsets are 0, it gives you (most of the time) the paper size selected in Print setup.
Some programs override the print setup settings and go for a custom paper size.

It doesn't really matter, every time you receive this, you should reset the output area (zero the buffer + size setup)
Sometimes, you will get SETUP commands with non zero offsets during the print. Didn't find any use for them, just ignore them

Suggested emulator implementation:
- set output size when offset are all zeroes from the width and height send by the driver.
- zeroise the buffer within the coordinates if offsets are both 0
- return STATUS_GOOD

0x0A PRINT (Standard & Mandatory)​

This is the command that will trigger the actual printing on the printer.
You wan to check if the MSB of the control bite is set. If it's set, then that's a printing order (cmd[5] & 0x80)
Sometimes the two adjacent bits are set, they are handled by the ROM to parts of the Canon engine using the controller VIA (6522).
You might see the second MSB set (0xC0) when doing multiple copies. It has something todo with the paper and restarting the raster engine

example for doing two copies of the same document:
CDB: PRINT [0A 00 00 00 00 C0] first page
CDB: PRINT [0A 00 00 00 00 80] second page

Suggested emulator implementation: save framebuffer to file here when MSB of the control bit is set.

0x12 INQUIRY (Standard & Mandatory)

The driver will scan for every print done the SCSI bus and look for the LaserWriter.
There is no need to have it connected at boot, you can "hot plug it", as far as the driver goes

The driver will scan the SCSI bus in the following order: 4, 3, 2, 1, 0, 6, 5
It will look for the following:
- SCSI type of 0x02 (pre SCSI-2 printer) (byte 0)
- Vendor string "APPLE " (bytes 8-15 )
- Product string "PERSONAL LASER " (bytes 16 31)

For MacOS operation, you also need to set bytes 1 to 3 to 0x00. Respectively (Non-removable device, ANSI SCSI version pre SCSI-2, response format SCSI-1 CCS)
The real printer also report the firmware version, and the ROM revision as vendor specific extra bytes (will show up in TattleTech)
Theses don't seem to matter to the driver, but better be sure and return the exact stuff below

Suggested emulator implementation:
return the full INQUIRY:
[02 00 00 00 1F 00 00 00 41 50 50 4C 45 20 20 20 50 45 52 53 4F 4E 41 4C 20 4C 41 53 45 52 20 20 31 2E 30 30 00 00 00 FE 20 27 20 FF]

0x15 MODE SELECT(6) (Standard & Optional)

Usage unknown. it's always [00 00 10 00 00 00 00 00 00 00] for me
According to the driver routine:
- byte 2 can be set either 0x00 or 0x10 after checking a flag (unknown origin)
- byte 4 can be set either 0x00 or 0x01 after checking a flag (unknown origin)
Other bytes are always initialised to 0x00

Suggested emulator implementation: read bytes, then ignore
 
Back
Top