diff --git a/src/core/bus/backup.zig b/src/core/bus/backup.zig index 48cb603..34692f7 100644 --- a/src/core/bus/backup.zig +++ b/src/core/bus/backup.zig @@ -2,6 +2,8 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const log = std.log.scoped(.Backup); +const Eeprom = @import("backup/eeprom.zig").Eeprom; + const escape = @import("../../util.zig").escape; const span = @import("../../util.zig").span; @@ -55,7 +57,7 @@ pub const Backup = struct { .title = title, .save_path = path, .flash = Flash.init(), - .eeprom = Eeprom.init(allocator), + .eeprom = Eeprom.create(allocator), }; if (backup.save_path) |p| backup.loadSaveFromDisk(allocator, p) catch |e| log.err("Failed to load save: {}", .{e}); @@ -300,267 +302,3 @@ const Flash = struct { return if (self.bank == 1) 0x10000 else @as(usize, 0); } }; - -const Eeprom = struct { - const Self = @This(); - - addr: u14, - - kind: Kind, - state: State, - writer: Writer, - reader: Reader, - - allocator: Allocator, - - const Kind = enum { - Unknown, - Small, // 512B - Large, // 8KB - }; - - const State = enum { - Ready, - Read, - Write, - WriteTransfer, - RequestEnd, - }; - - fn init(allocator: Allocator) Self { - return .{ - .kind = .Unknown, - .state = .Ready, - .writer = Writer.init(), - .reader = Reader.init(), - .addr = 0, - .allocator = allocator, - }; - } - - pub fn read(self: *Self) u1 { - return self.reader.read(); - } - - pub fn dbgRead(self: *const Self) u1 { - return self.reader.dbgRead(); - } - - pub fn write(self: *Self, word_count: u16, buf: *[]u8, bit: u1) void { - if (self.guessKind(word_count)) |found| { - log.info("EEPROM Kind: {}", .{found}); - self.kind = found; - - // buf.len will not equal zero when a save file was found and loaded. - // Right now, we assume that the save file is of the correct size which - // isn't necessarily true, since we can't trust anything a user can influence - // TODO: use ?[]u8 instead of a 0-sized slice? - if (buf.len == 0) { - const len: usize = switch (found) { - .Small => 0x200, - .Large => 0x2000, - else => unreachable, - }; - - buf.* = self.allocator.alloc(u8, len) catch |e| { - log.err("Failed to resize EEPROM buf to {} bytes", .{len}); - std.debug.panic("EEPROM entered irrecoverable state {}", .{e}); - }; - std.mem.set(u8, buf.*, 0xFF); - } - } - - if (self.state == .RequestEnd) { - if (bit != 0) log.debug("EEPROM Request did not end in 0u1. TODO: is this ok?", .{}); - self.state = .Ready; - return; - } - - switch (self.state) { - .Ready => self.writer.requestWrite(bit), - .Read, .Write => self.writer.addressWrite(self.kind, bit), - .WriteTransfer => self.writer.dataWrite(bit), - .RequestEnd => unreachable, // We return early just above this block - } - - self.tick(buf.*); - } - - fn guessKind(self: *const Self, word_count: u16) ?Kind { - if (self.kind != .Unknown or self.state != .Read) return null; - - return switch (word_count) { - 17 => .Large, - 9 => .Small, - else => blk: { - log.err("Unexpected length of DMA3 Transfer upon initial EEPROM read: {}", .{word_count}); - break :blk null; - }, - }; - } - - fn tick(self: *Self, buf: []u8) void { - switch (self.state) { - .Ready => { - if (self.writer.len() == 2) { - const req = @intCast(u2, self.writer.finish()); - switch (req) { - 0b11 => self.state = .Read, - 0b10 => self.state = .Write, - else => log.err("Unknown EEPROM Request 0b{b:0>2}", .{req}), - } - } - }, - .Read => { - switch (self.kind) { - .Large => { - if (self.writer.len() == 14) { - const addr = @intCast(u10, self.writer.finish()); - const value = std.mem.readIntSliceLittle(u64, buf[@as(u13, addr) * 8 ..][0..8]); - - self.reader.configure(value); - self.state = .RequestEnd; - } - }, - .Small => { - if (self.writer.len() == 6) { - // FIXME: Duplicated code from above - const addr = @intCast(u6, self.writer.finish()); - const value = std.mem.readIntSliceLittle(u64, buf[@as(u13, addr) * 8 ..][0..8]); - - self.reader.configure(value); - self.state = .RequestEnd; - } - }, - else => log.err("Unable to calculate EEPROM read address. EEPROM size UNKNOWN", .{}), - } - }, - .Write => { - switch (self.kind) { - .Large => { - if (self.writer.len() == 14) { - self.addr = @intCast(u10, self.writer.finish()); - self.state = .WriteTransfer; - } - }, - .Small => { - if (self.writer.len() == 6) { - self.addr = @intCast(u6, self.writer.finish()); - self.state = .WriteTransfer; - } - }, - else => log.err("Unable to calculate EEPROM write address. EEPROM size UNKNOWN", .{}), - } - }, - .WriteTransfer => { - if (self.writer.len() == 64) { - std.mem.writeIntSliceLittle(u64, buf[self.addr * 8 ..][0..8], self.writer.finish()); - self.state = .RequestEnd; - } - }, - .RequestEnd => unreachable, // We return early in write() if state is .RequestEnd - } - } - - const Reader = struct { - const This = @This(); - - data: u64, - i: u8, - enabled: bool, - - fn init() This { - return .{ - .data = 0, - .i = 0, - .enabled = false, - }; - } - - fn configure(self: *This, value: u64) void { - self.data = value; - self.i = 0; - self.enabled = true; - } - - fn read(self: *This) u1 { - if (!self.enabled) return 1; - - const bit = if (self.i < 4) blk: { - break :blk 0; - } else blk: { - const idx = @intCast(u6, 63 - (self.i - 4)); - break :blk @truncate(u1, self.data >> idx); - }; - - self.i = (self.i + 1) % (64 + 4); - if (self.i == 0) self.enabled = false; - - return bit; - } - - fn dbgRead(self: *const This) u1 { - if (!self.enabled) return 1; - - const bit = if (self.i < 4) blk: { - break :blk 0; - } else blk: { - const idx = @intCast(u6, 63 - (self.i - 4)); - break :blk @truncate(u1, self.data >> idx); - }; - - return bit; - } - }; - - const Writer = struct { - const This = @This(); - - data: u64, - i: u8, - - fn init() This { - return .{ .data = 0, .i = 0 }; - } - - fn requestWrite(self: *This, bit: u1) void { - const idx = @intCast(u1, 1 - self.i); - self.data = (self.data & ~(@as(u64, 1) << idx)) | (@as(u64, bit) << idx); - self.i += 1; - } - - fn addressWrite(self: *This, kind: Eeprom.Kind, bit: u1) void { - if (kind == .Unknown) return; - - const size: u4 = switch (kind) { - .Large => 13, - .Small => 5, - .Unknown => unreachable, - }; - - const idx = @intCast(u4, size - self.i); - self.data = (self.data & ~(@as(u64, 1) << idx)) | (@as(u64, bit) << idx); - self.i += 1; - } - - fn dataWrite(self: *This, bit: u1) void { - const idx = @intCast(u6, 63 - self.i); - self.data = (self.data & ~(@as(u64, 1) << idx)) | (@as(u64, bit) << idx); - self.i += 1; - } - - fn len(self: *const This) u8 { - return self.i; - } - - fn finish(self: *This) u64 { - defer self.reset(); - return self.data; - } - - fn reset(self: *This) void { - self.i = 0; - self.data = 0; - } - }; -}; diff --git a/src/core/bus/backup/eeprom.zig b/src/core/bus/backup/eeprom.zig new file mode 100644 index 0000000..f1d2238 --- /dev/null +++ b/src/core/bus/backup/eeprom.zig @@ -0,0 +1,269 @@ +const std = @import("std"); + +const Allocator = std.mem.Allocator; + +const log = std.log.scoped(.Eeprom); + +pub const Eeprom = struct { + const Self = @This(); + + addr: u14, + + kind: Kind, + state: State, + writer: Writer, + reader: Reader, + + allocator: Allocator, + + const Kind = enum { + Unknown, + Small, // 512B + Large, // 8KB + }; + + const State = enum { + Ready, + Read, + Write, + WriteTransfer, + RequestEnd, + }; + + pub fn read(self: *Self) u1 { + return self.reader.read(); + } + + pub fn dbgRead(self: *const Self) u1 { + return self.reader.dbgRead(); + } + + pub fn write(self: *Self, word_count: u16, buf: *[]u8, bit: u1) void { + if (self.guessKind(word_count)) |found| { + log.info("EEPROM Kind: {}", .{found}); + self.kind = found; + + // buf.len will not equal zero when a save file was found and loaded. + // Right now, we assume that the save file is of the correct size which + // isn't necessarily true, since we can't trust anything a user can influence + // TODO: use ?[]u8 instead of a 0-sized slice? + if (buf.len == 0) { + const len: usize = switch (found) { + .Small => 0x200, + .Large => 0x2000, + else => unreachable, + }; + + buf.* = self.allocator.alloc(u8, len) catch |e| { + log.err("Failed to resize EEPROM buf to {} bytes", .{len}); + std.debug.panic("EEPROM entered irrecoverable state {}", .{e}); + }; + std.mem.set(u8, buf.*, 0xFF); + } + } + + if (self.state == .RequestEnd) { + if (bit != 0) log.debug("EEPROM Request did not end in 0u1. TODO: is this ok?", .{}); + self.state = .Ready; + return; + } + + switch (self.state) { + .Ready => self.writer.requestWrite(bit), + .Read, .Write => self.writer.addressWrite(self.kind, bit), + .WriteTransfer => self.writer.dataWrite(bit), + .RequestEnd => unreachable, // We return early just above this block + } + + self.tick(buf.*); + } + + pub fn create(allocator: Allocator) Self { + return .{ + .kind = .Unknown, + .state = .Ready, + .writer = Writer.create(), + .reader = Reader.create(), + .addr = 0, + .allocator = allocator, + }; + } + + fn guessKind(self: *const Self, word_count: u16) ?Kind { + if (self.kind != .Unknown or self.state != .Read) return null; + + return switch (word_count) { + 17 => .Large, + 9 => .Small, + else => blk: { + log.err("Unexpected length of DMA3 Transfer upon initial EEPROM read: {}", .{word_count}); + break :blk null; + }, + }; + } + + fn tick(self: *Self, buf: []u8) void { + switch (self.state) { + .Ready => { + if (self.writer.len() == 2) { + const req = @intCast(u2, self.writer.finish()); + switch (req) { + 0b11 => self.state = .Read, + 0b10 => self.state = .Write, + else => log.err("Unknown EEPROM Request 0b{b:0>2}", .{req}), + } + } + }, + .Read => { + switch (self.kind) { + .Large => { + if (self.writer.len() == 14) { + const addr = @intCast(u10, self.writer.finish()); + const value = std.mem.readIntSliceLittle(u64, buf[@as(u13, addr) * 8 ..][0..8]); + + self.reader.configure(value); + self.state = .RequestEnd; + } + }, + .Small => { + if (self.writer.len() == 6) { + // FIXME: Duplicated code from above + const addr = @intCast(u6, self.writer.finish()); + const value = std.mem.readIntSliceLittle(u64, buf[@as(u13, addr) * 8 ..][0..8]); + + self.reader.configure(value); + self.state = .RequestEnd; + } + }, + else => log.err("Unable to calculate EEPROM read address. EEPROM size UNKNOWN", .{}), + } + }, + .Write => { + switch (self.kind) { + .Large => { + if (self.writer.len() == 14) { + self.addr = @intCast(u10, self.writer.finish()); + self.state = .WriteTransfer; + } + }, + .Small => { + if (self.writer.len() == 6) { + self.addr = @intCast(u6, self.writer.finish()); + self.state = .WriteTransfer; + } + }, + else => log.err("Unable to calculate EEPROM write address. EEPROM size UNKNOWN", .{}), + } + }, + .WriteTransfer => { + if (self.writer.len() == 64) { + std.mem.writeIntSliceLittle(u64, buf[self.addr * 8 ..][0..8], self.writer.finish()); + self.state = .RequestEnd; + } + }, + .RequestEnd => unreachable, // We return early in write() if state is .RequestEnd + } + } +}; + +const Reader = struct { + const Self = @This(); + + data: u64, + i: u8, + enabled: bool, + + fn create() Self { + return .{ + .data = 0, + .i = 0, + .enabled = false, + }; + } + + fn read(self: *Self) u1 { + if (!self.enabled) return 1; + + const bit = if (self.i < 4) blk: { + break :blk 0; + } else blk: { + const idx = @intCast(u6, 63 - (self.i - 4)); + break :blk @truncate(u1, self.data >> idx); + }; + + self.i = (self.i + 1) % (64 + 4); + if (self.i == 0) self.enabled = false; + + return bit; + } + + fn dbgRead(self: *const Self) u1 { + if (!self.enabled) return 1; + + const bit = if (self.i < 4) blk: { + break :blk 0; + } else blk: { + const idx = @intCast(u6, 63 - (self.i - 4)); + break :blk @truncate(u1, self.data >> idx); + }; + + return bit; + } + + fn configure(self: *Self, value: u64) void { + self.data = value; + self.i = 0; + self.enabled = true; + } +}; + +const Writer = struct { + const Self = @This(); + + data: u64, + i: u8, + + fn create() Self { + return .{ .data = 0, .i = 0 }; + } + + fn requestWrite(self: *Self, bit: u1) void { + const idx = @intCast(u1, 1 - self.i); + self.data = (self.data & ~(@as(u64, 1) << idx)) | (@as(u64, bit) << idx); + self.i += 1; + } + + fn addressWrite(self: *Self, kind: Eeprom.Kind, bit: u1) void { + if (kind == .Unknown) return; + + const size: u4 = switch (kind) { + .Large => 13, + .Small => 5, + .Unknown => unreachable, + }; + + const idx = @intCast(u4, size - self.i); + self.data = (self.data & ~(@as(u64, 1) << idx)) | (@as(u64, bit) << idx); + self.i += 1; + } + + fn dataWrite(self: *Self, bit: u1) void { + const idx = @intCast(u6, 63 - self.i); + self.data = (self.data & ~(@as(u64, 1) << idx)) | (@as(u64, bit) << idx); + self.i += 1; + } + + fn len(self: *const Self) u8 { + return self.i; + } + + fn finish(self: *Self) u64 { + defer self.reset(); + return self.data; + } + + fn reset(self: *Self) void { + self.i = 0; + self.data = 0; + } +};