Skip to content

Commit 3169e13

Browse files
Merge branch 'release/v0.3.2'
2 parents 2726b78 + 7577423 commit 3169e13

File tree

10 files changed

+373
-29
lines changed

10 files changed

+373
-29
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Change Log
22

3+
## Unreleased changes
4+
5+
## v0.3.2
6+
7+
* Add `date` command.
8+
* Add `lsblk` and `blkread` commands.
9+
* Renamed `bioshw` to `lshw`
10+
311
## v0.3.1
412

513
* Add `hexdump`, `load` and `run` commands.

Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
name = "neotron-os"
3-
version = "0.3.1"
4-
authors = ["Jonathan 'theJPster' Pallant <[email protected]>"]
3+
version = "0.3.2"
4+
authors = ["Jonathan 'theJPster' Pallant <[email protected]>", "The Neotron Developers"]
55
edition = "2018"
66
description = "The Neotron Operating System"
77
license = "GPL-3.0-or-later"
@@ -43,3 +43,4 @@ r0 = "1.0"
4343
postcard = "0.5"
4444
serde = { version = "1.0", default-features = false }
4545
menu = "0.3"
46+
chrono = { version = "0.4", default-features = false }

build.sh

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,18 @@ mkdir -p ${RELEASE_DIR}
88

99
# Build the embedded binaries for each core type and each flash layout
1010
for TARGET_ARCH in thumbv6m-none-eabi thumbv7m-none-eabi thumbv7em-none-eabi; do
11+
echo "TARGET is ${TARGET_ARCH}"
1112
for BINARY in flash0002 flash0802 flash1002; do
12-
# objcopy will do the build for us first
13+
echo "BINARY is ${BINARY}"
14+
cargo build --verbose --release --target=${TARGET_ARCH} --bin ${BINARY}
15+
# objcopy would do the build for us first, but it doesn't have good build output
1316
cargo objcopy --verbose --release --target=${TARGET_ARCH} --bin ${BINARY} -- -O binary ${RELEASE_DIR}/${TARGET_ARCH}-${BINARY}-libneotron_os.bin
1417
# Keep the ELF file too (for debugging)
1518
cp ./target/${TARGET_ARCH}/release/${BINARY} ${RELEASE_DIR}/${TARGET_ARCH}-${BINARY}-libneotron_os.elf
1619
done
1720
done
1821

1922
# Build the host version
23+
echo "Building HOST"
2024
cargo build --verbose --lib --release --target=x86_64-unknown-linux-gnu
2125
cp ./target/x86_64-unknown-linux-gnu/release/libneotron_os.so ${RELEASE_DIR}/x86_64-unknown-linux-gnu-libneotron_os.so

