From 396ac3e8e18aff0561097fec51dd17f8c18bce45 Mon Sep 17 00:00:00 2001 From: thesayol Date: Fri, 12 Apr 2024 20:37:32 +0800 Subject: [PATCH] add tty support --- Cargo.lock | 23 +- Cargo.toml | 3 +- api/ruxfeat/Cargo.toml | 27 ++- api/ruxos_posix_api/Cargo.toml | 2 + api/ruxos_posix_api/build.rs | 1 + api/ruxos_posix_api/ctypes.h | 2 + api/ruxos_posix_api/src/imp/getrandom.rs | 7 +- api/ruxos_posix_api/src/imp/ioctl.rs | 10 +- api/ruxos_posix_api/src/imp/stdio.rs | 44 ++-- apps/c/busybox/.gitignore | 10 + apps/c/busybox/README.md | 24 ++ apps/c/busybox/axbuild.mk | 5 + apps/c/busybox/config_linux.toml | 34 +++ apps/c/busybox/features.txt | 11 + apps/c/busybox/main.c | 7 + apps/c/dl/axbuild.mk | 1 + crates/tty/Cargo.toml | 9 + crates/tty/src/buffer.rs | 149 ++++++++++++ crates/tty/src/constant.rs | 29 +++ crates/tty/src/driver.rs | 167 +++++++++++++ crates/tty/src/ldisc.rs | 221 ++++++++++++++++++ crates/tty/src/lib.rs | 72 ++++++ crates/tty/src/tty.rs | 104 +++++++++ modules/ruxhal/Cargo.toml | 7 +- modules/ruxhal/src/lib.rs | 52 +++++ modules/ruxhal/src/mem.rs | 5 +- .../src/platform/aarch64_common/pl011.rs | 28 +++ modules/ruxruntime/Cargo.toml | 3 + modules/ruxruntime/src/lib.rs | 3 + scripts/make/build_musl.mk | 2 +- 30 files changed, 1012 insertions(+), 50 deletions(-) create mode 100644 apps/c/busybox/.gitignore create mode 100644 apps/c/busybox/README.md create mode 100644 apps/c/busybox/axbuild.mk create mode 100644 apps/c/busybox/config_linux.toml create mode 100644 apps/c/busybox/features.txt create mode 100644 apps/c/busybox/main.c create mode 100644 crates/tty/Cargo.toml create mode 100644 crates/tty/src/buffer.rs create mode 100644 crates/tty/src/constant.rs create mode 100644 crates/tty/src/driver.rs create mode 100644 crates/tty/src/ldisc.rs create mode 100644 crates/tty/src/lib.rs create mode 100644 crates/tty/src/tty.rs diff --git a/Cargo.lock b/Cargo.lock index c4fb205fb..b595587ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1617,6 +1617,7 @@ dependencies = [ "spinlock", "static_assertions", "tock-registers", + "tty", "x2apic", "x86", "x86_64", @@ -1680,6 +1681,7 @@ dependencies = [ "crate_interface", "elf", "flatten_objects", + "lazy_init", "lazy_static", "memory_addr", "page_table", @@ -1717,6 +1719,7 @@ dependencies = [ "ruxfutex", "ruxhal", "ruxtask", + "tty", ] [[package]] @@ -1756,21 +1759,18 @@ dependencies = [ [[package]] name = "sbi-rt" -version = "0.0.2" +version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c113c53291db8ac141e01f43224ed488b8d6001ab66737b82e04695a43a42b7" +checksum = "7fbaa69be1eedc61c426e6d489b2260482e928b465360576900d52d496a58bd0" dependencies = [ "sbi-spec", ] [[package]] name = "sbi-spec" -version = "0.0.4" +version = "0.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d4027cf9bb591a9fd0fc0e283be6165c5abe96cb73e9f0e24738c227f425377" -dependencies = [ - "static_assertions", -] +checksum = "e6e36312fb5ddc10d08ecdc65187402baba4ac34585cb9d1b78522ae2358d890" [[package]] name = "scheduler" @@ -2020,6 +2020,15 @@ dependencies = [ "winnow", ] +[[package]] +name = "tty" +version = "0.0.1" +dependencies = [ + "lazy_init", + "log", + "spinlock", +] + [[package]] name = "tuple_for_each" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index ff52575d4..762e9c5a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ members = [ "crates/driver_net", "crates/driver_pci", "crates/driver_virtio", - "crates/dtb", + "crates/dtb", "crates/flatten_objects", "crates/lazy_init", "crates/linked_list", @@ -33,6 +33,7 @@ members = [ "crates/spinlock", "crates/timer_list", "crates/tuple_for_each", + "crates/tty", "modules/axalloc", "modules/axlog", diff --git a/api/ruxfeat/Cargo.toml b/api/ruxfeat/Cargo.toml index f4aaf4d56..b10a160fa 100644 --- a/api/ruxfeat/Cargo.toml +++ b/api/ruxfeat/Cargo.toml @@ -27,7 +27,7 @@ irq = ["ruxhal/irq", "ruxruntime/irq", "ruxtask?/irq"] rtc = ["ruxhal/rtc", "ruxruntime/rtc"] # Memory -alloc = ["axalloc", "ruxruntime/alloc", "ruxfs/alloc"] +alloc = ["axalloc", "ruxruntime/alloc", "ruxfs/alloc", "ruxhal/alloc"] alloc-tlsf = ["axalloc/tlsf"] alloc-slab = ["axalloc/slab"] alloc-buddy = ["axalloc/buddy"] @@ -35,13 +35,18 @@ paging = ["alloc", "ruxhal/paging", "ruxruntime/paging"] tls = ["alloc", "ruxhal/tls", "ruxruntime/tls", "ruxtask?/tls"] # Multi-threading and scheduler -multitask = ["alloc", "ruxtask/multitask", "axsync/multitask", "ruxruntime/multitask"] +multitask = [ + "alloc", + "ruxtask/multitask", + "axsync/multitask", + "ruxruntime/multitask", +] sched_fifo = ["ruxtask/sched_fifo"] sched_rr = ["ruxtask/sched_rr", "irq"] sched_cfs = ["ruxtask/sched_cfs", "irq"] # File system -fs = ["alloc", "dep:ruxfs", "ruxruntime/fs"] +fs = ["alloc", "dep:ruxfs", "ruxruntime/fs"] blkfs = ["ruxdriver/virtio-blk", "ruxruntime/blkfs"] myfs = ["ruxfs?/myfs"] 9pfs = [] @@ -50,10 +55,20 @@ myfs = ["ruxfs?/myfs"] net = ["alloc", "ruxdriver/virtio-net", "dep:axnet", "ruxruntime/net"] # Display -display = ["alloc", "ruxdriver/virtio-gpu", "dep:ruxdisplay", "ruxruntime/display"] +display = [ + "alloc", + "ruxdriver/virtio-gpu", + "dep:ruxdisplay", + "ruxruntime/display", +] # 9P -virtio-9p = ["9pfs", "ruxdriver/virtio-9p", "rux9p/virtio-9p", "ruxruntime/virtio-9p"] +virtio-9p = [ + "9pfs", + "ruxdriver/virtio-9p", + "rux9p/virtio-9p", + "ruxruntime/virtio-9p", +] net-9p = ["9pfs", "net", "rux9p/net-9p", "ruxruntime/net-9p"] # Device drivers @@ -71,6 +86,8 @@ log-level-info = ["axlog/log-level-info"] log-level-debug = ["axlog/log-level-debug"] log-level-trace = ["axlog/log-level-trace"] +tty = ["ruxhal/tty", "ruxruntime/tty", "alloc", "irq"] + [dependencies] ruxruntime = { path = "../../modules/ruxruntime" } ruxhal = { path = "../../modules/ruxhal" } diff --git a/api/ruxos_posix_api/Cargo.toml b/api/ruxos_posix_api/Cargo.toml index b62f65c7b..5d28d15ea 100644 --- a/api/ruxos_posix_api/Cargo.toml +++ b/api/ruxos_posix_api/Cargo.toml @@ -65,5 +65,7 @@ cfg-if = "1.0" elf = { version = "0.7", default-features = false } bitflags = "2.2" +lazy_init = { path = "../../crates/lazy_init" } + [build-dependencies] bindgen = { version = "0.66" } diff --git a/api/ruxos_posix_api/build.rs b/api/ruxos_posix_api/build.rs index fe71136be..a5b17661b 100644 --- a/api/ruxos_posix_api/build.rs +++ b/api/ruxos_posix_api/build.rs @@ -129,6 +129,7 @@ typedef struct {{ "PROT_.+", "MS_.+", "MREMAP_.+", + "GRND_.*", ]; #[derive(Debug)] diff --git a/api/ruxos_posix_api/ctypes.h b/api/ruxos_posix_api/ctypes.h index 857e547b9..6298ce6c3 100644 --- a/api/ruxos_posix_api/ctypes.h +++ b/api/ruxos_posix_api/ctypes.h @@ -31,3 +31,5 @@ #include #include #include + +#include \ No newline at end of file diff --git a/api/ruxos_posix_api/src/imp/getrandom.rs b/api/ruxos_posix_api/src/imp/getrandom.rs index 8187bccc9..61cafb103 100644 --- a/api/ruxos_posix_api/src/imp/getrandom.rs +++ b/api/ruxos_posix_api/src/imp/getrandom.rs @@ -157,8 +157,11 @@ pub unsafe extern "C" fn sys_getrandom(buf: *mut c_void, buflen: size_t, flags: if buf.is_null() { return Err(LinuxError::EFAULT); } - if flags != 0 { - return Err(LinuxError::EINVAL); + + match flags as _ { + crate::ctypes::GRND_NONBLOCK => {} + crate::ctypes::GRND_RANDOM => {} + _ => return Err(LinuxError::EINVAL), } // fill the buffer 8 bytes at a time first, then fill the remaining bytes let buflen_mod = buflen % (core::mem::size_of::() / core::mem::size_of::()); diff --git a/api/ruxos_posix_api/src/imp/ioctl.rs b/api/ruxos_posix_api/src/imp/ioctl.rs index 245a3d358..4838772c9 100644 --- a/api/ruxos_posix_api/src/imp/ioctl.rs +++ b/api/ruxos_posix_api/src/imp/ioctl.rs @@ -7,7 +7,7 @@ * See the Mulan PSL v2 for more details. */ -use crate::imp::fd_ops::get_file_like; +use crate::{imp::fd_ops::get_file_like, sys_getpgid}; use axerrno::LinuxError; use core::ffi::c_int; @@ -46,14 +46,18 @@ pub fn sys_ioctl(fd: c_int, request: usize, data: usize) -> c_int { } Ok(0) } - TCGETS | TIOCSPGRP => { + TCGETS => { + debug!("sys_ioctl: tty TCGETS"); + Ok(0) + } + TIOCSPGRP => { warn!("stdout pretend to be tty"); Ok(0) } TIOCGPGRP => { warn!("stdout TIOCGPGRP, pretend to be have a tty process group."); unsafe { - *(data as *mut u32) = 0; + *(data as *mut u32) = sys_getpgid(0) as _; } Ok(0) } diff --git a/api/ruxos_posix_api/src/imp/stdio.rs b/api/ruxos_posix_api/src/imp/stdio.rs index 7b9699154..087e3d8a3 100644 --- a/api/ruxos_posix_api/src/imp/stdio.rs +++ b/api/ruxos_posix_api/src/imp/stdio.rs @@ -19,41 +19,37 @@ use { core::sync::atomic::{AtomicBool, Ordering}, }; -fn console_read_bytes() -> Option { - let ret = ruxhal::console::getchar().map(|c| if c == b'\r' { b'\n' } else { c }); - if let Some(c) = ret { - let _ = console_write_bytes(&[c]); - } - ret -} - -fn console_write_bytes(buf: &[u8]) -> AxResult { - ruxhal::console::write_bytes(buf); - Ok(buf.len()) -} - struct StdinRaw; struct StdoutRaw; +#[cfg(feature = "alloc")] +extern crate alloc; +#[cfg(feature = "alloc")] +static STDIO_TTY_NAME: lazy_init::LazyInit = lazy_init::LazyInit::new(); +#[cfg(not(feature = "alloc"))] +static STDIO_TTY_NAME: &str = "dummy"; + +fn get_stdio_tty_name() -> &'static str { + #[cfg(feature = "alloc")] + { + if !STDIO_TTY_NAME.is_init() { + let name = ruxhal::get_all_device_names().first().unwrap().clone(); + STDIO_TTY_NAME.init_by(name); + } + } + &STDIO_TTY_NAME +} + impl Read for StdinRaw { // Non-blocking read, returns number of bytes read. fn read(&mut self, buf: &mut [u8]) -> AxResult { - let mut read_len = 0; - while read_len < buf.len() { - if let Some(c) = console_read_bytes() { - buf[read_len] = c; - read_len += 1; - } else { - break; - } - } - Ok(read_len) + Ok(ruxhal::tty_read(buf, get_stdio_tty_name())) } } impl Write for StdoutRaw { fn write(&mut self, buf: &[u8]) -> AxResult { - console_write_bytes(buf) + Ok(ruxhal::tty_write(buf, get_stdio_tty_name())) } fn flush(&mut self) -> AxResult { diff --git a/apps/c/busybox/.gitignore b/apps/c/busybox/.gitignore new file mode 100644 index 000000000..e3ce33a96 --- /dev/null +++ b/apps/c/busybox/.gitignore @@ -0,0 +1,10 @@ +ruxgo_bld +compile_commands.json +.cache +/rootfs/bin/* +/rootfs/lib/* +/rootfs/dev +/rootfs/etc +/rootfs/proc +/rootfs/sys +/rootfs/tmp diff --git a/apps/c/busybox/README.md b/apps/c/busybox/README.md new file mode 100644 index 000000000..570cf1646 --- /dev/null +++ b/apps/c/busybox/README.md @@ -0,0 +1,24 @@ +# busybox + +## Quick Start + +1. Compile `busybox` or get its ELF binary (using Musl), then copy to `rootfs/bin`. + +2. Copy the Musl dyanmic linker to `rootfs/lib`. + +3. modify `axbuild.mk`, like: + +```makefile +app-objs=main.o + +ARGS = /bin/busybox,ls +ENVS = +V9P_PATH=${APP}/rootfs +``` + +4. Run + +```sh +# in the RuxOS main directory. +make run ARCH=aarch64 A=apps/c/busybox V9P=y MUSL=y +``` diff --git a/apps/c/busybox/axbuild.mk b/apps/c/busybox/axbuild.mk new file mode 100644 index 000000000..433b3b546 --- /dev/null +++ b/apps/c/busybox/axbuild.mk @@ -0,0 +1,5 @@ +app-objs=main.o + +ARGS = /bin/busybox,sh +ENVS = +V9P_PATH=${APP}/rootfs \ No newline at end of file diff --git a/apps/c/busybox/config_linux.toml b/apps/c/busybox/config_linux.toml new file mode 100644 index 000000000..61ba40629 --- /dev/null +++ b/apps/c/busybox/config_linux.toml @@ -0,0 +1,34 @@ +[build] +compiler = "gcc" +app = "./busybox" + +[os] +name = "ruxos" +services = [ + "alloc", + "paging", + "musl", + "multitask", + "fs", + "pipe", + "poll", + "rtc", + "virtio-9p", + "irq", + "signal", +] +ulib = "ruxmusl" +develop = "y" + +[os.platform] +name = "aarch64-qemu-virt" +mode = "release" +log = "debug" + + +[os.platform.qemu] +memory = "2g" +v9p = "y" +v9p_path = "./rootfs" +args = "/bin/busybox,sh" +qemu_log = "y" diff --git a/apps/c/busybox/features.txt b/apps/c/busybox/features.txt new file mode 100644 index 000000000..0b7506227 --- /dev/null +++ b/apps/c/busybox/features.txt @@ -0,0 +1,11 @@ +paging +alloc +irq +musl +multitask +fs +pipe +poll +rtc +signal +virtio-9p \ No newline at end of file diff --git a/apps/c/busybox/main.c b/apps/c/busybox/main.c new file mode 100644 index 000000000..cb479e391 --- /dev/null +++ b/apps/c/busybox/main.c @@ -0,0 +1,7 @@ +#include +#include + +int main(int argc, char** argv, char**envp) { + execv(argv[0], argv); + return 0; +} \ No newline at end of file diff --git a/apps/c/dl/axbuild.mk b/apps/c/dl/axbuild.mk index 55cc2d0f4..8c1a982aa 100644 --- a/apps/c/dl/axbuild.mk +++ b/apps/c/dl/axbuild.mk @@ -3,4 +3,5 @@ app-objs=main.o ARGS = /bin/hello ENVS = V9P_PATH=${APP}/rootfs + # make run ARCH=aarch64 A=apps/c/dl V9P=y MUSL=y LOG=debug \ No newline at end of file diff --git a/crates/tty/Cargo.toml b/crates/tty/Cargo.toml new file mode 100644 index 000000000..8d8da252c --- /dev/null +++ b/crates/tty/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "tty" +version = "0.0.1" +edition = "2021" + +[dependencies] +spinlock = { path = "../spinlock" } +lazy_init = { path = "../lazy_init" } +log = "0.4" diff --git a/crates/tty/src/buffer.rs b/crates/tty/src/buffer.rs new file mode 100644 index 000000000..2c63d2324 --- /dev/null +++ b/crates/tty/src/buffer.rs @@ -0,0 +1,149 @@ +/* Copyright (c) [2023] [Syswonder Community] + * [Ruxos] is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +//! functions for tty buffer. +//! Drivers should fill the buffer by functions below. +//! then the data will be passed to line discipline for processing. + +/// tty buffer size. +const TTY_BUF_SIZE: usize = 4096; + +/// ring buffer. +#[derive(Debug)] +struct RingBuffer { + /// data. + buf: [u8; TTY_BUF_SIZE], + + /// the first element or empty slot if buffer is empty. + head: usize, + + /// the first empty slot. + tail: usize, + + /// number of elements. + len: usize, +} + +/// tty buffer. +/// TODO: use flip buffer. +#[derive(Debug)] +pub struct TtyBuffer { + /// use ring buffer to save chars. + buffer: spinlock::SpinNoIrq, +} + +impl TtyBuffer { + pub fn new() -> Self { + Self { + buffer: spinlock::SpinNoIrq::new(RingBuffer { + buf: [0u8; TTY_BUF_SIZE], + head: 0, + tail: 0, + len: 0, + }), + } + } + + /// get `index`th element without changing buffer. + pub fn see(&self, index: usize) -> u8 { + let buf = self.buffer.lock(); + if index < buf.len { + buf.buf[(index + buf.head) % TTY_BUF_SIZE] + } else { + 0 + } + } + + /// push a char to tail. + pub fn push(&self, ch: u8) { + let mut buf = self.buffer.lock(); + if buf.len != TTY_BUF_SIZE { + buf.len += 1; + let idx = buf.tail; + buf.buf[idx] = ch; + buf.tail = (buf.tail + 1) % TTY_BUF_SIZE; + } + } + + /// delete and return the heading char. + pub fn pop(&self) -> u8 { + self.delete(0) + } + + /// insert `ch` to `index`th position. + pub fn insert(&self, ch: u8, index: usize) { + let mut buf = self.buffer.lock(); + // if not full and index is right + if buf.len != TTY_BUF_SIZE && index <= buf.len { + // shift buffer[index..move_len+index] one slot right. + let move_len = buf.len - index; + let mut i = buf.tail; + for _ in 0..move_len { + i -= 1; + buf.buf[(i + 1) % TTY_BUF_SIZE] = buf.buf[i % TTY_BUF_SIZE]; + } + // insert + let idx = (buf.head + index) % TTY_BUF_SIZE; + buf.buf[idx] = ch; + buf.len += 1; + buf.tail = (buf.tail + 1) % TTY_BUF_SIZE; + } + } + + /// delete and return the `index`th element. + pub fn delete(&self, index: usize) -> u8 { + let mut buf = self.buffer.lock(); + // if not empty and index is right + if buf.len != 0 && index < buf.len { + let move_len = buf.len - index; + let mut i = index + buf.head; + + // save retval + let ret = buf.buf[i % TTY_BUF_SIZE]; + + // copy move_len elements from buffer[index+head] to buffer[index+head-1]; + for _ in 0..move_len { + buf.buf[i % TTY_BUF_SIZE] = buf.buf[(i + 1) % TTY_BUF_SIZE]; + i += 1; + } + + // len -= 1 + buf.len -= 1; + buf.tail -= 1; + ret + } else { + 0 + } + } + + /// get current length of buffer. + pub fn len(&self) -> usize { + self.buffer.lock().len + } +} + +/// a buffer for echo of line discipline. +/// additionally saving the cursor position. +#[derive(Debug)] +pub struct EchoBuffer { + /// chars buffer. + pub buffer: TtyBuffer, + + /// current column of cursor. + pub col: usize, +} + +impl EchoBuffer { + pub fn new() -> Self { + Self { + buffer: TtyBuffer::new(), + col: 0, + } + } +} diff --git a/crates/tty/src/constant.rs b/crates/tty/src/constant.rs new file mode 100644 index 000000000..9a134764f --- /dev/null +++ b/crates/tty/src/constant.rs @@ -0,0 +1,29 @@ +/* Copyright (c) [2023] [Syswonder Community] + * [Ruxos] is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +pub const LF: u8 = b'\n'; +pub const CR: u8 = b'\r'; + +pub const DEL: u8 = b'\x7f'; +pub const BS: u8 = b'\x08'; + +pub const SPACE: u8 = b' '; + +/// escape +pub const ESC: u8 = 27; +/// [ +pub const LEFT_BRACKET: u8 = 91; + +/// an arrow char is `ARROW_PREFIX` + `UP/DOWN/RIGHT/LEFT` +pub const ARROW_PREFIX: [u8; 2] = [ESC, LEFT_BRACKET]; + +// const UP: u8 = 65; +// const DOWN: u8 = 66; +pub const RIGHT: u8 = 67; +pub const LEFT: u8 = 68; diff --git a/crates/tty/src/driver.rs b/crates/tty/src/driver.rs new file mode 100644 index 000000000..dfe106a31 --- /dev/null +++ b/crates/tty/src/driver.rs @@ -0,0 +1,167 @@ +/* Copyright (c) [2023] [Syswonder Community] + * [Ruxos] is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +//! the first thing a driver should do is registering itself by `register_driver()`, +//! which will allocate an index for this driver. +//! +//! then, driver should register every device it has by `register_device()`, +//! which will allocate an index for this device. + +use crate::tty::TtyStruct; +use alloc::collections::BTreeMap; +use alloc::string::String; +use alloc::sync::Arc; +use alloc::{vec, vec::Vec}; +use lazy_init::LazyInit; +use spinlock::SpinNoIrq; + +/// all tty drivers. +/// only be written when registering a driver. +pub(super) static ALL_DRIVERS: LazyInit>>> = LazyInit::new(); + +/// the operations a tty driver must implement. +/// passed by driver when registering itself. +#[derive(Debug)] +pub struct TtyDriverOps { + /// push a char to device. + pub putchar: fn(u8), +} + +/// tty driver. +#[derive(Debug)] +pub struct TtyDriver { + /// driver operations. + pub ops: TtyDriverOps, + + /// driver's devices. + /// TODO: maybe use rwlock for dynamicly adding devices is better. + ttys: SpinNoIrq>>, + + /// index of driver. + index: usize, + + /// name of driver. + name: String, +} + +impl TtyDriver { + pub fn new(ops: TtyDriverOps, name: &str) -> Self { + Self { + ops, + ttys: SpinNoIrq::new(BTreeMap::new()), + index: 0, + name: String::from(name), + } + } + + /// add a device, return its index, -1 means failure. + fn add_one_device(&self, tty: Arc) -> isize { + let mut index = 0; + if let Some(k) = self.ttys.lock().last_key_value() { + index = *k.0; + } + + // set index of device + tty.set_index(index); + + // set name of device + let mut name = self.name.clone(); + name.push(core::char::from_digit(index as _, 16).unwrap()); + tty.set_name(&name); + + // save this device + self.ttys.lock().insert(index, tty.clone()); + + // return device's index + index as _ + } + + pub fn name(&self) -> String { + self.name.clone() + } + + pub fn index(&self) -> usize { + self.index + } + + /// get all devices' name + pub fn get_all_device_names(&self) -> Vec { + let mut ret = vec![]; + for (_, tty) in self.ttys.lock().iter() { + ret.push(tty.name()); + } + ret + } + + /// get device + pub fn get_device_by_name(&self, name: &str) -> Option> { + for (_, tty) in self.ttys.lock().iter() { + if tty.name() == name { + return Some(tty.clone()); + } + } + None + } + + /// get device + pub fn get_device_by_index(&self, index: usize) -> Option> { + self.ttys.lock().get(&index).cloned() + } +} + +pub fn init() { + ALL_DRIVERS.init_by(SpinNoIrq::new(vec![])); +} + +/// get driver by index. +pub fn get_driver_by_index(index: usize) -> Option> { + let lock = ALL_DRIVERS.lock(); + for driver in lock.iter() { + if driver.index == index { + return Some(driver.clone()); + } + } + None +} + +/// called by driver to register itself. +/// return driver's index. +pub fn register_driver(ops: TtyDriverOps, name: &str) -> usize { + // create a tty driver structure + let mut driver = TtyDriver::new(ops, name); + + // lock + let mut lock = ALL_DRIVERS.lock(); + + // grant an index to the driver + let index = lock.len(); + driver.index = index; + + // push + lock.push(Arc::new(driver)); + + // return index + index +} + +/// called by driver to register device. +/// return device's index, or -1 on failure. +pub fn register_device(driver_index: usize) -> isize { + let mut index = -1; + // if driver is found + if let Some(driver) = get_driver_by_index(driver_index) { + // create a tty structure + let tty = Arc::new(TtyStruct::new(driver.clone())); + + // save this structure + index = driver.add_one_device(tty.clone()); + crate::tty::add_one_device(tty.clone()); + } + index +} diff --git a/crates/tty/src/ldisc.rs b/crates/tty/src/ldisc.rs new file mode 100644 index 000000000..e7394ac42 --- /dev/null +++ b/crates/tty/src/ldisc.rs @@ -0,0 +1,221 @@ +/* Copyright (c) [2023] [Syswonder Community] + * [Ruxos] is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +//! TTY line discipline process all incoming and outgoing chars from/to a tty device. +//! the currently implemented line discipline is N_TTY. +//! line disciplines are registered when a device is registered. + +use alloc::sync::Arc; +use spinlock::SpinNoIrq; + +use crate::{ + buffer::{EchoBuffer, TtyBuffer}, + tty::TtyStruct, +}; + +/// tty line discipline. +#[derive(Debug)] +pub struct TtyLdisc { + /// chars that can be read by kernel. + read_buf: TtyBuffer, + + /// chars being echoed on the screen. + echo_buf: SpinNoIrq, + + /// chars from driver, and not yet been processed. + rec_buf: TtyBuffer, +} + +/// implement N_TTY. +impl TtyLdisc { + pub fn new() -> Self { + Self { + read_buf: TtyBuffer::new(), + echo_buf: SpinNoIrq::new(EchoBuffer::new()), + rec_buf: TtyBuffer::new(), + } + } + + /// kernel reads data. + pub fn read(&self, buf: &mut [u8]) -> usize { + let read_buf = &self.read_buf; + + // len of this reading + let len = buf.len().min(read_buf.len()); + + // return if nothing can be read + if len == 0 { + return 0; + } + + // copy data from read_buf to `buf` + for ch in buf.iter_mut().take(len) { + *ch = read_buf.pop(); + } + + len + } + + /// driver sends data from device for processing and echoing. + /// running in irq. + pub fn receive_buf(&self, tty: Arc, buf: &[u8]) { + use crate::constant::*; + + let rec_buf = &self.rec_buf; + + // save data to receive buffer + for ch in buf { + rec_buf.push(*ch); + } + + // process chars in receive buffer + while rec_buf.len() > 0 { + let ch = rec_buf.see(0); + + // if char may be arrow char + if ch == ARROW_PREFIX[0] { + // no enough len, just break, waitting for next time + if rec_buf.len() < 3 { + break; + } + + // enough len, but not a arrow char, just ignore + if rec_buf.see(1) != ARROW_PREFIX[1] { + rec_buf.pop(); + rec_buf.pop(); + break; + } + + // it is an arrow char, get it + rec_buf.pop(); + rec_buf.pop(); + let ch = rec_buf.pop(); + + // deal with arrow char + match ch { + LEFT => { + let mut lock = self.echo_buf.lock(); + // if can go left + if lock.col > 0 { + self.write(tty.clone(), &[ARROW_PREFIX[0], ARROW_PREFIX[1], ch]); + lock.col -= 1; + } + } + RIGHT => { + let mut lock = self.echo_buf.lock(); + // if can go right + if lock.col < lock.buffer.len() { + self.write(tty.clone(), &[ARROW_PREFIX[0], ARROW_PREFIX[1], ch]); + lock.col += 1; + } + } + _ => { + // it is UP/DOWN, just ignore + } + } + // not a arrow char, handle it as a normal char + } else { + let ch = rec_buf.pop(); + match ch { + CR | LF => { + // always '\n' + let ch = LF; + + // echo + self.write(tty.clone(), &[ch]); + + // push this char to echo buffer + let mut lock = self.echo_buf.lock(); + lock.buffer.push(ch); + + // copy echo buffer to read buffer + // FIXME: currently will push all data to read_buf + let len = lock.buffer.len(); + for _ in 0..len { + self.read_buf.push(lock.buffer.pop()); + } + + // echo buffer's column is set to 0 + lock.col = 0; + } + BS | DEL => { + let mut lock = self.echo_buf.lock(); + let col = lock.col; + let len = lock.buffer.len(); + // if can delete + if col > 0 { + // perform a backspace + self.write(tty.clone(), &[BS, SPACE, BS]); + + // if cursor is not on the rightmost + if col != len { + for i in col..len { + let ch = lock.buffer.see(i); + self.write(tty.clone(), &[ch]); + } + self.write(tty.clone(), &[SPACE]); + for _ in 0..(len - col + 1) { + self.write( + tty.clone(), + &[ARROW_PREFIX[0], ARROW_PREFIX[1], LEFT], + ); + } + } + + // modify echo buffer + lock.buffer.delete(col - 1); + lock.col -= 1; + } + } + _ => { + // process normal chars. + let mut echo_buf = self.echo_buf.lock(); + let col = echo_buf.col; + let len = echo_buf.buffer.len(); + + // echo + self.write(tty.clone(), &[ch]); + + // if cursor is not on the rightmost + if col != len { + for i in col..len { + self.write(tty.clone(), &[echo_buf.buffer.see(i)]); + } + for _ in 0..(len - col) { + self.write(tty.clone(), &[ARROW_PREFIX[0], ARROW_PREFIX[1], LEFT]); + } + } + + // modify echo buffer + echo_buf.buffer.insert(ch, col); + echo_buf.col += 1; + } + } + } + } + } + + /// kernel writes data to device. + pub fn write(&self, tty: Arc, buf: &[u8]) -> usize { + let mut len = 0; + let driver = tty.driver(); + for ch in buf { + len += 1; + // call driver's method + (driver.ops.putchar)(*ch); + } + len + } +} + +impl Default for TtyLdisc { + fn default() -> Self { + Self::new() + } +} diff --git a/crates/tty/src/lib.rs b/crates/tty/src/lib.rs new file mode 100644 index 000000000..e6e30a134 --- /dev/null +++ b/crates/tty/src/lib.rs @@ -0,0 +1,72 @@ +/* Copyright (c) [2023] [Syswonder Community] + * [Ruxos] is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +//! Init +//! +//! firstly, a driver registers itself to get its index. +//! next, the driver registers all devices it found to get their indices. +//! +//! Read +//! +//! when a device receives data, it will cause a irq. +//! then the driver sends the data to tty layer using their indices. +//! finally, kernel will get the data using the device's name. +//! +//! Write +//! +//! kernel writes data to a device using its name. + +#![no_std] + +extern crate alloc; + +mod buffer; +mod constant; +mod driver; +mod ldisc; +mod tty; + +use driver::get_driver_by_index; + +pub use driver::{register_device, register_driver, TtyDriverOps}; +pub use tty::{get_all_device_names, get_device_by_name}; + +/// called by driver when irq, to send data from hardware. +pub fn tty_receive_buf(driver_index: usize, device_index: usize, buf: &[u8]) { + // check the validation of index + if let Some(driver) = get_driver_by_index(driver_index) { + if let Some(tty) = driver.get_device_by_index(device_index) { + tty.ldisc().receive_buf(tty.clone(), buf); + } + } +} + +/// called by kernel to read a tty device. +pub fn tty_read(buf: &mut [u8], dev_name: &str) -> usize { + if let Some(tty) = get_device_by_name(dev_name) { + tty.ldisc().read(buf) + } else { + 0 + } +} + +/// called by kernel to write a tty device. +pub fn tty_write(buf: &[u8], dev_name: &str) -> usize { + if let Some(tty) = get_device_by_name(dev_name) { + tty.ldisc().write(tty.clone(), buf) + } else { + 0 + } +} + +/// init +pub fn init() { + driver::init(); + tty::init(); +} diff --git a/crates/tty/src/tty.rs b/crates/tty/src/tty.rs new file mode 100644 index 000000000..2a54c2164 --- /dev/null +++ b/crates/tty/src/tty.rs @@ -0,0 +1,104 @@ +/* Copyright (c) [2023] [Syswonder Community] + * [Ruxos] is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +use core::sync::atomic::AtomicUsize; + +use alloc::{string::String, sync::Arc, vec, vec::Vec}; +use lazy_init::LazyInit; +use spinlock::SpinNoIrq; + +use crate::{driver::TtyDriver, ldisc::TtyLdisc}; + +/// all registered devices. +pub(super) static ALL_DEVICES: LazyInit>>> = LazyInit::new(); + +/// tty device. +#[derive(Debug)] +pub struct TtyStruct { + /// driver of device. + driver: Arc, + + /// device's line discipline. + ldisc: Arc, + + /// index of device. + index: AtomicUsize, + + /// name of device. + name: SpinNoIrq, +} + +impl TtyStruct { + pub fn new(driver: Arc) -> Self { + Self { + driver: driver.clone(), + ldisc: Arc::new(TtyLdisc::new()), + index: AtomicUsize::new(0), + name: SpinNoIrq::new(String::new()), + } + } + + /// get tty line discipline. + pub fn ldisc(&self) -> Arc { + self.ldisc.clone() + } + + /// set device index. + pub fn set_index(&self, index: usize) { + self.index + .store(index, core::sync::atomic::Ordering::Relaxed); + } + + /// set name of device + pub fn set_name(&self, name: &str) { + let mut lock = self.name.lock(); + lock.clone_from(&String::from(name)); + } + + /// Convert a tty structure into a name, reflecting the kernel naming policy. + pub fn name(&self) -> String { + self.name.lock().clone() + } + + /// get device's driver. + pub fn driver(&self) -> Arc { + self.driver.clone() + } +} + +/// called by kernel to get a device. +pub fn get_device_by_name(name: &str) -> Option> { + let lock = ALL_DEVICES.lock(); + for tty in lock.iter() { + if tty.name() == name { + return Some(tty.clone()); + } + } + None +} + +/// called by kernel to get all devices' names. +/// usually used in init to get the view of tty. +pub fn get_all_device_names() -> Vec { + let mut ret = vec![]; + let alldev = ALL_DEVICES.lock(); + for dev in alldev.iter() { + ret.push(dev.name()); + } + ret +} + +/// save a device when registered. +pub fn add_one_device(tty: Arc) { + ALL_DEVICES.lock().push(tty); +} + +pub fn init() { + ALL_DEVICES.init_by(SpinNoIrq::new(vec![])); +} diff --git a/modules/ruxhal/Cargo.toml b/modules/ruxhal/Cargo.toml index d485d82a8..7b4ea29fa 100644 --- a/modules/ruxhal/Cargo.toml +++ b/modules/ruxhal/Cargo.toml @@ -41,6 +41,7 @@ percpu = { path = "../../crates/percpu" } memory_addr = "0.1.0" handler_table = "0.1.0" crate_interface = "0.1.1" +tty = { path = "../../crates/tty", optional = true } [target.'cfg(target_arch = "x86_64")'.dependencies] x86 = "0.52" @@ -50,8 +51,8 @@ raw-cpuid = "11.0" [target.'cfg(any(target_arch = "riscv32", target_arch = "riscv64"))'.dependencies] riscv = "0.10" -sbi-rt = { version = "0.0.2", features = ["legacy"] } -dtb = {path = "../../crates/dtb" } +sbi-rt = { version = "0.0.3", features = ["legacy"] } +dtb = { path = "../../crates/dtb" } [target.'cfg(target_arch = "aarch64")'.dependencies] aarch64-cpu = "9.3" @@ -59,7 +60,7 @@ tock-registers = "0.8" arm_gic = { path = "../../crates/arm_gic" } arm_pl011 = { path = "../../crates/arm_pl011" } dw_apb_uart = { path = "../../crates/dw_apb_uart" } -dtb = {path = "../../crates/dtb" } +dtb = { path = "../../crates/dtb" } [build-dependencies] ruxconfig = { path = "../ruxconfig" } diff --git a/modules/ruxhal/src/lib.rs b/modules/ruxhal/src/lib.rs index fcb78a72d..7ecc3b81b 100644 --- a/modules/ruxhal/src/lib.rs +++ b/modules/ruxhal/src/lib.rs @@ -94,3 +94,55 @@ pub use self::platform::platform_init_secondary; /// so we should save cmdline in a buf before this memory is set free #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] pub static mut COMLINE_BUF: [u8; 256] = [0; 256]; + +#[allow(unused)] +/// read a tty device specified by its name. +pub fn tty_read(buf: &mut [u8], dev_name: &str) -> usize { + #[cfg(not(feature = "tty"))] + { + let mut read_len = 0; + while read_len < buf.len() { + if let Some(c) = console::getchar().map(|c| if c == b'\r' { b'\n' } else { c }) { + buf[read_len] = c; + read_len += 1; + } else { + break; + } + } + read_len + } + + #[cfg(feature = "tty")] + { + tty::tty_read(buf, dev_name) + } +} + +#[cfg(feature = "alloc")] +extern crate alloc; + +/// get all tty devices' names. +#[cfg(feature = "alloc")] +pub fn get_all_device_names() -> alloc::vec::Vec { + #[cfg(feature = "tty")] + { + tty::get_all_device_names() + } + #[cfg(not(feature = "tty"))] + { + alloc::vec![alloc::string::String::from("notty")] + } +} + +/// write a tty device specified by its name. +pub fn tty_write(buf: &[u8], _dev_name: &str) -> usize { + #[cfg(feature = "tty")] + { + tty::tty_write(buf, _dev_name) + } + #[cfg(not(feature = "tty"))] + { + console::write_bytes(buf); + return buf.len(); + } +} diff --git a/modules/ruxhal/src/mem.rs b/modules/ruxhal/src/mem.rs index b05409a6b..8c039c2ab 100644 --- a/modules/ruxhal/src/mem.rs +++ b/modules/ruxhal/src/mem.rs @@ -160,10 +160,7 @@ pub(crate) fn default_free_regions() -> impl Iterator { core::iter::once(MemRegion { paddr: start, size: end.as_usize() - start.as_usize(), - flags: MemRegionFlags::FREE - | MemRegionFlags::READ - | MemRegionFlags::WRITE - | MemRegionFlags::EXECUTE, + flags: MemRegionFlags::FREE | MemRegionFlags::READ | MemRegionFlags::WRITE, name: "free memory", }) } diff --git a/modules/ruxhal/src/platform/aarch64_common/pl011.rs b/modules/ruxhal/src/platform/aarch64_common/pl011.rs index 577371850..a311f9834 100644 --- a/modules/ruxhal/src/platform/aarch64_common/pl011.rs +++ b/modules/ruxhal/src/platform/aarch64_common/pl011.rs @@ -100,10 +100,24 @@ pub fn init_early() { UART.inner.lock().init(); } +#[cfg(feature = "tty")] +static DRIVER_INDEX: lazy_init::LazyInit = lazy_init::LazyInit::new(); +#[cfg(feature = "tty")] +static DEV_INDEX: lazy_init::LazyInit = lazy_init::LazyInit::new(); + /// Set UART IRQ Enable pub fn init() { #[cfg(feature = "irq")] { + #[cfg(feature = "tty")] + { + let ops = tty::TtyDriverOps { putchar }; + let driver_index = tty::register_driver(ops, "ttyS"); + let dev_index = tty::register_device(driver_index); + assert_ne!(dev_index, -1); + DRIVER_INDEX.init_by(driver_index); + DEV_INDEX.init_by(dev_index as _); + } crate::irq::register_handler(crate::platform::irq::UART_IRQ_NUM, irq_handler); crate::irq::set_enable(crate::platform::irq::UART_IRQ_NUM, true); } @@ -116,8 +130,22 @@ pub fn irq_handler() { let is_receive_interrupt = dev.is_receive_interrupt(); if is_receive_interrupt { dev.ack_interrupts(); + #[cfg(not(feature = "tty"))] while let Some(c) = dev.getchar() { UART.buffer.lock().push(c); } + #[cfg(feature = "tty")] + { + let mut buf = [0u8; 128]; + let mut len = 0; + + while let Some(c) = dev.getchar() { + buf[len] = c; + len += 1; + } + let drv_idx = *DRIVER_INDEX.try_get().unwrap(); + let dev_idx = *DEV_INDEX.try_get().unwrap(); + tty::tty_receive_buf(drv_idx, dev_idx, &buf[..len]); + } } } diff --git a/modules/ruxruntime/Cargo.toml b/modules/ruxruntime/Cargo.toml index 849acd6d5..c2fd7563c 100644 --- a/modules/ruxruntime/Cargo.toml +++ b/modules/ruxruntime/Cargo.toml @@ -34,6 +34,7 @@ signal = [] musl = ["dep:ruxfutex"] + [dependencies] cfg-if = "1.0" ruxhal = { path = "../ruxhal" } @@ -54,3 +55,5 @@ percpu = { path = "../../crates/percpu", optional = true } kernel_guard = { version = "0.1.0", optional = true } lazy_init = { path = "../../crates/lazy_init", optional = true } dtb = { path = "../../crates/dtb", optional = true } + +tty = { path = "../../crates/tty", optional = true } diff --git a/modules/ruxruntime/src/lib.rs b/modules/ruxruntime/src/lib.rs index f481f1fdf..2f11fb756 100644 --- a/modules/ruxruntime/src/lib.rs +++ b/modules/ruxruntime/src/lib.rs @@ -195,6 +195,9 @@ pub extern "C" fn rust_main(cpu_id: usize, dtb: usize) -> ! { remap_kernel_memory().expect("remap kernel memoy failed"); } + #[cfg(feature = "tty")] + tty::init(); + info!("Initialize platform devices..."); ruxhal::platform_init(); diff --git a/scripts/make/build_musl.mk b/scripts/make/build_musl.mk index 018967e57..44bcfa283 100644 --- a/scripts/make/build_musl.mk +++ b/scripts/make/build_musl.mk @@ -49,7 +49,7 @@ ifeq ($(wildcard $(build_dir)),) tar -zxvf $(muslibc_dir)/musl-1.2.3.tar.gz -C $(muslibc_dir) && rm -f $(muslibc_dir)/musl-1.2.3.tar.gz endif mkdir -p $(build_dir) - cd $(build_dir) && ../musl-1.2.3/configure --prefix=../install --exec-prefix=../ --syslibdir=../install/lib --disable-shared ARCH=$(RUX_ARCH) CC=$(CC) CROSS_COMPILE=$(CROSS_COMPILE) CFLAGS='$(CFLAGS)' + cd $(build_dir) && ../musl-1.2.3/configure --prefix=../$(install_dir_name) --exec-prefix=../ --syslibdir=../$(install_dir_name)/lib ARCH=$(RUX_ARCH) CC=$(CC) CROSS_COMPILE=$(CROSS_COMPILE) CFLAGS='$(CFLAGS)' cd $(build_dir) && $(MAKE) -j && $(MAKE) install endif