Skip to content

Commit

Permalink
Merge branch 'release/v0.3.2'
Browse files Browse the repository at this point in the history
  • Loading branch information
jonathanpallant committed May 5, 2023
2 parents 2726b78 + 7577423 commit 3169e13
Show file tree
Hide file tree
Showing 10 changed files with 373 additions and 29 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
@@ -1,5 +1,13 @@
# Change Log

## Unreleased changes

## v0.3.2

* Add `date` command.
* Add `lsblk` and `blkread` commands.
* Renamed `bioshw` to `lshw`

## v0.3.1

* Add `hexdump`, `load` and `run` commands.
Expand Down
5 changes: 3 additions & 2 deletions Cargo.toml
@@ -1,7 +1,7 @@
[package]
name = "neotron-os"
version = "0.3.1"
authors = ["Jonathan 'theJPster' Pallant <github@thejpster.org.uk>"]
version = "0.3.2"
authors = ["Jonathan 'theJPster' Pallant <github@thejpster.org.uk>", "The Neotron Developers"]
edition = "2018"
description = "The Neotron Operating System"
license = "GPL-3.0-or-later"
Expand Down Expand Up @@ -43,3 +43,4 @@ r0 = "1.0"
postcard = "0.5"
serde = { version = "1.0", default-features = false }
menu = "0.3"
chrono = { version = "0.4", default-features = false }
6 changes: 5 additions & 1 deletion build.sh
Expand Up @@ -8,14 +8,18 @@ mkdir -p ${RELEASE_DIR}

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

# Build the host version
echo "Building HOST"
cargo build --verbose --lib --release --target=x86_64-unknown-linux-gnu
cp ./target/x86_64-unknown-linux-gnu/release/libneotron_os.so ${RELEASE_DIR}/x86_64-unknown-linux-gnu-libneotron_os.so
117 changes: 117 additions & 0 deletions src/commands/block.rs
@@ -0,0 +1,117 @@
//! Block Device related commands for Neotron OS

use crate::{bios, print, println, Ctx, API};

pub static LSBLK_ITEM: menu::Item<Ctx> = menu::Item {
item_type: menu::ItemType::Callback {
function: lsblk,
parameters: &[],
},
command: "lsblk",
help: Some("List all the Block Devices"),
};

pub static READ_ITEM: menu::Item<Ctx> = menu::Item {
item_type: menu::ItemType::Callback {
function: read_block,
parameters: &[
menu::Parameter::Mandatory {
parameter_name: "device_idx",
help: Some("The block device ID to fetch from"),
},
menu::Parameter::Mandatory {
parameter_name: "block_idx",
help: Some("The block to fetch, 0..num_blocks"),
},
],
},
command: "readblk",
help: Some("List all the Block Devices"),
};

/// Called when the "lsblk" command is executed.
fn lsblk(_menu: &menu::Menu<Ctx>, _item: &menu::Item<Ctx>, _args: &[&str], _ctx: &mut Ctx) {
let api = API.get();
let mut found = false;

println!("Block Devices:");
for dev_idx in 0..=255u8 {
if let bios::Option::Some(device_info) = (api.block_dev_get_info)(dev_idx) {
let (bsize, bunits, dsize, dunits) =
match device_info.num_blocks * u64::from(device_info.block_size) {
x if x < (1024 * 1024 * 1024) => {
// Under 1 GiB, give it in 10s of MiB
(10 * x / (1024 * 1024), "MiB", x / 100_000, "MB")
}
x => {
// Anything else in GiB
(10 * x / (1024 * 1024 * 1024), "GiB", x / 100_000_000, "GB")
}
};
println!("Device {}:", dev_idx);
println!(" Name: {}", device_info.name);
println!(" Type: {:?}", device_info.device_type);
println!(" Block size: {}", device_info.block_size);
println!(" Num Blocks: {}", device_info.num_blocks);
println!(
" Card Size: {}.{} {} ({}.{} {})",
bsize / 10,
bsize % 10,
bunits,
dsize / 10,
dsize % 10,
dunits
);
println!(" Ejectable: {}", device_info.ejectable);
println!(" Removable: {}", device_info.removable);
println!(" Media Present: {}", device_info.media_present);
println!(" Read Only: {}", device_info.read_only);
found = true;
}
}
if !found {
println!(" None");
}
}

