Class: PSX::SIO0
- Inherits:
-
Object
- Object
- PSX::SIO0
- 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
-
#initialize(interrupts: nil, controller_state: -> { 0xFFFF }) ⇒ SIO0
constructor
A new instance of SIO0.
- #read16(offset) ⇒ Object
- #read32(offset) ⇒ Object
-
#read8(offset) ⇒ Object
Reads are byte-addressable; widen as needed for 16/32-bit accesses.
- #reset_all ⇒ Object
-
#status ⇒ Object
Read-side JOY_STAT word.
-
#tick(cycles) ⇒ Object
Drive the /ACK timing.
- #write16(offset, value) ⇒ Object
- #write32(offset, value) ⇒ Object
- #write8(offset, value) ⇒ Object
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_all ⇒ Object
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 |
#status ⇒ Object
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 |