summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/board_memory.zig69
-rw-r--r--src/pardes.zig49
2 files changed, 98 insertions, 20 deletions
diff --git a/src/board_memory.zig b/src/board_memory.zig
index 853ae5da..f99fc106 100644
--- a/src/board_memory.zig
+++ b/src/board_memory.zig
@@ -85,20 +85,41 @@ pub const Error = error{
ExtraArgument,
};
-/// hex (`0x4ff40000`), decimal (`1341718528`), and — for free, from base 0 —
-/// binary and octal. A bare `4ff40000` is deliberately NOT hex: it is a
-/// legal-looking decimal number, so guessing the base would make one typo
-/// silently address somewhere else entirely.
+/// EVERY literal these three words take is HEX, with or without an `0x`, and there is no way to
+/// write a decimal one.
+///
+/// This replaces base-0 parsing, which accepted `0x4ff40000` and `1341390848` and refused a bare
+/// `4ff40000` on the grounds that guessing between hex and decimal would make one typo address
+/// somewhere else entirely. That reasoning was sound and the conclusion was still wrong: the
+/// ambiguity it protected against is not a real one. Every address anybody has ever typed at these
+/// three words is hex - it came off a datasheet, a linker map, or a previous dump's own output, all
+/// of which print hex - so the base was never in doubt, and demanding `0x` on every one of them was
+/// a toll on the common case to guard a case that does not arise.
+///
+/// The COUNTS go with them, and that is the part worth stating out loud rather than leaving as a
+/// surprise: `Hexdump 4ff40000 100` shows 0x100 bytes, which is 256, not one hundred. One rule for
+/// every literal in the word is worth more than two rules that each fit their argument better,
+/// because the second kind is the sort of thing you have to remember at the moment you are already
+/// concentrating on something else. Everything these words PRINT is hex too, including the clamp
+/// notes, so a number can go back in where it came out.
+fn parseHex(comptime T: type, tok: []const u8, bad: Error) Error!T {
+ // `parseInt` only honours an `0x` when its base is 0, so with base 16 the prefix has to come off
+ // here. A bare `0x` leaves nothing behind and `parseInt` rejects the empty string, which is the
+ // answer that wants giving.
+ const body = if (tok.len > 2 and tok[0] == '0' and (tok[1] | 0x20) == 'x') tok[2..] else tok;
+ return std.fmt.parseInt(T, body, 16) catch bad;
+}
+
fn parseAddr(tok: []const u8) Error!u32 {
- return std.fmt.parseInt(u32, tok, 0) catch return Error.BadAddress;
+ return parseHex(u32, tok, Error.BadAddress);
}
fn parseCount(tok: []const u8) Error!u64 {
- return std.fmt.parseInt(u64, tok, 0) catch return Error.BadCount;
+ return parseHex(u64, tok, Error.BadCount);
}
fn parseValue(tok: []const u8) Error!u32 {
- return std.fmt.parseInt(u32, tok, 0) catch return Error.BadValue;
+ return parseHex(u32, tok, Error.BadValue);
}
/// A 32-bit peripheral or RAM read that the compiler may neither elide,
@@ -152,12 +173,14 @@ fn extent(addr: u32, requested: u64, unit: u32, cap: u32) Extent {
fn writeNote(w: *std.Io.Writer, e: Extent, requested: u64, unit_name: []const u8) !void {
switch (e.limit orelse return) {
+ // Hex, like everything else these words read and print, so the number in a clamp note can go
+ // straight back into the command that produced it.
.console => try w.print(
- "clamped: {d} {s} requested, {d} shown ({d}-byte cap, one 115200-baud console)\n",
+ "clamped: 0x{x} {s} requested, 0x{x} shown (0x{x}-byte cap, one 115200-baud console)\n",
.{ requested, unit_name, e.count, max_bytes },
),
.space => try w.print(
- "clamped: {d} {s} requested, {d} shown (the 32-bit address space ends at 0x100000000)\n",
+ "clamped: 0x{x} {s} requested, 0x{x} shown (the 32-bit address space ends at 0x100000000)\n",
.{ requested, unit_name, e.count },
),
}
@@ -192,24 +215,34 @@ test "a clamp note is written exactly when something was clamped" {
try writeNote(&w, extent(0x4ff40000, 99_999, 4, max_words), 99_999, "words");
try std.testing.expectEqualStrings(
- "clamped: 99999 words requested, 1024 shown (4096-byte cap, one 115200-baud console)\n",
+ "clamped: 0x1869f words requested, 0x400 shown (0x1000-byte cap, one 115200-baud console)\n",
w.buffered(),
);
w = .fixed(&buf);
try writeNote(&w, extent(0xfffffff0, 64, 1, max_bytes), 64, "bytes");
try std.testing.expectEqualStrings(
- "clamped: 64 bytes requested, 16 shown (the 32-bit address space ends at 0x100000000)\n",
+ "clamped: 0x40 bytes requested, 0x10 shown (the 32-bit address space ends at 0x100000000)\n",
w.buffered(),
);
}
-test "an address is hex or decimal, and a bare hex-looking token is decimal" {
- try std.testing.expectEqual(0x4ff40000, parseAddr("0x4ff40000"));
- try std.testing.expectEqual(0x4ff40000, parseAddr("1341390848"));
- // a bare hex-looking token is a decimal number, never a guess
- try std.testing.expectError(Error.BadAddress, parseAddr("4ff40000"));
+test "every literal is hex, with or without the prefix" {
+ const eq = std.testing.expectEqual;
+ // the prefix is optional, never required, and never changes the answer
+ try eq(0x4ff40000, parseAddr("0x4ff40000"));
+ try eq(0x4ff40000, parseAddr("4ff40000"));
+ try eq(0x4ff40000, parseAddr("0X4FF40000"));
+ try eq(0x4ff40000, parseAddr("4FF40000"));
+ // a token that looks decimal is hex too - the whole point, and the thing to remember
+ try eq(0x100, parseCount("100"));
+ try eq(0x256, parseCount("256"));
+ try eq(0xdeadbeef, parseValue("deadbeef"));
+ // and the refusals still refuse
try std.testing.expectError(Error.BadAddress, parseAddr("0x100000000"));
+ try std.testing.expectError(Error.BadAddress, parseAddr("0x"));
+ try std.testing.expectError(Error.BadAddress, parseAddr("nope"));
+ try std.testing.expectError(Error.BadAddress, parseAddr("12g4"));
try std.testing.expectError(Error.BadCount, parseCount("-1"));
try std.testing.expectError(Error.BadValue, parseValue("0x1_0000_0000"));
}
@@ -221,7 +254,7 @@ test "an address is hex or decimal, and a bare hex-looking token is decimal" {
pub fn peek(p: *Pardes, id: usize, argument: []const u8) !void {
var it = std.mem.tokenizeAny(u8, argument, " \t\r\n");
const addr = try parseAddr(it.next() orelse return Error.MissingAddress);
- const requested = if (it.next()) |tok| try parseCount(tok) else 1;
+ const requested = if (it.next()) |tok| try parseCount(tok) else 0x1;
if (it.next() != null) return Error.ExtraArgument;
if (addr % 4 != 0) return Error.MisalignedAddress;
@@ -271,7 +304,7 @@ pub fn poke(p: *Pardes, id: usize, argument: []const u8) !void {
pub fn hexdump(p: *Pardes, id: usize, argument: []const u8) !void {
var it = std.mem.tokenizeAny(u8, argument, " \t\r\n");
const addr = try parseAddr(it.next() orelse return Error.MissingAddress);
- const requested = if (it.next()) |tok| try parseCount(tok) else 256;
+ const requested = if (it.next()) |tok| try parseCount(tok) else 0x100;
if (it.next() != null) return Error.ExtraArgument;
const e = extent(addr, requested, 1, max_bytes);
diff --git a/src/pardes.zig b/src/pardes.zig
index dcc99aea..8d68cf22 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -5745,7 +5745,53 @@ pub const Pardes = struct {
// output buffer, so booting into one means the first dump lands in the same kind of
// pane the boot pane already is. It is editable text with no file behind it, which is
// the honest description of a buffer on a board with no filesystem.
- const content = try p.gpa.dupe(u8, "");
+ // AND IT BOOTS WITH SOMETHING IN IT. An empty buffer is honest and useless: the three
+ // words that make this board interesting take an address, and a board's address space is
+ // precisely the thing you cannot guess. So the boot buffer is a tour of it - every
+ // address below comes from this repository rather than from memory, which is why they are
+ // worth trusting: the two flash figures and the two RAM ones are the linker script's own
+ // ORIGINs (`05-zig-p4/build.zig`'s MEMORY block), and the peripheral bases are
+ // `DR_REG_*` from ESP-IDF's headers as `05-zig-p4/src/hal` uses them.
+ //
+ // Each command sits alone on its line because an argument list ends at the last argument:
+ // a trailing comment would be `ExtraArgument`, so the notes go above the lines they
+ // describe. Run one by putting the cursor on it, `x` to select the line, Tab to execute -
+ // the same two keystrokes as any other selection, because an output buffer is a file pane
+ // and these are ordinary words.
+ const content = try p.gpa.dupe(u8,
+ \\pardes on an ESP32-P4. No OS, no filesystem:
+ \\this buffer is text with nothing behind it.
+ \\Peek, Poke and Hexdump read the bus itself.
+ \\Numbers are hex, with or without 0x.
+ \\
+ \\Select a line with x, then Tab to run it.
+ \\
+ \\-- this image in flash: rodata, then code
+ \\Hexdump 40000020 60
+ \\Hexdump 40050000 60
+ \\
+ \\-- L2MEM: firmware data, then editor heap
+ \\Hexdump 4ff00000 60
+ \\Hexdump 4ff40000 60
+ \\
+ \\-- the mask ROM, where ets_printf lives
+ \\Hexdump 4fc00000 40
+ \\
+ \\-- UART0: the console you are reading on
+ \\Peek 500ca000 4
+ \\
+ \\-- systimer. Run twice: a register is not
+ \\-- memory, and the two reads differ.
+ \\Peek 500e2000 4
+ \\
+ \\-- GPIO_OUT, and pad 0's IO_MUX entry
+ \\Peek 500e0004
+ \\Peek 500e1004
+ \\
+ \\-- Poke needs a value. Harmless: a W1TC
+ \\-- register clears nothing given no bits.
+ \\Poke 500e000c 0
+ );
errdefer p.gpa.free(content);
_ = try output_pane.open(p, 0, "", .{ .cmd = .New }, "", content);
p.ncol = 1;
@@ -15940,4 +15986,3 @@ test "entering tty walks the shell cursor to the column clicked past the prompt"
try std.testing.expectEqual(@as(usize, 0), rights);
try std.testing.expectEqual(@as(usize, 7), lefts);
}
-