/// Called when the "read_block" command is executed.
fn read_block(_menu: &menu::Menu<Ctx>, _item: &menu::Item<Ctx>, args: &[&str], _ctx: &mut Ctx) {
let api = API.get();
let Ok(dev_idx) = args[0].parse::<u8>() else {
println!("Couldn't parse {:?}", args[0]);
return;
};
let Ok(block_idx) = args[1].parse::<u64>() else {
println!("Couldn't parse {:?}", args[1]);
return;
};
println!("Reading block {}:", block_idx);
let mut buffer = [0u8; 512];
match (api.block_read)(
dev_idx,
bios::block_dev::BlockIdx(block_idx),
1,
bios::ApiBuffer::new(&mut buffer),
) {
bios::Result::Ok(_) => {
// Carry on
let mut count = 0;
for chunk in buffer.chunks(16) {
print!("{:03x}: ", count);
for b in chunk {
print!("{:02x} ", *b);
}
print!(" ");
for b in chunk {
let c = char::from(*b);
print!("{}", if c.is_ascii_graphic() { c } else { '.' });
}
count += chunk.len();
println!();
}
}
bios::Result::Err(e) => {
println!("Failed to read: {:?}", e);
}
}
}
8 changes: 4 additions & 4 deletions src/commands/hardware.rs
Expand Up @@ -4,15 +4,15 @@ use crate::{bios, println, Ctx, API};

pub static LSHW_ITEM: menu::Item<Ctx> = menu::Item {
item_type: menu::ItemType::Callback {
function: bioshw,
function: lshw,
parameters: &[],
},
command: "bioshw",
command: "lshw",
help: Some("List all the BIOS hardware"),
};

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

Expand Down
7 changes: 7 additions & 0 deletions src/commands/mod.rs
Expand Up @@ -4,23 +4,30 @@

pub use super::Ctx;

mod block;
mod config;
mod hardware;
mod input;
mod ram;
mod screen;
mod timedate;

