-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathreader.rs
More file actions
475 lines (435 loc) · 15.7 KB
/
Copy pathreader.rs
File metadata and controls
475 lines (435 loc) · 15.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
use crate::bldb;
use crate::cons;
use crate::println;
use crate::repl::Value;
use crate::result::{Error, Result};
use alloc::boxed::Box;
use alloc::string::String;
use alloc::vec::Vec;
use core::fmt;
#[derive(Clone, Debug)]
pub enum Token {
Push,
Swap,
Term,
Value(Value),
}
#[derive(Clone)]
pub enum Command {
Push,
Swap,
Cmd(String, Vec<Token>),
}
impl fmt::Debug for Command {
fn fmt(
&self,
f: &mut fmt::Formatter<'_>,
) -> core::result::Result<(), fmt::Error> {
match self {
Self::Push => write!(f, "Push"),
Self::Swap => write!(f, "Swap"),
Self::Cmd(cmd, _) => write!(f, "{cmd}"),
}
}
}
pub(super) fn parse_num<T: Default + TryFrom<u128>>(num: &str) -> Result<T> {
let num = num.bytes().filter(|&b| b != b'_').collect::<Vec<_>>();
let num = unsafe { core::str::from_utf8_unchecked(&num) };
let (radix, numstr) = match num {
"0" => return Ok(T::default()),
s if s.starts_with("0x") || s.starts_with("0X") => (16, &s[2..]),
s if s.ends_with("h") || s.ends_with("H") => (16, &s[..s.len() - 1]),
s if s.starts_with("0t") || s.starts_with("0T") => (10, &s[2..]),
s if s.starts_with("0b") || s.starts_with("0B") => (2, &s[2..]),
s if s.starts_with("0i") || s.starts_with("0I") => (2, &s[2..]),
s if s.ends_with("b") || s.ends_with("B") => (2, &s[..s.len() - 1]),
s if s.starts_with("0") => (8, &s[0..]),
s => (10, s),
};
let num =
u128::from_str_radix(numstr, radix).map_err(|_| Error::NumParse)?;
T::try_from(num).map_err(|_| Error::NumRange)
}
fn parse_len<T: Default + TryFrom<u128>>(mut tok: &str) -> Result<T> {
let mut multiplier: u128 = 1;
while !tok.is_empty() {
if let Some(rest) = tok.strip_suffix(['k', 'K']) {
multiplier *= 1024;
tok = rest;
continue;
}
if let Some(rest) = tok.strip_suffix(['m', 'M']) {
multiplier *= 1024 * 1024;
tok = rest;
continue;
}
if let Some(rest) = tok.strip_suffix(['g', 'G']) {
multiplier *= 1024 * 1024 * 1024;
tok = rest;
continue;
}
break;
}
let num = if tok.is_empty() { 1 } else { parse_num(tok)? };
let num = multiplier.checked_mul(num).ok_or(Error::NumRange)?;
T::try_from(num).map_err(|_| Error::NumRange)
}
fn split_pair(s: &str, pat: char) -> Result<(&str, Option<&str>)> {
let mut it = s.split(pat);
let (Some(a), b, None) = (it.next(), it.next(), it.next()) else {
return Err(Error::BadArgs);
};
Ok((a, b))
}
fn eval_reader_command(
config: &mut bldb::Config,
cmd: &str,
env: &mut Vec<Value>,
lastval: &Value,
) -> bool {
match cmd {
"clear" => cons::clear(&mut config.cons),
"config" => println!("{config:#x?}"),
"result" | "res" => println!("{lastval:?}"),
"env" | "stack" => dumpenv(env),
"clrenv" => env.clear(),
"help" | "man" => help(),
_ => return false,
}
true
}
fn dumpenv(env: &[Value]) {
println!("environment:");
if !env.is_empty() {
for (k, val) in env.iter().rev().enumerate() {
println!("[{k}]: {val:?}");
}
} else {
println!("(empty)");
}
}
fn parse_value(s: &str) -> Result<Value> {
let v = match s.chars().next() {
Some(c) if c.is_ascii_digit() && !s.contains('/') => {
let (a, b) = split_pair(s, ',')?;
if let Some(b) = b {
Value::Pair(parse_num(a)?, parse_len(b)?)
} else {
Value::Unsigned(parse_num(a)?)
}
}
Some(_) => Value::Str(String::from(s)),
_ => Value::Nil,
};
Ok(v)
}
fn readline(config: &mut bldb::Config) -> Result<String> {
let prompt = match config.prompt {
cons::Prompt::Tenex => prompt::tenex,
cons::Prompt::Spinner => prompt::spin,
cons::Prompt::Pulser => prompt::pulse,
};
if config.prompt == cons::Prompt::Tenex {
let mut buf = [0u8; 1024];
cons::readline(prompt, &mut config.cons, &mut buf).map(String::from)
} else {
loop {
let mut buf = [0u8; 1024];
match cons::readline_timeout(
prompt,
&mut config.cons,
core::time::Duration::from_secs(10),
&mut buf,
) {
Err(Error::Timeout) => {
cons::backspace(&mut config.cons, false);
continue;
}
res => return res.map(String::from),
}
}
}
}
mod prompt {
use crate::{cons, uart};
use core::time::Duration;
pub(super) fn tenex(term: &mut uart::Uart) -> usize {
term.putb(b'@');
1
}
pub(super) fn pulse(term: &mut uart::Uart) -> usize {
cons::cycle(term, b"", b"oOo.", b" ", Duration::from_millis(500));
tenex(term)
}
pub(super) fn spin(term: &mut uart::Uart) -> usize {
cons::cycle(term, b"", b"|/-\\", b" ", Duration::from_millis(250));
tenex(term)
}
}
pub fn read(
config: &mut bldb::Config,
env: &mut Vec<Value>,
lastval: &Value,
) -> Result<Vec<Command>> {
let line = loop {
let Ok(s) = readline(config) else {
return Err(Error::Reader);
};
let line = s.as_str();
let line = line.trim();
if line.is_empty() {
continue;
}
if eval_reader_command(config, line, env, lastval) {
continue;
}
if let Some(expansion) = config.aliases.get(line) {
break expansion.clone();
}
break s;
};
let mut cmds = Vec::<Command>::new();
let cs: Box<dyn Iterator<Item = &str>> = if line.contains('|') {
Box::new(line.split('|').rev())
} else {
Box::new(line.split('.'))
};
for cmd in cs {
let mut cmd = cmd.trim();
let cmdline = String::from(cmd);
while !cmd.is_empty() {
if let Some(rest) = cmd.strip_prefix("@") {
cmds.push(Command::Push);
cmd = rest.trim();
continue;
}
if let Some(rest) = cmd.strip_prefix("#") {
cmds.push(Command::Swap);
cmd = rest.trim();
continue;
}
break;
}
let mut tokens = Vec::<Token>::new();
for mut tok in cmd.split_ascii_whitespace() {
while !tok.is_empty() {
if let Some(rest) = tok.strip_prefix("@") {
tokens.push(Token::Push);
tok = rest.trim();
continue;
}
if let Some(rest) = tok.strip_prefix("#") {
tokens.push(Token::Swap);
tok = rest.trim();
continue;
}
if let Some(rest) = tok.strip_prefix("$") {
tokens.push(Token::Term);
tok = rest.trim();
continue;
}
tokens.push(Token::Value(parse_value(tok)?));
break;
}
}
if !tokens.is_empty()
&& let Token::Value(Value::Str(cmd)) = tokens[0].clone()
{
tokens[0] = Token::Value(Value::Cmd(cmd));
}
cmds.push(Command::Cmd(cmdline, tokens));
}
Ok(cmds)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_len_suffix() {
assert_eq!(1024_usize, parse_len("k").unwrap());
assert_eq!(4096_usize, parse_len("4K").unwrap());
}
#[test]
fn parse_value_tests() {
assert!(matches!(parse_value("").unwrap(), Value::Nil));
assert!(matches!(
parse_value("0x1000,4k").unwrap(),
Value::Pair(0x1000, 4096)
));
}
}
fn help() {
println!(
r#"
## Basic Usage
You are at the `bldb` REPL, where you type commands to the
loader/debugger. Those commands can be chained, in a manner
similar to chaining function calls. If one wanted the effect
`f(g(h(x)))`, then as in Haskell one may write `f . g . h x`.
Commands use an "environment stack" for arguments and to save
values (when appropriate). The REPL will always print the value
returned by the last command.
The `@` command duplicates the value at the top of the stack and
pushes the duplicate. The `$` command will push a `nil`. The
`#` command swaps the two elements at the top of the stack.
To push an element onto the stack, use the `push` command. To
pop the top element, one may use the `pop` command. Note also
that one can use the `.` command separator and `$` to push and
pop items onto and from the environment stack. For example,
```
$a b c
```
Pushes the strings "a", "b", and "c" onto the stack, while,
```
.$
```
will pop the top element.
## Booting a machine
In the simplest case, run `zoxboot` and send your ramdisk via
ZMODEM. `zoxboot` is an alias that expands to to the command
line below.
To send a compressed ramdisk, inflate it, mount it, load a
kernel from it, and call into that kernel, passing the ramdisk
base address and length as arguments, run:
```
call . load /platform/oxide/kernel/amd64/unix . mount . @inflate . rz
```
And then send your compressed ramdisk image using ZMODEM. For
example, via `sz -w 1024 -b ramdisk.ufs.z`.
If you prefer the more traditional "pipe" syntax using `|`
characters, you may use that instead. The above example is
equivalent to:
```
rz | @inflate | mount | load /platform/oxide/kernel/amd64/unix | call
```
## Commands
The reader supports a handful of "reader commands":
* `clear` clears the terminal window
* `config` displays the current system configuration
* `env` or `stack` displays the current environment stack
* `clrenv` clears the environment stack
* `res` or `result` displays the last returned value
* `help` or `man` displays this text
Supported commands include:
* `push item(s)` to push one or more items onto the environment
stack.
* `pop` to pop and return the item currently at the top of the
environment stack. Returns nil if the stack is empty.
* `rz <addr,len>` to receive a file via ZMODEM
* `rx <addr,len>` to receive a file via XMODEM
* `inflate <src addr>,<src len> [<dst addr>,<dst len>]`
decompresses the a ZLIB compressed slice from the given
source to the given destination.
* `mount <addr,len>` to mount a UFS ramdisk or cpio miniroot.
* `umount` to unmount the ramdisk.
* `ls <file>` to list a file or directory on the ramdisk
* `cat <file>` to display the contents of a file
* `copy <file> <dst addr>,<dst len>` to copy the contents of a
file to a region of memory.
* `elfinfo <file>` to read the contents of the ELF header and
segment headers of an ELF file
* `load <file>` to load the given ELF file and retrieve its
entry point
* `loadmem <addr>,<len>` to load an ELF object from the given
region of memory.
* `call <location> [<up to 6 args>]` calls the System V ABI
compliant function at `<location>`, passing up to six
arguments taken from the environment stack argument list
terminated by nil.
* `rdmsr <u32>` to read the numbered MSR (note some MSRs can be
specified by name, such as `IA32_APIC_BASE`)
* `wrmsr <u32> <u64>` to write the given value to the given MSR
* `jfmt <num>` to format a number using the "jazzy" format from
the illumos `mdb` debugger
* `sha256 <file>` to compute the SHA256 checksum of a file in
the ramdisk
* `sha256mem <addr,len>` to compute the SHA256 checksum over a
region of memory
* `inb <port>`, `inw <port>`, `inl <port>` to read data from an
x86 IO port
* `outb <port> <u8>`, `outw <port> <u16>`, `outl <port> <u32>`
to write data to an x86 IO port
* `iomuxget <pin>` to get the function currently active in the
IO mux for the given pin
* `iomuxset <pin> <function>` to configure the IO mux for the
given pin to the given function, where `<function>` is one of,
`F0`, `F1`, `F2`, or `F3`
* `gpioget pin` to get the state of the given GPIO pin
* `gpioset pin <state>` to set the given GPIO pin to the given
state, which includes:
* `pu` to enable the internal pullup (`-pu` to disable)
* `pd` to enable the internal pulldown (`-pd` to disable)
* `ah` to configure active high
* `al` to configure active low
* `oh` to configure output high
* `ol` to configure output low
* `out` to configure as output (output enable is true)
* `in` to configure as input (output enable is false)
* `hexdump <addr>,<len>` to produce a hexdump of `len` bytes of
memory starting at `base`.
* `peek <addr>,<len>` to read `len` bytes starting at `addr`.
`len` must be 1, 2, 4, 8, or 16.
* `poke <addr>,<len> <value>` to poke a value into the `len`
bytes starting at `addr`. `len` must be 1, 2, 4, 8, or 16.
The value is written in native byte order.
* `mapping address` to display the page table mapping for the
given address, if any
* `mappings` to display all virtual memory mappings
* `map <phys addr>,<len> <virt addr> <attrs>` maps `len` bytes
at physical address `phys addr` to virtual address `virt addr`
with the given attributesk, which is a comma-separated list
of:
* `r` to enable page read permission (the default)
* `-r` to remove page read permission
* `w` to enable page write permission
* `-w` to remove page write permission (the default)
* `x` to enable page executable permission (the default)
* `-x` to remove page execute permission
* `c` to enable page cachability (the default)
* `-c` to disable page caching
* `g` to set this page as a "global" page
* `-g` to remove the global page attribute (the default)
`<phys addr>`, `<len>`, `<phys addr>` must all be multiples
of 4KiB. If these values are also multiples of 2MiB or 1GiB,
those size mappings will be used. To map such a region using
smaller page sizes, issue multiple `map` commands covering
smaller regions to make up a contiguous whole.
* `unmap <virt addr>,<len>` to remove a virtual memory mapping
for the range of given virtual address space covering `<len>`
bytes starting at `<virt addr>`. As with mapping, `<len>` and
`<virt addr>` must both be multiples of 4KiB. If these values
are also multiples of 2MiB or 1GiB, those size mappings will
be used. To unmap such a region mapped with smaller page
sizes, issue mulitple `unmap` calls.
* `rdsmn <addr>` to read a 32-bit word from the given SMN
address.
* `rdsmni <index> <addr>` like `rdsmn`, but using a specific
address/data register pair.
* `wrsmn <addr> <value>` to write a 32-bit word to the given SMN
address.
* `wrsmni <index> <addr>` like `wrsmn`, but using a spcecific
address/data register pair.
* `cpuid <leaf> <subleaf>` to return the results of the `CPUID`
instruction for the given leaf and subleaf.
* `ecamrd <b/d/f> <offset>` read a 32-bit word from PCIe
extended configuration space for the given bus/device/function
* `ecamwr <b/d/f> <offset> <value>` writes a 32-bit word to PCIe
extended configuration space for the given bus/device/function
* `getbits <start>,<end> <value>` returns the given bit range
from `<value>`
* `setbits <start>,<end> <new bits> <value>` sets the given bit
range in `<value>` to `<new bits>`
* `spinner` displays a moving "spinner" on the terminal until a
byte is received on the UART. The `pulser` and `throbber`
commands do essentially the same thing, with a different
character pattern. The `megapulser` command exists just for
fun.
* `prompt <tenex | spinner | pulser>` to change the default
prompt type. `tenex` is the "@" prompt. The other two are
animated; see the `spinner` and `pulser` commands above.
"#
);
}