Support pty packet mode
This commit is contained in:
parent
b552bdbc51
commit
3235175fcf
|
|
@ -5,7 +5,13 @@ use ostd::sync::SpinLock;
|
|||
|
||||
use super::file::PtySlaveFile;
|
||||
use crate::{
|
||||
device::tty::{Tty, TtyDriver, TtyFlags},
|
||||
device::{
|
||||
pty::packet::{PacketCtrl, PacketStatus},
|
||||
tty::{
|
||||
termio::{CCtrlCharId, CInputFlags, CLocalFlags, CTermios},
|
||||
Tty, TtyDriver, TtyFlags,
|
||||
},
|
||||
},
|
||||
events::IoEvents,
|
||||
fs::inode_handle::FileIo,
|
||||
prelude::*,
|
||||
|
|
@ -25,6 +31,7 @@ pub struct PtyDriver {
|
|||
pollee: Pollee,
|
||||
opened_slaves: SpinLock<usize>,
|
||||
tty_flags: TtyFlags,
|
||||
packet_ctrl: PacketCtrl,
|
||||
}
|
||||
|
||||
/// A pseudoterminal slave.
|
||||
|
|
@ -39,6 +46,7 @@ impl PtyDriver {
|
|||
pollee: Pollee::new(),
|
||||
opened_slaves: SpinLock::new(0),
|
||||
tty_flags,
|
||||
packet_ctrl: PacketCtrl::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -47,6 +55,27 @@ impl PtyDriver {
|
|||
return Ok(0);
|
||||
}
|
||||
|
||||
// Reference: <https://elixir.bootlin.com/linux/v6.17/source/drivers/tty/n_tty.c#L2245>.
|
||||
match self.packet_ctrl.take_status() {
|
||||
None => {
|
||||
// Packet mode is disabled.
|
||||
self.read_output(buf)
|
||||
}
|
||||
Some(packet_status) if !packet_status.is_empty() => {
|
||||
// Some packet status is pending.
|
||||
buf[0] = packet_status.bits();
|
||||
Ok(1)
|
||||
}
|
||||
Some(_) => {
|
||||
// There's no pending packet status.
|
||||
let data_len = self.read_output(&mut buf[1..])?;
|
||||
buf[0] = 0;
|
||||
Ok(data_len + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read_output(&self, buf: &mut [u8]) -> Result<usize> {
|
||||
let mut output = self.output.lock();
|
||||
if output.is_empty() {
|
||||
if self.tty_flags.is_other_closed() {
|
||||
|
|
@ -57,7 +86,6 @@ impl PtyDriver {
|
|||
|
||||
let read_len = output.len().min(buf.len());
|
||||
output.pop_slice(&mut buf[..read_len]).unwrap();
|
||||
|
||||
Ok(read_len)
|
||||
}
|
||||
|
||||
|
|
@ -76,6 +104,10 @@ impl PtyDriver {
|
|||
pub(super) fn tty_flags(&self) -> &TtyFlags {
|
||||
&self.tty_flags
|
||||
}
|
||||
|
||||
pub(super) fn packet_ctrl(&self) -> &PacketCtrl {
|
||||
&self.packet_ctrl
|
||||
}
|
||||
}
|
||||
|
||||
impl TtyDriver for PtyDriver {
|
||||
|
|
@ -110,7 +142,7 @@ impl TtyDriver for PtyDriver {
|
|||
len += 1;
|
||||
}
|
||||
|
||||
self.pollee.notify(IoEvents::IN);
|
||||
self.pollee.notify(IoEvents::IN | IoEvents::RDNORM);
|
||||
Ok(len)
|
||||
}
|
||||
|
||||
|
|
@ -129,7 +161,7 @@ impl TtyDriver for PtyDriver {
|
|||
}
|
||||
|
||||
if !has_notified {
|
||||
self.pollee.notify(IoEvents::IN);
|
||||
self.pollee.notify(IoEvents::IN | IoEvents::RDNORM);
|
||||
has_notified = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -147,4 +179,40 @@ impl TtyDriver for PtyDriver {
|
|||
fn console(&self) -> Option<&dyn AnyConsoleDevice> {
|
||||
None
|
||||
}
|
||||
|
||||
fn on_termios_change(&self, old_termios: &CTermios, new_termios: &CTermios) {
|
||||
// Reference: <https://elixir.bootlin.com/linux/v6.17/source/drivers/tty/pty.c#L246>.
|
||||
let extproc = old_termios.local_flags().contains(CLocalFlags::EXTPROC)
|
||||
|| new_termios.local_flags().contains(CLocalFlags::EXTPROC);
|
||||
let old_flow = old_termios.input_flags().contains(CInputFlags::IXON)
|
||||
&& old_termios.special_char(CCtrlCharId::VSTOP) == 0o23
|
||||
&& old_termios.special_char(CCtrlCharId::VSTART) == 0o21;
|
||||
let new_flow = new_termios.input_flags().contains(CInputFlags::IXON)
|
||||
&& new_termios.special_char(CCtrlCharId::VSTOP) == 0o23
|
||||
&& new_termios.special_char(CCtrlCharId::VSTART) == 0o21;
|
||||
|
||||
if (old_flow == new_flow) && !extproc {
|
||||
return;
|
||||
}
|
||||
|
||||
let has_set = self.packet_ctrl.set_status(|packet_status| {
|
||||
if old_flow != new_flow {
|
||||
*packet_status &= !(PacketStatus::DOSTOP | PacketStatus::NOSTOP);
|
||||
if new_flow {
|
||||
*packet_status |= PacketStatus::DOSTOP;
|
||||
} else {
|
||||
*packet_status |= PacketStatus::NOSTOP;
|
||||
}
|
||||
}
|
||||
|
||||
if extproc {
|
||||
*packet_status |= PacketStatus::IOCTL;
|
||||
}
|
||||
});
|
||||
|
||||
if has_set {
|
||||
self.pollee
|
||||
.notify(IoEvents::PRI | IoEvents::IN | IoEvents::RDNORM);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ impl PtyMaster {
|
|||
let mut events = IoEvents::empty();
|
||||
|
||||
if self.slave().driver().buffer_len() > 0 {
|
||||
events |= IoEvents::IN;
|
||||
events |= IoEvents::IN | IoEvents::RDNORM;
|
||||
}
|
||||
|
||||
if self.slave().can_push() {
|
||||
|
|
@ -74,6 +74,12 @@ impl PtyMaster {
|
|||
events |= IoEvents::HUP;
|
||||
}
|
||||
|
||||
// Deal with packet mode.
|
||||
let packet_ctrl = self.slave.driver().packet_ctrl();
|
||||
if packet_ctrl.has_status() {
|
||||
events |= IoEvents::PRI | IoEvents::IN | IoEvents::RDNORM;
|
||||
}
|
||||
|
||||
events
|
||||
}
|
||||
}
|
||||
|
|
@ -144,6 +150,9 @@ mod ioctl_defs {
|
|||
pub(super) type GetPtyLock = ioc!(TIOCGPTLCK, b'T', 0x39, OutData<i32>);
|
||||
|
||||
pub(super) type OpenPtySlave = ioc!(TIOCGPTPEER, b'T', 0x41, NoData);
|
||||
|
||||
pub(super) type SetPktMode = ioc!(TIOCPKT, 0x5420, InData<i32>);
|
||||
pub(super) type GetPktMode = ioc!(TIOCGPKT, b'T', 0x38, OutData<i32>);
|
||||
}
|
||||
|
||||
impl FileIo for PtyMaster {
|
||||
|
|
@ -223,6 +232,20 @@ impl FileIo for PtyMaster {
|
|||
|
||||
cmd.write(&len)?;
|
||||
}
|
||||
cmd @ SetPktMode => {
|
||||
let new_mode = cmd.read()? != 0;
|
||||
|
||||
self.slave.driver().packet_ctrl().set_mode(new_mode);
|
||||
}
|
||||
cmd @ GetPktMode => {
|
||||
let packet_mode = if self.slave.driver().packet_ctrl().mode() {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
cmd.write(&packet_mode)?;
|
||||
}
|
||||
|
||||
_ => (self.slave.clone() as Arc<dyn Terminal>).job_ioctl(raw_ioctl, true)?,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ use crate::{
|
|||
mod driver;
|
||||
mod file;
|
||||
mod master;
|
||||
mod packet;
|
||||
|
||||
pub use driver::PtySlave;
|
||||
pub use master::PtyMaster;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,93 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use core::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
/// Control states related to packet mode.
|
||||
///
|
||||
/// Reference: <https://man7.org/linux/man-pages/man2/TIOCPKT.2const.html>.
|
||||
pub(super) struct PacketCtrl {
|
||||
mode: AtomicBool,
|
||||
status: SpinLock<PacketStatus>,
|
||||
}
|
||||
|
||||
impl PacketCtrl {
|
||||
// Creates a new `PacketCtrl`.
|
||||
pub(super) fn new() -> Self {
|
||||
Self {
|
||||
mode: AtomicBool::new(false),
|
||||
status: SpinLock::new(PacketStatus::empty()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether packet mode is enabled.
|
||||
pub(super) fn mode(&self) -> bool {
|
||||
self.mode.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Sets whether packet mode is enabled.
|
||||
pub(super) fn set_mode(&self, mode: bool) {
|
||||
// Reference: <https://elixir.bootlin.com/linux/v6.17/source/drivers/tty/pty.c#L158>.
|
||||
let mut status = self.status.lock();
|
||||
|
||||
let old_mode = self.mode.load(Ordering::Relaxed);
|
||||
self.mode.store(mode, Ordering::Relaxed);
|
||||
|
||||
if mode && !old_mode {
|
||||
*status = PacketStatus::empty();
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets packet status if packet mode is enabled.
|
||||
pub(super) fn set_status(&self, set_packet_status: impl FnOnce(&mut PacketStatus)) -> bool {
|
||||
// Fast path: Packet mode is disabled.
|
||||
if !self.mode.load(Ordering::Relaxed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Packet mode is enabled.
|
||||
let mut packet_status = self.status.lock();
|
||||
set_packet_status(&mut packet_status);
|
||||
true
|
||||
}
|
||||
|
||||
/// Checks if packet mode is enabled and there is pending packet status.
|
||||
pub(super) fn has_status(&self) -> bool {
|
||||
// Fast path: Packet mode is disabled.
|
||||
if !self.mode.load(Ordering::Relaxed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Packet mode is enabled.
|
||||
!self.status.lock().is_empty()
|
||||
}
|
||||
|
||||
/// Takes out packet status if packet mode is enabled.
|
||||
pub(super) fn take_status(&self) -> Option<PacketStatus> {
|
||||
// Fast path: Packed mode is disabled.
|
||||
if !self.mode.load(Ordering::Relaxed) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Packet mode is enabled.
|
||||
let mut status = self.status.lock();
|
||||
let old_status = *status;
|
||||
*status = PacketStatus::empty();
|
||||
Some(old_status)
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// Reference: <https://elixir.bootlin.com/linux/v6.18/source/include/uapi/asm-generic/ioctls.h#L110>.
|
||||
pub(super) struct PacketStatus: u8 {
|
||||
const DATA = 0;
|
||||
const FLUSHREAD = 1 << 0;
|
||||
const FLUSHWRITE = 1 << 1;
|
||||
const STOP = 1 << 2;
|
||||
const START = 1 << 3;
|
||||
const NOSTOP = 1 << 4;
|
||||
const DOSTOP = 1 << 5;
|
||||
const IOCTL = 1 << 6;
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,11 @@
|
|||
|
||||
use aster_console::AnyConsoleDevice;
|
||||
|
||||
use crate::{device::tty::Tty, fs::inode_handle::FileIo, prelude::*};
|
||||
use crate::{
|
||||
device::tty::{termio::CTermios, Tty},
|
||||
fs::inode_handle::FileIo,
|
||||
prelude::*,
|
||||
};
|
||||
|
||||
/// A TTY driver.
|
||||
///
|
||||
|
|
@ -58,4 +62,9 @@ pub trait TtyDriver: Send + Sync + 'static {
|
|||
///
|
||||
/// If the TTY is not associated with any console device, this method will return `None`.
|
||||
fn console(&self) -> Option<&dyn AnyConsoleDevice>;
|
||||
|
||||
/// Notifies that the TTY termios is changed.
|
||||
///
|
||||
/// This method will be called with a spin lock held, so it cannot break atomic mode.
|
||||
fn on_termios_change(&self, old_termios: &CTermios, new_termios: &CTermios);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ use ostd::const_assert;
|
|||
|
||||
use super::termio::{CCtrlCharId, CTermios, CWinSize};
|
||||
use crate::{
|
||||
device::tty::termio::{CInputFlags, CLocalFlags},
|
||||
prelude::*,
|
||||
process::signal::{
|
||||
constants::{SIGINT, SIGQUIT},
|
||||
|
|
@ -98,7 +99,7 @@ impl LineDiscipline {
|
|||
mut signal_callback: F1,
|
||||
echo_callback: F2,
|
||||
) -> Result<()> {
|
||||
let ch = if self.termios.contains_icrnl() && ch == b'\r' {
|
||||
let ch = if self.termios.input_flags().contains(CInputFlags::ICRNL) && ch == b'\r' {
|
||||
b'\n'
|
||||
} else {
|
||||
ch
|
||||
|
|
@ -111,7 +112,7 @@ impl LineDiscipline {
|
|||
|
||||
// Typically, a TTY in raw mode does not echo. But the TTY can also be in a CBREAK mode,
|
||||
// with ICANON closed and ECHO opened.
|
||||
if self.termios.contain_echo() {
|
||||
if self.termios.local_flags().contains(CLocalFlags::ECHO) {
|
||||
self.output_char(ch, echo_callback);
|
||||
}
|
||||
|
||||
|
|
@ -166,7 +167,7 @@ impl LineDiscipline {
|
|||
echo_callback(b"\x08");
|
||||
}
|
||||
ch if is_printable_char(ch) => echo_callback(&[ch]),
|
||||
ch if is_ctrl_char(ch) && self.termios.contains_echo_ctl() => {
|
||||
ch if is_ctrl_char(ch) && self.termios.local_flags().contains(CLocalFlags::ECHOCTL) => {
|
||||
echo_callback(&[b'^', ctrl_char_to_printable(ch)]);
|
||||
}
|
||||
_ => {}
|
||||
|
|
@ -269,7 +270,9 @@ fn is_line_terminator(ch: u8, termios: &CTermios) -> bool {
|
|||
return true;
|
||||
}
|
||||
|
||||
if termios.contains_iexten() && ch == termios.special_char(CCtrlCharId::VEOL2) {
|
||||
if termios.local_flags().contains(CLocalFlags::IEXTEN)
|
||||
&& ch == termios.special_char(CCtrlCharId::VEOL2)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -293,7 +296,7 @@ fn is_ctrl_char(ch: u8) -> bool {
|
|||
}
|
||||
|
||||
fn char_to_signal(ch: u8, termios: &CTermios) -> Option<SigNum> {
|
||||
if !termios.is_canonical_mode() || !termios.contains_isig() {
|
||||
if !termios.is_canonical_mode() || !termios.local_flags().contains(CLocalFlags::ISIG) {
|
||||
return None;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ mod flags;
|
|||
pub(super) mod ioctl_defs;
|
||||
mod line_discipline;
|
||||
mod n_tty;
|
||||
mod termio;
|
||||
pub(super) mod termio;
|
||||
|
||||
pub use device::SystemConsole;
|
||||
pub use driver::TtyDriver;
|
||||
|
|
@ -152,7 +152,7 @@ impl<D: TtyDriver> Tty<D> {
|
|||
}
|
||||
}
|
||||
|
||||
self.pollee.notify(IoEvents::IN);
|
||||
self.pollee.notify(IoEvents::IN | IoEvents::RDNORM);
|
||||
Ok(len)
|
||||
}
|
||||
|
||||
|
|
@ -160,7 +160,7 @@ impl<D: TtyDriver> Tty<D> {
|
|||
let mut events = IoEvents::empty();
|
||||
|
||||
if self.ldisc.lock().buffer_len() > 0 {
|
||||
events |= IoEvents::IN;
|
||||
events |= IoEvents::IN | IoEvents::RDNORM;
|
||||
}
|
||||
|
||||
if self.driver.can_push() {
|
||||
|
|
@ -315,12 +315,17 @@ impl<D: TtyDriver> Tty<D> {
|
|||
cmd @ SetTermios => {
|
||||
let termios = cmd.read()?;
|
||||
|
||||
self.ldisc.lock().set_termios(termios);
|
||||
let mut ldisc = self.ldisc.lock();
|
||||
let old_termios = ldisc.termios();
|
||||
self.driver().on_termios_change(old_termios, &termios);
|
||||
ldisc.set_termios(termios);
|
||||
}
|
||||
cmd @ SetTermiosDrain => {
|
||||
let termios = cmd.read()?;
|
||||
|
||||
let mut ldisc = self.ldisc.lock();
|
||||
let old_termios = ldisc.termios();
|
||||
self.driver().on_termios_change(old_termios, &termios);
|
||||
ldisc.set_termios(termios);
|
||||
self.driver.drain_output();
|
||||
}
|
||||
|
|
@ -328,6 +333,8 @@ impl<D: TtyDriver> Tty<D> {
|
|||
let termios = cmd.read()?;
|
||||
|
||||
let mut ldisc = self.ldisc.lock();
|
||||
let old_termios = ldisc.termios();
|
||||
self.driver().on_termios_change(old_termios, &termios);
|
||||
ldisc.set_termios(termios);
|
||||
ldisc.drain_input();
|
||||
self.driver.drain_output();
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use spin::Once;
|
|||
|
||||
use super::{Tty, TtyDriver};
|
||||
use crate::{
|
||||
device::registry::char,
|
||||
device::{registry::char, tty::termio::CTermios},
|
||||
events::IoEvents,
|
||||
fs::{
|
||||
inode_handle::FileIo,
|
||||
|
|
@ -61,6 +61,8 @@ impl TtyDriver for VtDriver {
|
|||
fn console(&self) -> Option<&dyn AnyConsoleDevice> {
|
||||
Some(&*self.console)
|
||||
}
|
||||
|
||||
fn on_termios_change(&self, _old_termios: &CTermios, _new_termios: &CTermios) {}
|
||||
}
|
||||
|
||||
/// The driver for hypervisor console devices.
|
||||
|
|
@ -101,6 +103,8 @@ impl TtyDriver for HvcDriver {
|
|||
fn console(&self) -> Option<&dyn AnyConsoleDevice> {
|
||||
Some(&*self.console)
|
||||
}
|
||||
|
||||
fn on_termios_change(&self, _old_termios: &CTermios, _new_termios: &CTermios) {}
|
||||
}
|
||||
|
||||
struct TtyFile<D>(Arc<Tty<D>>);
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ bitflags! {
|
|||
/// The input flags; `c_iflags` bits in Linux.
|
||||
#[derive(Pod)]
|
||||
#[repr(C)]
|
||||
pub(super) struct CInputFlags: u32 {
|
||||
pub struct CInputFlags: u32 {
|
||||
// https://elixir.bootlin.com/linux/v6.0.9/source/include/uapi/asm-generic/termbits-common.h
|
||||
const IGNBRK = 0x001; /* Ignore break condition */
|
||||
const BRKINT = 0x002; /* Signal interrupt on break */
|
||||
|
|
@ -136,7 +136,7 @@ bitflags! {
|
|||
/// The local flags; `c_lflags` bits in Linux.
|
||||
#[repr(C)]
|
||||
#[derive(Pod)]
|
||||
pub(super) struct CLocalFlags: u32 {
|
||||
pub struct CLocalFlags: u32 {
|
||||
// https://elixir.bootlin.com/linux/v6.0.9/source/include/uapi/asm-generic/termbits.h#L127
|
||||
const ISIG = 0x00001;
|
||||
const ICANON = 0x00002;
|
||||
|
|
@ -174,7 +174,7 @@ impl Default for CLocalFlags {
|
|||
#[repr(u32)]
|
||||
#[derive(Debug, Clone, Copy, TryFromInt)]
|
||||
#[expect(clippy::upper_case_acronyms)]
|
||||
pub(super) enum CCtrlCharId {
|
||||
pub enum CCtrlCharId {
|
||||
// https://elixir.bootlin.com/linux/v6.0.9/source/include/uapi/asm-generic/termbits.h#L42
|
||||
VINTR = 0,
|
||||
VQUIT = 1,
|
||||
|
|
@ -276,7 +276,7 @@ impl CTermios {
|
|||
/// Reference: <https://elixir.bootlin.com/linux/v6.0.9/source/include/uapi/asm-generic/termbits.h#L9>.
|
||||
const NUM_CTRL_CHARS: usize = 19;
|
||||
|
||||
pub(super) fn special_char(&self, id: CCtrlCharId) -> CCtrlChar {
|
||||
pub fn special_char(&self, id: CCtrlCharId) -> CCtrlChar {
|
||||
self.c_cc[id as usize]
|
||||
}
|
||||
|
||||
|
|
@ -292,27 +292,14 @@ impl CTermios {
|
|||
self.c_lflags.contains(CLocalFlags::ICANON)
|
||||
}
|
||||
|
||||
/// Returns whether the input flags contain `ICRNL`.
|
||||
///
|
||||
/// The `ICRNL` flag means the `\r` characters in the input should be mapped to `\n`.
|
||||
pub(super) fn contains_icrnl(&self) -> bool {
|
||||
self.c_iflags.contains(CInputFlags::ICRNL)
|
||||
/// Returns the input flags.
|
||||
pub fn input_flags(&self) -> &CInputFlags {
|
||||
&self.c_iflags
|
||||
}
|
||||
|
||||
pub(super) fn contains_isig(&self) -> bool {
|
||||
self.c_lflags.contains(CLocalFlags::ISIG)
|
||||
}
|
||||
|
||||
pub(super) fn contain_echo(&self) -> bool {
|
||||
self.c_lflags.contains(CLocalFlags::ECHO)
|
||||
}
|
||||
|
||||
pub(super) fn contains_echo_ctl(&self) -> bool {
|
||||
self.c_lflags.contains(CLocalFlags::ECHOCTL)
|
||||
}
|
||||
|
||||
pub(super) fn contains_iexten(&self) -> bool {
|
||||
self.c_lflags.contains(CLocalFlags::IEXTEN)
|
||||
/// Returns the local flags.
|
||||
pub fn local_flags(&self) -> &CLocalFlags {
|
||||
&self.c_lflags
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,13 +5,14 @@ use crate::prelude::*;
|
|||
|
||||
bitflags! {
|
||||
pub struct IoEvents: u32 {
|
||||
const IN = 0x0001;
|
||||
const PRI = 0x0002;
|
||||
const OUT = 0x0004;
|
||||
const ERR = 0x0008;
|
||||
const HUP = 0x0010;
|
||||
const NVAL = 0x0020;
|
||||
const RDHUP = 0x2000;
|
||||
const IN = 0x0001;
|
||||
const PRI = 0x0002;
|
||||
const OUT = 0x0004;
|
||||
const ERR = 0x0008;
|
||||
const HUP = 0x0010;
|
||||
const NVAL = 0x0020;
|
||||
const RDNORM = 0x0040;
|
||||
const RDHUP = 0x2000;
|
||||
/// Events that are always polled even without specifying them.
|
||||
const ALWAYS_POLL = Self::ERR.bits | Self::HUP.bits;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue