I/O Queues

I/O queues are a generic system of circular buffers specialized in byte-oriented I/O communications.

Global Settings

CH_CFG_USE_QUEUES This switch enables the I/O queues API in the kernel.

Description

I/O queues are explicitly meant to connect ISRs and threads. There are two kind of queues derived from a common ancestor called Generic I/O Queue.

Generic I/O Queues

It is the common ancestor for all I/O queues. This object is not directly usable except for deriving more specialized objects.

Generic I/O Queue

I/O queues have a notification mechanism that informs the ISR side when data has been read or written from the thread side. Notification callbacks can be used to re-enable interrupts or restart operations at HW level.

Queues are asymmetric in nature, the ISR side is always non-blocking, this means that trying to write a full input queue or read an empty output queue fails with an error code, there is no wait involved. On the other hand the thread side can be:

  • Blocking. Threads are suspended until the specified amount of data has been read from an input queue or written on an output queue.
  • Blocking with Timeout. The operation is blocking but has a timeout specification.
  • Non-Blocking. The read or write operation only transfers the amount of data that can be transferred immediately without blocking.

The following operations are defined for all I/O queues.

Getting Queue Size

The size operation returns the size of the circular buffer allocated to the queue.

Resetting Queue

The reset operation clears the queue, threads waiting are notified via a MSG_RESET message.

Input Queues

An input queue object is a circular buffer written from ISR side and is read from thread side.

Input Queue

The following operations are defined for input queues.

Getting Space

The space operation returns the number of filled slots in the input queue.

Putting Data

The put operation is performed on the ISR side and writes a byte into the queue, if the queue is full then the operation fails.

Getting Data

The get operation returns one byte taken from the input queue waiting if necessary. A timeout can be specified.

Reading Data

The read operation receives the specified amount of data from the input queue waiting if necessary. A timeout can be specified.

Output Queues

An output queue is a circular buffer written from thread side and is read from ISR side.

Output Queue

The following operations are defined for output queues.

Getting Space

The space operation returns the number of empty slots in the output queue.

Getting Data

The get operation is performed on the ISR side and reads a byte from the queue, if the queue is empty then the operation fails.

Putting Data

The put operation writes one byte to the output queue waiting if necessary. A timeout can be specified.

Writing Data

The write operation sends the specified amount of data to the output queue waiting if necessary. A timeout can be specified.

API

chQSizeI() Returns the queue size.
chQSpaceI() Returns the queue filled/empty space.
chQGetLinkX() Return the user data associated to the queue.
INPUTQUEUE_DECL() Input queues static initializer.
chIQObjectInit() Initializes a mailbox object of type input_queue_t.
chIQResetI() Resets an input queue.
chIQPutI() Puts a byte into the input queue (I-Class variant).
chIQGetTimeout() Fetches a byte from the input queue with timeout.
chIQReadTimeout() Reads a block of data from the input queue with timeout.
OUTPUTQUEUE_DECL() Output queues static initializer.
chOQObjectInit() Initializes a mailbox object of type output_queue_t.
chOQResetI() Resets an output queue.
chOQGetI() Fetches a byte from the output queue (I-Class variant).
chOQPutTimeout() Puts a byte into the output queue with timeout.
chOQWriteTimeout() Writes a block of data into the output queue with timeout.

Examples

Buffered UART Driver

The most common use case for I/O queues is the UART driver with circular buffers. In this example an input queue and an output queue are associated to an UART in order to implement the driver.

#define SERIAL_BUFFERS_SIZE 128
 
static uint8_t ibuf[SERIAL_BUFFERS_SIZE];
static uint8_t obuf[SERIAL_BUFFERS_SIZE];
static input_queue_t iq;
static output_queue_t oq;
 
/*
 * ISR serving the UART interrupt.
 */
CH_IRQ_HANDLER(UART_RX_IRQ) {
  msg_t msg;
 
  CH_IRQ_PROLOGUE();
 
  /* If there is data available in the UART RX FIFO.*/
  while ((UART->SR & UART_SR_RXNOTEMPTY) != 0) {
 
    /* Entering I-Locked state and putting the frame into
       the input queue.*/
    chSysLockFromISR();
    msg = chIQPutI(&iq, UART->DR);
    chSysUnlockFromISR();
 
    /* Handling the condition of a full input queue.*/
    if (msg == MSG_TIMEOUT) {
      handle_overflow();
    }
  }
 
  /* If there is space in the UART TX FIFO.*/
  while ((UART->SR & UART_SR_TXNOTFULL) != 0) {
 
    /* Entering I-Locked state and getting a frame from
       the output queue.*/
    chSysLockFromISR();
    msg = chOQGetI(&oq);
    chSysUnlockFromISR();
 
    if (msg < MSG_OK) {
      /* No data to transmit, disabling the TX interrupt.*/
      UART->IER &= ~UART_IER_TXNOTFULL;
      break;
    }
    else {
      /* Transmitting the data, the interrupt stays enabled.*/
      UART->DR = (uint8_t)msg;
    }
  }
 
  CH_IRQ_EPILOGUE();
}
 
/*
 * Output queue notification, called when data has been inserted
 * in the output queue.
 */
static void notify(io_queue_t *qp) {
 
  (void)qp;
 
  /* Enabling the TX interrupt, the transmission will resume
     with next ISR.*/
  UART->IER |= UART_IER_TXNOTFULL;
}
 
/*
 * Initialization.
 */
void main(void) {
 
  /*
   * System initializations.
   * - Kernel initialization, the main() function becomes a thread and the
   *   RTOS is active.
   */
  chSysInit();
 
  /* Initializing the UART queues.*/
  iqObjectInit(&iq, ibuf, SERIAL_BUFFERS_SIZE, NULL, NULL);
  oqObjectInit(&oq, obuf, SERIAL_BUFFERS_SIZE, notify, NULL);
  uart_init();
 
  /* Printing on serial port.*/
  chOQWriteTimeout(&oq, (uint8_t *)"Hello world!\r\n", 14, TIME_INFINITE);
 
  /* Continuing.*/
  ...;
}