Index: lldb/include/lldb/Host/Terminal.h =================================================================== --- lldb/include/lldb/Host/Terminal.h +++ lldb/include/lldb/Host/Terminal.h @@ -15,6 +15,20 @@ namespace lldb_private { +enum class TerminalStopBits { + One, + OneAndAHalf, + Two, +}; + +enum class TerminalParity { + No, + Even, + Odd, + Space, + Mark, +}; + class Terminal { public: Terminal(int fd = -1) : m_fd(fd) {} @@ -35,6 +49,16 @@ bool SetCanonical(bool enabled); + bool SetRaw(); + + bool SetBaudRate(unsigned int baud_rate); + + bool SetStopBits(TerminalStopBits stop_bits); + + bool SetParity(TerminalParity parity); + + bool SetHardwareFlowControl(bool enabled); + protected: int m_fd; // This may or may not be a terminal file descriptor }; Index: lldb/source/Host/common/Terminal.cpp =================================================================== --- lldb/source/Host/common/Terminal.cpp +++ lldb/source/Host/common/Terminal.cpp @@ -81,6 +81,302 @@ return false; } +bool Terminal::SetRaw() { +#if LLDB_ENABLE_TERMIOS + if (IsATerminal()) { + struct termios fd_termios; + if (::tcgetattr(m_fd, &fd_termios) == 0) { + ::cfmakeraw(&fd_termios); + + // Make sure only one character is needed to return from a read + // (cfmakeraw() doesn't do this on NetBSD) + fd_termios.c_cc[VMIN] = 1; + fd_termios.c_cc[VTIME] = 0; + + return ::tcsetattr(m_fd, TCSANOW, &fd_termios) == 0; + } + } +#endif // #if LLDB_ENABLE_TERMIOS + return false; +} + +static llvm::Optional baudRateToConst(unsigned int baud_rate) { + switch (baud_rate) { +#if defined(B0) + case 0: + return B0; +#endif +#if defined(B50) + case 50: + return B50; +#endif +#if defined(B75) + case 75: + return B75; +#endif +#if defined(B110) + case 110: + return B110; +#endif +#if defined(B134) + case 134: + return B134; +#endif +#if defined(B150) + case 150: + return B150; +#endif +#if defined(B200) + case 200: + return B200; +#endif +#if defined(B300) + case 300: + return B300; +#endif +#if defined(B600) + case 600: + return B600; +#endif +#if defined(B1200) + case 1200: + return B1200; +#endif +#if defined(B1800) + case 1800: + return B1800; +#endif +#if defined(B2400) + case 2400: + return B2400; +#endif +#if defined(B4800) + case 4800: + return B4800; +#endif +#if defined(B9600) + case 9600: + return B9600; +#endif +#if defined(B19200) + case 19200: + return B19200; +#endif +#if defined(B38400) + case 38400: + return B38400; +#endif +#if defined(B57600) + case 57600: + return B57600; +#endif +#if defined(B115200) + case 115200: + return B115200; +#endif +#if defined(B230400) + case 230400: + return B230400; +#endif +#if defined(B460800) + case 460800: + return B460800; +#endif +#if defined(B500000) + case 500000: + return B500000; +#endif +#if defined(B576000) + case 576000: + return B576000; +#endif +#if defined(B921600) + case 921600: + return B921600; +#endif +#if defined(B1000000) + case 1000000: + return B1000000; +#endif +#if defined(B1152000) + case 1152000: + return B1152000; +#endif +#if defined(B1500000) + case 1500000: + return B1500000; +#endif +#if defined(B2000000) + case 2000000: + return B2000000; +#endif +#if defined(B76800) + case 76800: + return B76800; +#endif +#if defined(B153600) + case 153600: + return B153600; +#endif +#if defined(B307200) + case 307200: + return B307200; +#endif +#if defined(B614400) + case 614400: + return B614400; +#endif +#if defined(B2500000) + case 2500000: + return B2500000; +#endif +#if defined(B3000000) + case 3000000: + return B3000000; +#endif +#if defined(B3500000) + case 3500000: + return B3500000; +#endif +#if defined(B4000000) + case 4000000: + return B4000000; +#endif + default: + return llvm::None; + } +} + +bool Terminal::SetBaudRate(unsigned int baud_rate) { +#if LLDB_ENABLE_TERMIOS + if (IsATerminal()) { + struct termios fd_termios; + if (::tcgetattr(m_fd, &fd_termios) == 0) { + llvm::Optional val = baudRateToConst(baud_rate); + if (!val) // invalid value + return false; + if (::cfsetispeed(&fd_termios, val.getValue()) != 0) + return false; + if (::cfsetospeed(&fd_termios, val.getValue()) != 0) + return false; + return ::tcsetattr(m_fd, TCSANOW, &fd_termios) == 0; + } + } +#endif // #if LLDB_ENABLE_TERMIOS + return false; +} + +bool Terminal::SetStopBits(TerminalStopBits stop_bits) { +#if LLDB_ENABLE_TERMIOS + if (IsATerminal()) { + struct termios fd_termios; + if (::tcgetattr(m_fd, &fd_termios) == 0) { + switch (stop_bits) { + case TerminalStopBits::One: + fd_termios.c_cflag &= ~CSTOPB; + break; + case TerminalStopBits::OneAndAHalf: + // 1.5 stop bits are not supported by POSIX, round it up to 2 + case TerminalStopBits::Two: + fd_termios.c_cflag |= CSTOPB; + break; + } + return ::tcsetattr(m_fd, TCSANOW, &fd_termios) == 0; + } + } +#endif // #if LLDB_ENABLE_TERMIOS + return false; +} + +bool Terminal::SetParity(TerminalParity parity) { +#if LLDB_ENABLE_TERMIOS + if (IsATerminal()) { + struct termios fd_termios; + if (::tcgetattr(m_fd, &fd_termios) == 0) { + bool parity_bit = true; + bool odd_bit = false; + bool stick_bit = false; + + switch (parity) { + case TerminalParity::No: + parity_bit = false; + break; + case TerminalParity::Even: + break; + case TerminalParity::Odd: + odd_bit = true; + break; + case TerminalParity::Space: + stick_bit = true; + break; + case TerminalParity::Mark: + stick_bit = true; + odd_bit = true; + break; + } + + printf("before: %08x\n", fd_termios.c_cflag); + printf("PARENB %08x\n", PARENB); + if (!parity_bit) + fd_termios.c_cflag &= ~PARENB; + else { + fd_termios.c_cflag |= PARENB; + if (odd_bit) + fd_termios.c_cflag |= PARODD; + else + fd_termios.c_cflag &= ~PARODD; +#if defined(CMSPAR) + if (stick_bit) + fd_termios.c_cflag |= CMSPAR; + else + fd_termios.c_cflag &= ~CMSPAR; +#else + if (stick_bit) + return false; +#endif + } + printf("after: %08x\n", fd_termios.c_cflag); + + return ::tcsetattr(m_fd, TCSANOW, &fd_termios) == 0; + } + } +#endif // #if LLDB_ENABLE_TERMIOS + return false; +} + +bool Terminal::SetHardwareFlowControl(bool enabled) { + if (FileDescriptorIsValid()) { +#if LLDB_ENABLE_TERMIOS + if (IsATerminal()) { +#if defined(CRTSCTS) + struct termios fd_termios; + if (::tcgetattr(m_fd, &fd_termios) == 0) { + bool set_corectly = false; + if (enabled) { + if (fd_termios.c_cflag & CRTSCTS) + set_corectly = true; + else + fd_termios.c_cflag |= CRTSCTS; + } else { + if (fd_termios.c_cflag & CRTSCTS) + fd_termios.c_cflag &= ~CRTSCTS; + else + set_corectly = true; + } + + if (set_corectly) + return true; + return ::tcsetattr(m_fd, TCSANOW, &fd_termios) == 0; + } +#else // !defined(CRTSCTS) + return !enabled; +#endif + } +#endif // #if LLDB_ENABLE_TERMIOS + } + return false; +} + struct TerminalState::Data { #if LLDB_ENABLE_TERMIOS struct termios m_termios; ///< Cached terminal state information. Index: lldb/source/Host/posix/ConnectionFileDescriptorPosix.cpp =================================================================== --- lldb/source/Host/posix/ConnectionFileDescriptorPosix.cpp +++ lldb/source/Host/posix/ConnectionFileDescriptorPosix.cpp @@ -250,22 +250,8 @@ Terminal term{fd}; if (term.IsATerminal()) { m_saved_term_state.Save(term, false); - // Set up serial terminal emulation - struct termios options; - ::tcgetattr(fd, &options); - - // Set port speed to maximum - ::cfsetospeed(&options, B115200); - ::cfsetispeed(&options, B115200); - - // Raw input, disable echo and signals - options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); - - // Make sure only one character is needed to return from a read - options.c_cc[VMIN] = 1; - options.c_cc[VTIME] = 0; - - llvm::sys::RetryAfterSignal(-1, ::tcsetattr, fd, TCSANOW, &options); + term.SetRaw(); + term.SetBaudRate(115200); } int flags = ::fcntl(fd, F_GETFL, 0); Index: lldb/unittests/Host/TerminalTest.cpp =================================================================== --- lldb/unittests/Host/TerminalTest.cpp +++ lldb/unittests/Host/TerminalTest.cpp @@ -78,6 +78,134 @@ EXPECT_EQ(terminfo.c_lflag & ICANON, 0U); } +TEST_F(TerminalTest, SetRaw) { + struct termios terminfo; + Terminal term{m_slave}; + + ASSERT_EQ(term.SetRaw(), true); + ASSERT_EQ(tcgetattr(m_slave, &terminfo), 0); + // NB: cfmakeraw() on glibc disables IGNBRK, on FreeBSD sets it + EXPECT_EQ(terminfo.c_iflag & + (BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON), + 0U); + EXPECT_EQ(terminfo.c_oflag & OPOST, 0U); + EXPECT_EQ(terminfo.c_lflag & (ICANON | ECHO | ISIG | IEXTEN), 0U); + EXPECT_EQ(terminfo.c_cflag & (CSIZE | PARENB), 0U | CS8); + EXPECT_EQ(terminfo.c_cc[VMIN], 1); + EXPECT_EQ(terminfo.c_cc[VTIME], 0); +} + +TEST_F(TerminalTest, SetBaudRate) { + struct termios terminfo; + Terminal term{m_slave}; + + ASSERT_EQ(term.SetBaudRate(38400), true); + ASSERT_EQ(tcgetattr(m_slave, &terminfo), 0); + EXPECT_EQ(cfgetispeed(&terminfo), static_cast(B38400)); + EXPECT_EQ(cfgetospeed(&terminfo), static_cast(B38400)); + + ASSERT_EQ(term.SetBaudRate(115200), true); + ASSERT_EQ(tcgetattr(m_slave, &terminfo), 0); + EXPECT_EQ(cfgetispeed(&terminfo), static_cast(B115200)); + EXPECT_EQ(cfgetospeed(&terminfo), static_cast(B115200)); + + // uncommon value +#if defined(B153600) + ASSERT_EQ(term.SetBaudRate(153600), true); + ASSERT_EQ(tcgetattr(m_slave, &terminfo), 0); + EXPECT_EQ(cfgetispeed(&terminfo), static_cast(B153600)); + EXPECT_EQ(cfgetospeed(&terminfo), static_cast(B153600)); +#else + EXPECT_EQ(term.SetBaudRate(153600), false); +#endif +} + +TEST_F(TerminalTest, SetStopBits) { + struct termios terminfo; + Terminal term{m_slave}; + + ASSERT_EQ(term.SetStopBits(TerminalStopBits::One), true); + ASSERT_EQ(tcgetattr(m_slave, &terminfo), 0); + EXPECT_EQ(terminfo.c_cflag & CSTOPB, 0U); + + ASSERT_EQ(term.SetStopBits(TerminalStopBits::OneAndAHalf), true); + ASSERT_EQ(tcgetattr(m_slave, &terminfo), 0); + EXPECT_NE(terminfo.c_cflag & CSTOPB, 0U); + + ASSERT_EQ(term.SetStopBits(TerminalStopBits::Two), true); + ASSERT_EQ(tcgetattr(m_slave, &terminfo), 0); + EXPECT_NE(terminfo.c_cflag & CSTOPB, 0U); +} + +TEST_F(TerminalTest, SetParity) { + struct termios terminfo; + Terminal term{m_slave}; + + ASSERT_EQ(term.SetParity(TerminalParity::No), true); + ASSERT_EQ(tcgetattr(m_slave, &terminfo), 0); + EXPECT_EQ(terminfo.c_cflag & PARENB, 0U); + + ASSERT_EQ(term.SetParity(TerminalParity::Even), true); + ASSERT_EQ(tcgetattr(m_slave, &terminfo), 0); + printf("cflag: %08x\n", terminfo.c_cflag); +#if !defined(__linux__) // Linux pty devices strip PARENB + EXPECT_NE(terminfo.c_cflag & PARENB, 0U); +#endif + EXPECT_EQ(terminfo.c_cflag & PARODD, 0U); +#if defined(CMSPAR) + EXPECT_EQ(terminfo.c_cflag & CMSPAR, 0U); +#endif + + ASSERT_EQ(term.SetParity(TerminalParity::Odd), true); + ASSERT_EQ(tcgetattr(m_slave, &terminfo), 0); +#if !defined(__linux__) // Linux pty devices strip PARENB + EXPECT_NE(terminfo.c_cflag & PARENB, 0U); +#endif + EXPECT_NE(terminfo.c_cflag & PARODD, 0U); +#if defined(CMSPAR) + EXPECT_EQ(terminfo.c_cflag & CMSPAR, 0U); +#endif + +#if defined(CMSPAR) + ASSERT_EQ(term.SetParity(TerminalParity::Space), true); + ASSERT_EQ(tcgetattr(m_slave, &terminfo), 0); +#if !defined(__linux__) // Linux pty devices strip PARENB + EXPECT_NE(terminfo.c_cflag & PARENB, 0U); +#endif + EXPECT_EQ(terminfo.c_cflag & PARODD, 0U); + EXPECT_NE(terminfo.c_cflag & CMSPAR, 0U); + + ASSERT_EQ(term.SetParity(TerminalParity::Mark), true); + ASSERT_EQ(tcgetattr(m_slave, &terminfo), 0); +#if !defined(__linux__) // Linux pty devices strip PARENB + EXPECT_NE(terminfo.c_cflag & PARENB, 0U); +#endif + EXPECT_NE(terminfo.c_cflag & PARODD, 0U); + EXPECT_NE(terminfo.c_cflag & CMSPAR, 0U); +#else + EXPECT_EQ(term.SetParity(TerminalParity::Space), false); + EXPECT_EQ(term.SetParity(TerminalParity::Mark), false); +#endif +} + +TEST_F(TerminalTest, SetHardwareFlowControl) { + struct termios terminfo; + Terminal term{m_slave}; + +#if defined(CRTSCTS) + ASSERT_EQ(term.SetHardwareFlowControl(true), true); + ASSERT_EQ(tcgetattr(m_slave, &terminfo), 0); + EXPECT_NE(terminfo.c_cflag & CRTSCTS, 0U); + + ASSERT_EQ(term.SetHardwareFlowControl(false), true); + ASSERT_EQ(tcgetattr(m_slave, &terminfo), 0); + EXPECT_EQ(terminfo.c_cflag & CRTSCTS, 0U); +#else + ASSERT_EQ(term.SetHardwareFlowControl(true), false); + ASSERT_EQ(term.SetHardwareFlowControl(false), true); +#endif +} + TEST_F(TerminalTest, SaveRestoreRAII) { struct termios orig_terminfo; struct termios terminfo;