jak-project/common/dma/dma_chain_read.h

124 lines
3.5 KiB
C++

#pragma once
#include <cstring>
#include "common/dma/dma.h"
#include "common/util/Assert.h"
/*!
* @file dma_chain_read.h
* This file contains utilities for reading/following a DMA chain.
*
* This allows you to iterate through transfers like this:
*
* DmaFollower dma(mem, start_addr);
* while (!reader.ended) {
* DmaTransfer transfer = reader.advance_to_next_transfer();
* // do something with the data in transfer.
* }
*/
/*!
* Represents a DMA transfer, including 64-bits of VIF tag.
*/
struct DmaTransfer {
const u8* data = nullptr;
u32 data_offset = 0;
u32 size_bytes = 0;
u64 transferred_tag = 0;
u32 vif0() const { return transferred_tag & 0xffffffff; }
u32 vif1() const { return (transferred_tag >> 32) & 0xffffffff; }
VifCode vifcode0() const { return VifCode(vif0()); }
VifCode vifcode1() const { return VifCode(vif1()); }
template <typename T>
T read_val(u32 offset) const {
T result;
memcpy(&result, (const u8*)data + offset, sizeof(T));
return result;
}
};
class DmaFollower {
public:
DmaFollower() { m_ended = true; }
DmaFollower(const void* data, u32 start_offset) : m_base(data), m_tag_offset(start_offset) {}
template <typename T>
T read_val(u32 offset) const {
T result;
memcpy(&result, (const u8*)m_base + offset, sizeof(T));
return result;
}
/*!
* Read the current tag, return its transfer, then advance to the next.
*/
DmaTransfer read_and_advance() {
DmaTag tag(read_val<u64>(m_tag_offset));
DmaTransfer result;
result.transferred_tag = read_val<u64>(m_tag_offset + 8);
result.size_bytes = (u32)tag.qwc * 16;
ASSERT(!tag.spr);
ASSERT(!m_ended);
switch (tag.kind) {
case DmaTag::Kind::CNT:
// data, then next tag. doesn't read address.
ASSERT(tag.addr == 0);
result.data_offset = m_tag_offset + 16;
m_tag_offset = result.data_offset + result.size_bytes;
break;
case DmaTag::Kind::NEXT:
result.data_offset = m_tag_offset + 16;
m_tag_offset = tag.addr;
break;
case DmaTag::Kind::REF:
case DmaTag::Kind::REFS:
result.data_offset = tag.addr;
m_tag_offset = m_tag_offset + 16;
break;
case DmaTag::Kind::REFE:
result.data_offset = tag.addr;
m_tag_offset = m_tag_offset + 16;
m_ended = true;
break;
case DmaTag::Kind::CALL:
result.data_offset = m_tag_offset + 16;
ASSERT(m_sp <= 1);
m_tag_offset = tag.addr;
m_stack[m_sp++] = result.data_offset + tag.qwc * 16;
break;
case DmaTag::Kind::RET:
ASSERT(m_sp > 0);
result.data_offset = m_tag_offset + 16;
m_tag_offset = m_stack[--m_sp];
break;
case DmaTag::Kind::END:
result.data_offset = m_tag_offset + 16;
m_ended = true;
break;
default:
ASSERT(false);
}
result.data = (const u8*)m_base + result.data_offset;
return result;
}
DmaTag current_tag() const { return DmaTag(read_val<u64>(m_tag_offset)); }
u32 current_tag_vif0() const { return read_val<u32>(m_tag_offset + 8); }
u32 current_tag_vif1() const { return read_val<u32>(m_tag_offset + 12); }
VifCode current_tag_vifcode0() const { return VifCode(current_tag_vif0()); }
VifCode current_tag_vifcode1() const { return VifCode(current_tag_vif1()); }
u32 current_tag_offset() const { return m_tag_offset; }
bool ended() const { return m_ended; }
private:
const void* m_base = nullptr;
u32 m_tag_offset = 0;
s32 m_sp = 0;
s32 m_stack[2] = {-1, -1};
bool m_ended = false;
};