pub static OS_MENU: menu::Menu<Ctx> = menu::Menu {
label: "root",
items: &[
&timedate::DATE_ITEM,
&config::COMMAND_ITEM,
&block::LSBLK_ITEM,
&block::READ_ITEM,
&hardware::LSHW_ITEM,
&ram::HEXDUMP_ITEM,
&ram::LOAD_ITEM,
#[cfg(target_os = "none")]
&ram::RUN_ITEM,
&screen::CLEAR_ITEM,
&screen::BENCH_ITEM,
&screen::FILL_ITEM,
&screen::MANDEL_ITEM,
&input::KBTEST_ITEM,
],
entry: None,
Expand Down
112 changes: 105 additions & 7 deletions src/commands/screen.rs
@@ -1,5 +1,7 @@
//! Screen-related commands for Neotron OS

use neotron_common_bios::video::{Attr, TextBackgroundColour, TextForegroundColour};

use crate::{print, println, Ctx, API, VGA_CONSOLE};

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

pub static BENCH_ITEM: menu::Item<Ctx> = menu::Item {
item_type: menu::ItemType::Callback {
function: bench,
parameters: &[],
},
command: "screen_bench",
help: Some("Time how long to put 1,000,000 characters on the screen, with scrolling."),
};

pub static MANDEL_ITEM: menu::Item<Ctx> = menu::Item {
item_type: menu::ItemType::Callback {
function: mandel,
parameters: &[],
},
command: "screen_mandel",
help: Some("Calculate the Mandelbrot set"),
};

/// Called when the "clear" command is executed.
fn clear(_menu: &menu::Menu<Ctx>, _item: &menu::Item<Ctx>, _args: &[&str], _ctx: &mut Ctx) {
if let Some(ref mut console) = unsafe { &mut VGA_CONSOLE } {
Expand All @@ -31,19 +51,97 @@ fn clear(_menu: &menu::Menu<Ctx>, _item: &menu::Item<Ctx>, _args: &[&str], _ctx:
fn fill(_menu: &menu::Menu<Ctx>, _item: &menu::Item<Ctx>, _args: &[&str], _ctx: &mut Ctx) {
if let Some(ref mut console) = unsafe { &mut VGA_CONSOLE } {
console.clear();
let api = API.get();
let mode = (api.video_get_mode)();
let (Some(width), Some(height)) = (mode.text_width(), mode.text_height()) else {
println!("Unable to get console size");
return;
};
// A range of printable ASCII compatible characters
let mut char_cycle = (b' '..=b'~').cycle();
let mut remaining = height * width;

// Scroll two screen fulls
'outer: for bg in (0..=7).cycle() {
let bg_colour = TextBackgroundColour::new(bg).unwrap();
for fg in 1..=15 {
if fg == bg {
continue;
}
let fg_colour = TextForegroundColour::new(fg).unwrap();
remaining -= 1;
if remaining == 0 {
break 'outer;
}
let attr = Attr::new(fg_colour, bg_colour, false);
let glyph = char_cycle.next().unwrap();
console.set_attr(attr);
console.write_bstr(&[glyph]);
}
}
let attr = Attr::new(
TextForegroundColour::WHITE,
TextBackgroundColour::BLACK,
false,
);
console.set_attr(attr);
}
}

/// Called when the "bench" command is executed.
fn bench(_menu: &menu::Menu<Ctx>, _item: &menu::Item<Ctx>, _args: &[&str], _ctx: &mut Ctx) {
const NUM_CHARS: u64 = 1_000_000;
if let Some(ref mut console) = unsafe { &mut VGA_CONSOLE } {
let api = API.get();
let start = (api.time_ticks_get)();
console.clear();
let glyphs = &[b'x'];
for _idx in 0..NUM_CHARS {
console.write_bstr(glyphs);
}
let end = (api.time_ticks_get)();
let delta = end.0 - start.0;
let chars_per_second = (NUM_CHARS * (api.time_ticks_per_second)().0) / delta;
println!(
"{} chars in {} ticks, or {} chars per second",
NUM_CHARS, delta, chars_per_second
);
}
}

/// Called when the "mandel" command is executed.
fn mandel(_menu: &menu::Menu<Ctx>, _item: &menu::Item<Ctx>, _args: &[&str], _ctx: &mut Ctx) {
fn mandelbrot(cx: f64, cy: f64, max_loops: u32) -> u32 {
let mut x = cx;
let mut y = cy;
for i in 1..max_loops {
let x_squared = x * x;
let y_squared = y * y;
if x_squared + y_squared > 4.0 {
return i;
}
let x1 = x_squared - y_squared + cx;
let y1 = (2.0 * x * y) + cy;
x = x1;
y = y1;
}
0
}

let api = API.get();
let mode = (api.video_get_mode)();
let (Some(width), Some(height)) = (mode.text_width(), mode.text_height()) else {
println!("Unable to get console size");
println!("Unable to get screen size");
return;
};
// A range of printable ASCII compatible characters
let mut char_cycle = (' '..='~').cycle();
// Scroll two screen fulls
for _row in 0..height * 2 {
for _col in 0..width {
print!("{}", char_cycle.next().unwrap());

let glyphs = b" .,'~!^:;[/<&?oxOX# ";
for y_pos in 0..height - 2 {
let y = (f64::from(y_pos) * 4.0 / f64::from(height)) - 2.0;
for x_pos in 0..width {
let x = (f64::from(x_pos) * 4.0 / f64::from(width)) - 2.0;
let result = mandelbrot(x, y, 20);
print!("{}", glyphs[result as usize] as char);
}
}
}
43 changes: 43 additions & 0 deletions src/commands/timedate.rs
@@ -0,0 +1,43 @@
//! CLI commands for getting/setting time/date

use chrono::{Datelike, Timelike};

use crate::{println, Ctx, API};

pub static DATE_ITEM: menu::Item<Ctx> = menu::Item {
item_type: menu::ItemType::Callback {
function: date,
parameters: &[menu::Parameter::Optional {
parameter_name: "timestamp",
help: Some("The new date/time, in ISO8601 format"),
}],
},
command: "date",
help: Some("Get/set the time and date"),
};

/// Called when the "date" command is executed.
fn date(_menu: &menu::Menu<Ctx>, item: &menu::Item<Ctx>, args: &[&str], _ctx: &mut Ctx) {
if let Ok(Some(timestamp)) = menu::argument_finder(item, args, "timestamp") {
println!("Setting date/time to {:?}", timestamp);
static DATE_FMT: &str = "%Y-%m-%dT%H:%M:%S";
let Ok(timestamp) = chrono::NaiveDateTime::parse_from_str(timestamp, DATE_FMT) else {
println!("Unable to parse date/time");
return;
};
API.set_time(timestamp);
}

let time = API.get_time();
// Ensure this matches `DATE_FMT`, for consistency
println!(
"The time is {:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:09}",
time.year(),
time.month(),
time.day(),
time.hour(),
time.minute(),
time.second(),
time.nanosecond()
);
}

0 comments on commit 3169e13

Please sign in to comment.