Skip to content

Commit 5237b43

Browse files
committed
spinner: add a moving spinner
The `spinner` command displays a moving spinner until a byte is received over the UART. This is useful for determining whether the machine is frozen or not. Also fix a panic in UFS. Signed-off-by: Dan Cross <cross@oxidecomputer.com>
1 parent ed9fcd1 commit 5237b43

14 files changed

Lines changed: 338 additions & 65 deletions

File tree

Cargo.toml

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ version = "0.1.0"
55
edition = "2024"
66
license = "MPL-2.0"
77

8-
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8+
[features]
9+
spin_prompt = []
10+
pulse_prompt = []
911

1012
[dependencies]
1113
bit_field = "0.10"
@@ -18,10 +20,16 @@ goblin = { version = "0.9", default-features = false, features = [
1820
"elf32",
1921
"alloc",
2022
] }
21-
iced-x86 = { version = "1.21.0", features = ["decoder", "gas", "no_std"], default-features = false }
23+
iced-x86 = { version = "1.21.0", features = [
24+
"decoder",
25+
"gas",
26+
"no_std",
27+
], default-features = false }
2228
miniz_oxide = "0.8"
2329
seq-macro = "0.3"
24-
sha2 = { version = "0.10.8", default-features = false, features = ["force-soft"] }
30+
sha2 = { version = "0.10.8", default-features = false, features = [
31+
"force-soft",
32+
] }
2533
spin = { version = "0.10.0", default-features = false, features = [
2634
"barrier",
2735
"mutex",

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,10 +208,14 @@ Supported commands include:
208208
extended configuration space for the given bus/device/function
209209
* `ecamwr <b/d/f> <offset> <value>` writes a 32-bit word to PCIe
210210
extended configuration space for the given bus/device/function
211-
* `getbits <start>,<end> <value>` returns the given bit range
211+
* `getbits <start>,<end> <value>` returns the given bit range
212212
from `<value>`
213213
* `setbits <start>,<end> <new bits> <value>` sets the given bit
214214
range in `<value>` to `<new bits>`
215+
* `spinner` displays a moving "spinner" on the terminal until a
216+
byte is received on the UART. The `pulser` and `throbber`
217+
commands do essentially the same thing, with a different
218+
character pattern.
215219

216220
## Building bldb
217221

src/bldb.rs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
3939
extern crate alloc;
4040

41+
use crate::cons;
4142
use crate::gpio;
4243
use crate::idt;
4344
use crate::iomux;
@@ -55,6 +56,11 @@ use core::sync::atomic::{AtomicBool, Ordering};
5556
#[cfg(not(test))]
5657
core::arch::global_asm!(include_str!("start.S"), options(att_syntax));
5758

59+
#[cfg(all(feature = "pulse_prompt", feature = "spin_prompt"))]
60+
compile_error!(
61+
"The `pulse_prompt` and `spin_prompt` features are mutually exclusive"
62+
);
63+
5864
/// The loader configuration, consumed by the rest of PHBL.
5965
pub(crate) struct Config {
6066
pub(crate) cons: Uart,
@@ -63,6 +69,7 @@ pub(crate) struct Config {
6369
pub(crate) loader_region: Range<mem::V4KA>,
6470
pub(crate) page_table: mmu::LoaderPageTable,
6571
pub(crate) ramdisk: Option<ufs::FileSystem<'static>>,
72+
pub(crate) prompt: cons::Prompt,
6673
}
6774

6875
impl Config {
@@ -82,11 +89,8 @@ impl fmt::Debug for Config {
8289
let vend = self.loader_region.end.addr();
8390
writeln!(f, " loader: {:#x?}", vstart..vend)?;
8491
writeln!(f, " pageroot: P4KA({:#x}),", self.page_table.phys_addr())?;
85-
writeln!(
86-
f,
87-
" ramdisk: {:?}",
88-
self.ramdisk.as_ref().map(|_| "mounted")
89-
)?;
92+
writeln!(f, " ramdisk: {:?}", self.ramdisk.as_ref())?;
93+
writeln!(f, " prompt: {:?}", self.prompt)?;
9094
write!(f, "}}")
9195
}
9296
}
@@ -143,6 +147,12 @@ pub(crate) unsafe extern "C" fn init(bist: u32) -> &'static mut Config {
143147
&mmio_region,
144148
),
145149
ramdisk: None,
150+
#[cfg(not(any(feature = "pulse_prompt", feature = "spin_prompt")))]
151+
prompt: cons::Prompt::Tenex,
152+
#[cfg(feature = "pulse_prompt")]
153+
prompt: cons::Prompt::Pulser,
154+
#[cfg(feature = "spin_prompt")]
155+
prompt: cons::Prompt::Spinner,
146156
});
147157
if false {
148158
say_hi_sp(&mut config, 4);

src/clock.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright 2021 The Hypatia Authors
2+
// All rights reserved
3+
//
4+
// Use of this source code is governed by an MIT-style
5+
// license that can be found in the LICENSE file or at
6+
// https://opensource.org/licenses/MIT.
7+
8+
use crate::cpuid;
9+
10+
pub const NANOS_PER_SEC: u128 = 1_000_000_000;
11+
12+
/// Returns the clock frequency of the current CPU in Hertz.
13+
pub fn frequency() -> u128 {
14+
const DEFAULT_HZ: u128 = 2_000_000_000;
15+
if let Some(tsc_info) = cpuid::tscinfo() {
16+
if tsc_info.nominal_frequency() != 0 {
17+
return tsc_info
18+
.tsc_frequency()
19+
.map(|freq| freq.into())
20+
.unwrap_or(DEFAULT_HZ);
21+
}
22+
}
23+
DEFAULT_HZ
24+
}
25+
26+
pub fn rdtsc() -> u64 {
27+
unsafe { core::arch::x86_64::_rdtsc() }
28+
}

src/cons.rs

Lines changed: 89 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,50 @@
77

88
use crate::result::{Error, Result};
99
use crate::uart::Uart;
10+
use core::time::Duration;
1011

11-
pub fn readline<'a>(
12-
prompt: &str,
12+
#[derive(Debug, Eq, PartialEq)]
13+
pub enum Prompt {
14+
Tenex,
15+
Spinner,
16+
Pulser,
17+
}
18+
19+
const BS: u8 = 8;
20+
const TAB: u8 = 9;
21+
const NL: u8 = 10;
22+
const CR: u8 = 13;
23+
const CTLU: u8 = 21;
24+
const CTLW: u8 = 23;
25+
const ESC: u8 = 27;
26+
const DEL: u8 = 127;
27+
28+
pub fn readline<'a, F>(
29+
prompt: F,
1330
uart: &mut Uart,
1431
line: &'a mut [u8],
15-
) -> Result<&'a str> {
16-
const BS: u8 = 8;
17-
const TAB: u8 = 9;
18-
const NL: u8 = 10;
19-
const CR: u8 = 13;
20-
const CTLU: u8 = 21;
21-
const CTLW: u8 = 23;
22-
const DEL: u8 = 127;
32+
) -> Result<&'a str>
33+
where
34+
F: FnOnce(&mut Uart) -> usize,
35+
{
36+
readline_timeout(prompt, uart, Duration::ZERO, line)
37+
}
2338

