µOS++ IIIe Reference 7.0.0
The third edition of µOS++, a POSIX inspired open source framework, written in C++
Loading...
Searching...
No Matches
device-serial-buffered.h
Go to the documentation of this file.
1/*
2 * This file is part of the µOS++ project (https://micro-os-plus.github.io/).
3 * Copyright (c) 2015-2025 Liviu Ionescu. All rights reserved.
4 *
5 * Permission to use, copy, modify, and/or distribute this software
6 * for any purpose is hereby granted, under the terms of the MIT license.
7 *
8 * If a copy of the license was not distributed with this file, it can
9 * be obtained from https://opensource.org/licenses/mit.
10 */
11
12#ifndef CMSIS_PLUS_POSIX_DRIVER_DEVICE_SERIAL_BUFFERED_H_
13#define CMSIS_PLUS_POSIX_DRIVER_DEVICE_SERIAL_BUFFERED_H_
14
15#if defined(__cplusplus)
16
17// ----------------------------------------------------------------------------
18
19#include <cmsis-plus/rtos/os.h>
20
21#include <cmsis-plus/posix-io/device-char.h>
24
25// ----------------------------------------------------------------------------
26
27// TODO: (multiline)
28// - add flow control on both send & receive
29// - cancel pending reads/writes at close (partly done)
30// - add error processing
31
32namespace os
33{
34 namespace posix
35 {
36 // ------------------------------------------------------------------------
37
38#pragma GCC diagnostic push
39#if defined(__clang__)
40#pragma clang diagnostic ignored "-Wpadded"
41#elif defined(__GNUC__)
42#pragma GCC diagnostic ignored "-Wpadded"
43#endif
44
51 template <typename CS>
52 class device_serial_buffered : public os::posix::device_char
53 {
54 using critical_section = CS;
55
56 // ----------------------------------------------------------------------
57
63 public:
64 device_serial_buffered (const char* device_name,
65 os::driver::Serial* driver,
68
73 // The rule of five.
77 operator= (const device_serial_buffered&)
78 = delete;
80 operator= (device_serial_buffered&&)
81 = delete;
82
87 virtual ~device_serial_buffered ();
88
93 // ----------------------------------------------------------------------
99 public:
100 // Static function called by the CMSIS driver in an
101 // interrupt context.
102
103 static void
104 signal_event (device_serial_buffered* object, uint32_t event);
105
110 // ----------------------------------------------------------------------
116 protected:
117 virtual int
118 do_vopen (const char* path, int oflag, std::va_list args) override;
119
120 virtual int
121 do_close (void) override;
122
123 virtual ssize_t
124 do_read (void* buf, std::size_t nbyte) override;
125
126 virtual ssize_t
127 do_write (const void* buf, std::size_t nbyte) override;
128
129#if 0
130 virtual ssize_t
131 do_writev (const struct iovec* iov, int iovcnt) override;
132
133 virtual int
134 do_vioctl (int request, std::va_list args) override;
135
136 virtual int
137 do_vfcntl (int cmd, va_list args) override;
138#endif
139
140 virtual bool
141 do_is_opened (void) override;
142
143 virtual bool
144 do_is_connected (void) override;
145
150 // ----------------------------------------------------------------------
151 private:
156 // Pointer to actual CMSIS-like serial driver (usart or usb cdc acm)
157 os::driver::Serial* driver_ = nullptr;
158
159 os::rtos::semaphore_binary open_sem_{ "open", 0 };
160 os::rtos::semaphore_binary rx_sem_{ "rx", 0 };
161 os::rtos::semaphore_binary tx_sem_{ "tx", 0 };
162
163 os::posix::circular_buffer_bytes* rx_buf_ = nullptr;
164 os::posix::circular_buffer_bytes* tx_buf_ = nullptr;
165
166 std::size_t rx_count_ = 0; //
167 bool volatile tx_busy_ = false;
168 bool volatile is_connected_ = false;
169 bool volatile is_opened_ = false;
170 // Padding!
171
175 };
176
177#pragma GCC diagnostic pop
178
179 } /* namespace posix */
180} /* namespace os */
181
182// ===== Inline & template implementations ====================================
183
184namespace os
185{
186 namespace posix
187 {
188 // ------------------------------------------------------------------------
189
190 template <typename CS>
192 const char* device_name, //
195 : //
196 device_char (device_name), // Construct parent.
197 driver_ (driver), //
198 rx_buf_ (rx_buf), //
199 tx_buf_ (tx_buf) //
200 {
201 trace::printf ("%s(\"%s\",%p,%p,%p) %p\n", __func__, device_name, driver,
202 rx_buf, tx_buf, this);
203
204 assert (rx_buf != nullptr);
205
206 // Do not check the same for tx_buf, it may be null.
207
208 driver_->register_callback (
209 reinterpret_cast<os::driver::signal_event_t> (signal_event), this);
210 }
211
212 template <typename CS>
214 {
215 trace::printf ("%s() %p\n", __func__, this);
216
217 driver_ = nullptr;
218 is_connected_ = false;
219 is_opened_ = false;
220 }
221
222 // ------------------------------------------------------------------------
223
224#pragma GCC diagnostic push
225#if defined(__clang__)
226#elif defined(__GNUC__)
227#pragma GCC diagnostic ignored "-Wunused-parameter"
228#endif
229
230 template <typename CS>
231 int
232 device_serial_buffered<CS>::do_vopen (const char* path, int oflag,
233 std::va_list args)
234 {
235 if (is_opened_)
236 {
237 errno = EEXIST; // Already opened
238 return -1;
239 }
240
241 int32_t result;
242
243 do
244 {
245 // Reset semaphores, in case we come here after close.
246 open_sem_.reset ();
247 rx_sem_.reset ();
248 tx_sem_.reset ();
249
250 is_opened_ = true;
251
252 // Clear buffers.
253 rx_buf_->clear ();
254 rx_count_ = 0;
255
256 if (tx_buf_ != nullptr)
257 {
258 tx_buf_->clear ();
259 }
260
261 // Default configuration: 8 bits, no parity, 1 stop bit,
262 // no flow control, 115200 bps.
263 result = driver_->configure (
269 115200);
270 // assert(result == os::driver::RETURN_OK);
271 if (result != os::driver::RETURN_OK)
272 break;
273
274 // Enable TX output.
275 result = driver_->control (os::driver::serial::Control::enable_tx);
276 if (result != os::driver::RETURN_OK)
277 break;
278
279 // Enable RX input.
280 result = driver_->control (os::driver::serial::Control::enable_rx);
281 if (result != os::driver::RETURN_OK)
282 break;
283 }
284 while (false); // Actually NOT a loop, just a sequence of ifs!
285
286 if (result != os::driver::RETURN_OK)
287 {
288 errno = EIO;
289 return -1;
290 }
291
293 capa = driver_->get_capabilities ();
294 if (capa.dcd)
295 {
297 for (;;)
298 {
299 {
300 // ----- Enter critical section ---------------------------
302
303 status = driver_->get_modem_status ();
304 // ----- Exit critical section ----------------------------
305 }
306 if (status.is_dcd_active ())
307 {
308 break;
309 }
310 open_sem_.wait ();
311 }
312 }
313
314 uint8_t* pbuf;
315 std::size_t nbyte = rx_buf_->back_contiguous_buffer (&pbuf);
316
317 result = driver_->receive (pbuf, nbyte);
318 if (result != os::driver::RETURN_OK)
319 {
320 errno = EIO;
321 return -1;
322 }
323
324 is_connected_ = true;
325
326 // Return POSIX idea of OK.
327 return 0;
328 }
329
330 template <typename CS>
331 bool
333 {
334 return is_opened_;
335 }
336
337 template <typename CS>
338 bool
340 {
341 return is_connected_;
342 }
343
344 template <typename CS>
345 int
347 {
348
349 if (is_connected_)
350 {
351 // Wait for write to complete.
352 // TODO: what if flow control prevents this?
353 if (tx_buf_ != nullptr)
354 {
355 for (;;)
356 {
357 if (tx_buf_->empty ())
358 {
359 break;
360 }
361 tx_sem_.wait ();
362 }
363 }
364 }
365
366 // Abort pending reads.
368 ret = driver_->control (os::driver::serial::Control::abort_receive);
369 assert (ret == os::driver::RETURN_OK);
370
371 // Abort pending writes.
372 ret = driver_->control (os::driver::serial::Control::abort_send);
373 assert (ret == os::driver::RETURN_OK);
374
375 // Disable transmitter and receiver.
376 ret = driver_->control (os::driver::serial::Control::disable_tx);
377 assert (ret == os::driver::RETURN_OK);
378
379 ret = driver_->control (os::driver::serial::Control::disable_rx);
380 assert (ret == os::driver::RETURN_OK);
381 ret = driver_->control (os::driver::serial::Control::disable_break);
382 assert (ret == os::driver::RETURN_OK);
383
384 is_opened_ = false;
385 is_connected_ = false;
386
387 // Return POSIX idea of OK.
388 return 0;
389 }
390
391 template <typename CS>
392 ssize_t
393 device_serial_buffered<CS>::do_read (void* buf, std::size_t nbyte)
394 {
395 // TODO: implement cases when 0 must be returned
396 // (disconnects, timeouts).
397 while (true)
398 {
399 std::size_t count;
400 {
401 // ----- Enter critical section -------------------------------
403
404 count = rx_buf_->pop_front (static_cast<uint8_t*> (buf), nbyte);
405 // ----- Exit critical section --------------------------------
406 }
407 if (count > 0)
408 {
409 // Actual number of chars received in buffer.
410 return count;
411 }
412 if (!is_connected_)
413 {
414 errno = EIO;
415 return -1;
416 }
417 // Block and wait for bytes to arrive.
418 rx_sem_.wait ();
419 }
420 }
421
422 template <typename CS>
423 ssize_t
424 device_serial_buffered<CS>::do_write (const void* buf, std::size_t nbyte)
425 {
426 std::size_t count;
427
428 if (tx_buf_ != nullptr)
429 {
430 count = 0;
431 {
432 // ----- Enter critical section -------------------------------
434
435 if (tx_buf_->below_high_water_mark ())
436 {
437 // If there is more space in the buffer, try to fill it.
438 count = tx_buf_->push_back (static_cast<const uint8_t*> (buf),
439 nbyte);
440 }
441 // ----- Exit critical section --------------------------------
442 }
443 while (true)
444 {
446 {
447 // ----- Enter critical section ---------------------------
449
450#pragma GCC diagnostic push
451#if defined(__clang__)
452#elif defined(__GNUC__)
453#pragma GCC diagnostic ignored "-Waggregate-return"
454#endif
455 status = driver_->get_status ();
456#pragma GCC diagnostic pop
457
458 // ----- Exit critical section ----------------------------
459 }
460 if (!status.tx_busy)
461 {
462 uint8_t* pbuf;
463 std::size_t nb;
464 {
465 // ----- Enter critical section -----------------------
466 critical_section cs; // -----
467
468 nb = tx_buf_->front_contiguous_buffer (&pbuf);
469 // ----- Exit critical section ------------------------
470 }
471 if (nb > 0)
472 {
473 if (driver_->send (pbuf, nb) != os::driver::RETURN_OK)
474 {
475 errno = EIO;
476 return -1;
477 }
478 }
479 }
480
481 // bool isBelowHWM;
482 // {
483 // critical_section cs; // -----
484 //
485 // isBelowHWM = tx_buf_->below_high_water_mark
486 // ();
487 // }
488 if (count == nbyte)
489 {
490 return nbyte;
491 }
492
493 if (!is_connected_)
494 {
495 if (count > 0)
496 {
497 return count;
498 }
499
500 errno = EIO;
501 return -1;
502 }
503
504 // Block and wait for buffer to be freed.
505 tx_sem_.wait ();
506
507 if (count < nbyte)
508 {
509 // ----- Enter critical section ---------------------------
511
512 std::size_t n;
513 // If there is more space in the buffer, try to fill it.
514 n = tx_buf_->push_back (static_cast<const uint8_t*> (buf)
515 + count,
516 nbyte - count);
517 count += n;
518 // ----- Exit critical section ----------------------------
519 }
520 }
521 }
522 else
523 {
524 // Do not use a transmit buffer, send directly from the user buffer.
525 // Wait while transmitting.
527 for (;;)
528 {
529 if (!is_connected_)
530 {
531 errno = EIO;
532 return -1;
533 }
534
535 status = driver_->get_status ();
536 if (!status.is_tx_busy ())
537 {
538 break;
539 }
540 tx_sem_.wait ();
541 }
542
543 if ((driver_->send (buf, nbyte)) == os::driver::RETURN_OK)
544 {
545 for (;;)
546 {
547 if (!is_connected_)
548 {
549 errno = EIO;
550 return -1;
551 }
552
553 status = driver_->get_status ();
554 if (!status.is_tx_busy ())
555 {
556 break;
557 }
558 tx_sem_.wait ();
559 }
560 count = driver_->get_tx_count ();
561 }
562 else
563 {
564 count = -1;
565 errno = EIO;
566 }
567 }
568
569 // Actual number of bytes transmitted from buffer.
570 return count;
571 }
572
573#if 0
574 template<typename CS>
575 ssize_t
576 device_serial_buffered<CS>::do_writev (const struct iovec* iov, int iovcnt)
577 {
578 errno = ENOSYS; // Not implemented
579 return -1;
580 }
581
582 template<typename CS>
583 int
584 device_serial_buffered<CS>::do_vioctl (int request, std::va_list args)
585 {
586 errno = ENOSYS; // Not implemented
587 return -1;
588 }
589
590 template<typename CS>
591 int
592 device_serial_buffered<CS>::do_vfcntl (int cmd, std::va_list args)
593 {
594 errno = ENOSYS; // Not implemented
595 return -1;
596 }
597#endif
598
599 // ------------------------------------------------------------------------
600
601 template <typename CS>
602 void
604 uint32_t event)
605 {
606 if (!object->is_opened_)
607 {
608 // After close(), ignore interrupts.
609 return;
610 }
611 if ((event
615 {
616 // TODO: process errors and timeouts
617 std::size_t tmpCount = object->driver_->get_rx_count ();
618 std::size_t count = tmpCount - object->rx_count_;
619 object->rx_count_ = tmpCount;
620 std::size_t adjust = object->rx_buf_->advance_back (count);
621 assert (count == adjust);
622
624 {
625 uint8_t* pbuf;
626 std::size_t nbyte
627 = object->rx_buf_->back_contiguous_buffer (&pbuf);
628 if (nbyte == 0)
629 {
630 // Overwrite the last byte, but keep the driver in
631 // receive mode continuously.
632 object->rx_buf_->retreat_back ();
633 nbyte = object->rx_buf_->back_contiguous_buffer (&pbuf);
634 }
635 assert (nbyte > 0);
636
637 // Read as much as we can.
638 int32_t status;
639 status = object->driver_->receive (pbuf, nbyte);
640 // TODO: implement error processing.
641 assert (status == os::driver::RETURN_OK);
642
643 object->rx_count_ = 0;
644 }
645 if (count > 0)
646 {
647 // Immediately wake up, do not wait to reach any water mark.
648 object->rx_sem_.post ();
649 }
650 }
652 {
653 if (object->tx_buf_ != nullptr)
654 {
655 std::size_t count = object->driver_->get_tx_count ();
656 std::size_t adjust = object->tx_buf_->advance_front (count);
657 assert (count == adjust);
658
659 uint8_t* pbuf;
660 std::size_t nbyte
661 = object->tx_buf_->front_contiguous_buffer (&pbuf);
662 if (nbyte > 0)
663 {
664 int32_t status;
665 status = object->driver_->send (pbuf, nbyte);
666 // TODO: implement error processing
667 assert (status == os::driver::RETURN_OK);
668 }
669 else
670 {
671 object->tx_busy_ = false;
672 }
673 if (object->tx_buf_->below_low_water_mark ())
674 {
675 // Wake up thread, to come and send more bytes.
676 object->tx_sem_.post ();
677 }
678 }
679 else
680 {
681 // No buffer, wake up the thread to return from write().
682 object->tx_sem_.post ();
683 }
684 }
686 {
688 status = object->driver_->get_modem_status ();
689
690 bool is_dcd_active = status.is_dcd_active ();
691 object->is_connected_ = is_dcd_active;
692 if (is_dcd_active)
693 {
694 // Connected, wake-up open().
695 object->open_sem_.post ();
696 }
697 else
698 {
699 // Disconnected, cancel read.
700 object->rx_sem_.post ();
701
702 // Cancel write.
703 object->tx_sem_.post ();
704 }
705 }
707 {
708 // TODO: add flow control
709 }
711 {
712 // TODO: add flow control
713 }
714 }
715
716#pragma GCC diagnostic pop
717
718 } /* namespace posix */
719} /* namespace os */
720
721// ----------------------------------------------------------------------------
722
723#endif /* __cplusplus */
724
725// ----------------------------------------------------------------------------
726
727#endif /* CMSIS_PLUS_POSIX_DRIVER_DEVICE_SERIAL_BUFFERED_H_ */
Serial device driver capabilities.
Definition serial.h:455
bool dcd
DCD Line: false=not available, true=available.
Definition serial.h:506
Serial modem status
Definition serial.h:355
Serial port status
Definition serial.h:259
bool tx_busy
Transmitter busy flag.
Definition serial.h:289
Circular buffer class template.<cmsis-plus/posix-driver/circular-buffer.h>
Buffered serial driver class template.<cmsis-plus/posix-driver/circular-buffer.h>
static void signal_event(device_serial_buffered *object, uint32_t event)
virtual ssize_t do_read(void *buf, std::size_t nbyte) override
virtual bool do_is_connected(void) override
device_serial_buffered(const char *device_name, os::driver::Serial *driver, os::posix::circular_buffer_bytes *rx_buf, os::posix::circular_buffer_bytes *tx_buf)
virtual bool do_is_opened(void) override
virtual int do_vopen(const char *path, int oflag, std::va_list args) override
virtual ssize_t do_write(const void *buf, std::size_t nbyte) override
POSIX compliant binary semaphore.
int printf(const char *format,...)
Write a formatted string to the trace device.
Definition trace.cpp:59
constexpr config_t PARITY_NONE
No Parity = (default);.
Definition serial.h:104
constexpr config_t DATA_BITS_8
8 Data bits = (default);.
Definition serial.h:94
constexpr config_t STOP_BITS_1
1 Stop bit = (default);.
Definition serial.h:117
constexpr config_t MODE_ASYNCHRONOUS
UART = (Asynchronous);; arg = Baudrate.
Definition serial.h:62
constexpr config_t FLOW_CONTROL_NONE
No Flow Control = (default);.
Definition serial.h:133
@ abort_receive
Abort Serial::receive().
Definition serial.h:203
@ abort_send
Abort Serial::send().
Definition serial.h:200
@ disable_break
Disable Continuous Break transmission;.
Definition serial.h:215
@ disable_tx
Disable Transmitter.
Definition serial.h:209
@ enable_tx
Enable Transmitter.
Definition serial.h:191
@ enable_rx
Enable Receiver.
Definition serial.h:194
@ disable_rx
Disable Receiver.
Definition serial.h:212
@ cts
CTS state changed (optional).
Definition serial.h:430
@ rx_framing_error
Framing error detected on receive.
Definition serial.h:424
@ dsr
DSR state changed (optional).
Definition serial.h:433
@ dcd
DCD state changed (optional).
Definition serial.h:436
@ rx_timeout
Receive character timeout (optional).
Definition serial.h:418
@ receive_complete
Receive completed.
Definition serial.h:403
@ tx_complete
Transmit completed (optional).
Definition serial.h:409
constexpr return_t RETURN_OK
Definition common.h:52
int32_t return_t
Definition common.h:46
void(* signal_event_t)(const void *object, event_t event)
Definition common.h:67
System namespace.
Single file µOS++ RTOS definitions.
Definition uio.h:40