src/commands/block.rs

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
//! Block Device related commands for Neotron OS
2+
3+
use crate::{bios, print, println, Ctx, API};
4+
5+
pub static LSBLK_ITEM: menu::Item<Ctx> = menu::Item {
6+
item_type: menu::ItemType::Callback {
7+
function: lsblk,
8+
parameters: &[],
9+
},
10+
command: "lsblk",
11+
help: Some("List all the Block Devices"),
12+
};
13+
14+
pub static READ_ITEM: menu::Item<Ctx> = menu::Item {
15+
item_type: menu::ItemType::Callback {
16+
function: read_block,
17+
parameters: &[
18+
menu::Parameter::Mandatory {
19+
parameter_name: "device_idx",
20+
help: Some("The block device ID to fetch from"),
21+
},
22+
menu::Parameter::Mandatory {
23+
parameter_name: "block_idx",
24+
help: Some("The block to fetch, 0..num_blocks"),
25+
},
26+
],
27+
},
28+
command: "readblk",
29+
help: Some("List all the Block Devices"),
30+
};
31+
32+
/// Called when the "lsblk" command is executed.
33+
fn lsblk(_menu: &menu::Menu<Ctx>, _item: &menu::Item<Ctx>, _args: &[&str], _ctx: &mut Ctx) {
34+
let api = API.get();
35+
let mut found = false;
36+
37+
println!("Block Devices:");
38+
for dev_idx in 0..=255u8 {
39+
if let bios::Option::Some(device_info) = (api.block_dev_get_info)(dev_idx) {
40+
let (bsize, bunits, dsize, dunits) =
41+
match device_info.num_blocks * u64::from(device_info.block_size) {
42+
x if x < (1024 * 1024 * 1024) => {
43+
// Under 1 GiB, give it in 10s of MiB
44+
(10 * x / (1024 * 1024), "MiB", x / 100_000, "MB")
45+
}
46+
x => {
47+
// Anything else in GiB
48+
(10 * x / (1024 * 1024 * 1024), "GiB", x / 100_000_000, "GB")
49+
}
50+
};
51+
println!("Device {}:", dev_idx);
52+
println!(" Name: {}", device_info.name);
53+
println!(" Type: {:?}", device_info.device_type);
54+
println!(" Block size: {}", device_info.block_size);
55+
println!(" Num Blocks: {}", device_info.num_blocks);
56+
println!(
57+
" Card Size: {}.{} {} ({}.{} {})",
58+
bsize / 10,
59+
bsize % 10,
60+
bunits,
61+
dsize / 10,
62+
dsize % 10,
63+
dunits
64+
);
65+
println!(" Ejectable: {}", device_info.ejectable);
66+
println!(" Removable: {}", device_info.removable);
67+
println!(" Media Present: {}", device_info.media_present);
68+
println!(" Read Only: {}", device_info.read_only);
69+
found = true;
70+
}
71+
}
72+
if !found {
73+
println!(" None");
74+
}
75+
}
76+
77+
/// Called when the "read_block" command is executed.
78+
fn read_block(_menu: &menu::Menu<Ctx>, _item: &menu::Item<Ctx>, args: &[&str], _ctx: &mut Ctx) {
79+
let api = API.get();
80+
let Ok(dev_idx) = args[0].parse::<u8>() else {
81+
println!("Couldn't parse {:?}", args[0]);
82+
return;
83+
};
84+
let Ok(block_idx) = args[1].parse::<u64>() else {
85+
println!("Couldn't parse {:?}", args[1]);
86+
return;
87+
};
88+
println!("Reading block {}:", block_idx);
89+
let mut buffer = [0u8; 512];
90+
match (api.block_read)(
91+
dev_idx,
92+
bios::block_dev::BlockIdx(block_idx),
93+
1,
94+
bios::ApiBuffer::new(&mut buffer),
95+
) {
96+
bios::Result::Ok(_) => {
97+
// Carry on
98+
let mut count = 0;
99+
for chunk in buffer.chunks(16) {
100+
print!("{:03x}: ", count);
101+
for b in chunk {
102+
print!("{:02x} ", *b);
103+
}
104+
print!(" ");
105+
for b in chunk {
106+
let c = char::from(*b);
107+
print!("{}", if c.is_ascii_graphic() { c } else { '.' });
108+
}
109+
count += chunk.len();
110+
println!();
111+
}
112+
}
113+
bios::Result::Err(e) => {
114+
println!("Failed to read: {:?}", e);
115+
}
116+
}
117+
}

src/commands/hardware.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@ use crate::{bios, println, Ctx, API};
44

55
pub static LSHW_ITEM: menu::Item<Ctx> = menu::Item {
66
item_type: menu::ItemType::Callback {
7-
function: bioshw,
7+
function: lshw,
88
parameters: &[],
99
},
10-
command: "bioshw",
10+
command: "lshw",
1111
help: Some("List all the BIOS hardware"),
1212
};
1313