39+
pub fn readline_timeout<'a, F>(
40+
prompt: F,
41+
uart: &mut Uart,
42+
timeout: Duration,
43+
line: &'a mut [u8],
44+
) -> Result<&'a str>
45+
where
46+
F: FnOnce(&mut Uart) -> usize,
47+
{
2448
fn find_prev_col(line: &[u8], start: usize) -> usize {
2549
line.iter()
2650
.fold(start, |v, &b| v + if b == TAB { 8 - (v & 0b111) } else { 1 })
2751
}
2852

29-
fn backspace(
53+
fn backup(
3054
uart: &mut Uart,
3155
line: &[u8],
3256
start: usize,
@@ -43,11 +67,7 @@ pub fn readline<'a>(
4367
_ => (col - 1, true),
4468
};
4569
for _ in pcol..col {
46-
uart.putb(BS);
47-
if overstrike {
48-
uart.putb(b' ');
49-
uart.putb(BS);
50-
}
70+
backspace(uart, overstrike);
5171
}
5272
(pcol, line.len() - 1)
5373
}
@@ -60,43 +80,47 @@ pub fn readline<'a>(
6080
return Ok("");
6181
}
6282

63-
let start = prompt.len();
64-
uart.puts(prompt);
83+
let start = prompt(uart);
6584

