Make UARTSerial send all data when blocking

Previously, write() was somewhat soft - it only ever made one attempt to
wait for buffer space, so it would take as much data as would fit in the
buffer in one call.

This is not the intent of a POSIX filehandle write. It should try to
send everything if blocking, and only send less if interrupted by a
signal:

 - If the O_NONBLOCK flag is clear, write() shall block the calling
   thread until the data can be accepted.

 - If the O_NONBLOCK flag is set, write() shall not block the thread.
   If some data can be written without blocking the thread, write()
   shall write what it can and return the number of bytes written.
   Otherwise, it shall return -1 and set errno to [EAGAIN].

This "send all" behaviour is of slightly limited usefulness in POSIX, as
you still usually have to worry about the interruption possibility:

  - If write() is interrupted by a signal before it writes any data, it
    shall return -1 with errno set to [EINTR].

  - If write() is interrupted by a signal after it successfully writes
    some data, it shall return the number of bytes written.

But as mbed OS does not have the possibility of signal interruption, if we
strengthen write to write everything, we can make applications' lives
easier - they can just do "write(large amount)" confident that it will
all go in one call (if no errors).

So, rework to make multiple writes to the buffer, blocking as necessary,
until all data is written.

This change does not apply to read(), which is correct in only blocking until
some data is available:

 - If O_NONBLOCK is set, read() shall return -1 and set errno to [EAGAIN].

 - If O_NONBLOCK is clear, read() shall block the calling thread until some
   data becomes available.

 - The use of the O_NONBLOCK flag has no effect if there is some data
   available.
pull/5466/head
Kevin Bracey 2017-11-09 14:30:55 +02:00
parent 67b97d39c4
commit 9678c8052e
3 changed files with 50 additions and 23 deletions

View File

@ -138,36 +138,47 @@ ssize_t UARTSerial::write(const void* buffer, size_t length)
size_t data_written = 0; size_t data_written = 0;
const char *buf_ptr = static_cast<const char *>(buffer); const char *buf_ptr = static_cast<const char *>(buffer);
if (length == 0) {
return 0;
}
api_lock(); api_lock();
while (_txbuf.full()) { // Unlike read, we should write the whole thing if blocking. POSIX only
if (!_blocking) { // allows partial as a side-effect of signal handling; it normally tries to
api_unlock(); // write everything if blocking. Without signals we can always write all.
return -EAGAIN; while (data_written < length) {
}
api_unlock();
wait_ms(1); // XXX todo - proper wait, WFE for non-rtos ?
api_lock();
}
while (data_written < length && !_txbuf.full()) { if (_txbuf.full()) {
_txbuf.push(*buf_ptr++); if (!_blocking) {
data_written++; break;
} }
do {
core_util_critical_section_enter(); api_unlock();
if (!_tx_irq_enabled) { wait_ms(1); // XXX todo - proper wait, WFE for non-rtos ?
UARTSerial::tx_irq(); // only write to hardware in one place api_lock();
if (!_txbuf.empty()) { } while (_txbuf.full());
SerialBase::attach(callback(this, &UARTSerial::tx_irq), TxIrq);
_tx_irq_enabled = true;
} }
while (data_written < length && !_txbuf.full()) {
_txbuf.push(*buf_ptr++);
data_written++;
}
core_util_critical_section_enter();
if (!_tx_irq_enabled) {
UARTSerial::tx_irq(); // only write to hardware in one place
if (!_txbuf.empty()) {
SerialBase::attach(callback(this, &UARTSerial::tx_irq), TxIrq);
_tx_irq_enabled = true;
}
}
core_util_critical_section_exit();
} }
core_util_critical_section_exit();
api_unlock(); api_unlock();
return data_written; return data_written != 0 ? (ssize_t) data_written : (ssize_t) -EAGAIN;
} }
ssize_t UARTSerial::read(void* buffer, size_t length) ssize_t UARTSerial::read(void* buffer, size_t length)
@ -176,6 +187,10 @@ ssize_t UARTSerial::read(void* buffer, size_t length)
char *ptr = static_cast<char *>(buffer); char *ptr = static_cast<char *>(buffer);
if (length == 0) {
return 0;
}
api_lock(); api_lock();
while (_rxbuf.empty()) { while (_rxbuf.empty()) {

View File

@ -70,6 +70,12 @@ public:
using FileHandle::writable; using FileHandle::writable;
/** Write the contents of a buffer to a file /** Write the contents of a buffer to a file
*
* Follows POSIX semantics:
*
* * if blocking, block until all data is written
* * if no data can be written, and non-blocking set, return -EAGAIN
* * if some data can be written, and non-blocking set, write partial
* *
* @param buffer The buffer to write from * @param buffer The buffer to write from
* @param length The number of bytes to write * @param length The number of bytes to write

View File

@ -51,7 +51,7 @@ public:
* Devices acting as FileHandles should follow POSIX semantics: * Devices acting as FileHandles should follow POSIX semantics:
* *
* * if no data is available, and non-blocking set return -EAGAIN * * if no data is available, and non-blocking set return -EAGAIN
* * if no data is available, and blocking set, wait until data is available * * if no data is available, and blocking set, wait until some data is available
* * If any data is available, call returns immediately * * If any data is available, call returns immediately
* *
* @param buffer The buffer to read in to * @param buffer The buffer to read in to
@ -61,6 +61,12 @@ public:
virtual ssize_t read(void *buffer, size_t size) = 0; virtual ssize_t read(void *buffer, size_t size) = 0;
/** Write the contents of a buffer to a file /** Write the contents of a buffer to a file
*
* Devices acting as FileHandles should follow POSIX semantics:
*
* * if blocking, block until all data is written
* * if no data can be written, and non-blocking set, return -EAGAIN
* * if some data can be written, and non-blocking set, write partial
* *
* @param buffer The buffer to write from * @param buffer The buffer to write from
* @param size The number of bytes to write * @param size The number of bytes to write