Podule access

 

Introduction

One of the main reasons for writing in assembler is interfacing with hardware.

A complete description is beyond the scope of this article. If you would like details (39 pages of it, PostScript or DrawFiles), then you will find it at the Acorn ftp site mirror.
Oddly, the Acorn ftp site at RISC OS Ltd doesn't appear to have these files.

 

I/O controllers

The I/O controllers reside in the area &3000000 to &3400000. They are mapped in the following way:
  Address:  %000000110aabbcccaaaaaaaaaddddd00

  Where:  aa..aa is the device address in the memory map

          bb     is the access type:
                    %00 slow
                    %01 medium
                    %10 fast
                    %11 synchronous

          ccc    is the bank:
                    0 - IOC control registers
                    1 - Floppy disc controller (fast)
                    2 - Econet (sync)
                    3 - Serial port (sync)
                    4 - Expansion cards (slow/med/fast/sync)
                    5 - Harddisc (med)
                    5 - Printer (fast)
                    7 - Expansion cards (slow)

          ddd    is an offset in the device

Thus, basically, the I/O memory map looks like:
    &3310000    Floppy disc controller
    &33A0000    Econet ADLC
    &33B0000    Serial port controller
    &3240000    Internal expansion cards (slow)
    &32C0000    Internal expansion cards (medium)
    &3340000    Internal expansion cards (fast)
    &33C0000    Internal expansion cards (sync)
    &32D0000    Harddisc interface
    &3350010    Printer Data
    &3270000    External expansion cards (slow)

    THIS IS FOR THE ARCHIMEDES; THINGS ARE DIFFERENT ON THE RISCPC

 

Messing with the parallel port

The I/O area is not accessible in USR mode. For an example:
>SYS "Parallel_HardwareAddress" TO addr%
>P. ~addr%
   30109E0
>P. !addr%

