Skip to content

Instantly share code, notes, and snippets.

@skiselev
Last active March 23, 2024 00:40
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save skiselev/12e46b214aa173fdbea8209929824d79 to your computer and use it in GitHub Desktop.
Save skiselev/12e46b214aa173fdbea8209929824d79 to your computer and use it in GitHub Desktop.
Ericsson PC - BIOS / Keyboard Interface Reverse-Engineering

Ericsson PC - BIOS / Keyboard Interface Reverse-Engineering

Hardware

  • An 8251 USART next the 8255 PPI. Apparently it is used for the keyboard interfacing
  • BIOS size 16 KiB, two 2764 EPROMs

BIOS Code

  • All numeric values are in hexadecimal

FFF0 - Reset vector

JMP F000:E05B

E05B - POST entry point (IBM / PC compatibility)

JMP	C0D1

C0D1 - POST code

  • C0D1:
    • DS=0000
    • SS=0000
    • SP=0040 ; stack at 0000:0040
    • OUT 3D0,00 ; Disable video?
    • SI=C07D ; pointer to the table with the initial values for I/O ports - 25 values:
      • OUT 71,00 ; Set USART to command mode: configure sync operation
      • OUT 71,00 ; Write two dummy sync characters
      • OUT 71,00
      • OUT 71,40 ; Issue reset command
      • OUT 71,4E ; Write mode instruction: 1 stop bit, no parity, 8 bits, divide clock by 16
      • OUT 71,11 ; Write command instruction: Set Error Reset - ER, bit 4, and Transmit Enable - TE, bit 0
      • OUT 83,00 ; DMA channel 1 page register
      • OUT A0,00 ; Disable NMIs
      • OUT B8,01 ; ??
      • OUT 63,99 ; PPI control register - control word: group A&B: mode 0; port A - input, port B - output; port C - input
      • OUT 61,B5 ; PPI port B: clear kbd/enable SW1, disable kbd clock, disable I/O check, disable parity check, read memory size from port C; turn off speaker, enable PIT channel 2 (speaker)
      • OUT 08,04 ; DMA initialization...
      • OUT 0D,03 ;
      • OUT 00,00 ;
      • OUT 00,00 ;
      • OUT 01,FF ;
      • OUT 01,FF ;
      • OUT 02,00 ;
      • OUT 02,00 ;
      • OUT 03,00 ;
      • OUT 03,00 ;
      • OUT 04,00 ;
      • OUT 04,00 ;
      • OUT 05,00 ;
      • OUT 05,00 ;
      • OUT 06,00 ;
      • OUT 06,00 ;
      • OUT 07,00 ;
      • OUT 07,00 ;
      • OUT 0B,5B ;
      • OUT 08,00 ;
      • OUT 0A,00 ;
      • OUT 43,54 ; PIT initialization...
      • OUT 41,12 ;
      • OUT 0B,41 ; DMA - enable memory refresh?!
      • OUT 0B,42 ;
      • OUT 0B,43 ;
    • DI=C0E9 ; return address
    • JMP C2C5 ; set I/O ports
  • C0E9: ; Test first 64 KiB of RAM
    • OUT 213,01 ; ??
    • BX=[0472] ; BX - soft reset flag
    • SI=C138
    • ES=0000
    • DL=54
    • IF BX=1234 JMP C108 ; jump on soft reset
    • DX=8080
    • AX=5555 ; memory test pattern?
  • C108:
    • DI=AX ; save AX to DI
    • IN AL,61
    • OR AL,30 ; Disable I/O channel check, disable memory parity check
    • OUT 61,AL
    • OUT 61,AL ; I/O delay?!
    • AND AL,CF ; Enable I/O channel check, enable memory parity check
    • OUT 61,AL
    • AX=DI ; restore AX (5555 for cold boot, 0000 for warm boot)
    • DI=0000
    • CX=8000
    • REP STOSW ; first 64KiB of RAM: cold boot - initialize RAM with the pattern ; warm boot - clear RAM
    • Compare initialized memory with the pattern / 0000 if doesn't match JMP C136
    • Apparently repeat again with AAAA pattern for cold boot
    • IN AL,62 ; Read PPI Port C
    • AND AL,C0 ; Isolate I/O check and parity flags
    • JMP SI ; SI = C138
  • C138:
    • JZ C143 ; Jump if no memory errors
    • DX=E389
    • SI=C13A
    • JMP C2DC ; something to do with memory errors?!
  • C13A:
    • [0472]=BX ; restore soft reset flag after memory test
    • DX=A000
    • DI=0000
  • C14C: ; Test/size the rest of RAM
    • DX=DX-1000
    • ES=DX
    • AH=DH
    • AL=AH
    • AL=!AL
    • ES:[DI]=AX
    • IF AH != 00 JMP C14C
    • DX=1000
    • BX=0040
  • C166:
    • ES=DX
    • AH=DH ; 10?
    • AL=AH
    • AL=!AL
    • CX=8000
    • SUB AX,ES:[DI]
    • JNZ C184
    • REP STOSW
    • ADD DX,1000
    • ADD BX,40
    • CMP AH,A0
    • JNZ C166
  • C184:
    • MOV [0413],BX - store memory size to BDA
    • SI=C0C8 ; pointer to the table with the values for I/O ports - 4 values:
      • OUT 20,13 ; PIC initialization
      • OUT 21,08
      • OUT 21,09
      • OUT 21,FF
    • CALL C2C4 ; set I/O ports
    • SP=0080
    • CX=0020 - number of vectors to initialize - INT 0 - INT 19
    • SI,FF21 - interrupt vectors, offsets only - this is the top of the table, 20 word entries
      • INT 00 = F000:F862, INT 01 = F000:F862, INT 02 = F000:E2C3, INT 03 = F000:F862
      • INT 04 = F000:F862, INT 05 = F000:D03A, INT 06 = F000:F862, INT 07 = F000:F862
      • INT 08 = F000:D2E4, INT 09 = F000:F862, INT 0A = F000:F862, INT 0B = F000:F862
      • INT 0C = F000:F862, INT 0D = F000:F862, INT 0E = F000:C84D, INT 0F = F000:F862
      • INT 10 = F000:D53E, INT 11 = F000:F84D, INT 12 = F000:F841, INT 13 = F000:C443
      • INT 14 = F000:D0E6, INT 15 = F000:FF53, INT 16 = F000:CA6F, INT 17 = F000:CF56
      • INT 18 = F000:C0D1, INT 19 = F000:C38C, INT 1A = F000:D26E, INT 1B = F000:FF53
      • INT 1C = F000:FF53, INT 1D = F000:D328, INT 1E = F000:C42A, INT 1F = F000:0000
  • C197: ; Initialize Interrupt vector table
    • PUSH CS
    • MOV AX,CS:[SI]
    • DEC SI
    • DEC SI
    • PUSH AX
    • LOOPW C197
  • C1A0:
    • SP=0400
    • CALL C372 ; read switches to AL and AH
    • AH = 00
    • [0410]=AX ; store to BDA / equipment list
    • CALL DF62 ; initialize video controller
    • if warm boot jump to C1BC
  • C1B6:
    • MOV SI,C046 ; 'POWER ON TEST'
    • CALL C2B5 ; print string
  • C1BC:
    • DI=C000
    • DX=C800
    • CALL C319 ; extension ROM scan - video controllers only (C000-C800)
    • CALL CEC8 ; initialize keyboard and enable keyboard interrupt
    • if warm boot jump to C1D3
    • CALL C23E ; RAM test continued?!
  • C1D3:
    • CALL C29F ; Configure PIT channel 0 and enable timer interrupt
    • DI=C800
    • DX=F600
    • CALL C319 ; extension ROM scan - the rest of upper memeory (C800-F600)
    • CALL C29F ; Configure PIT channel 0 and enable timer interrupt (in case extension ROMs disabled it?!)
    • CALL C28C ; Enable FDC interrupt and reset FDC
    • JAE C1ED ; jump if FDC initialized successfully?
    • SI=C2B0 ; 'DISKETTE ERROR'
    • CALL C2B0 ; set error flag [0412]=FF, and print error message
  • C1ED:
    • IN AL,21
    • AND AL,FC ; clear bits 1 and 0, unmask IRQ1 and IRQ0
    • OUT 21,AL
    • INC BYTE PTR [0412] ; increment error flag, will set ZF on error?
    • PUSHF
    • DX=C002
    • JZ C200 ; jump on error
    • DX=8001
  • C200:
    • CALL C2DB ; beep, pattern depends on errors
    • POPF
    • JNZ C21C ; jump if no error
    • TEST BYTE PTR [0410],01 ; Equipment word, IPL installed?
    • JZ C21C ; yes IPL
    • SI=C058 ; 'PRESS F1 KEY'
    • CALL C2B5 ; print string
  • C213:
  • MOV AH,00
  • INT 16 ; wait for key
  • CMP AH,3B ; F1?
  • JNZ C213 ; nope
  • C21C:
    • CALL CFE2 ; initialize LPT ports
    • ROR AL,1 ; shift LPT bits for equipment word?
    • ROR AL,1
    • PUSH AX
    • CALL D203 ; initialize COM ports, also enabled USART tx/rx...
    • POP SI
    • ROL AL,1
    • OR AX,SI
    • MOV [0411],AL ; update LPT and COM port count in BDA equipment word
    • CALL C367 ; reset I/O check and parity NMIs
    • OUT A0,80 ; enable NMIs
    • SI=C070 ; 'WAITING...'
    • CALL C2B5 ; print string
    • INT 19 ; call IPL loader