14-
/// Called when the "bioshw" command is executed.
15-
fn bioshw(_menu: &menu::Menu<Ctx>, _item: &menu::Item<Ctx>, _args: &[&str], _ctx: &mut Ctx) {
14+
/// Called when the "lshw" command is executed.
15+
fn lshw(_menu: &menu::Menu<Ctx>, _item: &menu::Item<Ctx>, _args: &[&str], _ctx: &mut Ctx) {
1616
let api = API.get();
1717
let mut found = false;
1818

src/commands/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,30 @@
44
55
pub use super::Ctx;
66

7+
mod block;
78
mod config;
89
mod hardware;
910
mod input;
1011
mod ram;
1112
mod screen;
13+
mod timedate;
1214

1315
pub static OS_MENU: menu::Menu<Ctx> = menu::Menu {
1416
label: "root",
1517
items: &[
18+
&timedate::DATE_ITEM,
1619
&config::COMMAND_ITEM,
20+
&block::LSBLK_ITEM,
21+
&block::READ_ITEM,
1722
&hardware::LSHW_ITEM,
1823
&ram::HEXDUMP_ITEM,
1924
&ram::LOAD_ITEM,
2025
#[cfg(target_os = "none")]
2126
&ram::RUN_ITEM,
2227
&screen::CLEAR_ITEM,
28+
&screen::BENCH_ITEM,
2329
&screen::FILL_ITEM,
30+
&screen::MANDEL_ITEM,
2431
&input::KBTEST_ITEM,
2532
],
2633
entry: None,

src/commands/screen.rs

Lines changed: 105 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
//! Screen-related commands for Neotron OS
22
3+
use neotron_common_bios::video::{Attr, TextBackgroundColour, TextForegroundColour};
4+
35
use crate::{print, println, Ctx, API, VGA_CONSOLE};
46

57
pub static CLEAR_ITEM: menu::Item<Ctx> = menu::Item {
@@ -20,6 +22,24 @@ pub static FILL_ITEM: menu::Item<Ctx> = menu::Item {
2022
help: Some("Fill the screen with characters"),
2123
};
2224

25+
pub static BENCH_ITEM: menu::Item<Ctx> = menu::Item {
26+
item_type: menu::ItemType::Callback {
27+
function: bench,
28+
parameters: &[],
29+
},
30+
command: "screen_bench",
31+
help: Some("Time how long to put 1,000,000 characters on the screen, with scrolling."),
32+
};
33+
34+
pub static MANDEL_ITEM: menu::Item<Ctx> = menu::Item {
35+
item_type: menu::ItemType::Callback {
36+
function: mandel,
37+
parameters: &[],
38+
},
39+
command: "screen_mandel",
40+
help: Some("Calculate the Mandelbrot set"),
41+
};
42+
2343
/// Called when the "clear" command is executed.
2444
fn clear(_menu: &menu::Menu<Ctx>, _item: &menu::Item<Ctx>, _args: &[&str], _ctx: &mut Ctx) {
2545
if let Some(ref mut console) = unsafe { &mut VGA_CONSOLE } {
@@ -31,19 +51,97 @@ fn clear(_menu: &menu::Menu<Ctx>, _item: &menu::Item<Ctx>, _args: &[&str], _ctx:
3151
fn fill(_menu: &menu::Menu<Ctx>, _item: &menu::Item<Ctx>, _args: &[&str], _ctx: &mut Ctx) {
3252
if let Some(ref mut console) = unsafe { &mut VGA_CONSOLE } {
3353
console.clear();
54+
let api = API.get();
55+
let mode = (api.video_get_mode)();
56+
let (Some(width), Some(height)) = (mode.text_width(), mode.text_height()) else {
57+
println!("Unable to get console size");
58+
return;
59+
};
60+
// A range of printable ASCII compatible characters
61+
let mut char_cycle = (b' '..=b'~').cycle();
62+
let mut remaining = height * width;
63+
64+
// Scroll two screen fulls
65+
'outer: for bg in (0..=7).cycle() {
66+
let bg_colour = TextBackgroundColour::new(bg).unwrap();
67+
for fg in 1..=15 {
68+
if fg == bg {
69+
continue;
70+
}
71+
let fg_colour = TextForegroundColour::new(fg).unwrap();
72+
remaining -= 1;
73+
if remaining == 0 {
74+
break 'outer;
75+
}
76+
let attr = Attr::new(fg_colour, bg_colour, false);
77+
let glyph = char_cycle.next().unwrap();
78+
console.set_attr(attr);
79+
console.write_bstr(&[glyph]);
80+
}
81+
}
82+
let attr = Attr::new(
83+
TextForegroundColour::WHITE,
84+
TextBackgroundColour::BLACK,
85+
false,
86+
);
87+
console.set_attr(attr);
3488
}
89+
}
90+
91+
/// Called when the "bench" command is executed.
92+
fn bench(_menu: &menu::Menu<Ctx>, _item: &menu::Item<Ctx>, _args: &[&str], _ctx: &mut Ctx) {
93+
const NUM_CHARS: u64 = 1_000_000;
94+
if let Some(ref mut console) = unsafe { &mut VGA_CONSOLE } {
95+
let api = API.get();
96+
let start = (api.time_ticks_get)();
97+
console.clear();
98+
let glyphs = &[b'x'];
99+
for _idx in 0..NUM_CHARS {
100+
console.write_bstr(glyphs);
101+
}
102+
let end = (api.time_ticks_get)();
103+
let delta = end.0 - start.0;
104+
let chars_per_second = (NUM_CHARS * (api.time_ticks_per_second)().0) / delta;
105+
println!(
106+
"{} chars in {} ticks, or {} chars per second",
107+
NUM_CHARS, delta, chars_per_second
108+
);
109+
}
110+
}
111+
112+
/// Called when the "mandel" command is executed.
113+
fn mandel(_menu: &menu::Menu<Ctx>, _item: &menu::Item<Ctx>, _args: &[&str], _ctx: &mut Ctx) {
114+
fn mandelbrot(cx: f64, cy: f64, max_loops: u32) -> u32 {
115+
let mut x = cx;
116+
let mut y = cy;
117+
for i in 1..max_loops {
118+
let x_squared = x * x;
119+
let y_squared = y * y;
120+
if x_squared + y_squared > 4.0 {
121+
return i;
122+
}
123+
let x1 = x_squared - y_squared + cx;
124+
let y1 = (2.0 * x * y) + cy;
125+
x = x1;
126+
y = y1;
127+
}
128+
0
129+
}
130+
35131
let api = API.get();
36132
let mode = (api.video_get_mode)();
37133
let (Some(width), Some(height)) = (mode.text_width(), mode.text_height()) else {
38-
println!("Unable to get console size");
134+
println!("Unable to get screen size");
39135
return;
40136
};
41-
// A range of printable ASCII compatible characters
42-
let mut char_cycle = (' '..='~').cycle();
43-
// Scroll two screen fulls
44-
for _row in 0..height * 2 {
45-
for _col in 0..width {
46-
print!("{}", char_cycle.next().unwrap());
137+
138+
let glyphs = b" .,'~!^:;[/<&?oxOX# ";
139+
for y_pos in 0..height - 2 {
140+
let y = (f64::from(y_pos) * 4.0 / f64::from(height)) - 2.0;
141+
for x_pos in 0..width {
142+
let x = (f64::from(x_pos) * 4.0 / f64::from(width)) - 2.0;
143+
let result = mandelbrot(x, y, 20);
144+
print!("{}", glyphs[result as usize] as char);
47145
}
48146
}
49147
}

src/commands/timedate.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
//! CLI commands for getting/setting time/date
2+
3+
use chrono::{Datelike, Timelike};
4+
5+
use crate::{println, Ctx, API};
6+
7+
pub static DATE_ITEM: menu::Item<Ctx> = menu::Item {
8+
item_type: menu::ItemType::Callback {
9+
function: date,
10+
parameters: &[menu::Parameter::Optional {
11+
parameter_name: "timestamp",
12+
help: Some("The new date/time, in ISO8601 format"),
13+
}],
14+
},
15+
command: "date",
16+
help: Some("Get/set the time and date"),
17+
};
18+
19+
/// Called when the "date" command is executed.
20+
fn date(_menu: &menu::Menu<Ctx>, item: &menu::Item<Ctx>, args: &[&str], _ctx: &mut Ctx) {
21+
if let Ok(Some(timestamp)) = menu::argument_finder(item, args, "timestamp") {
22+
println!("Setting date/time to {:?}", timestamp);
23+
static DATE_FMT: &str = "%Y-%m-%dT%H:%M:%S";
24+
let Ok(timestamp) = chrono::NaiveDateTime::parse_from_str(timestamp, DATE_FMT) else {
25+
println!("Unable to parse date/time");
26+
return;
27+
};
28+
API.set_time(timestamp);
29+
}
30+
31+
let time = API.get_time();
32+
// Ensure this matches `DATE_FMT`, for consistency
33+
println!(
34+
"The time is {:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:09}",
35+
time.year(),
36+
time.month(),
37+
time.day(),
38+
time.hour(),
39+
time.minute(),
40+
time.second(),
41+
time.nanosecond()
42+
);
43+
}

0 commit comments

Comments
 (0)