turbo/src/core/io.zig

326 lines
9.5 KiB
Zig
Raw Normal View History

const std = @import("std");
const Bitfield = @import("bitfield").Bitfield;
const Bit = @import("bitfield").Bit;
const log = std.log.scoped(.shared_io);
// FIXME: This whole thing is bad bad bad bad bad
// I think only the IPC stuff needs to be here, since they talk to each other.
// every other "shared I/O register" is just duplicated on both CPUs. So they shouldn't be here
pub const Io = struct {
/// Interrupt Master Enable
/// Read/Write
ime: bool = false,
/// Interrupt Enable
/// Read/Write
///
/// Caller must cast the `u32` to either `nds7.IntEnable` or `nds9.IntEnable`
ie: u32 = 0x0000_0000,
/// IF - Interrupt Request
/// Read/Write
///
/// Caller must cast the `u32` to either `nds7.IntRequest` or `nds9.IntRequest`
irq: u32 = 0x0000_0000,
2023-09-16 02:25:40 +00:00
/// Inter Process Communication FIFO
ipc_fifo: IpcFifo = .{},
/// Post Boot Flag
/// Read/Write
///
/// Caller must cast the `u8` to either `nds7.PostFlg` or `nds9.PostFlg`
post_flg: u8 = @intFromEnum(nds7.PostFlag.in_progress),
// TODO: DS Cartridge I/O Ports
};
fn warn(comptime format: []const u8, args: anytype) u0 {
log.warn(format, args);
return 0;
}
2023-09-16 02:25:40 +00:00
const IpcFifo = struct {
const Sync = IpcSync;
const Control = IpcFifoCnt;
_nds7: Impl = .{},
_nds9: Impl = .{},
2023-09-16 02:25:40 +00:00
const Source = enum { nds7, nds9 };
const Impl = struct {
/// IPC Synchronize
/// Read/Write
sync: Sync = .{ .raw = 0x0000_0000 },
2023-09-16 02:25:40 +00:00
/// IPC Fifo Control
/// Read/Write
cnt: Control = .{ .raw = 0x0000_0101 },
fifo: Fifo = Fifo{},
/// Latch containing thel last read value from a FIFO
last_read: ?u32 = null,
};
/// IPCSYNC
/// Read/Write
pub fn setIpcSync(self: *@This(), comptime src: Source, value: anytype) void {
switch (src) {
.nds7 => {
self._nds7.sync.raw = masks.ipcFifoSync(self._nds7.sync.raw, value);
self._nds9.sync.raw = masks.mask(self._nds9.sync.raw, (self._nds7.sync.raw >> 8) & 0xF, 0xF);
},
.nds9 => {
self._nds9.sync.raw = masks.ipcFifoSync(self._nds9.sync.raw, value);
self._nds7.sync.raw = masks.mask(self._nds7.sync.raw, (self._nds9.sync.raw >> 8) & 0xF, 0xF);
},
}
}
2023-09-16 02:25:40 +00:00
/// IPCFIFOCNT
/// Read/Write
pub fn setIpcFifoCnt(self: *@This(), comptime src: Source, value: anytype) void {
switch (src) {
.nds7 => self._nds7.cnt.raw = masks.ipcFifoCnt(self._nds7.cnt.raw, value),
.nds9 => self._nds9.cnt.raw = masks.ipcFifoCnt(self._nds9.cnt.raw, value),
}
}
2023-09-16 02:25:40 +00:00
/// IPC Send FIFO
/// Write-Only
pub fn send(self: *@This(), comptime src: Source, value: u32) !void {
switch (src) {
.nds7 => {
if (!self._nds7.cnt.enable_fifos.read()) return;
try self._nds7.fifo.push(value);
2023-09-16 02:25:40 +00:00
// update status bits
self._nds7.cnt.send_fifo_empty.write(self._nds7.fifo._len() == 0);
self._nds9.cnt.recv_fifo_empty.write(self._nds7.fifo._len() == 0);
2023-09-16 02:25:40 +00:00
self._nds7.cnt.send_fifo_full.write(self._nds7.fifo._len() == 0x10);
self._nds9.cnt.recv_fifo_full.write(self._nds7.fifo._len() == 0x10);
},
.nds9 => {
if (!self._nds9.cnt.enable_fifos.read()) return;
try self._nds9.fifo.push(value);
// update status bits
self._nds9.cnt.send_fifo_empty.write(self._nds9.fifo._len() == 0);
self._nds7.cnt.recv_fifo_empty.write(self._nds9.fifo._len() == 0);
self._nds9.cnt.send_fifo_full.write(self._nds9.fifo._len() == 0x10);
self._nds7.cnt.recv_fifo_full.write(self._nds9.fifo._len() == 0x10);
},
}
2023-09-16 02:25:40 +00:00
}
/// IPC Receive FIFO
/// Read-Only
pub fn recv(self: *@This(), comptime src: Source) u32 {
switch (src) {
.nds7 => {
const enabled = self._nds7.cnt.enable_fifos.read();
const val_opt = if (enabled) self._nds9.fifo.pop() else self._nds9.fifo.peek();
const value = if (val_opt) |val| blk: {
self._nds9.last_read = val;
break :blk val;
} else blk: {
self._nds7.cnt.fifo_error.set();
break :blk self._nds7.last_read orelse 0x0000_0000;
};
2023-09-16 02:25:40 +00:00
// update status bits
self._nds7.cnt.recv_fifo_empty.write(self._nds9.fifo._len() == 0);
self._nds9.cnt.send_fifo_empty.write(self._nds9.fifo._len() == 0);
2023-09-16 02:25:40 +00:00
self._nds7.cnt.recv_fifo_full.write(self._nds9.fifo._len() == 0x10);
self._nds9.cnt.send_fifo_full.write(self._nds9.fifo._len() == 0x10);
2023-09-16 02:25:40 +00:00
return value;
},
.nds9 => {
const enabled = self._nds9.cnt.enable_fifos.read();
const val_opt = if (enabled) self._nds7.fifo.pop() else self._nds7.fifo.peek();
const value = if (val_opt) |val| blk: {
self._nds7.last_read = val;
break :blk val;
} else blk: {
self._nds9.cnt.fifo_error.set();
break :blk self._nds7.last_read orelse 0x0000_0000;
};
// update status bits
self._nds9.cnt.recv_fifo_empty.write(self._nds7.fifo._len() == 0);
self._nds7.cnt.send_fifo_empty.write(self._nds7.fifo._len() == 0);
self._nds9.cnt.recv_fifo_full.write(self._nds7.fifo._len() == 0x10);
self._nds7.cnt.send_fifo_full.write(self._nds7.fifo._len() == 0x10);
return value;
},
}
2023-09-16 02:25:40 +00:00
}
};
const IpcSync = extern union {
/// Data input to IPCSYNC Bit 8->11 of remote CPU
/// Read-Only
data_input: Bitfield(u32, 0, 4),
/// Data output to IPCSYNC Bit 0->3 of remote CPU
/// Read/Write
data_output: Bitfield(u32, 8, 4),
/// Send IRQ to remote CPU
/// Write-Only
send_irq: Bit(u32, 13),
/// Enable IRQ from remote CPU
/// Read/Write
recv_irq: Bit(u32, 14),
raw: u32,
};
const IpcFifoCnt = extern union {
/// Read-Only
send_fifo_empty: Bit(u32, 0),
/// Read-Only
send_fifo_full: Bit(u32, 1),
/// Read/Write
send_fifo_irq_enable: Bit(u32, 2),
/// Write-Only
send_fifo_clear: Bit(u32, 3),
/// Read-Only
recv_fifo_empty: Bit(u32, 8),
/// Read-Only
recv_fifo_full: Bit(u32, 9),
/// IRQ for when the Receive FIFO is **not empty**
/// Read/Write
recv_fifo_irq_enable: Bit(u32, 10),
/// Error, recv FIFO empty or send FIFO full
/// Read/Write
fifo_error: Bit(u32, 14),
/// Read/Write
enable_fifos: Bit(u32, 15),
raw: u32,
};
2023-09-20 00:08:33 +00:00
pub const masks = struct {
const Bus9 = @import("nds9/Bus.zig");
const Bus7 = @import("nds7/Bus.zig");
inline fn ipcFifoSync(sync: u32, value: anytype) u32 {
const _mask: u32 = 0x6F00;
return (@as(u32, value) & _mask) | (sync & ~_mask);
2023-09-20 00:08:33 +00:00
}
inline fn ipcFifoCnt(cnt: u32, value: anytype) u32 {
const _mask: u32 = 0xC40C;
const err_mask: u32 = 0x4000; // bit 14
const err_bit = (cnt & err_mask) & ~(value & err_mask);
if (value & 0b1000 != 0) log.err("TODO: handle IPCFIFOCNT.3", .{});
2023-09-20 00:08:33 +00:00
const without_err = (@as(u32, value) & _mask) | (cnt & ~_mask);
return (without_err & ~err_mask) | err_bit;
}
/// General Mask helper
pub inline fn mask(original: anytype, value: @TypeOf(original), _mask: @TypeOf(original)) @TypeOf(original) {
return (value & _mask) | (original & ~_mask);
2023-09-20 00:08:33 +00:00
}
};
pub const nds7 = struct {
pub const IntEnable = extern union {
raw: u32,
};
pub const IntRequest = IntEnable;
pub const PostFlag = enum(u8) { in_progress = 0, completed };
};
pub const nds9 = struct {
pub const IntEnable = extern union {
raw: u32,
};
pub const IntRequest = IntEnable;
pub const PostFlag = enum(u8) { in_progress = 0, completed };
};
2023-09-16 02:25:40 +00:00
const Fifo = struct {
const Index = u8;
const Error = error{full};
const len = 0x10;
read_idx: Index = 0,
write_idx: Index = 0,
buf: [len]u32 = [_]u32{undefined} ** len,
comptime {
const max_capacity = (@as(Index, 1) << @typeInfo(Index).Int.bits - 1) - 1; // half the range of index type
std.debug.assert(std.math.isPowerOfTwo(len));
std.debug.assert(len <= max_capacity);
}
pub fn reset(self: *@This()) void {
self.read_idx = 0;
self.write_idx = 0;
}
pub fn push(self: *@This(), value: u32) Error!void {
if (self.isFull()) return Error.full;
defer self.write_idx += 1;
self.buf[self.mask(self.write_idx)] = value;
}
pub fn pop(self: *@This()) ?u32 {
if (self.isEmpty()) return null;
defer self.read_idx += 1;
return self.buf[self.mask(self.read_idx)];
}
pub fn peek(self: *const @This()) ?u32 {
if (self.isEmpty()) return null;
return self.buf[self.mask(self.read_idx)];
}
fn _len(self: *const @This()) Index {
return self.write_idx - self.read_idx;
}
fn isFull(self: *const @This()) bool {
return self._len() == self.buf.len;
}
fn isEmpty(self: *const @This()) bool {
return self.read_idx == self.write_idx;
}
inline fn mask(self: *const @This(), idx: Index) Index {
const _mask: Index = @intCast(self.buf.len - 1);
return idx & _mask;
}
};