This revision was automatically updated to reflect the committed changes.
Closed by commit rG4a7b4beac759: [lldb] Add serial:// protocol for connecting 
to serial port (authored by mgorny).
Herald added a project: LLDB.

Changed prior to commit:
  https://reviews.llvm.org/D111355?vs=380985&id=381178#toc

Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D111355/new/

https://reviews.llvm.org/D111355

Files:
  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

Index: lldb/test/API/functionalities/gdb_remote_client/gdbclientutils.py
===================================================================
--- lldb/test/API/functionalities/gdb_remote_client/gdbclientutils.py
+++ lldb/test/API/functionalities/gdb_remote_client/gdbclientutils.py
@@ -427,7 +427,7 @@
         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()
Index: lldb/test/API/functionalities/gdb_remote_client/TestPty.py
===================================================================
--- lldb/test/API/functionalities/gdb_remote_client/TestPty.py
+++ lldb/test/API/functionalities/gdb_remote_client/TestPty.py
@@ -9,6 +9,38 @@
     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 @@
                         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 @@
                         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)
Index: lldb/source/Host/posix/ConnectionFileDescriptorPosix.cpp
===================================================================
--- lldb/source/Host/posix/ConnectionFileDescriptorPosix.cpp
+++ lldb/source/Host/posix/ConnectionFileDescriptorPosix.cpp
@@ -156,6 +156,7 @@
 #if LLDB_ENABLE_POSIX
             .Case("fd", &ConnectionFileDescriptor::ConnectFD)
             .Case("file", &ConnectionFileDescriptor::ConnectFile)
+            .Case("serial", &ConnectionFileDescriptor::ConnectSerialPort)
 #endif
             .Default(nullptr);
 
@@ -735,6 +736,55 @@
   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);
Index: lldb/source/Host/common/File.cpp
===================================================================
--- lldb/source/Host/common/File.cpp
+++ lldb/source/Host/common/File.cpp
@@ -769,5 +769,87 @@
   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;
Index: lldb/include/lldb/Host/posix/ConnectionFileDescriptorPosix.h
===================================================================
--- lldb/include/lldb/Host/posix/ConnectionFileDescriptorPosix.h
+++ lldb/include/lldb/Host/posix/ConnectionFileDescriptorPosix.h
@@ -88,6 +88,9 @@
 
   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>
Index: lldb/include/lldb/Host/File.h
===================================================================
--- lldb/include/lldb/Host/File.h
+++ 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 @@
   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
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to