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, /// 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; } const IpcFifo = struct { const Sync = IpcSync; const Control = IpcFifoCnt; _nds7: Impl = .{}, _nds9: Impl = .{}, const Source = enum { nds7, nds9 }; const Impl = struct { /// IPC Synchronize /// Read/Write sync: Sync = .{ .raw = 0x0000_0000 }, /// 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); }, } } /// 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), } } /// 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); // 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); 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); }, } } /// 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; }; // 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); self._nds7.cnt.recv_fifo_full.write(self._nds9.fifo._len() == 0x10); self._nds9.cnt.send_fifo_full.write(self._nds9.fifo._len() == 0x10); 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; }, } } }; 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, }; 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); } 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", .{}); 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); } }; 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 }; }; 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; } };