C23E - Looks like RAM test

C28C - Enable FDC interrupt and reset FDC

  • C28C:
    • IN AL,21
    • AND AL,BF - clear bit 6 - umask IRQ 6
    • OUT 21,AL
    • AX=0000
    • DL=00
    • INT 13
    • JB C29E ; jump if CF == 1
    • DEC AH
    • INT 13
  • C29E:
    • RET

C29F - Configure PIT channel 0 and enable timer interrupt?

  • OUT 43,36
  • OUT 40,00
  • OUT 40,00
  • IN AL,21
  • AND AL,FE ; clear bit 0 - unmask IRQ 0
  • OUT 21,AL

C2B0 - Set error flag [0412]=FF, and print zero zero-terminated string pointed by CS:SI to the screen

C2B5 - Print zero-terminated string pointed by CS:SI to the screen

C2C4 - Set IO ports, return

  • POP DI

C2C5 - Set IO ports, jump to DI when complete

  • Inputs
    • SI - pointer to the table with the initial values for I/O ports:
      • First byte - number of ports to set
      • Followed by pairs of bytes: value to write and port number
    • DI - return address

C2DB - Beep and return, DX - beep pattern

C2DC - Beep and jump to SI, DX - beep pattern

C319 - BIOS Extension ROM scan; DI - start segment, DX - end segment

