Class: PSX::DMA
- Inherits:
-
Object
- Object
- PSX::DMA
- Defined in:
- lib/psx/dma.rb
Overview
DMA Controller Handles bulk memory transfers between RAM and devices
Defined Under Namespace
Classes: Channel
Constant Summary collapse
- MDEC_IN =
DMA Channels
0- MDEC_OUT =
MDEC decoder input
1- GPU =
MDEC decoder output
2- CDROM =
Graphics Processing Unit
3- SPU =
CD-ROM drive
4- PIO =
Sound Processing Unit
5- OTC =
Expansion port
6- NUM_CHANNELS =
Ordering Table Clear
7- CTRL_DIRECTION =
Channel control register bits
0x0000_0001- CTRL_STEP =
0=To RAM, 1=From RAM
0x0000_0002- CTRL_CHOPPING =
0=Forward (+4), 1=Backward (-4)
0x0000_0100- CTRL_SYNC_MODE =
Enable chopping
0x0000_0600- CTRL_CHOP_DMA =
Sync mode (bits 9-10)
0x0007_0000- CTRL_CHOP_CPU =
Chopping DMA window (bits 16-18)
0x0700_0000- CTRL_START_BUSY =
Chopping CPU window (bits 20-22)
0x0100_0000- CTRL_START_TRIGGER =
Start/Busy (bit 24)
0x1000_0000- SYNC_MANUAL =
Sync modes
0- SYNC_REQUEST =
Transfer all at once (burst)
1- SYNC_LINKED =
Sync blocks to DRQ
2- DICR_FORCE_IRQ =
DICR bits
0x0000_8000- DICR_IRQ_ENABLE =
Force IRQ (bit 15)
0x007F_0000- DICR_IRQ_MASTER =
Per-channel IRQ enable (bits 16-22)
0x0080_0000- DICR_IRQ_FLAGS =
Master IRQ enable (bit 23)
0x7F00_0000- DICR_IRQ_MASTER_FLAG =
Per-channel IRQ flags (bits 24-30)
0x8000_0000
Instance Attribute Summary collapse
-
#channels ⇒ Object
readonly
Returns the value of attribute channels.
-
#dicr ⇒ Object
readonly
Returns the value of attribute dicr.
-
#dpcr ⇒ Object
readonly
Returns the value of attribute dpcr.
-
#spu ⇒ Object
Returns the value of attribute spu.
Instance Method Summary collapse
- #channel_enabled?(n) ⇒ Boolean
-
#initialize(interrupts: nil, spu: nil) ⇒ DMA
constructor
A new instance of DMA.
-
#read(offset) ⇒ Object
Register access (offset from 0x1F801080).
- #set_irq_flag(channel) ⇒ Object
- #tick(memory, gpu: nil) ⇒ Object
-
#tick_cycles(cycles) ⇒ Object
Execute pending DMA transfers Returns true if any transfer was performed.
- #update_master_flag ⇒ Object
- #write(offset, value) ⇒ Object
- #write_dicr(value) ⇒ Object
Constructor Details
#initialize(interrupts: nil, spu: nil) ⇒ DMA
Returns a new instance of DMA.
101 102 103 104 105 106 107 108 |
# File 'lib/psx/dma.rb', line 101 def initialize(interrupts: nil, spu: nil) @interrupts = interrupts @spu = spu @channels = Array.new(NUM_CHANNELS) { Channel.new } @dpcr = 0x0765_4321 # Default: all channels enabled with default priorities @dicr = 0 @master_flag_latched = false # Has IRQ#3 fired for the current rising edge? end |
Instance Attribute Details
#channels ⇒ Object (readonly)
Returns the value of attribute channels.
98 99 100 |
# File 'lib/psx/dma.rb', line 98 def channels @channels end |
#dicr ⇒ Object (readonly)
Returns the value of attribute dicr.
98 99 100 |
# File 'lib/psx/dma.rb', line 98 def dicr @dicr end |
#dpcr ⇒ Object (readonly)
Returns the value of attribute dpcr.
98 99 100 |
# File 'lib/psx/dma.rb', line 98 def dpcr @dpcr end |
#spu ⇒ Object
Returns the value of attribute spu.
99 100 101 |
# File 'lib/psx/dma.rb', line 99 def spu @spu end |
Instance Method Details
#channel_enabled?(n) ⇒ Boolean
210 211 212 213 214 |
# File 'lib/psx/dma.rb', line 210 def channel_enabled?(n) # Check DPCR enable bit for channel # Each channel has 4 bits in DPCR, bit 3 of each is enable (@dpcr >> (n * 4 + 3)) & 1 == 1 end |
#read(offset) ⇒ Object
Register access (offset from 0x1F801080)
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 |
# File 'lib/psx/dma.rb', line 111 def read(offset) if offset < 0x70 # Channel registers channel_num = offset >> 4 reg = offset & 0xF return 0 if channel_num >= NUM_CHANNELS channel = @channels[channel_num] case reg when 0x0 then channel.base_addr when 0x4 then channel.block_ctrl when 0x8 then channel.channel_ctrl else 0 end elsif offset == 0x70 @dpcr elsif offset == 0x74 @dicr else 0 end end |
#set_irq_flag(channel) ⇒ Object
216 217 218 219 220 221 222 |
# File 'lib/psx/dma.rb', line 216 def set_irq_flag(channel) # Per Nocash: per-channel IRQ flag is set on completion only when the # corresponding per-channel IRQ enable (bits 16-22) is also set. return unless ((@dicr >> (16 + channel)) & 1) == 1 @dicr |= (1 << (24 + channel)) update_master_flag end |
#tick(memory, gpu: nil) ⇒ Object
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 |
# File 'lib/psx/dma.rb', line 242 def tick(memory, gpu: nil) NUM_CHANNELS.times do |n| next unless channel_enabled?(n) sync_mode = (@channels[n].channel_ctrl >> 9) & 0x3 # Only OTC needs an explicit manual trigger in SyncMode 0; channels # backed by a device (GPU/SPU/...) start on BUSY alone. needs_trigger = (n == OTC) && (sync_mode == SYNC_MANUAL) next unless @channels[n].active?(needs_trigger: needs_trigger) case n when GPU transfer_gpu(memory, gpu) when SPU transfer_spu(memory) when OTC transfer_otc(memory) # Other channels can be added as needed end end end |
#tick_cycles(cycles) ⇒ Object
Execute pending DMA transfers Returns true if any transfer was performed
226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 |
# File 'lib/psx/dma.rb', line 226 def tick_cycles(cycles) return unless @pending_completions && !@pending_completions.empty? @pending_completions.reject! do |n| ch = @channels[n] ch.busy_cycles -= cycles if ch.busy_cycles <= 0 ch.finish! set_irq_flag(n) true else false end end end |
#update_master_flag ⇒ Object
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 |
# File 'lib/psx/dma.rb', line 186 def update_master_flag # PSX semantics: IRQ#3 fires on the rising edge of master-flag (bit 31). # The master flag itself is "calculated" from current state. We expose it # as DICR bit 31 for software to read, and use an internal latch so the # IRQ fires exactly once per rising edge — until the condition goes false # again (BIOS acks channel flags or clears master enable). force = (@dicr & DICR_FORCE_IRQ) != 0 master_enable = (@dicr & DICR_IRQ_MASTER) != 0 flags = (@dicr >> 24) & 0x7F enables = (@dicr >> 16) & 0x7F master = force || (master_enable && (flags & enables) != 0) if master @dicr |= DICR_IRQ_MASTER_FLAG unless @master_flag_latched @master_flag_latched = true @interrupts&.request(Interrupts::IRQ_DMA) end else @dicr &= ~DICR_IRQ_MASTER_FLAG @master_flag_latched = false end end |
#write(offset, value) ⇒ Object
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 |
# File 'lib/psx/dma.rb', line 135 def write(offset, value) if offset < 0x70 # Channel registers channel_num = offset >> 4 reg = offset & 0xF return if channel_num >= NUM_CHANNELS channel = @channels[channel_num] case reg when 0x0 channel.base_addr = value & 0x00FF_FFFC # Word-aligned, 24-bit when 0x4 channel.block_ctrl = value when 0x8 # OTC (channel 6) has hard-wired CHCR bits: only bits 24, 28, 30 # are writable, and bit 1 (Memory Address Step = backward) always # reads as 1. Verified against ps1-tests/dma/otc-test. if channel_num == OTC channel.channel_ctrl = (value & 0x5100_0000) | 0x0000_0002 else channel.channel_ctrl = value end end elsif offset == 0x70 @dpcr = value elsif offset == 0x74 write_dicr(value) end end |
#write_dicr(value) ⇒ Object
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
# File 'lib/psx/dma.rb', line 166 def write_dicr(value) # Bits 0-5: Unknown/unused # Bit 15: Force IRQ # Bits 16-22: IRQ enable for channels 0-6 # Bit 23: Master IRQ enable # Bits 24-30: IRQ flags for channels 0-6 (write 1 to acknowledge) # Bit 31: Master IRQ flag (read-only) # Acknowledge flags by writing 1 ack = value & DICR_IRQ_FLAGS @dicr &= ~ack # Update writable bits (preserve flags that weren't acknowledged) @dicr = (@dicr & DICR_IRQ_FLAGS) | (value & 0x00FF_803F) # Update master flag update_master_flag end |