[Lldb-commits] [lldb] 4a7b4be - [lldb] Add serial:// protocol for connecting to serial port

Michał Górny via lldb-commits lldb-commits at lists.llvm.org
Thu Oct 21 01:46:56 PDT 2021


Author: Michał Górny
Date: 2021-10-21T10:46:45+02:00
New Revision: 4a7b4beac759ad9001671a61846ee2bfc9076eec

URL: https://github.com/llvm/llvm-project/commit/4a7b4beac759ad9001671a61846ee2bfc9076eec
DIFF: https://github.com/llvm/llvm-project/commit/4a7b4beac759ad9001671a61846ee2bfc9076eec.diff

LOG: [lldb] Add serial:// protocol for connecting to serial port

Add a new serial:// protocol along with SerialPort that provides a new
API to open serial ports.  The URL consists of serial device path
followed by URL-style options, e.g.:

    serial:///dev/ttyS0?baud=115200&parity=even

If no options are provided, the serial port is only set to raw mode
and the other attributes remain unchanged.  Attributes provided via
options are modified to the specified values.  Upon closing the serial
port, its original attributes are restored.

Differential Revision: https://reviews.llvm.org/D111355

Added: 
    

Modified: 
    lldb/include/lldb/Host/File.h
    lldb/include/lldb/Host/posix/ConnectionFileDescriptorPosix.h
    lldb/source/Host/common/File.cpp
    lldb/source/Host/posix/ConnectionFileDescriptorPosix.cpp
    lldb/test/API/functionalities/gdb_remote_client/TestPty.py
    lldb/test/API/functionalities/gdb_remote_client/gdbclientutils.py

Removed: 
    


################################################################################
diff  --git a/lldb/include/lldb/Host/File.h b/lldb/include/lldb/Host/File.h
index b0bc7d9535f6c..c192700590a30 100644
--- a/lldb/include/lldb/Host/File.h
+++ b/lldb/include/lldb/Host/File.h
@@ -10,6 +10,7 @@
 #define LLDB_HOST_FILE_H
 
 #include "lldb/Host/PosixApi.h"
+#include "lldb/Host/Terminal.h"
 #include "lldb/Utility/IOObject.h"
 #include "lldb/Utility/Status.h"
 #include "lldb/lldb-private.h"
@@ -433,6 +434,44 @@ class NativeFile : public File {
   const NativeFile &operator=(const NativeFile &) = delete;
 };
 
+class SerialPort : public NativeFile {
+public:
+  struct Options {
+    llvm::Optional<unsigned int> BaudRate = llvm::None;
+    llvm::Optional<Terminal::Parity> Parity = llvm::None;
+    llvm::Optional<unsigned int> StopBits = llvm::None;
+  };
+
+  // Obtain Options corresponding to the passed URL query string
+  // (i.e. the part after '?').
+  static llvm::Expected<Options> OptionsFromURL(llvm::StringRef urlqs);
+
+  static llvm::Expected<std::unique_ptr<SerialPort>>
+  Create(int fd, OpenOptions options, Options serial_options,
+         bool transfer_ownership);
+
+  bool IsValid() const override {
+    return NativeFile::IsValid() && m_is_interactive == eLazyBoolYes;
+  }
+
+  Status Close() override;
+
+  static char ID;
+  virtual bool isA(const void *classID) const override {
+    return classID == &ID || File::isA(classID);
+  }
+  static bool classof(const File *file) { return file->isA(&ID); }
+
+private:
+  SerialPort(int fd, OpenOptions options, Options serial_options,
+             bool transfer_ownership);
+
+  SerialPort(const SerialPort &) = delete;
+  const SerialPort &operator=(const SerialPort &) = delete;
+
+  TerminalState m_state;
+};
+
 } // namespace lldb_private
 
 #endif // LLDB_HOST_FILE_H