Internal error: abort on data transfer at &022B1254
>
As BASIC runs in USR mode, it fails.
So we need a little bit of assembler to push us into SVC mode for the read/write.
   10 DIM code% 128
   20 P% = code%
   30 [ OPT 2
   40   SWI     "Parallel_HardwareAddress"  ; address is in R0
   50   SWI     "OS_EnterOS"                ; go to SVC mode to access hardware!
   60   MOV     R2, #&FE
   70   STRB    R2, [R0, #0]  ; write &FE to parallel port data register
   80   LDRB    R0, [R0, #0]  ; load R0 with word pointed to by R0
   90   TEQP    PC, #0
  100   MOV     R0, R0
  110   MOV     PC, R14
  120 ]
  130 PRINT ~USR(code%)
The result of this is FE.

 

You can, from RISC OS, use:
SYS "Parallel_Op", 0 TO ,, status%
to read the value of the status bits.

With my printer (HP DeskJet 500, CC's TurboDriver cable), the results are:

 

The same thing may be performed with direct hardware access:

   10 DIM code% 128
   20 P% = code%
   30 [ OPT 2
   40   SWI     "Parallel_HardwareAddress"
   50   SWI     "OS_EnterOS"
   80   LDRB    R0, [R0, #4]
   90   TEQP    PC, #0
  100   MOV     R0, R0
  110   MOV     PC, R14
  120 ]
  130 PRINT USR(code%)
Once you have the address of the parallel port cached, you don't need to look it up again. So you can reduce the call to the basics. Indeed, if you include this in SVC code (ie, some form of device driver) you could reduce the call to just an LDRB (if the address is preloaded into a register), instead of calling OS_ParallelOp each time around.

Obviously, such hardware poking is frowned upon, but it is sometimes necessary in the quest for maximum speed. D'you think any of the iomega Zip drivers use OS_ParallelOp? :-)

This will only work for devices using an ISA mapping. This means the A5000 (82C710), and the RiscPC/A7000 (37C665 see below). I would imagine the RiscStation and Mico, and (when it finally turns up) the Omega would also all use off-the-shelf ISA combo chips instead of discrete hardware.
What this does mean, however, is the software that does this may fail on the early machines like the A310 and the A3000. I don't have one running to check this. Caveat emptor.

For what it is worth, here are the addresses from the 37C665 datasheet:

  DATA port        Base address + &00     LDRB Rx, [Rx, #0]
  STATUS port      Base address + &01     LDRB Rx, [Rx, #4]
  CONTROL port     Base address + &02     LDRB Rx, [Rx, #8]
  EPP ADDR port    Base address + &03     LDRB Rx, [Rx, #12]
  EPP DATA port 0  Base address + &04     LDRB Rx, [Rx, #16]
  EPP DATA port 1  Base address + &05     LDRB Rx, [Rx, #20]
  EPP DATA port 2  Base address + &06     LDRB Rx, [Rx, #24]
  EPP DATA port 3  Base address + &07     LDRB Rx, [Rx, #28]
Remember, the bottom two bits are zero, so all addresses are shifted. The LDRB shows this.
  PORT       D0      D1      D2      D3      D4      D5      D6      D7

  DATA          ....bits 0-7 of the data to be sent to the printer....

  STATUS     TMOUT   -       -       |ERR    SLCT    PE      |ACK    |BUSY

  CONTROL    STROBE  AUTOFD  |INIT   SLC     IRQE    PCD     -       -

  ERR ADDR   PD0     PD1     PD2     PD3     PD5     PD5     PD6     AD7

  EPP DATA             ...PD0 to PD7 for all EPP DATA ports...

 

Podule allocations

The podules are located from &33C0000, incrementing each 16K, using 4K for each cycle type (slow, medium, fast, synchronous).
On a RiscPC, they are allocated slightly differently, but the base four podules are at the same places in memory. The IOMD takes care of the IOC cycle types, and also provides the extended addressing space and DMA functions (which are outside of the scope of this document).
SYS "Podule_ReturnNumber" TO podcnt%
FOR podule% = 0 TO (podcnt%-1)
  SYS "XPodule_HardwareAddresses",,,, podule% TO baseaddress% ; err%
  IF (err% AND 1) THEN
    PRINT "Podule "+STR$(podule%)+" is empty"
  ELSE
    PRINT "Podule "+STR$(podule%)+" base &"+STR$~(baseaddress%)
  ENDIF
NEXT
The program above returns the address used for synchronous access. The podule identity stuff is always accessed synchronous.

You can find out more about podules at the above link, or you can download the older A-series podule information (more out of date, but it is in text format).

You might also enjoy a perusal of Theo Markettos' web site.

 

HCCS Vision digitiser

We can extend this program to detect a Vision digitiser. I will leave it to you to trace through the program.
REM >visioncode

ON ERROR PRINT REPORT$+" at "+STR$(ERL/10) : END

PROCassemble
visionbase% = 0

SYS "Podule_ReturnNumber" TO podcnt%
FOR podule% = 0 TO (podcnt%-1)
  SYS "XPodule_HardwareAddresses",,,, podule% TO baseaddress% ; err%
  IF (err% AND 1) THEN
    PRINT "Podule "+STR$(podule%)+" not installed"
  ELSE
    PRINT "Podule "+STR$(podule%)+" base &"+STR$~(baseaddress%);
    B% = baseaddress%
    IF USR(find_podule%) = 1 THEN
      visionbase% = baseaddress%
      PRINT "...this is a Vision."
    ELSE
      PRINT
    ENDIF
  ENDIF
NEXT

REM Once we know our base address, we can set up some offsets.
REM The down conversion changes our podule access methods.
visionheader% = visionbase% - &180000 : REM Base, slow access
visionctrl% = visionheader% + &102800 : REM Control, fast access
visiondata% = visionheader% + &103800 : REM Data, fast access
visionstat% = visionheader% + &100080 : REM Status, fast access
visionrst%  = visionheader% + &2000   : REM Reset, slow access

REM Code to reset, check, and fetch image removed
REM not necessary for this example

END

DEFPROCassemble
  DIM code% 76
  FOR loop% = 0 TO 2 STEP 2
    P% = code%
    [ OPT     loop%

      \ On entry, R1 = base address
      \ On exit,  R2 = 1 if Vision, else 0
    .find_podule%
      ; Stash R14 and enter SVC mode.
      STMFD   R13!, {R14}
      SWI     "OS_EnterOS"

      ; Set flag to TRUE. If a test fails, flag will be FALSified. <g>
      MOV     R0, #1

      ; Is +12 &AF?
      LDRB    R2, [R1, #12]
      CMP     R2, #&AF
      MOVNE   R0, #0

      ; Is +16 &00?
      LDRB    R2, [R1, #16]
      CMP     R2, #&00
      MOVNE   R0, #0

      ; Is +20 &2D?
      LDRB    R2, [R1, #20]
      CMP     R2, #&2D
      MOVNE   R0, #0

      ; Is +24 &00?
      LDRB    R2, [R1, #24]
      CMP     R2, #&00
      MOVNE   R0, #0

      TEQP    PC, #0
      MOV     R0, R0

      LDMFD   R13!, {PC}
    ]
  NEXT
ENDPROC
For an interesting look at the Vision digitiser in use, you can either see the Willow (Alyson Hannigan) area of my website at http://www.heyrick.co.uk/willow/, or, maybe even more interesting, a person has taken pictures of the moon with a video camera and digitised them with a monochrome Vision, at http://www.newtownbreda.demon.co.uk/ruadhan/asmoon.html.

 

SMC37C665

JPEG; 16K The name may not make much sense to you, but if you are using a RiscPC then it is a very important name. The 37C665 (pictured) is the device that handles your parallel port, serial port, floppy disc, IDE bus...
The original Archimedes machines used separate integrated circuits for these facilities; the A310 having no harddisc as standard, the A3000 having no harddisc or serial port as standard.
The A5000 / A4 machines were the first to utilise an off-the-shelf 'combo chip' for the standard interfacing. The chip was the 82C710.
The RiscPC decided to alter the combo chip to be a 37C665. My personal suspicion is that the 37C665 was preferred because it features on-chip protection circuitry so your serial and parallel ports can be wired without additional filters. It is clean, it is simple. Both devices conform to the ISA specification for memory locations in a PC (ie, where COM1 and LPT1 live), so both will be memory-mapped kinda similar.
What I don't get, for what it's worth, is why Acorn didn't bother to fit a second serial port? If you've ever opened your RiscPC (who hasn't?), you'll notice the serial hardware is really quite simple (see the serial port, see the little chip just below it - that's the 37C665). The 37C665 supports TWO serial ports, right there in the hardware. Or maybe 1 serial port and one MIDI port (differing configurations). It's just a shame Acorn didn't take advantage of this!
Later machines (Mico, RiscStation, Bush internet box, etc) appear to use a 37C669 which allows for the I/O addresses to be altered in software.

In order to read the configuration of the 37C665, we need to write &55 (85) to port &3F0. However, we don't yet know where in memory the device is located. There are two ways to determine the location of the device:

So, port &3F0 (which becomes &FC0 when shifted) is where we write &55 to set the device into configuration mode. Two consecutive writes must be made, so it is worth switching off interrupts.
This will only work on the RiscPC, and you MUST release yourself from configuration mode before attempting to use your computer. It goes without saying that you ONLY read the configuration registers, never write to them!

In case you were wondering, &55 is %01010101 and &AA is %10101010.

Once we are in configuration mode, we write a register number (0-15) to &3F0 and we can then read the value of that register from &3F1. It is simple to whizz through the registers, dumping the contents to memory as we go.

To leave configuration mode, we write &AA to &3F0. This must be done or your computer's I/O will just cease to function.

This code will ONLY work on the 37C665 fitted into the RiscPC. It is worth noting that the 37C666 (basically a 665 but uses hardware links to configure it, the sort of thing you'd find on a cheap ISA combo-card) uses the magic value &66 to enter configuration mode.

 

The FDC37C669 is pretty much the same as the 37C665. The interfacing provided is, obviously, better (additions such as iRDA). For the purposes of communicating with the device, you can normally treat it just like the 37C665 (i.e. write &55 twice to enter the configuration mode, and &AA once to leave it). The only potential issue is that the configuration base address is variable. If, upon hardware reset, the value of nRTS2 is 0, the configuration registers will be at &3F0 and &3F1; otherwise you'll find them at &370 and &371. In RISC OS equipment, I would expect to see them at &3F0...

The Bush box uses the FDC37C669 part. Apparently it uses a serial port for the IR keypad interface (?), and the parallel port. Unused is the floppy disc, the floppy-via-parallel option, the other serial port (which may be made to do MIDI), the IDE harddisc, the iRDA... These things must be dirt cheap for them to include it within the box and then not use much of its capabilities!

Below, you will find some example code expressed in ARM assembler and 8086 assembler. Unlike the example further on, this code has been written to provide a 1:1 translation between the two, as far as is possible. The 8086 assembler is a tidied version of the code fragment given in the device datasheet.
8086 fragment
;-----------------------------.
; ENTER CONFIGURATION MODE    |
;-----------------------------'
MOV    DX,3F0H
MOV    AX,055H
CLI             ; disable interrupts
OUT    DX,AL
OUT    DX,AL
STI             ; enable interrupts
;-----------------------------.
; CONFIGURE REGISTERS CR0-CRx |
;-----------------------------'
MOV    DX,3F0H
MOV    AL,00H
OUT    DX,AL    ; Point to CR0
MOV    DX,3F1H
MOV    AL,3FH
OUT    DX,AL    ; Update CR0
;
MOV    DX,3F0H
MOV    AL,01H
OUT    DX,AL    ; Point to CR1
MOV    DX,3F1H
MOV    AL,9FH
OUT    DX,AL    ; Update CR1
;
; Repeat for all CRx registers
;
;-----------------------------.
; EXIT CONFIGURATION MODE     |
;-----------------------------'
MOV    DX,3F0H
MOV    AX,0AAH
OUT    DX,AL






ARM fragment

;-----------------------------.
; ENTER CONFIGURATION MODE    |
;-----------------------------'
LDR    R1, base_address
MOV    R2, #&55
SWI    "OS_EnterOS"
TEQP   PC, #&0C000003     ; disable interrupts
STRB   R2, [R1, #&FC0]
; we might as well leave interrupts OFF for now...
;-----------------------------.
; CONFIGURE REGISTERS CR0-CRx |
;-----------------------------'
                          ; address is already set up
MOV    R2, #&00
STRB   R2, [R1, #&FC0]    ; Point to CR0
                          ; address is already set up
MOV    R2, #&3F
STRB   R2, [R1, #&FC1]    ; Update CR0
;
                          ; address is already set up
MOV    R2, #&01
STRB   R2, [R1, #&FC0]    ; Point to CR1
                          ; address is already set up
MOV    R2, #&9F
STRB   R2, [R1, #&FC1]    ; Update CR1
;
; Repeat for all CRx registers
;
;-----------------------------.
; EXIT CONFIGURATION MODE     |
;-----------------------------'
                          ; address is already set up
MOV    R2, #&55
STRB   R2, [R1, #&FC0]

TEQP    PC, #&08000000    ; interrupts enabled, USR mode
MOV     R0, R0

.base_address
EQUD    &03010000
21 instructions, would create a 41 byte executable... 18 instructions and 1 data word, would create a 72 byte executable...
Before we leave this comparison, a few small observations:

 

Anyway, we were talking about switching the 37C66x into configuration mode...

 

This takes place in SVC mode, with interrupts disabled. The code is pretty basic really.

REM Talk to the combi I/O
REM
REM by Richard Murray
REM Updated 2004/02/17 with 32bit stuff.
REM
REM Downloaded from: http://www.heyrick.co.uk/assembler/

ON ERROR PRINT REPORT$+" at line "+STR$(ERL/10) : END

DIM code% 128

REM For 32bit systems (ARM6 or later), you can alter the following...
code_32_bit% = TRUE

FOR l% = 0 TO 2 STEP 2
  P% = code%
  [ OPT     l%
    EXT     1

    STR     R14, [R13, #-4]!

    SWI     "OS_EnterOS"         ; go to SVC mode
  ]

  IF (code_32_bit% = FALSE) THEN
  [ OPT     l%
    ; 26 bit way (ARM2 to SA110)
    TEQP    PC, #&0C000003     ; interrupts disabled
  ]
  ELSE
  [ OPT     l%

    ; 32 bit way (ARM6 to 'Present Day')
    MRS     R0, CPSR_all
    BIC     R0, R0, #192           ; IRQ and FIQ
    EOR     R0, R0, #192
    MSR     CPSR_all, R0
  ]
  ENDIF

  [ OPT     l%
    LDR     R0, base_address
    MOV     R1, #&55
    STRB    R1, [R0, #&FC0]    ; port &3F0
    STRB    R1, [R0, #&FC0]
    ; Now in 37C665 software configuration mode

    ADR     R2, registers      ; Where to store registers
    MOV     R3, #0             ; Register number (& offset)

  .read_loop
    ; Write desired register number to address &3F0
    STRB    R3, [R0, #&FC0]
    ; Now read register contents from address &3F1
    LDRB    R1, [R0, #&FC4]
    ; Store it in our register block
    STRB    R1, [R2, R3]

    ADD     R3, R3, #1
    CMP     R3, #16
    BLT     read_loop

    MOV     R1, #&AA
    STRB    R1, [R0, #&FC0]
    ; Now out of software configuration mode
    ]

  IF (code_32_bit% = FALSE) THEN
  [
    OPT     l%
    ; 26 bit way (ARM2 to SA110)
    TEQP    PC, #&08000000     ; interrupts enabled, USR mode
    MOV     R0, R0
  ]
  ELSE
  [ OPT     l%
    ; 32 bit way (ARM6 to 'Present Day')

    ; ****************************************************
    ; IMPORTANT!!!  The '#192' restores USR26 mode. For
    ;               an Iyonix, you would want USR32 mode,
    ;               so you should therefore use #224...
    ; ***************************************************

    MOV     R0, #192           ; IRQ and FIQ, then **USR26**
    MSR     CPSR_all, R0
  ]
  ENDIF

  [ OPT     l%

    LDR     R14, [R13], #4
    MOV     PC, R14


  .base_address
    EQUD    &03010000

  .registers
    EQUD    0
    EQUD    0
    EQUD    0
    EQUD    0
  ]
NEXT

PRINT "Examining multi-I/O chip configuration...";
CALL code%
PRINT "done."''

PRINT "Device identification ";
CASE registers?13 OF
  WHEN &65 : PRINT "FDC37C665GT";
  WHEN &66 : PRINT "FDC37C666GT"; : REM Different magic value, so should not happen!
OTHERWISE  : PRINT "Error! Device ID "+STR$~(registers?13)+" unrecognised!" : END
ENDCASE
PRINT ", revision "+STR$(registers?14)

END
Download example: combi_io.basic

Briefly, the registers are:

  1. IDE status, floppy status, oscillator status
  2. Parallel status, COM 3 & 4 status
  3. Primary and secondary serial status
  4. Floppy mode
  5. Parallel mode / prim/sec serial clock (for MIDI)
  6. IDE mode, floppy
  7. Floppy drive types
  8. Floppy boot / media ID
  9. ADRx address decoder (lower eight bits)
  10. ADRx address decoder (upper three bits)
  11. ECP FIFO threshold
  12. (reserved)
  13. (reserved)
  14. Device ID (should be &65)
  15. Device revision (should be &02)
  16. (reserved - sets up test modes)
You'll need the SMC37C665 device datasheet if you care to fiddle around with the registers, find out what is where...

 


Return to assembler index
Copyright © 2004 Richard Murray