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 <sys/uio.h>
 #include <unistd.h>
 #include <dirent.h>
+
+#include <sys/random.h>
\ 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::<i64>() / core::mem::size_of::<u8>());
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<u8> {
-    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<usize> {
-    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<alloc::string::String> = 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<usize> {
-        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<usize> {
-        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 <stdio.h>
+#include <unistd.h>
+
+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<RingBuffer>,
+}
+
+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<SpinNoIrq<Vec<Arc<TtyDriver>>>> = 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<BTreeMap<usize, Arc<TtyStruct>>>,
+
+    /// 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<TtyStruct>) -> 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<String> {
+        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<Arc<TtyStruct>> {
+        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<Arc<TtyStruct>> {
+        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<Arc<TtyDriver>> {
+    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<EchoBuffer>,
+
+    /// 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<TtyStruct>, 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<TtyStruct>, 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<SpinNoIrq<Vec<Arc<TtyStruct>>>> = LazyInit::new();
+
+/// tty device.
+#[derive(Debug)]
+pub struct TtyStruct {
+    /// driver of device.
+    driver: Arc<TtyDriver>,
+
+    /// device's line discipline.
+    ldisc: Arc<TtyLdisc>,
+
+    /// index of device.
+    index: AtomicUsize,
+
+    /// name of device.
+    name: SpinNoIrq<String>,
+}
+
+impl TtyStruct {
+    pub fn new(driver: Arc<TtyDriver>) -> 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<TtyLdisc> {
+        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<TtyDriver> {
+        self.driver.clone()
+    }
+}
+
+/// called by kernel to get a device.
+pub fn get_device_by_name(name: &str) -> Option<Arc<TtyStruct>> {
+    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<String> {
+    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<TtyStruct>) {
+    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<alloc::string::String> {
+    #[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<Item = MemRegion> {
     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<usize> = lazy_init::LazyInit::new();
+#[cfg(feature = "tty")]
+static DEV_INDEX: lazy_init::LazyInit<usize> = 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