diff --git a/components/axio/src/lib.rs b/components/axio/src/lib.rs index 334eb22f11..a5fb578506 100644 --- a/components/axio/src/lib.rs +++ b/components/axio/src/lib.rs @@ -33,4 +33,6 @@ pub struct PollState { pub readable: bool, /// Object can be writen now. pub writable: bool, + /// Monotonic token changed when the object's readiness may have changed. + pub readiness_version: u64, } diff --git a/os/arceos/api/arceos_posix_api/src/imp/fs.rs b/os/arceos/api/arceos_posix_api/src/imp/fs.rs index 20ba2e5517..e761a17b82 100644 --- a/os/arceos/api/arceos_posix_api/src/imp/fs.rs +++ b/os/arceos/api/arceos_posix_api/src/imp/fs.rs @@ -241,6 +241,7 @@ impl FileLike for File { Ok(PollState { readable: true, writable: true, + readiness_version: 0, }) } @@ -281,6 +282,7 @@ impl FileLike for Directory { Ok(PollState { readable: true, writable: false, + readiness_version: 0, }) } diff --git a/os/arceos/api/arceos_posix_api/src/imp/io_mpx/epoll.rs b/os/arceos/api/arceos_posix_api/src/imp/io_mpx/epoll.rs index f2b6736e90..9537f130ca 100644 --- a/os/arceos/api/arceos_posix_api/src/imp/io_mpx/epoll.rs +++ b/os/arceos/api/arceos_posix_api/src/imp/io_mpx/epoll.rs @@ -1,10 +1,8 @@ //! `epoll` implementation. -//! -//! TODO: do not support `EPOLLET` flag use alloc::{ collections::{BTreeMap, btree_map::Entry}, - sync::Arc, + sync::{Arc, Weak}, }; use core::{ffi::c_int, time::Duration}; @@ -17,21 +15,106 @@ use crate::{ imp::fd_ops::{FileLike, add_file_like, get_file_like}, }; +const EPOLL_READ_EVENTS: u32 = + ctypes::EPOLLIN | ctypes::EPOLLPRI | ctypes::EPOLLRDNORM | ctypes::EPOLLRDBAND; +const EPOLL_WRITE_EVENTS: u32 = ctypes::EPOLLOUT | ctypes::EPOLLWRNORM | ctypes::EPOLLWRBAND; +const EPOLL_ERROR_EVENTS: u32 = ctypes::EPOLLERR | ctypes::EPOLLHUP | ctypes::EPOLLRDHUP; +const EPOLL_RETURN_EVENTS: u32 = EPOLL_READ_EVENTS | EPOLL_WRITE_EVENTS | EPOLL_ERROR_EVENTS; +const EPOLL_BEHAVIOR_FLAGS: u32 = ctypes::EPOLLET | ctypes::EPOLLONESHOT; +const EPOLL_SUPPORTED_EVENTS: u32 = EPOLL_RETURN_EVENTS | EPOLL_BEHAVIOR_FLAGS; +const EPOLL_CREATE1_SUPPORTED_FLAGS: u32 = ctypes::EPOLL_CLOEXEC; + pub struct EpollInstance { - events: Mutex>, + events: Mutex>, +} + +struct WatchedEvent { + file: Weak, + event: ctypes::epoll_event, + last_ready: u32, + last_readiness_version: u64, + disabled: bool, } unsafe impl Send for ctypes::epoll_event {} unsafe impl Sync for ctypes::epoll_event {} -impl EpollInstance { - // TODO: parse flags - pub fn new(_flags: usize) -> Self { +impl WatchedEvent { + fn new(file: Arc, event: ctypes::epoll_event) -> Self { Self { - events: Mutex::new(BTreeMap::new()), + file: Arc::downgrade(&file), + event, + last_ready: 0, + last_readiness_version: 0, + disabled: false, + } + } + + fn update(&mut self, file: Arc, event: ctypes::epoll_event) { + self.file = Arc::downgrade(&file); + self.event = event; + self.last_ready = 0; + self.last_readiness_version = 0; + self.disabled = false; + } + + fn is_closed(&self) -> bool { + self.file.strong_count() == 0 + } + + fn is_edge_triggered(&self) -> bool { + self.event.events & ctypes::EPOLLET != 0 + } + + fn is_oneshot(&self) -> bool { + self.event.events & ctypes::EPOLLONESHOT != 0 + } + + fn current_ready(&self) -> (u32, u64) { + let Some(file) = self.file.upgrade() else { + return (0, 0); + }; + match file.poll() { + Ok(state) => { + let mut ready = 0; + let interest = self.event.events; + if state.readable { + ready |= interest & EPOLL_READ_EVENTS; + } + if state.writable { + ready |= interest & EPOLL_WRITE_EVENTS; + } + (ready, state.readiness_version) + } + Err(_) => (ctypes::EPOLLERR, 0), } } + fn deliverable_events(&self, ready: u32, readiness_version: u64) -> u32 { + if self.disabled { + return 0; + } + let events = if self.is_edge_triggered() { + if readiness_version == self.last_readiness_version { + ready & !self.last_ready + } else { + ready + } + } else { + ready + }; + events & EPOLL_RETURN_EVENTS + } +} + +impl EpollInstance { + pub fn new(flags: usize) -> LinuxResult { + validate_create1_flags(flags)?; + Ok(Self { + events: Mutex::new(BTreeMap::new()), + }) + } + fn from_fd(fd: c_int) -> LinuxResult> { get_file_like(fd)? .into_any() @@ -39,31 +122,46 @@ impl EpollInstance { .map_err(|_| LinuxError::EINVAL) } - fn control(&self, op: usize, fd: usize, event: &ctypes::epoll_event) -> LinuxResult { - match get_file_like(fd as c_int) { - Ok(_) => {} - Err(e) => return Err(e), - } - + fn control( + &self, + op: c_int, + fd: c_int, + event: Option<&ctypes::epoll_event>, + ) -> LinuxResult { match op as u32 { ctypes::EPOLL_CTL_ADD => { - if let Entry::Vacant(e) = self.events.lock().entry(fd) { - e.insert(*event); + let event = *event.ok_or(LinuxError::EFAULT)?; + validate_event_flags(event.events)?; + let file = get_file_like(fd)?; + if is_epoll_file(&file) { + return Err(LinuxError::ELOOP); + } + let mut events = self.events.lock(); + events.retain(|_, watch| !watch.is_closed()); + if let Entry::Vacant(e) = events.entry(fd as usize) { + e.insert(WatchedEvent::new(file, event)); } else { return Err(LinuxError::EEXIST); } } ctypes::EPOLL_CTL_MOD => { + let event = *event.ok_or(LinuxError::EFAULT)?; + validate_event_flags(event.events)?; + let file = get_file_like(fd)?; + if is_epoll_file(&file) { + return Err(LinuxError::ELOOP); + } let mut events = self.events.lock(); - if let Entry::Occupied(mut ocp) = events.entry(fd) { - ocp.insert(*event); + events.retain(|_, watch| !watch.is_closed()); + if let Entry::Occupied(mut ocp) = events.entry(fd as usize) { + ocp.get_mut().update(file, event); } else { return Err(LinuxError::ENOENT); } } ctypes::EPOLL_CTL_DEL => { let mut events = self.events.lock(); - if let Entry::Occupied(ocp) = events.entry(fd) { + if let Entry::Occupied(ocp) = events.entry(fd as usize) { ocp.remove_entry(); } else { return Err(LinuxError::ENOENT); @@ -77,44 +175,51 @@ impl EpollInstance { } fn poll_all(&self, events: &mut [ctypes::epoll_event]) -> LinuxResult { - let ready_list = self.events.lock(); + let mut ready_list = self.events.lock(); + ready_list.retain(|_, watch| !watch.is_closed()); let mut events_num = 0; - for (infd, ev) in ready_list.iter() { - match get_file_like(*infd as c_int)?.poll() { - Err(_) => { - if (ev.events & ctypes::EPOLLERR) != 0 { - events[events_num].events = ctypes::EPOLLERR; - events[events_num].data = ev.data; - events_num += 1; - } - } - Ok(state) => { - if state.readable && (ev.events & ctypes::EPOLLIN != 0) { - events[events_num].events = ctypes::EPOLLIN; - events[events_num].data = ev.data; - events_num += 1; - } - - if state.writable && (ev.events & ctypes::EPOLLOUT != 0) { - events[events_num].events = ctypes::EPOLLOUT; - events[events_num].data = ev.data; - events_num += 1; - } - } + for watch in ready_list.values_mut() { + if events_num == events.len() { + break; + } + + let (ready, readiness_version) = watch.current_ready(); + let deliverable = watch.deliverable_events(ready, readiness_version); + watch.last_ready = ready; + watch.last_readiness_version = readiness_version; + if deliverable == 0 { + continue; + } + + events[events_num].events = deliverable; + events[events_num].data = watch.event.data; + events_num += 1; + + if watch.is_oneshot() { + watch.disabled = true; } } Ok(events_num) } + + fn has_ready_events(&self) -> bool { + let mut ready_list = self.events.lock(); + ready_list.retain(|_, watch| !watch.is_closed()); + ready_list.values().any(|watch| { + let (ready, readiness_version) = watch.current_ready(); + watch.deliverable_events(ready, readiness_version) != 0 + }) + } } impl FileLike for EpollInstance { fn read(&self, _buf: &mut [u8]) -> LinuxResult { - Err(LinuxError::ENOSYS) + Err(LinuxError::EINVAL) } fn write(&self, _buf: &[u8]) -> LinuxResult { - Err(LinuxError::ENOSYS) + Err(LinuxError::EINVAL) } fn stat(&self) -> LinuxResult { @@ -132,7 +237,11 @@ impl FileLike for EpollInstance { } fn poll(&self) -> LinuxResult { - Err(LinuxError::ENOSYS) + Ok(ax_io::PollState { + readable: self.has_ready_events(), + writable: false, + readiness_version: 0, + }) } fn set_nonblocking(&self, _nonblocking: bool) -> LinuxResult { @@ -140,16 +249,25 @@ impl FileLike for EpollInstance { } } +/// Creates a new epoll instance with creation flags. +pub fn sys_epoll_create1(flags: c_int) -> c_int { + debug!("sys_epoll_create1 <= {flags}"); + syscall_body!(sys_epoll_create1, { + let epoll_instance = EpollInstance::new(flags as usize)?; + add_file_like(Arc::new(epoll_instance)) + }) +} + /// Creates a new epoll instance. /// /// It returns a file descriptor referring to the new epoll instance. pub fn sys_epoll_create(size: c_int) -> c_int { debug!("sys_epoll_create <= {size}"); syscall_body!(sys_epoll_create, { - if size < 0 { + if size <= 0 { return Err(LinuxError::EINVAL); } - let epoll_instance = EpollInstance::new(0); + let epoll_instance = EpollInstance::new(0)?; add_file_like(Arc::new(epoll_instance)) }) } @@ -163,9 +281,20 @@ pub unsafe fn sys_epoll_ctl( ) -> c_int { debug!("sys_epoll_ctl <= epfd: {epfd} op: {op} fd: {fd}"); syscall_body!(sys_epoll_ctl, { - let ret = unsafe { - EpollInstance::from_fd(epfd)?.control(op as usize, fd as usize, &(*event))? as c_int + if epfd == fd { + return Err(LinuxError::EINVAL); + } + let event = match op as u32 { + ctypes::EPOLL_CTL_ADD | ctypes::EPOLL_CTL_MOD => { + if event.is_null() { + return Err(LinuxError::EFAULT); + } + Some(unsafe { &*event }) + } + ctypes::EPOLL_CTL_DEL => None, + _ => None, }; + let ret = EpollInstance::from_fd(epfd)?.control(op, fd, event)? as c_int; Ok(ret) }) } @@ -183,6 +312,9 @@ pub unsafe fn sys_epoll_wait( if maxevents <= 0 { return Err(LinuxError::EINVAL); } + if events.is_null() { + return Err(LinuxError::EFAULT); + } let events = unsafe { core::slice::from_raw_parts_mut(events, maxevents as usize) }; let deadline = (!timeout.is_negative()).then(|| wall_time() + Duration::from_millis(timeout as u64)); @@ -203,3 +335,21 @@ pub unsafe fn sys_epoll_wait( } }) } + +fn validate_create1_flags(flags: usize) -> LinuxResult { + if (flags as u32) & !EPOLL_CREATE1_SUPPORTED_FLAGS != 0 { + return Err(LinuxError::EINVAL); + } + Ok(()) +} + +fn validate_event_flags(events: u32) -> LinuxResult { + if events & !EPOLL_SUPPORTED_EVENTS != 0 { + return Err(LinuxError::EINVAL); + } + Ok(()) +} + +fn is_epoll_file(file: &Arc) -> bool { + file.clone().into_any().downcast::().is_ok() +} diff --git a/os/arceos/api/arceos_posix_api/src/imp/io_mpx/mod.rs b/os/arceos/api/arceos_posix_api/src/imp/io_mpx/mod.rs index cfd882fe8d..a407cee5a8 100644 --- a/os/arceos/api/arceos_posix_api/src/imp/io_mpx/mod.rs +++ b/os/arceos/api/arceos_posix_api/src/imp/io_mpx/mod.rs @@ -3,6 +3,7 @@ //! * [`select`](select::sys_select) //! * [`poll`](poll::sys_poll) //! * [`epoll_create`](epoll::sys_epoll_create) +//! * [`epoll_create1`](epoll::sys_epoll_create1) //! * [`epoll_ctl`](epoll::sys_epoll_ctl) //! * [`epoll_wait`](epoll::sys_epoll_wait) @@ -14,7 +15,7 @@ mod poll; mod select; #[cfg(feature = "epoll")] -pub use self::epoll::{sys_epoll_create, sys_epoll_ctl, sys_epoll_wait}; +pub use self::epoll::{sys_epoll_create, sys_epoll_create1, sys_epoll_ctl, sys_epoll_wait}; #[cfg(feature = "poll")] pub use self::poll::sys_poll; #[cfg(feature = "select")] diff --git a/os/arceos/api/arceos_posix_api/src/imp/pipe.rs b/os/arceos/api/arceos_posix_api/src/imp/pipe.rs index d36227fad2..ee51850301 100644 --- a/os/arceos/api/arceos_posix_api/src/imp/pipe.rs +++ b/os/arceos/api/arceos_posix_api/src/imp/pipe.rs @@ -22,6 +22,8 @@ pub struct PipeRingBuffer { head: usize, tail: usize, status: RingBufferStatus, + read_readiness_version: u64, + write_readiness_version: u64, } impl PipeRingBuffer { @@ -31,28 +33,51 @@ impl PipeRingBuffer { head: 0, tail: 0, status: RingBufferStatus::Empty, + read_readiness_version: 0, + write_readiness_version: 0, } } pub fn write_byte(&mut self, byte: u8) { + let old_status = self.status; self.status = RingBufferStatus::Normal; self.arr[self.tail] = byte; self.tail = (self.tail + 1) % RING_BUFFER_SIZE; if self.tail == self.head { self.status = RingBufferStatus::Full; } + self.refresh_readiness_versions(old_status); } pub fn read_byte(&mut self) -> u8 { + let old_status = self.status; self.status = RingBufferStatus::Normal; let c = self.arr[self.head]; self.head = (self.head + 1) % RING_BUFFER_SIZE; if self.head == self.tail { self.status = RingBufferStatus::Empty; } + self.refresh_readiness_versions(old_status); c } + fn refresh_readiness_versions(&mut self, old_status: RingBufferStatus) { + if Self::is_readable(old_status) != Self::is_readable(self.status) { + self.read_readiness_version = self.read_readiness_version.wrapping_add(1); + } + if Self::is_writable(old_status) != Self::is_writable(self.status) { + self.write_readiness_version = self.write_readiness_version.wrapping_add(1); + } + } + + const fn is_readable(status: RingBufferStatus) -> bool { + !matches!(status, RingBufferStatus::Empty) + } + + const fn is_writable(status: RingBufferStatus) -> bool { + !matches!(status, RingBufferStatus::Full) + } + /// Get the length of remaining data in the buffer pub const fn available_read(&self) -> usize { if matches!(self.status, RingBufferStatus::Empty) { @@ -72,6 +97,14 @@ impl PipeRingBuffer { RING_BUFFER_SIZE - self.available_read() } } + + pub const fn readiness_version(&self, readable_end: bool) -> u64 { + if readable_end { + self.read_readiness_version + } else { + self.write_readiness_version + } + } } pub struct Pipe { @@ -111,6 +144,9 @@ impl FileLike for Pipe { if !self.readable() { return Err(LinuxError::EPERM); } + if buf.is_empty() { + return Ok(0); + } let mut read_size = 0usize; let max_len = buf.len(); loop { @@ -131,6 +167,12 @@ impl FileLike for Pipe { } buf[read_size] = ring_buffer.read_byte(); read_size += 1; + if read_size == max_len { + return Ok(read_size); + } + } + if read_size > 0 { + return Ok(read_size); } } } @@ -139,6 +181,9 @@ impl FileLike for Pipe { if !self.writable() { return Err(LinuxError::EPERM); } + if buf.is_empty() { + return Ok(0); + } let mut write_size = 0usize; let max_len = buf.len(); loop { @@ -156,6 +201,9 @@ impl FileLike for Pipe { } ring_buffer.write_byte(buf[write_size]); write_size += 1; + if write_size == max_len { + return Ok(write_size); + } } } } @@ -182,6 +230,7 @@ impl FileLike for Pipe { Ok(PollState { readable: self.readable() && buf.available_read() > 0, writable: self.writable() && buf.available_write() > 0, + readiness_version: buf.readiness_version(self.readable()), }) } diff --git a/os/arceos/api/arceos_posix_api/src/imp/stdio.rs b/os/arceos/api/arceos_posix_api/src/imp/stdio.rs index ee9f157632..1835047a0b 100644 --- a/os/arceos/api/arceos_posix_api/src/imp/stdio.rs +++ b/os/arceos/api/arceos_posix_api/src/imp/stdio.rs @@ -131,6 +131,7 @@ impl super::fd_ops::FileLike for Stdin { Ok(PollState { readable: true, writable: true, + readiness_version: 0, }) } @@ -167,6 +168,7 @@ impl super::fd_ops::FileLike for Stdout { Ok(PollState { readable: true, writable: true, + readiness_version: 0, }) } diff --git a/os/arceos/api/arceos_posix_api/src/lib.rs b/os/arceos/api/arceos_posix_api/src/lib.rs index 03cc1725d3..d62be320d0 100644 --- a/os/arceos/api/arceos_posix_api/src/lib.rs +++ b/os/arceos/api/arceos_posix_api/src/lib.rs @@ -50,7 +50,7 @@ pub use imp::io_mpx::sys_poll; #[cfg(feature = "select")] pub use imp::io_mpx::sys_select; #[cfg(feature = "epoll")] -pub use imp::io_mpx::{sys_epoll_create, sys_epoll_ctl, sys_epoll_wait}; +pub use imp::io_mpx::{sys_epoll_create, sys_epoll_create1, sys_epoll_ctl, sys_epoll_wait}; #[cfg(feature = "net")] pub use imp::net::{ sys_accept, sys_bind, sys_connect, sys_freeaddrinfo, sys_getaddrinfo, sys_getpeername, diff --git a/os/arceos/modules/axnet/src/smoltcp_impl/tcp.rs b/os/arceos/modules/axnet/src/smoltcp_impl/tcp.rs index 5506fe71cd..e02df49f77 100644 --- a/os/arceos/modules/axnet/src/smoltcp_impl/tcp.rs +++ b/os/arceos/modules/axnet/src/smoltcp_impl/tcp.rs @@ -361,6 +361,7 @@ impl TcpSocket { _ => Ok(PollState { readable: false, writable: false, + readiness_version: 0, }), } } @@ -505,6 +506,7 @@ impl TcpSocket { Ok(PollState { readable: false, writable, + readiness_version: 0, }) } @@ -515,6 +517,7 @@ impl TcpSocket { Ok(PollState { readable: !socket.may_recv() || socket.can_recv(), writable: !socket.may_send() || socket.can_send(), + readiness_version: 0, }) }) } @@ -525,6 +528,7 @@ impl TcpSocket { Ok(PollState { readable: LISTEN_TABLE.can_accept(local_addr.port)?, writable: false, + readiness_version: 0, }) } diff --git a/os/arceos/modules/axnet/src/smoltcp_impl/udp.rs b/os/arceos/modules/axnet/src/smoltcp_impl/udp.rs index 1e997b2700..4f47c6e5fe 100644 --- a/os/arceos/modules/axnet/src/smoltcp_impl/udp.rs +++ b/os/arceos/modules/axnet/src/smoltcp_impl/udp.rs @@ -189,12 +189,14 @@ impl UdpSocket { return Ok(PollState { readable: false, writable: false, + readiness_version: 0, }); } SOCKET_SET.with_socket_mut::(self.handle, |socket| { Ok(PollState { readable: socket.can_recv(), writable: socket.can_send(), + readiness_version: 0, }) }) } diff --git a/os/arceos/ulib/axlibc/include/sys/epoll.h b/os/arceos/ulib/axlibc/include/sys/epoll.h index bd23bcf46b..3b4d376b2f 100644 --- a/os/arceos/ulib/axlibc/include/sys/epoll.h +++ b/os/arceos/ulib/axlibc/include/sys/epoll.h @@ -50,6 +50,7 @@ __attribute__((__packed__)) ; int epoll_create(int __size); +int epoll_create1(int __flags); int epoll_ctl(int, int, int, struct epoll_event *); int epoll_wait(int, struct epoll_event *, int, int); diff --git a/os/arceos/ulib/axlibc/src/io_mpx.rs b/os/arceos/ulib/axlibc/src/io_mpx.rs index ed755c98ca..cdd8b4ab35 100644 --- a/os/arceos/ulib/axlibc/src/io_mpx.rs +++ b/os/arceos/ulib/axlibc/src/io_mpx.rs @@ -3,7 +3,7 @@ use core::ffi::c_int; #[cfg(feature = "select")] use ax_posix_api::sys_select; #[cfg(feature = "epoll")] -use ax_posix_api::{sys_epoll_create, sys_epoll_ctl, sys_epoll_wait}; +use ax_posix_api::{sys_epoll_create, sys_epoll_create1, sys_epoll_ctl, sys_epoll_wait}; use crate::{ctypes, utils::e}; @@ -16,6 +16,13 @@ pub unsafe extern "C" fn epoll_create(size: c_int) -> c_int { e(sys_epoll_create(size)) } +/// Creates a new epoll instance with creation flags. +#[cfg(feature = "epoll")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn epoll_create1(flags: c_int) -> c_int { + e(sys_epoll_create1(flags)) +} + /// Control interface for an epoll file descriptor #[cfg(feature = "epoll")] #[unsafe(no_mangle)] diff --git a/os/arceos/ulib/axlibc/src/lib.rs b/os/arceos/ulib/axlibc/src/lib.rs index acb2864927..c1bf8be2fe 100644 --- a/os/arceos/ulib/axlibc/src/lib.rs +++ b/os/arceos/ulib/axlibc/src/lib.rs @@ -85,7 +85,7 @@ pub use self::io::write; #[cfg(feature = "select")] pub use self::io_mpx::select; #[cfg(feature = "epoll")] -pub use self::io_mpx::{epoll_create, epoll_ctl, epoll_wait}; +pub use self::io_mpx::{epoll_create, epoll_create1, epoll_ctl, epoll_wait}; #[cfg(feature = "alloc")] pub use self::malloc::{free, malloc}; #[cfg(feature = "net")] diff --git a/test-suit/arceos/c/epoll/build-aarch64-unknown-none-softfloat.toml b/test-suit/arceos/c/epoll/build-aarch64-unknown-none-softfloat.toml new file mode 100644 index 0000000000..eaafb0a608 --- /dev/null +++ b/test-suit/arceos/c/epoll/build-aarch64-unknown-none-softfloat.toml @@ -0,0 +1,7 @@ +features = ["alloc", "paging", "multitask", "fd", "pipe", "epoll"] +log = "Info" +max_cpu_num = 4 + +[env] +AX_GW = "10.0.2.2" +AX_IP = "10.0.2.15" diff --git a/test-suit/arceos/c/epoll/build-loongarch64-unknown-none-softfloat.toml b/test-suit/arceos/c/epoll/build-loongarch64-unknown-none-softfloat.toml new file mode 100644 index 0000000000..eaafb0a608 --- /dev/null +++ b/test-suit/arceos/c/epoll/build-loongarch64-unknown-none-softfloat.toml @@ -0,0 +1,7 @@ +features = ["alloc", "paging", "multitask", "fd", "pipe", "epoll"] +log = "Info" +max_cpu_num = 4 + +[env] +AX_GW = "10.0.2.2" +AX_IP = "10.0.2.15" diff --git a/test-suit/arceos/c/epoll/build-riscv64gc-unknown-none-elf.toml b/test-suit/arceos/c/epoll/build-riscv64gc-unknown-none-elf.toml new file mode 100644 index 0000000000..eaafb0a608 --- /dev/null +++ b/test-suit/arceos/c/epoll/build-riscv64gc-unknown-none-elf.toml @@ -0,0 +1,7 @@ +features = ["alloc", "paging", "multitask", "fd", "pipe", "epoll"] +log = "Info" +max_cpu_num = 4 + +[env] +AX_GW = "10.0.2.2" +AX_IP = "10.0.2.15" diff --git a/test-suit/arceos/c/epoll/build-x86_64-unknown-none.toml b/test-suit/arceos/c/epoll/build-x86_64-unknown-none.toml new file mode 100644 index 0000000000..eaafb0a608 --- /dev/null +++ b/test-suit/arceos/c/epoll/build-x86_64-unknown-none.toml @@ -0,0 +1,7 @@ +features = ["alloc", "paging", "multitask", "fd", "pipe", "epoll"] +log = "Info" +max_cpu_num = 4 + +[env] +AX_GW = "10.0.2.2" +AX_IP = "10.0.2.15" diff --git a/test-suit/arceos/c/epoll/c/main.c b/test-suit/arceos/c/epoll/c/main.c new file mode 100644 index 0000000000..49c8a7793d --- /dev/null +++ b/test-suit/arceos/c/epoll/c/main.c @@ -0,0 +1,187 @@ +#include +#include +#include +#include +#include +#include +#include + +static void expect_errno_int(int ret, int err) +{ + assert(ret == -1); + assert(errno == err); +} + +static void expect_errno_ssize(ssize_t ret, int err) +{ + assert(ret == -1); + assert(errno == err); +} + +static struct epoll_event make_event(uint32_t events, int fd) +{ + struct epoll_event event; + memset(&event, 0, sizeof(event)); + event.events = events; + event.data.fd = fd; + return event; +} + +static void expect_no_event(int epfd) +{ + struct epoll_event event; + memset(&event, 0, sizeof(event)); + assert(epoll_wait(epfd, &event, 1, 0) == 0); +} + +static void expect_one_event(int epfd, uint32_t events, int fd) +{ + struct epoll_event event; + memset(&event, 0, sizeof(event)); + assert(epoll_wait(epfd, &event, 1, 0) == 1); + assert(event.data.fd == fd); + assert((event.events & events) == events); +} + +static void test_epoll_arguments(void) +{ + struct epoll_event event = make_event(EPOLLIN, 0); + char byte = 0; + int epfd; + int pipefd[2]; + + errno = 0; + expect_errno_int(epoll_create(0), EINVAL); + + errno = 0; + expect_errno_int(epoll_create1(EPOLL_NONBLOCK), EINVAL); + + epfd = epoll_create1(EPOLL_CLOEXEC); + assert(epfd >= 0); + + errno = 0; + expect_errno_int(epoll_wait(epfd, &event, 0, 0), EINVAL); + + errno = 0; + expect_errno_int(epoll_wait(epfd, NULL, 1, 0), EFAULT); + + errno = 0; + expect_errno_ssize(read(epfd, &byte, sizeof(byte)), EINVAL); + + errno = 0; + expect_errno_ssize(write(epfd, "x", 1), EINVAL); + + assert(pipe(pipefd) == 0); + + errno = 0; + expect_errno_int(epoll_ctl(epfd, EPOLL_CTL_ADD, pipefd[0], NULL), EFAULT); + + errno = 0; + expect_errno_int(epoll_ctl(epfd, EPOLL_CTL_ADD, epfd, &event), EINVAL); + + close(pipefd[0]); + close(pipefd[1]); + close(epfd); +} + +static void test_level_triggered_epoll(int epfd, int write_fd) +{ + struct epoll_event event = make_event(EPOLLOUT, write_fd); + + assert(epoll_ctl(epfd, EPOLL_CTL_ADD, write_fd, &event) == 0); + expect_one_event(epfd, EPOLLOUT, write_fd); + expect_one_event(epfd, EPOLLOUT, write_fd); + assert(epoll_ctl(epfd, EPOLL_CTL_DEL, write_fd, NULL) == 0); + expect_no_event(epfd); +} + +static void test_edge_triggered_epoll(int epfd, int read_fd, int write_fd) +{ + char buf[4]; + struct epoll_event event = make_event(EPOLLIN | EPOLLET, read_fd); + + assert(epoll_ctl(epfd, EPOLL_CTL_ADD, read_fd, &event) == 0); + expect_no_event(epfd); + + assert(write(write_fd, "abc", 3) == 3); + expect_one_event(epfd, EPOLLIN, read_fd); + expect_no_event(epfd); + + assert(read(read_fd, buf, 1) == 1); + expect_no_event(epfd); + + assert(read(read_fd, buf, 2) == 2); + + /* No empty epoll_wait here: draining the pipe must still allow the next + write to produce a fresh edge-triggered event. */ + assert(write(write_fd, "d", 1) == 1); + expect_one_event(epfd, EPOLLIN, read_fd); + assert(epoll_ctl(epfd, EPOLL_CTL_DEL, read_fd, NULL) == 0); + assert(read(read_fd, buf, 1) == 1); + expect_no_event(epfd); +} + +static void test_oneshot_epoll(int epfd, int read_fd, int write_fd) +{ + char buf[1]; + struct epoll_event event = make_event(EPOLLIN | EPOLLONESHOT, read_fd); + + assert(epoll_ctl(epfd, EPOLL_CTL_ADD, read_fd, &event) == 0); + assert(write(write_fd, "z", 1) == 1); + + expect_one_event(epfd, EPOLLIN, read_fd); + expect_no_event(epfd); + + assert(epoll_ctl(epfd, EPOLL_CTL_MOD, read_fd, &event) == 0); + expect_one_event(epfd, EPOLLIN, read_fd); + + assert(read(read_fd, buf, 1) == 1); + assert(epoll_ctl(epfd, EPOLL_CTL_DEL, read_fd, NULL) == 0); + expect_no_event(epfd); +} + +static void test_registered_fd_close(void) +{ + char buf[1]; + int epfd; + int pipefd[2]; + struct epoll_event event; + + epfd = epoll_create(1); + assert(epfd >= 0); + assert(pipe(pipefd) == 0); + + event = make_event(EPOLLOUT, pipefd[1]); + assert(epoll_ctl(epfd, EPOLL_CTL_ADD, pipefd[1], &event) == 0); + expect_one_event(epfd, EPOLLOUT, pipefd[1]); + + close(pipefd[1]); + expect_no_event(epfd); + assert(read(pipefd[0], buf, sizeof(buf)) == 0); + + close(pipefd[0]); + close(epfd); +} + +void main() +{ + int epfd; + int pipefd[2]; + + test_epoll_arguments(); + + epfd = epoll_create(1); + assert(epfd >= 0); + assert(pipe(pipefd) == 0); + + test_level_triggered_epoll(epfd, pipefd[1]); + test_edge_triggered_epoll(epfd, pipefd[0], pipefd[1]); + test_oneshot_epoll(epfd, pipefd[0], pipefd[1]); + test_registered_fd_close(); + + close(pipefd[0]); + close(pipefd[1]); + close(epfd); + + puts("(C)Epoll tests run OK"); +} diff --git a/test-suit/arceos/c/epoll/qemu-aarch64.toml b/test-suit/arceos/c/epoll/qemu-aarch64.toml new file mode 100644 index 0000000000..a7d421b9a0 --- /dev/null +++ b/test-suit/arceos/c/epoll/qemu-aarch64.toml @@ -0,0 +1,18 @@ +args = [ + "-machine", + "virt", + "-cpu", + "cortex-a72", + "-m", + "512M", + "-smp", + "4", + "-nographic", + "-serial", + "mon:stdio", +] +uefi = false +to_bin = true +success_regex = ['\(C\)Epoll tests run OK'] +fail_regex = ['(?i)\bpanic(?:ked)?\b'] +timeout = 120 diff --git a/test-suit/arceos/c/epoll/qemu-loongarch64.toml b/test-suit/arceos/c/epoll/qemu-loongarch64.toml new file mode 100644 index 0000000000..0b466ba421 --- /dev/null +++ b/test-suit/arceos/c/epoll/qemu-loongarch64.toml @@ -0,0 +1,18 @@ +args = [ + "-machine", + "virt", + "-cpu", + "la464", + "-m", + "128M", + "-smp", + "4", + "-nographic", + "-serial", + "mon:stdio", +] +uefi = false +to_bin = true +success_regex = ['\(C\)Epoll tests run OK'] +fail_regex = ['(?i)\bpanic(?:ked)?\b'] +timeout = 120 diff --git a/test-suit/arceos/c/epoll/qemu-riscv64.toml b/test-suit/arceos/c/epoll/qemu-riscv64.toml new file mode 100644 index 0000000000..9fe09dd9de --- /dev/null +++ b/test-suit/arceos/c/epoll/qemu-riscv64.toml @@ -0,0 +1,18 @@ +args = [ + "-machine", + "virt", + "-cpu", + "rv64", + "-m", + "512M", + "-smp", + "4", + "-nographic", + "-serial", + "mon:stdio", +] +uefi = false +to_bin = true +success_regex = ['\(C\)Epoll tests run OK'] +fail_regex = ['(?i)\bpanic(?:ked)?\b'] +timeout = 120 diff --git a/test-suit/arceos/c/epoll/qemu-x86_64.toml b/test-suit/arceos/c/epoll/qemu-x86_64.toml new file mode 100644 index 0000000000..c9070d2c9b --- /dev/null +++ b/test-suit/arceos/c/epoll/qemu-x86_64.toml @@ -0,0 +1,6 @@ +args = ["-nographic", "-smp", "4"] +uefi = false +to_bin = false +success_regex = ['\(C\)Epoll tests run OK'] +fail_regex = ['(?i)\bpanic(?:ked)?\b'] +timeout = 120