diff  --git a/lldb/include/lldb/Host/posix/ConnectionFileDescriptorPosix.h b/lldb/include/lldb/Host/posix/ConnectionFileDescriptorPosix.h
index 720c019c5a019..7500ec63707b1 100644
--- a/lldb/include/lldb/Host/posix/ConnectionFileDescriptorPosix.h
+++ b/lldb/include/lldb/Host/posix/ConnectionFileDescriptorPosix.h
@@ -88,6 +88,9 @@ class ConnectionFileDescriptor : public Connection {
 
   lldb::ConnectionStatus ConnectFile(llvm::StringRef args, Status *error_ptr);
 
+  lldb::ConnectionStatus ConnectSerialPort(llvm::StringRef args,
+                                           Status *error_ptr);
+
   lldb::IOObjectSP m_io_sp;
 
   Predicate<uint16_t>

diff  --git a/lldb/source/Host/common/File.cpp b/lldb/source/Host/common/File.cpp
index 87ed405c4428a..68543535687a7 100644
--- a/lldb/source/Host/common/File.cpp
+++ b/lldb/source/Host/common/File.cpp
@@ -769,5 +769,87 @@ mode_t File::ConvertOpenOptionsForPOSIXOpen(OpenOptions open_options) {
   return mode;
 }
 
+llvm::Expected<SerialPort::Options>
+SerialPort::OptionsFromURL(llvm::StringRef urlqs) {
+  SerialPort::Options serial_options;
+  for (llvm::StringRef x : llvm::Split(urlqs, '&')) {
+    if (x.consume_front("baud=")) {
+      unsigned int baud_rate;
+      if (!llvm::to_integer(x, baud_rate, 10))
+        return llvm::createStringError(llvm::inconvertibleErrorCode(),
+                                       "Invalid baud rate: %s",
+                                       x.str().c_str());
+      serial_options.BaudRate = baud_rate;
+    } else if (x.consume_front("parity=")) {
+      serial_options.Parity =
+          llvm::StringSwitch<llvm::Optional<Terminal::Parity>>(x)
+              .Case("no", Terminal::Parity::No)
+              .Case("even", Terminal::Parity::Even)
+              .Case("odd", Terminal::Parity::Odd)
+              .Case("mark", Terminal::Parity::Mark)
+              .Case("space", Terminal::Parity::Space)
+              .Default(llvm::None);
+      if (!serial_options.Parity)
+        return llvm::createStringError(
+            llvm::inconvertibleErrorCode(),
+            "Invalid parity (must be no, even, odd, mark or space): %s",
+            x.str().c_str());
+    } else if (x.consume_front("stop-bits=")) {
+      unsigned int stop_bits;
+      if (!llvm::to_integer(x, stop_bits, 10) ||
+          (stop_bits != 1 && stop_bits != 2))
+        return llvm::createStringError(
+            llvm::inconvertibleErrorCode(),
+            "Invalid stop bit number (must be 1 or 2): %s", x.str().c_str());
+      serial_options.StopBits = stop_bits;
+    } else
+      return llvm::createStringError(llvm::inconvertibleErrorCode(),
+                                     "Unknown parameter: %s", x.str().c_str());
+  }
+  return serial_options;
+}
+
+llvm::Expected<std::unique_ptr<SerialPort>>
+SerialPort::Create(int fd, OpenOptions options, Options serial_options,
+                   bool transfer_ownership) {
+  std::unique_ptr<SerialPort> out{
+      new SerialPort(fd, options, serial_options, transfer_ownership)};
+
+  if (!out->GetIsInteractive())
+    return llvm::createStringError(llvm::inconvertibleErrorCode(),
+                                   "the specified file is not a teletype");
+
+  Terminal term{fd};
+  if (llvm::Error error = term.SetRaw())
+    return error;
+  if (serial_options.BaudRate) {
+    if (llvm::Error error =
+            term.SetBaudRate(serial_options.BaudRate.getValue()))
+      return error;
+  }
+  if (serial_options.Parity) {
+    if (llvm::Error error = term.SetParity(serial_options.Parity.getValue()))
+      return error;
+  }
+  if (serial_options.StopBits) {
+    if (llvm::Error error =
+            term.SetStopBits(serial_options.StopBits.getValue()))
+      return error;
+  }
+
+  return out;
+}
+
+SerialPort::SerialPort(int fd, OpenOptions options,
+                       SerialPort::Options serial_options,
+                       bool transfer_ownership)
+    : NativeFile(fd, options, transfer_ownership), m_state(fd) {}
+
+Status SerialPort::Close() {
+  m_state.Restore();
+  return NativeFile::Close();
+}
+
 char File::ID = 0;
 char NativeFile::ID = 0;
+char SerialPort::ID = 0;

diff  --git a/lldb/source/Host/posix/ConnectionFileDescriptorPosix.cpp b/lldb/source/Host/posix/ConnectionFileDescriptorPosix.cpp
index 8d17ae0313217..e333394f0ea79 100644
--- a/lldb/source/Host/posix/ConnectionFileDescriptorPosix.cpp
+++ b/lldb/source/Host/posix/ConnectionFileDescriptorPosix.cpp
@@ -156,6 +156,7 @@ ConnectionStatus ConnectionFileDescriptor::Connect(llvm::StringRef path,
 #if LLDB_ENABLE_POSIX
             .Case("fd", &ConnectionFileDescriptor::ConnectFD)
             .Case("file", &ConnectionFileDescriptor::ConnectFile)
+            .Case("serial", &ConnectionFileDescriptor::ConnectSerialPort)
 #endif
             .Default(nullptr);
 
@@ -735,6 +736,55 @@ ConnectionStatus ConnectionFileDescriptor::ConnectFile(llvm::StringRef s,
   llvm_unreachable("this function should be only called w/ LLDB_ENABLE_POSIX");
 }
 
+ConnectionStatus
+ConnectionFileDescriptor::ConnectSerialPort(llvm::StringRef s,
+                                            Status *error_ptr) {
+#if LLDB_ENABLE_POSIX
+  llvm::StringRef path, qs;
+  // serial:///PATH?k1=v1&k2=v2...
+  std::tie(path, qs) = s.split('?');
+
+  llvm::Expected<SerialPort::Options> serial_options =
+      SerialPort::OptionsFromURL(qs);
+  if (!serial_options) {
+    if (error_ptr)
+      *error_ptr = serial_options.takeError();
+    else
+      llvm::consumeError(serial_options.takeError());
+    return eConnectionStatusError;
+  }
+
+  int fd = llvm::sys::RetryAfterSignal(-1, ::open, path.str().c_str(), O_RDWR);
+  if (fd == -1) {
+    if (error_ptr)
+      error_ptr->SetErrorToErrno();
+    return eConnectionStatusError;
+  }
+
+  int flags = ::fcntl(fd, F_GETFL, 0);
+  if (flags >= 0) {
+    if ((flags & O_NONBLOCK) == 0) {
+      flags |= O_NONBLOCK;
+      ::fcntl(fd, F_SETFL, flags);
+    }
+  }
+
+  llvm::Expected<std::unique_ptr<SerialPort>> serial_sp = SerialPort::Create(
+      fd, File::eOpenOptionReadWrite, serial_options.get(), true);
+  if (!serial_sp) {
+    if (error_ptr)
+      *error_ptr = serial_sp.takeError();
+    else
+      llvm::consumeError(serial_sp.takeError());
+    return eConnectionStatusError;
+  }
+  m_io_sp = std::move(serial_sp.get());
+
+  return eConnectionStatusSuccess;
+#endif // LLDB_ENABLE_POSIX
+  llvm_unreachable("this function should be only called w/ LLDB_ENABLE_POSIX");
+}
+
 uint16_t
 ConnectionFileDescriptor::GetListeningPort(const Timeout<std::micro> &timeout) {
   auto Result = m_port_predicate.WaitForValueNotEqualTo(0, timeout);

diff  --git a/lldb/test/API/functionalities/gdb_remote_client/TestPty.py b/lldb/test/API/functionalities/gdb_remote_client/TestPty.py
index 55935d901c665..c7ddebd892b95 100644
--- a/lldb/test/API/functionalities/gdb_remote_client/TestPty.py
+++ b/lldb/test/API/functionalities/gdb_remote_client/TestPty.py
@@ -9,6 +9,38 @@ class TestPty(GDBRemoteTestBase):
     mydir = TestBase.compute_mydir(__file__)
     server_socket_class = PtyServerSocket
 
+    def get_term_attrs(self):
+        import termios
+        return termios.tcgetattr(self.server._socket._slave)
+
+    def setUp(self):
+        super().setUp()
+        self.orig_attr = self.get_term_attrs()
+
+    def assert_raw_mode(self, current_attr):
+        import termios
+        self.assertEqual(current_attr[0] & (termios.BRKINT |
+                                            termios.PARMRK |
+                                            termios.ISTRIP | termios.INLCR |
+                                            termios.IGNCR | termios.ICRNL |
+                                            termios.IXON),
+                         0)
+        self.assertEqual(current_attr[1] & termios.OPOST, 0)
+        self.assertEqual(current_attr[2] & termios.CSIZE, termios.CS8)
+        self.assertEqual(current_attr[3] & (termios.ICANON | termios.ECHO |
+                                            termios.ISIG | termios.IEXTEN),
+                         0)
+        self.assertEqual(current_attr[6][termios.VMIN], 1)
+        self.assertEqual(current_attr[6][termios.VTIME], 0)
+
+    def get_parity_flags(self, attr):
+        import termios
+        return attr[2] & (termios.PARENB | termios.PARODD)
+
+    def get_stop_bit_flags(self, attr):
+        import termios
+        return attr[2] & termios.CSTOPB
+
     def test_process_connect_sync(self):
         """Test the process connect command in synchronous mode"""
         try:
@@ -17,8 +49,20 @@ def test_process_connect_sync(self):
                         substrs=['Platform: remote-gdb-server', 'Connected: no'])
             self.expect("process connect " + self.server.get_connect_url(),
                         substrs=['Process', 'stopped'])
+
+            current_attr = self.get_term_attrs()
+            # serial:// should set raw mode
+            self.assert_raw_mode(current_attr)
+            # other parameters should be unmodified
+            self.assertEqual(current_attr[4:6], self.orig_attr[4:6])
+            self.assertEqual(self.get_parity_flags(current_attr),
+                             self.get_parity_flags(self.orig_attr))
+            self.assertEqual(self.get_stop_bit_flags(current_attr),
+                             self.get_stop_bit_flags(self.orig_attr))
         finally:
             self.dbg.GetSelectedTarget().GetProcess().Kill()
+        # original mode should be restored on exit
+        self.assertEqual(self.get_term_attrs(), self.orig_attr)
 
     def test_process_connect_async(self):
         """Test the process connect command in asynchronous mode"""
@@ -31,7 +75,63 @@ def test_process_connect_async(self):
                         substrs=['Process', 'stopped'])
             lldbutil.expect_state_changes(self, self.dbg.GetListener(),
                                           self.process(), [lldb.eStateStopped])
+
+            current_attr = self.get_term_attrs()
+            # serial:// should set raw mode
+            self.assert_raw_mode(current_attr)
+            # other parameters should be unmodified
+            self.assertEqual(current_attr[4:6], self.orig_attr[4:6])
+            self.assertEqual(self.get_parity_flags(current_attr),
+                             self.get_parity_flags(self.orig_attr))
+            self.assertEqual(self.get_stop_bit_flags(current_attr),
+                             self.get_stop_bit_flags(self.orig_attr))
         finally:
             self.dbg.GetSelectedTarget().GetProcess().Kill()
         lldbutil.expect_state_changes(self, self.dbg.GetListener(),
                                       self.process(), [lldb.eStateExited])
+        # original mode should be restored on exit
+        self.assertEqual(self.get_term_attrs(), self.orig_attr)
+
+    def test_connect_via_file(self):
+        """Test connecting via the legacy file:// URL"""
+        import termios
+        try:
+            self.expect("platform select remote-gdb-server",
+                        substrs=['Platform: remote-gdb-server', 'Connected: no'])
+            self.expect("process connect file://" +
+                        self.server.get_connect_address(),
+                        substrs=['Process', 'stopped'])
+
+            # file:// sets baud rate and some raw-related flags
+            current_attr = self.get_term_attrs()
+            self.assertEqual(current_attr[3] & (termios.ICANON | termios.ECHO |
+                                                termios.ECHOE | termios.ISIG),
+                             0)
+            self.assertEqual(current_attr[4], termios.B115200)
+            self.assertEqual(current_attr[5], termios.B115200)
+            self.assertEqual(current_attr[6][termios.VMIN], 1)
+            self.assertEqual(current_attr[6][termios.VTIME], 0)
+        finally:
+            self.dbg.GetSelectedTarget().GetProcess().Kill()
+
+    def test_process_connect_params(self):
+        """Test serial:// URL with parameters"""
+        import termios
+        try:
+            self.expect("platform select remote-gdb-server",
+                        substrs=['Platform: remote-gdb-server', 'Connected: no'])
+            self.expect("process connect " + self.server.get_connect_url() +
+                        "?baud=115200&stop-bits=2",
+                        substrs=['Process', 'stopped'])
+
+            current_attr = self.get_term_attrs()
+            self.assert_raw_mode(current_attr)
+            self.assertEqual(current_attr[4:6], 2 * [termios.B115200])
+            self.assertEqual(self.get_parity_flags(current_attr),
+                             self.get_parity_flags(self.orig_attr))
+            self.assertEqual(self.get_stop_bit_flags(current_attr),
+                             termios.CSTOPB)
+        finally:
+            self.dbg.GetSelectedTarget().GetProcess().Kill()
+        # original mode should be restored on exit
+        self.assertEqual(self.get_term_attrs(), self.orig_attr)

diff  --git a/lldb/test/API/functionalities/gdb_remote_client/gdbclientutils.py b/lldb/test/API/functionalities/gdb_remote_client/gdbclientutils.py
index c2974366e3457..64330db86c09d 100644
--- a/lldb/test/API/functionalities/gdb_remote_client/gdbclientutils.py
+++ b/lldb/test/API/functionalities/gdb_remote_client/gdbclientutils.py
@@ -427,7 +427,7 @@ def get_connect_address(self):
         return libc.ptsname(self._master.fileno()).decode()
 
     def get_connect_url(self):
-        return "file://" + self.get_connect_address()
+        return "serial://" + self.get_connect_address()
 
     def close_server(self):
         self._slave.close()


        


More information about the lldb-commits mailing list