6685
let mut k = 0;
6786
let mut col = start;
6887
while k < line.len() {
69-
match uart.getb() {
70-
CR | NL => {
88+
match uart.getb_timeout(timeout) {
89+
None => {
90+
if k == 0 {
91+
return Err(Error::Timeout);
92+
}
93+
}
94+
Some(CR | NL) => {
7195
uart.putb(CR);
7296
uart.putb(NL);
7397
break;
7498
}
75-
BS | DEL => {
99+
Some(BS | DEL) => {
76100
if k > 0 {
77-
(col, k) = backspace(uart, &line[..k], start, col);
101+
(col, k) = backup(uart, &line[..k], start, col);
78102
}
79103
}
80-
CTLU => {
104+
Some(CTLU) => {
81105
while k > 0 {
82-
(col, k) = backspace(uart, &line[..k], start, col);
106+
(col, k) = backup(uart, &line[..k], start, col);
83107
}
84108
}
85-
CTLW => {
109+
Some(CTLW) => {
86110
while k > 0 && line[k - 1].is_ascii_whitespace() {
87-
(col, k) = backspace(uart, &line[..k], start, col);
111+
(col, k) = backup(uart, &line[..k], start, col);
88112
}
89113
if k > 0 {
90114
let cond = isword(line[k - 1]);
91115
while k > 0
92116
&& !line[k - 1].is_ascii_whitespace()
93117
&& isword(line[k - 1]) == cond
94118
{
95-
(col, k) = backspace(uart, &line[..k], start, col);
119+
(col, k) = backup(uart, &line[..k], start, col);
96120
}
97121
}
98122
}
99-
TAB => {
123+
Some(TAB) => {
100124
line[k] = TAB;
101125
k += 1;
102126
let ncol = (8 + col) & !0b111;
@@ -105,7 +129,7 @@ pub fn readline<'a>(
105129
}
106130
col = ncol;
107131
}
108-
b => {
132+
Some(b) => {
109133
line[k] = b;
110134
k += 1;
111135
uart.putb(b);
@@ -117,10 +141,45 @@ pub fn readline<'a>(
117141
core::str::from_utf8(&line[..k]).map_err(|_| Error::Utf8)
118142
}
119143

144+
pub fn backspace(term: &mut Uart, overstrike: bool) {
145+
term.putb(BS);
146+
if overstrike {
147+
term.putb(b' ');
148+
term.putb(BS);
149+
}
150+
}
151+
120152
pub fn clear(term: &mut Uart) {
121-
const ESC: u8 = 27;
122153
term.putb(ESC);
123154
term.puts("[H");
124155
term.putb(ESC);
125156
term.puts("[2J");
126157
}
158+
159+
pub fn cycle(
160+
term: &mut Uart,
161+
prefix: &[u8],
162+
cycle: &[u8],
163+
suffix: &[u8],
164+
wait: Duration,
165+
) {
166+
fn erase(term: &mut Uart, bs: &[u8]) {
167+
for &b in bs.iter().rev() {
168+
backspace(term, b != b' ');
169+
}
170+
}
171+
let _ = term.putbs(prefix);
172+
for &b in cycle.iter().cycle() {
173+
term.putb(b);
174+
let _ = term.putbs(suffix);
175+
match term.wait_data_ready(wait) {
176+
Ok(true) | Err(_) => break,
177+
_ => {}
178+
}
179+
erase(term, suffix);
180+
erase(term, &[b]);
181+
}
182+
erase(term, suffix);
183+
erase(term, &[0]);
184+
erase(term, prefix);
185+
}

src/cpuid.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,24 @@
22
// License, v. 2.0. If a copy of the MPL was not distributed with this
33
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
44

5-
pub(crate) fn cpuid(leaf: u32, subleaf: u32) -> x86::cpuid::CpuIdResult {
6-
x86::cpuid::native_cpuid::cpuid_count(leaf, subleaf)
5+
use x86::cpuid;
6+
7+
pub(crate) fn cpuid(leaf: u32, subleaf: u32) -> cpuid::CpuIdResult {
8+
cpuid::native_cpuid::cpuid_count(leaf, subleaf)
79
}
810

911
/// Returns information about the current processor and its
1012
/// package.
1113
pub(crate) fn cpuinfo() -> Option<(u8, u8, u8, Option<u32>)> {
12-
let cpuid = x86::cpuid::CpuId::new();
14+
let cpuid = cpuid::CpuId::new();
1315
let features = cpuid.get_feature_info()?;
1416
let family = features.family_id();
1517
let ext = cpuid.get_extended_processor_and_feature_identifiers()?;
1618
let pkg_type = (family > 0x10).then_some(ext.pkg_type());
1719
Some((family, features.model_id(), features.stepping_id(), pkg_type))
1820
}
21+
22+
pub(crate) fn tscinfo() -> Option<cpuid::TscInfo> {
23+
let cpuid = cpuid::CpuId::new();
24+
cpuid.get_tsc_info()
25+
}

src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ extern crate alloc;
1616

1717
mod allocator;
1818
mod bldb;
19+
mod clock;
1920
mod cons;
2021
mod cpuid;
2122
mod gpio;

src/ramdisk.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use crate::ufs;
1212
pub fn mount(
1313
ramdisk: &'static [u8],
1414
) -> Result<Option<ufs::FileSystem<'static>>> {
15-
let fs = ufs::FileSystem::new(ramdisk);
15+
let fs = ufs::FileSystem::new(ramdisk)?;
1616
if let Ok(ufs::State::Clean) = fs.state() {
1717
let flags = fs.flags();
1818
println!("ramdisk mounted successfully (Clean, {flags:?})");

src/repl/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ mod memory;
3131
mod mount;
3232
mod msr;
3333
mod pio;
34+
mod prompt;
3435
mod reader;
3536
mod rx;
3637
mod rz;
@@ -244,13 +245,16 @@ fn evalcmd(
244245
"map" => vm::map(config, env),
245246
"mapping" => vm::mapping(config, env),
246247
"mappings" => vm::mappings(config, env),
248+
"megapulser" => prompt::mega_pulser(config, env),
247249
"mount" => mount::run(config, env),
248250
"outb" => pio::outb(config, env),
249251
"outl" => pio::outl(config, env),
250252
"outw" => pio::outw(config, env),
251253
"peek" => memory::read(config, env),
252254
"poke" => memory::write(config, env),
253255
"pop" => Ok(pop2(env)),
256+
"prompt" => prompt::prompt(config, env),
257+
"pulser" | "throbber" => prompt::pulser(config, env),
254258
"push" => Ok(Value::Nil),
255259
"rdmsr" => msr::read(config, env),
256260
"rdsmn" => smn::read(config, env),
@@ -259,6 +263,7 @@ fn evalcmd(
259263
"setbits" => bits::set(config, env),
260264
"sha256" => sha::run(config, env),
261265
"sha256mem" => sha::mem(config, env),
266+
"spinner" => prompt::spinner(config, env),
262267
"unmap" => vm::unmap(config, env),
263268
"wrmsr" => msr::write(config, env),
264269
"wrsmn" => smn::write(config, env),

0 commit comments

Comments
 (0)