Class: PSX::SIO0

Inherits:
Object
  • Object
show all
Defined in:
lib/psx/sio0.rb

Overview

SIO0: Controller and memory-card serial port.

The BIOS uses this to probe slots 1 and 2 every VBlank (“PadAutoPolling”): it pulls /JOYn low via JOY_CTRL, sends a series of bytes through JOY_DATA, and waits for the device to /ACK each byte. The /ACK signal latches as JOY_STAT bit 9 and raises IRQ_CONTROLLER (I_STAT bit 7). If no /ACK arrives within ~81 polling iterations the BIOS treats the slot as empty and moves on.

We model just enough of this to convince the BIOS that a digital pad is connected in slot 1 (so we get past PadAutoPolling and into the shell) and that nothing is in slot 2 / memory-card port (so the BIOS doesn’t wait for an empty memcard to respond). The host SDL keyboard feeds the button state through a callback supplied at construction time.

Constant Summary collapse

STAT_TX_READY_1 =

JOY_STAT (0x1F801044) bits

1 << 0
STAT_RX_FIFO_NE =

TX FIFO has room

1 << 1
STAT_TX_READY_2 =

RX FIFO not empty

1 << 2
STAT_RX_PARITY =

No active transfer / TX done

1 << 3
STAT_ACK_INPUT =

/ACK input level (0=device pulling low)

1 << 7
STAT_IRQ_REQUEST =
1 << 9
CTRL_TXEN =

JOY_CTRL (0x1F80104A) bits

1 << 0
CTRL_JOYN_OUTPUT =

/JOYn output level (1 = device selected)

1 << 1
CTRL_RXEN =
1 << 2
CTRL_ACK =

write 1 to reset IRQ + parity bits

1 << 4
CTRL_RESET =

write 1 to reset entire SIO state

1 << 6
CTRL_RX_INT_EN =
1 << 11
CTRL_TX_INT_EN =
1 << 10
CTRL_ACK_INT_EN =
1 << 12
CTRL_SLOT =

0 = slot 1, 1 = slot 2

1 << 13
DIGITAL_PAD_IDHI =

Digital pad protocol response bytes

0x41
PAD_READY_BYTE =
0x5A

Instance Method Summary collapse

Constructor Details

#initialize(interrupts: nil, controller_state: -> { 0xFFFF }) ⇒ SIO0

Returns a new instance of SIO0.



42
43
44
45
46
# File 'lib/psx/sio0.rb', line 42

def initialize(interrupts: nil, controller_state: -> { 0xFFFF })
  @interrupts = interrupts
  @controller_state = controller_state
  reset_all
end

Instance Method Details

#read16(offset) ⇒ Object



92
93
94
95
96
97
98
99
100
101
102
# File 'lib/psx/sio0.rb', line 92

def read16(offset)
  case offset
  when 0x40 then pop_rx
  when 0x44 then status & 0xFFFF
  when 0x46 then (status >> 16) & 0xFFFF
  when 0x48 then @mode
  when 0x4A then @ctrl
  when 0x4E then @baud
  else 0
  end
end

#read32(offset) ⇒ Object



104
105
106
107
108
109
110
111
112
# File 'lib/psx/sio0.rb', line 104

def read32(offset)
  case offset
  when 0x40 then pop_rx
  when 0x44 then status
  when 0x48 then @mode | (@ctrl << 16)
  when 0x4C then @baud # unaligned but harmless
  else 0
  end
end

#read8(offset) ⇒ Object

Reads are byte-addressable; widen as needed for 16/32-bit accesses.



74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/psx/sio0.rb', line 74

def read8(offset)
  case offset
  when 0x40 then pop_rx
  when 0x41, 0x42, 0x43 then 0
  when 0x44 then status & 0xFF
  when 0x45 then (status >> 8) & 0xFF
  when 0x46 then (status >> 16) & 0xFF
  when 0x47 then (status >> 24) & 0xFF
  when 0x48 then @mode & 0xFF
  when 0x49 then (@mode >> 8) & 0xFF
  when 0x4A then @ctrl & 0xFF
  when 0x4B then (@ctrl >> 8) & 0xFF
  when 0x4E then @baud & 0xFF
  when 0x4F then (@baud >> 8) & 0xFF
  else 0
  end
end

#reset_allObject



48
49
50
51
52
53
54
55
56
# File 'lib/psx/sio0.rb', line 48

def reset_all
  @ctrl   = 0
  @mode   = 0
  @baud   = 0
  @rx     = []
  @irq    = false   # JOY_STAT bit 9 (and source of IRQ_CONTROLLER)
  @device_step = 0  # 0 = waiting for select byte
  @pending_ack_cycles = nil  # countdown before /ACK pulse fires
end

#statusObject

Read-side JOY_STAT word.



144
145
146
147
148
149
# File 'lib/psx/sio0.rb', line 144

def status
  s = STAT_TX_READY_1 | STAT_TX_READY_2 | STAT_ACK_INPUT
  s |= STAT_RX_FIFO_NE unless @rx.empty?
  s |= STAT_IRQ_REQUEST if @irq
  s
end

#tick(cycles) ⇒ Object

Drive the /ACK timing. The BIOS clears I_STAT bit 7 right after writing JOY_DATA and only then enters the polling loop, so we must wait a beat before raising IRQ_CONTROLLER – otherwise the BIOS clear wipes our IRQ and the poll spins until timeout.



62
63
64
65
66
67
68
69
# File 'lib/psx/sio0.rb', line 62

def tick(cycles)
  return unless @pending_ack_cycles
  @pending_ack_cycles -= cycles
  return if @pending_ack_cycles > 0
  @pending_ack_cycles = nil
  @irq = true
  @interrupts&.request(Interrupts::IRQ_CONTROLLER)
end

#write16(offset, value) ⇒ Object



122
123
124
125
126
127
128
129
# File 'lib/psx/sio0.rb', line 122

def write16(offset, value)
  case offset
  when 0x40 then transmit(value & 0xFF)
  when 0x48 then @mode = value & 0xFFFF
  when 0x4A then write_ctrl(value & 0xFFFF)
  when 0x4E then @baud = value & 0xFFFF
  end
end

#write32(offset, value) ⇒ Object



131
132
133
134
135
136
137
138
139
# File 'lib/psx/sio0.rb', line 131

def write32(offset, value)
  case offset
  when 0x40 then transmit(value & 0xFF)
  when 0x48
    @mode = value & 0xFFFF
    write_ctrl((value >> 16) & 0xFFFF)
  when 0x4C then @baud = value & 0xFFFF
  end
end

#write8(offset, value) ⇒ Object



114
115
116
117
118
119
120
# File 'lib/psx/sio0.rb', line 114

def write8(offset, value)
  case offset
  when 0x40 then transmit(value & 0xFF)
  when 0x4A then write_ctrl((@ctrl & 0xFF00) | (value & 0xFF))
  when 0x4B then write_ctrl((@ctrl & 0x00FF) | ((value & 0xFF) << 8))
  end
end