Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 20 additions & 17 deletions kernel/src/driver/tty/termios.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,13 @@ impl PosixTermios {
}

pub fn to_kernel_termios(self) -> Termios {
// TODO:这里没有考虑非规范模式
// Linux copies the raw flag words, including unnamed bits. Preserve
// them at the ABI boundary; drivers normalize hardware settings.
Termios {
input_mode: InputMode::from_bits_truncate(self.c_iflag),
output_mode: OutputMode::from_bits_truncate(self.c_oflag),
control_mode: ControlMode::from_bits_truncate(self.c_cflag),
local_mode: LocalMode::from_bits_truncate(self.c_lflag),
input_mode: InputMode { bits: self.c_iflag },
output_mode: OutputMode { bits: self.c_oflag },
control_mode: ControlMode { bits: self.c_cflag },
local_mode: LocalMode { bits: self.c_lflag },
control_characters: self.c_cc,
line: LineDisciplineType::from_line(self.c_line),
c_line_abi: self.c_line,
Expand Down Expand Up @@ -472,22 +473,24 @@ impl PosixTermio {
let mut cc = old.control_characters;
cc[..NCC].copy_from_slice(&self.c_cc);

let control_mode = ControlMode::from_bits_truncate(
(old.control_mode.bits & 0xffff_0000) | self.c_cflag as u32,
);
// As with termios, neither half of the merged ABI flags may be
// truncated to the set of bits currently named by the kernel.
let control_mode = ControlMode {
bits: (old.control_mode.bits & 0xffff_0000) | self.c_cflag as u32,
};
let (input_speed, output_speed) = Self::speeds_from_cflag(control_mode);

Termios {
input_mode: InputMode::from_bits_truncate(
(old.input_mode.bits & 0xffff_0000) | self.c_iflag as u32,
),
output_mode: OutputMode::from_bits_truncate(
(old.output_mode.bits & 0xffff_0000) | self.c_oflag as u32,
),
input_mode: InputMode {
bits: (old.input_mode.bits & 0xffff_0000) | self.c_iflag as u32,
},
output_mode: OutputMode {
bits: (old.output_mode.bits & 0xffff_0000) | self.c_oflag as u32,
},
control_mode,
local_mode: LocalMode::from_bits_truncate(
(old.local_mode.bits & 0xffff_0000) | self.c_lflag as u32,
),
local_mode: LocalMode {
bits: (old.local_mode.bits & 0xffff_0000) | self.c_lflag as u32,
},
control_characters: cc,
line: LineDisciplineType::from_line(self.c_line),
c_line_abi: self.c_line,
Expand Down
7 changes: 5 additions & 2 deletions kernel/src/driver/tty/tty_core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -651,8 +651,11 @@ impl TtyCore {
let mut termios = tty.core().termios_write();
if ret.is_err() {
termios.control_mode &= ControlMode::HUPCL | ControlMode::CREAD | ControlMode::CLOCAL;
termios.control_mode |= old_termios.control_mode
& !(ControlMode::HUPCL | ControlMode::CREAD | ControlMode::CLOCAL);
// bitflags' !mask truncates unnamed bits. Linux preserves all
// old hardware bits outside this software-controlled mask.
termios.control_mode |= old_termios
.control_mode
.difference(ControlMode::HUPCL | ControlMode::CREAD | ControlMode::CLOCAL);
termios.input_speed = old_termios.input_speed;
termios.output_speed = old_termios.output_speed;
}
Expand Down
169 changes: 169 additions & 0 deletions user/apps/tests/dunitest/suites/normal/tty_termios.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <stdint.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <termios.h>
#include <time.h>
#include <unistd.h>
Expand All @@ -33,6 +34,8 @@ struct TermioCompat {
unsigned char _pad = 0; /* match kernel PosixTermio layout */
};

static_assert(sizeof(TermioCompat) == 18, "Linux generic termio ABI");

#ifndef TCGETA
#define TCGETA 0x5405
#endif
Expand All @@ -46,6 +49,28 @@ struct TermioCompat {
#define TCSETAF 0x5408
#endif

// TCGETS/TCSETS use 19 control characters, even when libc NCCS is larger.
constexpr size_t kKernelNccs = 19;
constexpr int kSetCommands[] = {TCSETS, TCSETSW, TCSETSF, TCSETA, TCSETAW, TCSETAF};

bool IsLegacySet(int cmd) {
return cmd == TCSETA || cmd == TCSETAW || cmd == TCSETAF;
}

int SetAttributes(int fd, int cmd, const struct termios& term) {
if (!IsLegacySet(cmd)) {
return ioctl(fd, cmd, &term);
}
TermioCompat legacy = {};
legacy.c_iflag = term.c_iflag;
legacy.c_oflag = term.c_oflag;
legacy.c_cflag = term.c_cflag;
legacy.c_lflag = term.c_lflag;
legacy.c_line = term.c_line;
memcpy(legacy.c_cc, term.c_cc, kNcc);
return ioctl(fd, cmd, &legacy);
}

class UniqueFd {
public:
UniqueFd() = default;
Expand Down Expand Up @@ -802,6 +827,150 @@ TEST(TtyTermios, TermioMergeHighBits) {
<< "TCSETA should apply low-16-bit change";
}

TEST(TtyTermios, SetFamilyPreservesRawFlagsAndLegacyTail) {
// Unnamed bits must survive the ABI boundary. Avoid flags normalized by
// the PTY driver and the core (CSIZE, PARENB, CREAD, baud masks and ADDRB).
constexpr tcflag_t high = 0x80000000u;
constexpr tcflag_t unnamed_input_low = 0x8000u;
for (int cmd : kSetCommands) {
SCOPED_TRACE(cmd);
auto pty = OpenRawPty();
ASSERT_GE(pty.slave.get(), 0);
struct termios initial = {};
ASSERT_EQ(tcgetattr(pty.slave.get(), &initial), 0);
initial.c_iflag |= high;
initial.c_oflag |= high;
initial.c_cflag |= high;
initial.c_lflag |= high;
for (size_t i = kNcc; i < kKernelNccs; ++i) {
initial.c_cc[i] = static_cast<cc_t>(0x60 + i);
}
// Exercise the kernel ABI directly: libc may reserve flag bits for
// its own speed bookkeeping before issuing TCSETS.
ASSERT_EQ(ioctl(pty.slave.get(), TCSETS, &initial), 0);

struct termios requested = initial;
requested.c_iflag |= unnamed_input_low;
requested.c_lflag |= ECHONL;
requested.c_cc[VINTR] = 0x1c;
ASSERT_EQ(SetAttributes(pty.slave.get(), cmd, requested), 0) << strerror(errno);
struct termios back = {};
ASSERT_EQ(tcgetattr(pty.slave.get(), &back), 0);
EXPECT_EQ(back.c_iflag & (high | unnamed_input_low), high | unnamed_input_low);
EXPECT_EQ(back.c_oflag & high, high);
EXPECT_EQ(back.c_cflag & high, high);
EXPECT_EQ(back.c_lflag & (high | ECHONL), high | ECHONL);
EXPECT_EQ(back.c_cc[VINTR], requested.c_cc[VINTR]);
for (size_t i = kNcc; i < kKernelNccs; ++i) {
EXPECT_EQ(back.c_cc[i], initial.c_cc[i]) << "c_cc[" << i << "]";
}
}
}

TEST(TtyTermios, SetFamilyFlushesOnlyForFlushCommands) {
for (int cmd : kSetCommands) {
SCOPED_TRACE(cmd);
auto pty = OpenRawPty();
ASSERT_GE(pty.slave.get(), 0);
ASSERT_TRUE(SetNonBlocking(pty.slave.get()));
struct termios term = {};
ASSERT_EQ(tcgetattr(pty.slave.get(), &term), 0);
const char old_input = 'a';
ASSERT_EQ(write(pty.master.get(), &old_input, 1), 1);
struct pollfd ready = {pty.slave.get(), POLLIN, 0};
ASSERT_EQ(poll(&ready, 1, 1000), 1);
ASSERT_NE(ready.revents & POLLIN, 0);

ASSERT_EQ(SetAttributes(pty.slave.get(), cmd, term), 0) << strerror(errno);
char ch = 0;
if (cmd == TCSETSF || cmd == TCSETAF) {
errno = 0;
EXPECT_EQ(read(pty.slave.get(), &ch, 1), -1);
EXPECT_TRUE(errno == EAGAIN || errno == EWOULDBLOCK) << strerror(errno);
} else {
ASSERT_EQ(read(pty.slave.get(), &ch, 1), 1);
EXPECT_EQ(ch, old_input);
}
const char new_input = 'b';
ASSERT_EQ(write(pty.master.get(), &new_input, 1), 1);
ASSERT_EQ(ReadEventually(pty.slave.get(), &ch, 1), 1);
EXPECT_EQ(ch, new_input);
}
}

TEST(TtyTermios, BadSetPointerDoesNotChangeAttributesOrFlushInput) {
for (int cmd : kSetCommands) {
SCOPED_TRACE(cmd);
auto pty = OpenRawPty();
ASSERT_GE(pty.slave.get(), 0);
struct termios before = {};
ASSERT_EQ(tcgetattr(pty.slave.get(), &before), 0);
const char marker = 'x';
ASSERT_EQ(write(pty.master.get(), &marker, 1), 1);
struct pollfd ready = {pty.slave.get(), POLLIN, 0};
ASSERT_EQ(poll(&ready, 1, 1000), 1);
ASSERT_NE(ready.revents & POLLIN, 0);
errno = 0;
EXPECT_EQ(ioctl(pty.slave.get(), cmd, nullptr), -1);
EXPECT_EQ(errno, EFAULT);
struct termios after = {};
ASSERT_EQ(tcgetattr(pty.slave.get(), &after), 0);
EXPECT_EQ(after.c_iflag, before.c_iflag);
EXPECT_EQ(after.c_oflag, before.c_oflag);
EXPECT_EQ(after.c_cflag, before.c_cflag);
EXPECT_EQ(after.c_lflag, before.c_lflag);
EXPECT_EQ(after.c_line, before.c_line);
EXPECT_EQ(memcmp(after.c_cc, before.c_cc, kKernelNccs), 0);
char ch = 0;
ASSERT_EQ(ReadEventually(pty.slave.get(), &ch, 1), 1);
EXPECT_EQ(ch, marker);
}
}

TEST(TtyTermios, LegacyAbiAtPageBoundaryAndZeroPadding) {
auto pty = OpenRawPty();
ASSERT_GE(pty.slave.get(), 0);
const long page_size = sysconf(_SC_PAGESIZE);
ASSERT_GT(page_size, static_cast<long>(sizeof(TermioCompat)));
void* mapping = mmap(nullptr, 2 * page_size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
ASSERT_NE(mapping, MAP_FAILED);
// Release the mapping even if a fatal assertion below returns early.
struct UnmapOnExit {
void* addr;
size_t size;
~UnmapOnExit() { munmap(addr, size); }
} unmap{mapping, static_cast<size_t>(2 * page_size)};
ASSERT_EQ(mprotect(static_cast<char*>(mapping) + page_size, page_size, PROT_NONE), 0);
auto* legacy = reinterpret_cast<TermioCompat*>(
static_cast<char*>(mapping) + page_size - sizeof(TermioCompat));
for (int cmd : {TCSETA, TCSETAW, TCSETAF}) {
SCOPED_TRACE(cmd);
memset(legacy, 0xa5, sizeof(*legacy));
ASSERT_EQ(ioctl(pty.slave.get(), TCGETA, legacy), 0) << strerror(errno);
EXPECT_EQ(legacy->_pad, 0);
legacy->_pad = 0xa5;
legacy->c_line = 42;
ASSERT_EQ(ioctl(pty.slave.get(), cmd, legacy), 0) << strerror(errno);
ASSERT_EQ(ioctl(pty.slave.get(), TCGETA, legacy), 0);
EXPECT_EQ(legacy->c_line, 42);
EXPECT_EQ(legacy->_pad, 0);
}
}

TEST(TtyTermios, StdinFlushAndLegacySettings) {
if (!isatty(STDIN_FILENO)) {
GTEST_SKIP() << "stdin is not a TTY; run from the QEMU console for acceptance";
}
struct termios original = {};
ASSERT_EQ(tcgetattr(STDIN_FILENO, &original), 0) << strerror(errno);
TermiosRestorer restore(STDIN_FILENO, original);
ASSERT_EQ(tcsetattr(STDIN_FILENO, TCSAFLUSH, &original), 0) << strerror(errno);
for (int cmd : {TCSETA, TCSETAW, TCSETAF}) {
EXPECT_EQ(SetAttributes(STDIN_FILENO, cmd, original), 0) << cmd << ": " << strerror(errno);
}
}

/* --------------------------------------------------------------------------
* Serial8250 termios must reach the UART hardware callback. Before the
* regression fix the default ENOSYS callback made the TTY core restore all
Expand Down
Loading