C367 - Reset I/O check and parity NMIs

  • IN AL,61
  • OR AL,30
  • OUT 61,AL
  • AND AL,CF
  • OUT 61,AL
  • RET

C372 - Read Switches, return result in AL and AH

  • C372:
    • AH=00
    • CALL C37B
  • C377:
    • OUT 61,BD ; previous value was B5, so setting bit 3, probably enables the other half of switches?
  • C37B:
    • IN AL,62 ; read PPI Port C
    • AND AL,0F ; get switches
    • OR AL,AH ; set bits in AL according to AH bit mask
    • MOV CL,04
    • ROL AL,CL ; rotate
    • MOV AH,AL
    • RET

CEC8 - Keyboard initialization

CEC8:
	PUSH	AX
	PUSH	CX
	MOV	AX,001E			; Pointer to the beginning of the keyboard buffer in the BIOS Data Area
	MOV	[041A],AX		; Keyboard buffer head
	MOV	[041C],AX		; Keyboard buffer tail
	MOV	[0480],AX		; Keyboard buffer start offset
	MOV	WORD PTR [0482],003E	; Keyboard buffer end offset
	MOV	AL,40
	OUT	61,AL			; Enable keyboard clock
	MOV	AL,00
	OUT	60,AL			; Send 0 to the keyboard?! What does it do? Resets the keyboard? Runs diagnostics?
