SatCat5
posix_uart.cc
1 // Copyright 2022-2024 The Aerospace Corporation.
3 // This file is a part of SatCat5, licensed under CERN-OHL-W v2 or later.
5 
6 #include <cstdio>
7 #include <hal_posix/posix_uart.h>
8 #include <satcat5/log.h>
9 #include <satcat5/utils.h>
10 
11 // Additional includes for specific platforms:
12 #if SATCAT5_WIN32
13  #include <windows.h> // All-in-one for Win32 API
14  #undef ERROR // Deconflict Windows "ERROR" macro
15 #else
16  #include <fcntl.h> // Various file-related constants
17  #include <termios.h> // Termios and related constants
18  #include <unistd.h> // For open(), close(), etc.
19  #include <sys/ioctl.h> // For ioctl() etc.
20 
21  // POSIX support for rates above 230 kbaud is optional.
22  #ifndef B460800
23  #define B460800 0
24  #define B500000 0
25  #define B576000 0
26  #define B921600 0
27  #define B1000000 0
28  #define B1152000 0
29  #define B1500000 0
30  #define B2000000 0
31  #endif
32 
33  // Baud-rate lookup for the predefined constants.
34  inline tcflag_t baud_lookup(unsigned baud) {
35  switch(baud) {
36  case 9600: return B9600;
37  case 19200: return B19200;
38  case 38400: return B38400;
39  case 57600: return B57600;
40  case 115200: return B115200;
41  case 230400: return B230400;
42  case 460800: return B460800;
43  case 500000: return B500000;
44  case 576000: return B576000;
45  case 921600: return B921600;
46  case 1000000: return B1000000;
47  case 1152000: return B1152000;
48  case 1500000: return B1500000;
49  case 2000000: return B2000000;
50  default: return 0;
51  }
52  }
53 #endif
54 
58 namespace log = satcat5::log;
59 
60 PosixUart::PosixUart(const char* device, unsigned baud, unsigned buffer_size_bytes)
61  : satcat5::io::BufferedIO(
62  new u8[buffer_size_bytes], buffer_size_bytes, 0, // Tx buffer
63  new u8[buffer_size_bytes], buffer_size_bytes, 0) // Rx buffer
64  , m_ok(true)
65  , m_uart(0)
66 {
67  // Platform-specific calls to open and configure the UART.
68 #if SATCAT5_WIN32
69  // Convert short name to full device name ("COM3" -> "\\.\COM3")
70  char full_name[32];
71  snprintf(full_name, sizeof(full_name), "\\\\.\\%s", device);
72 
73  // Open the device.
74  m_uart = CreateFile(full_name,
75  GENERIC_READ | GENERIC_WRITE,
76  0, NULL, OPEN_EXISTING, 0, NULL);
77 
78  // Set configuration.
79  DCB dcb;
80  m_ok = m_ok && GetCommState(m_uart, &dcb);
81  if (m_ok) {
82  dcb.BaudRate = baud;
83  dcb.fBinary = 1;
84  dcb.fParity = 0;
85  dcb.fOutxCtsFlow = 0;
86  dcb.fOutxDsrFlow = 0;
87  dcb.fDtrControl = DTR_CONTROL_DISABLE;
88  dcb.fDsrSensitivity = 0;
89  dcb.fTXContinueOnXoff = 0;
90  dcb.fOutX = 0;
91  dcb.fInX = 0;
92  dcb.fErrorChar = 0;
93  dcb.fNull = 0;
94  dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;
95  dcb.fAbortOnError = 0;
96  dcb.wReserved = 0;
97  dcb.ByteSize = 8;
98  dcb.Parity = NOPARITY;
99  dcb.StopBits = ONESTOPBIT;
100  m_ok = !!SetCommState(m_uart, &dcb);
101  }
102 
103  // Disable read timeouts (i.e., always return immediately)
104  // https://learn.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-commtimeouts
105  COMMTIMEOUTS timeout;
106  m_ok = m_ok && GetCommTimeouts(m_uart, &timeout);
107  if (m_ok) {
108  timeout.ReadIntervalTimeout = MAXDWORD;
109  timeout.ReadTotalTimeoutMultiplier = 0;
110  timeout.ReadTotalTimeoutConstant = 0;
111  m_ok = !!SetCommTimeouts(m_uart, &timeout);
112  }
113 
114  // If an error occurred, log the error number.
115  if (!m_ok) {
116  s32 errnum = GetLastError();
117  log::Log(log::ERROR, "UART setup error").write10(errnum);
118  }
119 
120 #else
121  // Open the specified device.
122  m_uart = open(device, O_RDWR | O_NDELAY | O_NOCTTY);
123  m_ok = m_ok && (m_uart >= 0);
124 
125  // Attempt baud-rate lookup.
126  tcflag_t bcode = baud_lookup(baud);
127  m_ok = m_ok && (bcode != 0);
128 
129  // Set terminal options using the legacy API.
130  struct termios tty;
131  m_ok = m_ok && (tcgetattr(m_uart, &tty) >= 0);
132  if (m_ok) {
133  tty.c_iflag = IGNBRK | IGNPAR;
134  tty.c_oflag = 0;
135  tty.c_cflag = CS8 | CREAD | CLOCAL;
136  tty.c_lflag = 0;
137  cfsetispeed(&tty, bcode);
138  cfsetospeed(&tty, bcode);
139  m_ok = (tcsetattr(m_uart, TCSANOW, &tty) >= 0);
140  }
141 
142  // Ignore CTS, but keep RTS asserted.
143  int rts_flag = TIOCM_RTS;
144  m_ok = m_ok && (ioctl(m_uart, TIOCMBIS, &rts_flag) >= 0);
145 #endif
146 }
147 
148 PosixUart::~PosixUart()
149 {
150  // Close platform-specific device.
151 #if SATCAT5_WIN32
152  CloseHandle(m_uart);
153 #else
154  close(m_uart);
155 #endif
156 
157  // Free Tx and Rx buffers.
158  delete[] m_tx.get_buff_dtor();
159  delete[] m_rx.get_buff_dtor();
160 }
161 
163 {
164  // Copy data from transmit buffer to the UART object.
165  while (m_ok && chunk_tx()) {} // Copy data until none is left.
166  m_tx.read_finalize(); // End of packet, move to the next.
167 }
168 
170 {
171  // Copy data from UART object to the receive buffer.
172  while (m_ok && chunk_rx()) {} // Copy data until none is left.
173 }
174 
175 unsigned PosixUart::chunk_rx()
176 {
177  // Copy a single block of received data.
178  u8 buff[64];
179  unsigned cpy_bytes = 0;
180 #if SATCAT5_WIN32
181  DWORD status = 0;
182  m_ok = ReadFile(m_uart, buff, sizeof(buff), &status, NULL);
183  if (!m_ok) {
184  s32 errnum = GetLastError();
185  log::Log(log::ERROR, "UART Rx error").write10(errnum);
186  } else if (status > 0) {
187  cpy_bytes = (unsigned)status;
188  }
189 #else
190  int status = 0;
191  m_ok = (ioctl(m_uart, FIONREAD, &status) >= 0);
192  if (status > 0) {
193  cpy_bytes = min_unsigned(status, sizeof(buff));
194  status = read(m_uart, buff, cpy_bytes);
195  cpy_bytes = (unsigned)status;
196  }
197 #endif
198  // Copy that data to the receive buffer.
199  if (cpy_bytes) {
200  m_rx.write_bytes(cpy_bytes, buff);
202  }
203  return cpy_bytes;
204 }
205 
206 unsigned PosixUart::chunk_tx()
207 {
208  // Copy a single chunk of transmit data.
209  unsigned cpy_bytes = 0;
210  unsigned max_bytes = m_tx.get_peek_ready();
211  const u8* buff = m_tx.peek(max_bytes);
212 #if SATCAT5_WIN32
213  DWORD status = 0;
214  m_ok = WriteFile(m_uart, buff, max_bytes, &status, NULL);
215  if (!m_ok) {
216  s32 errnum = GetLastError();
217  log::Log(log::ERROR, "UART Tx error").write10(errnum);
218  } else if (status > 0) {
219  cpy_bytes = (unsigned)status;
220  }
221 #else
222  int status = write(m_uart, buff, max_bytes);
223  if (status > 0) cpy_bytes = (unsigned)status;
224 #endif
225  // Consume copied data, but do not finalize.
226  // (There may still be additional data in the same packet.)
227  if (cpy_bytes) {
228  m_tx.read_consume(cpy_bytes);
229  }
230  return cpy_bytes;
231 }
232 
233 SlipUart::SlipUart(const char* device, unsigned baud, unsigned buffer)
234  : satcat5::io::WriteableRedirect(&m_slip)
235  , satcat5::io::ReadableRedirect(&m_slip)
236  , m_uart(device, baud, buffer)
237  , m_slip(&m_uart, &m_uart)
238 {
239  // Nothing else to initialize.
240 }
Extensible transmit and receive buffer.
Definition: io_buffer.h:42
satcat5::io::PacketBuffer m_tx
Transmit data (user writes, child reads)
Definition: io_buffer.h:57
satcat5::io::PacketBuffer m_rx
Receive data (user reads, child writes)
Definition: io_buffer.h:60
const u8 * peek(unsigned nbytes) const
Peek nbytes into the circular buffer.
Definition: pkt_buffer.cc:248
unsigned get_peek_ready() const
Find the longest available contiguous segment that can be requested by peek().
Definition: pkt_buffer.cc:242
bool write_finalize() override
Mark end of frame and release temporary working data.
Definition: pkt_buffer.cc:105
void write_bytes(unsigned nbytes, const void *src) override
Write 0 or more bytes from a buffer.
Definition: pkt_buffer.cc:73
u8 * get_buff_dtor() const
Accessor for children that need to delete underlying buffer.
Definition: pkt_buffer.h:137
bool read_consume(unsigned nbytes) override
Read and discard 0 or more bytes.
Definition: pkt_buffer.cc:256
void read_finalize() override
Consume any remaining bytes in this frame, if applicable.
Definition: pkt_buffer.cc:266
POSIX and Windows interface objects for connecting to a UART.
Definition: posix_uart.h:31
void data_rcvd(satcat5::io::Readable *src) override
The data_rcvd() callback is polled whenever data is available.
Definition: posix_uart.cc:162
void poll_always() override
Child class MUST override this method.
Definition: posix_uart.cc:169
Abstract API for reading byte-streams and packets.
Definition: io_readable.h:68
SLIP-encoded wrapper for the PosixUart class.
Definition: posix_uart.h:58
The Log class creates and formats one log message.
Definition: log.h:195
Log & write10(s32 val)
Print integer as a decimal value with no leading zeros.
Definition: log.cc:261
Diagnostic logging to UART and/or Ethernet ports.
bool write(satcat5::io::Writeable *dst, unsigned nbytes, const u8 *data)
Write byte array and finalize.
Definition: sim_utils.cc:62
Miscellaneous mathematical utility functions.
constexpr unsigned min_unsigned(unsigned a, unsigned b)
Min and max functions.
Definition: utils.h:111