CEE4:
	IN	AL,71			; Read USART status register
	AND	AL,04			; Isolate TxEMPTY bit
	JZ	CEE4			; Wait until TxEMPTY is set
	MOV	AL,05
	OUT	AL,71			; Set Receive Enable, bit 2 and Transmit Enable, bit 0
	MOV	BYTE PTR [046B],00	; This location is unused on a typical PC/XT, but it appears to be used as some kind of flag here
	IN	AL,21			; Read interrupt mask from PIC to AL
	AND	AL,FD			; Reset bit 1 - unmask IRQ1
	OUT	21,AL			; Write new interrupt mask to PIC
	STI
	MOV	CX,0000			; Timeout counter
	MOV	AH,03			; High byte of timeout counter
CEFF:
	TEST	BYTE PTR [046B],02	; Check if IRQ1 have happened
	JNZ	CF0E			; Bit 1 is set, exit the loop
	LOOPW	CEFF
	DEC	AH
	JNZ	CEFF
	JMP	CF20			; Jump if timeout waiting for bit 1
	CLI
	IN	AL,71			; Read USART status register
	AND	AL,38			; Check Error bits
	JNZ	CF20			; Jump if USART error bits set
	IN	AL,60			; Read the keyboard. What are we expecting to get here? Keyboard diagnostics / POST status?
	MOV	[04EE],AL		; Store in the BIOS Data Area, note 04EE is not typically used on PC/XT
	AND	AL,F0			; Isolate top 4 bits
	CMP	AL,A0			; Check bits 7 and 5
	JZ	CF2D			; Jump if bits 7 and 5 are not set
CF20:
	PUSH	SI
	MOV	SI,CF3E			; "Keyboard Error"
	CALL	E05E			; Print zero-terminated string pointed by CS:SI on the screen
	POP	SI
	MOV	BYTE PTR [0412],FF	; 0412 is IBM PCjr infrared keyboard error count... Perhaps indicates keyboard error here?!
	MOV	AL,C0
	OUT	61,AL			; Clear keyboard shift regiser, enable keyboard clock
	MOV	AL,40
	OUT	61,AL			; Enable keyboard, enable keyboard clock
	MOV	WORD PTR [0024],CB32	; Set IRQ1 handler offset to CB32 (originally it is pointing to the default interrupt handler)
	POP	CX
	POP	AX
	RET

CFE2 - Initialize LPT ports

D203 - Initialize COM ports

  • OUT 71,05 ; why here? USART - transmit enable, receive enable

DF62 - Video controller initialization (MDA only?)

F862 - Default interrupt handler

  • F862:
    • PUSH AX
    • PUSH DS
    • MOV BYTE PTR [046B],FF - indicate an unknown unhandled interrupt?
    • OUT 20,0B ; PIC OCW3 - read in-service register
    • IN AL,20 ; get IRQ number
    • CMP AL,00 ; unknown interrupt?
    • JZ F887 ; exit
    • MOV AH,AL ; save vector to AH
    • MOV [046B],AL ; Last spurious IRQ number
    • IN AL,21
    • OR AL,AH
    • OUT 21,AL ; clear interrupt
    • OUT 20,20 ; signal end of interrupt to PIC
  • F887:
    • POP DS
    • POP AX
    • RET
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment