summaryrefslogblamecommitdiffstats
path: root/testsuites/libtests/termios09/init.c
blob: 5490b2b9182ff5c4f1475c30db615b2936e3f4b2 (plain) (tree)





































                                                                 

                            










                                                 


                                             
                       





                                       

                             






                                                                   
                                                                













































                                                                   
        
 





                                                                

































                                                                    

                                                                   



































                                                                   

                                         

                   
                                                                    

                


                                                                  





                           

















                                                                         











                                 











                                 











                                 























                                   






























                                         






















































































































































































































                                                      

                                
                                    



                                    









                                                



                                



                                    
           








                                                

                                
                                   



                                    
 
            








                                                

                                
                                   



                                    









                                                  


                                  





                                     































                                                         



                                                     
             
 


                                                                             






                                                     
                       















































































































































































































































                                                   
                                   
















































































                                                 





















                                                        
                                           
































                                                      




                                                          

























                                                                           




                                                          








                                                                           
                                                                 















                                                          




                                                          













                                                                           
























                                                







                                         





                               

                                   







                         
                  
                        




                     






                                                                             
                                                
                                                         
 
                                            
 
                                 


                                      


                                          





                                        
/*
 * Copyright (c) 2017 embedded brains GmbH.  All rights reserved.
 *
 *  embedded brains GmbH
 *  Dornierstr. 4
 *  82178 Puchheim
 *  Germany
 *  <rtems@embedded-brains.de>
 *
 * The license and distribution terms for this file may be
 * found in the file LICENSE in this distribution or at
 * http://www.rtems.org/license/LICENSE.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>

#include <rtems/termiostypes.h>

#include "tmacros.h"

const char rtems_test_name[] = "TERMIOS 9";

#define INTERRUPT 0

#define POLLED 1

#define DEVICE_COUNT 2

#define OUTPUT_BUFFER_SIZE 64

#define INPUT_BUFFER_SIZE 64

static const char * const paths[DEVICE_COUNT] = {
  "/interrupt",
  "/polled"
};

typedef struct {
  rtems_termios_device_context base;
  rtems_termios_tty *tty;
  size_t output_pending;
  size_t output_count;
  char output_buf[OUTPUT_BUFFER_SIZE];
  size_t input_head;
  size_t input_tail;
  unsigned char input_buf[INPUT_BUFFER_SIZE];
  int callback_counter;
} device_context;

typedef struct {
  device_context devices[DEVICE_COUNT];
  int fds[DEVICE_COUNT];
  struct termios term[DEVICE_COUNT];
  int context_switch_counter;
  rtems_id flush_task_id;
} test_context;

static test_context test_instance = {
  .devices = {
    {
      .base = RTEMS_TERMIOS_DEVICE_CONTEXT_INITIALIZER("Interrupt")
    }, {
      .base = RTEMS_TERMIOS_DEVICE_CONTEXT_INITIALIZER("Polled")
    }
  }
};

static bool first_open(
  rtems_termios_tty *tty,
  rtems_termios_device_context *base,
  struct termios *term,
  rtems_libio_open_close_args_t *args
)
{
  device_context *dev = (device_context *) base;

  dev->tty = tty;

  return true;
}

static void write_polled(
  rtems_termios_device_context *base,
  const char *buf,
  size_t len
)
{
  device_context *dev = (device_context *) base;

  rtems_test_assert(dev->output_count + len <= OUTPUT_BUFFER_SIZE);
  memcpy(&dev->output_buf[dev->output_count], buf, len);
  dev->output_count += len;
}

static void write_interrupt(
  rtems_termios_device_context *base,
  const char *buf,
  size_t len
)
{
  device_context *dev = (device_context *) base;

  write_polled(base, buf, len);
  dev->output_pending = len;
}

static int read_polled(rtems_termios_device_context *base)
{
  device_context *dev = (device_context *) base;
  int c;

  if (dev->input_head != dev->input_tail) {
    c = dev->input_buf[dev->input_head];
    dev->input_head = (dev->input_head + 1) % INPUT_BUFFER_SIZE;
  } else {
    c = -1;
  }

  return c;
}

static const rtems_termios_device_handler handlers[DEVICE_COUNT] = {
  {
    .first_open = first_open,
    .write = write_interrupt,
    .mode = TERMIOS_IRQ_DRIVEN
  }, {
    .first_open = first_open,
    .write = write_polled,
    .poll_read = read_polled,
    .mode = TERMIOS_POLLED
  }
};

static void set_term(test_context *ctx, size_t i)
{
  int rv;

  rv = tcsetattr(ctx->fds[i], TCSANOW, &ctx->term[i]);
  rtems_test_assert(rv == 0);
}

static void init_term(test_context *ctx, size_t i)
{
  int rv;

  rv = tcgetattr(ctx->fds[i], &ctx->term[i]);
  rtems_test_assert(rv == 0);

  ctx->term[i].c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
    | INLCR | IGNCR | ICRNL | IXON);
  ctx->term[i].c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL | ECHOPRT
    | ECHOCTL | ECHOKE | ICANON | ISIG | IEXTEN);
  ctx->term[i].c_cflag &= ~(CSIZE | PARENB);
  ctx->term[i].c_cflag |= CS8;
  ctx->term[i].c_oflag &= ~(OPOST | ONLRET | ONLCR | OCRNL | ONLRET
    | TABDLY | OLCUC);

  ctx->term[i].c_cc[VMIN] = 0;
  ctx->term[i].c_cc[VTIME] = 0;

  set_term(ctx, i);
}

static void setup(test_context *ctx)
{
  rtems_status_code sc;
  size_t i;

  rtems_termios_initialize();

  for (i = 0; i < DEVICE_COUNT; ++i) {
    sc = rtems_termios_device_install(
      paths[i],
      &handlers[i],
      NULL,
      &ctx->devices[i].base
    );
    rtems_test_assert(sc == RTEMS_SUCCESSFUL);

    ctx->fds[i] = open(paths[i], O_RDWR);
    rtems_test_assert(ctx->fds[i] >= 0);

    init_term(ctx, i);
  }
}

static void input(test_context *ctx, size_t i, char c)
{
  device_context *dev = &ctx->devices[i];

  switch (i) {
    case INTERRUPT:
      rtems_termios_enqueue_raw_characters(dev->tty, &c, sizeof(c));
      break;
    case POLLED:
      dev->input_buf[dev->input_tail] = (unsigned char) c;
      dev->input_tail = (dev->input_tail + 1) % INPUT_BUFFER_SIZE;
      rtems_test_assert(dev->input_head != dev->input_tail);
      break;
    default:
      rtems_test_assert(0);
  }
}

static void enable_non_blocking(test_context *ctx, size_t i, bool enable)
{
  int flags;
  int rv;

  flags = fcntl(ctx->fds[i], F_GETFL, 0);
  rtems_test_assert(flags >= 0);

  if (enable) {
    flags |= O_NONBLOCK;
  } else {
    flags &= ~O_NONBLOCK;
  }

  rv = fcntl(ctx->fds[i], F_SETFL, flags);
  rtems_test_assert(rv == 0);
}

static void clear_set_iflag(
  test_context *ctx,
  size_t i,
  tcflag_t clear,
  tcflag_t set
)
{
  ctx->term[i].c_iflag &= ~clear;
  ctx->term[i].c_iflag |= set;
  set_term(ctx, i);
}

static void clear_set_lflag(
  test_context *ctx,
  size_t i,
  tcflag_t clear,
  tcflag_t set
)
{
  ctx->term[i].c_lflag &= ~clear;
  ctx->term[i].c_lflag |= set;
  set_term(ctx, i);
}

static void clear_set_oflag(
  test_context *ctx,
  size_t i,
  tcflag_t clear,
  tcflag_t set
)
{
  ctx->term[i].c_oflag &= ~clear;
  ctx->term[i].c_oflag |= set;
  set_term(ctx, i);
}

static void set_vmin_vtime(
  test_context *ctx,
  size_t i,
  cc_t vmin,
  cc_t vtime
)
{
  ctx->term[i].c_cc[VMIN] = vmin;
  ctx->term[i].c_cc[VTIME] = vtime;
  set_term(ctx, i);
}

static void set_veol_veol2(
  test_context *ctx,
  size_t i,
  cc_t veol,
  cc_t veol2
)
{
  ctx->term[i].c_cc[VEOL] = veol;
  ctx->term[i].c_cc[VEOL2] = veol2;
  set_term(ctx, i);
}

static void test_igncr(test_context *ctx)
{
  size_t i;

  for (i = 0; i < DEVICE_COUNT; ++i) {
    ssize_t n;
    char c;

    c = 'x';

    clear_set_iflag(ctx, i, 0, IGNCR);

    n = read(ctx->fds[i], &c, sizeof(c));
    rtems_test_assert(n == 0);
    rtems_test_assert(c == 'x');

    input(ctx, i, '\r');

    n = read(ctx->fds[i], &c, sizeof(c));
    rtems_test_assert(n == 0);
    rtems_test_assert(c == 'x');

    clear_set_iflag(ctx, i, IGNCR, 0);
    input(ctx, i, '\r');

    n = read(ctx->fds[i], &c, sizeof(c));
    rtems_test_assert(n == 1);
    rtems_test_assert(c == '\r');
  }
}

static void test_istrip(test_context *ctx)
{
  size_t i;

  for (i = 0; i < DEVICE_COUNT; ++i) {
    ssize_t n;
    char c;

    c = 'x';

    clear_set_iflag(ctx, i, 0, ISTRIP);

    n = read(ctx->fds[i], &c, sizeof(c));
    rtems_test_assert(n == 0);
    rtems_test_assert(c == 'x');

    input(ctx, i, '\376');

    n = read(ctx->fds[i], &c, sizeof(c));
    rtems_test_assert(n == 1);
    rtems_test_assert(c == '~');

    clear_set_iflag(ctx, i, ISTRIP, 0);
    input(ctx, i, '\376');

    n = read(ctx->fds[i], &c, sizeof(c));
    rtems_test_assert(n == 1);
    rtems_test_assert(c == '\376');
  }
}

static void test_iuclc(test_context *ctx)
{
  size_t i;

  for (i = 0; i < DEVICE_COUNT; ++i) {
    ssize_t n;
    char c;

    c = 'x';

    clear_set_iflag(ctx, i, 0, IUCLC);

    n = read(ctx->fds[i], &c, sizeof(c));
    rtems_test_assert(n == 0);
    rtems_test_assert(c == 'x');

    input(ctx, i, 'A');

    n = read(ctx->fds[i], &c, sizeof(c));
    rtems_test_assert(n == 1);
    rtems_test_assert(c == 'a');

    clear_set_iflag(ctx, i, IUCLC, 0);
    input(ctx, i, 'A');

    n = read(ctx->fds[i], &c, sizeof(c));
    rtems_test_assert(n == 1);
    rtems_test_assert(c == 'A');
  }
}

static void test_icrnl(test_context *ctx)
{
  size_t i;

  for (i = 0; i < DEVICE_COUNT; ++i) {
    ssize_t n;
    char c;

    c = 'x';

    clear_set_iflag(ctx, i, 0, ICRNL);

    n = read(ctx->fds[i], &c, sizeof(c));
    rtems_test_assert(n == 0);
    rtems_test_assert(c == 'x');

    input(ctx, i, '\r');

    n = read(ctx->fds[i], &c, sizeof(c));
    rtems_test_assert(n == 1);
    rtems_test_assert(c == '\n');

    clear_set_iflag(ctx, i, ICRNL, 0);
    input(ctx, i, '\r');

    n = read(ctx->fds[i], &c, sizeof(c));
    rtems_test_assert(n == 1);
    rtems_test_assert(c == '\r');
  }
}

static void test_inlcr(test_context *ctx)
{
  size_t i;

  for (i = 0; i < DEVICE_COUNT; ++i) {
    ssize_t n;
    char c;

    c = 'x';

    clear_set_iflag(ctx, i, 0, INLCR);

    n = read(ctx->fds[i], &c, sizeof(c));
    rtems_test_assert(n == 0);
    rtems_test_assert(c == 'x');

    input(ctx, i, '\n');

    n = read(ctx->fds[i], &c, sizeof(c));
    rtems_test_assert(n == 1);
    rtems_test_assert(c == '\r');

    clear_set_iflag(ctx, i, INLCR, 0);
    input(ctx, i, '\n');

    n = read(ctx->fds[i], &c, sizeof(c));
    rtems_test_assert(n == 1);
    rtems_test_assert(c == '\n');
  }
}

static void callback(struct termios *tty, void *arg)
{
  device_context *ctx = arg;

  ++ctx->callback_counter;
}

static void test_rx_callback(test_context *ctx)
{
  size_t i = INTERRUPT;
  device_context *dev = &ctx->devices[i];
  ssize_t n;
  char buf[3];

  buf[0] = 'x';

  dev->callback_counter = 0;
  dev->tty->tty_rcv.sw_pfn = callback;
  dev->tty->tty_rcv.sw_arg = dev;
  clear_set_lflag(ctx, i, ICANON, 0);

  set_vmin_vtime(ctx, i, 0, 0);

  n = read(ctx->fds[i], buf, 1);
  rtems_test_assert(n == 0);
  rtems_test_assert(buf[0] == 'x');

  input(ctx, i, 'a');
  rtems_test_assert(dev->callback_counter == 1);

  input(ctx, i, 'b');
  rtems_test_assert(dev->callback_counter == 1);

  n = read(ctx->fds[i], buf, 2);
  rtems_test_assert(n == 2);
  rtems_test_assert(buf[0] == 'a');
  rtems_test_assert(buf[1] == 'b');

  set_vmin_vtime(ctx, i, 2, 0);

  input(ctx, i, 'd');
  rtems_test_assert(dev->callback_counter == 1);

  input(ctx, i, 'e');
  rtems_test_assert(dev->callback_counter == 2);

  input(ctx, i, 'f');
  rtems_test_assert(dev->callback_counter == 2);

  n = read(ctx->fds[i], buf, 3);
  rtems_test_assert(n == 3);
  rtems_test_assert(buf[0] == 'd');
  rtems_test_assert(buf[1] == 'e');
  rtems_test_assert(buf[2] == 'f');

  dev->tty->tty_rcv.sw_pfn = NULL;
  dev->tty->tty_rcv.sw_arg = NULL;
}

static void test_rx_callback_icanon(test_context *ctx)
{
  size_t i = INTERRUPT;
  device_context *dev = &ctx->devices[i];
  ssize_t n;
  char buf[255];
  size_t j;

  buf[0] = 'x';

  dev->callback_counter = 0;
  dev->tty->tty_rcv.sw_pfn = callback;
  dev->tty->tty_rcv.sw_arg = dev;

  set_vmin_vtime(ctx, i, 0, 0);

  n = read(ctx->fds[i], buf, 1);
  rtems_test_assert(n == 0);
  rtems_test_assert(buf[0] == 'x');

  clear_set_lflag(ctx, i, 0, ICANON);
  set_veol_veol2(ctx, i, '1', '2');

  input(ctx, i, '\n');
  rtems_test_assert(dev->callback_counter == 1);

  input(ctx, i, 'a');
  rtems_test_assert(dev->callback_counter == 1);

  input(ctx, i, '\n');
  rtems_test_assert(dev->callback_counter == 1);

  n = read(ctx->fds[i], buf, 2);
  rtems_test_assert(n == 1);
  rtems_test_assert(buf[0] == '\n');
  n = read(ctx->fds[i], buf, 3);
  rtems_test_assert(n == 2);
  rtems_test_assert(buf[0] == 'a');
  rtems_test_assert(buf[1] == '\n');

  input(ctx, i, '\4');
  rtems_test_assert(dev->callback_counter == 2);

  input(ctx, i, 'b');
  rtems_test_assert(dev->callback_counter == 2);

  input(ctx, i, '\n');
  rtems_test_assert(dev->callback_counter == 2);

  n = read(ctx->fds[i], buf, 1);
  rtems_test_assert(n == 0);

  n = read(ctx->fds[i], buf, 3);
  rtems_test_assert(n == 2);
  rtems_test_assert(buf[0] == 'b');
  rtems_test_assert(buf[1] == '\n');

  /* EOL */
  input(ctx, i, '1');
  rtems_test_assert(dev->callback_counter == 3);

  input(ctx, i, 'c');
  rtems_test_assert(dev->callback_counter == 3);

  input(ctx, i, '\n');
  rtems_test_assert(dev->callback_counter == 3);

  n = read(ctx->fds[i], buf, 2);
  rtems_test_assert(n == 1);
  rtems_test_assert(buf[0] == '1');
  n = read(ctx->fds[i], buf, 3);
  rtems_test_assert(n == 2);
  rtems_test_assert(buf[0] == 'c');
  rtems_test_assert(buf[1] == '\n');

  /* EOL2 */
  input(ctx, i, '2');
  rtems_test_assert(dev->callback_counter == 4);

  input(ctx, i, 'd');
  rtems_test_assert(dev->callback_counter == 4);

  input(ctx, i, '\n');
  rtems_test_assert(dev->callback_counter == 4);

  n = read(ctx->fds[i], buf, 2);
  rtems_test_assert(n == 1);
  rtems_test_assert(buf[0] == '2');
  n = read(ctx->fds[i], buf, 3);
  rtems_test_assert(n == 2);
  rtems_test_assert(buf[0] == 'd');
  rtems_test_assert(buf[1] == '\n');

  for (j = 0; j < 255; ++j) {
    input(ctx, i, 'e');
    rtems_test_assert(dev->callback_counter == 4);
  }

  /* Raw input buffer overflow */
  input(ctx, i, 'e');
  rtems_test_assert(dev->callback_counter == 5);

  n = read(ctx->fds[i], buf, 255);
  rtems_test_assert(n == 255);

  dev->tty->tty_rcv.sw_pfn = NULL;
  dev->tty->tty_rcv.sw_arg = NULL;
  set_veol_veol2(ctx, i, '\0', '\0');
  clear_set_lflag(ctx, i, ICANON, 0);
}

static void test_read_icanon(test_context *ctx, size_t i)
{
  ssize_t n;
  char buf[3];

  clear_set_lflag(ctx, i, 0, ICANON);

  input(ctx, i, 'a');
  input(ctx, i, '\n');
  input(ctx, i, 'b');
  input(ctx, i, '\n');
  input(ctx, i, 'c');
  input(ctx, i, '\n');

  n = read(ctx->fds[i], buf, 3);
  rtems_test_assert(n == 2);
  rtems_test_assert(buf[0] == 'a');
  rtems_test_assert(buf[1] == '\n');

  n = read(ctx->fds[i], buf, 3);
  rtems_test_assert(n == 2);
  rtems_test_assert(buf[0] == 'b');
  rtems_test_assert(buf[1] == '\n');

  n = read(ctx->fds[i], buf, 3);
  rtems_test_assert(n == 2);
  rtems_test_assert(buf[0] == 'c');
  rtems_test_assert(buf[1] == '\n');

  clear_set_lflag(ctx, i, ICANON, 0);
}

static void flush_output(test_context *ctx, size_t i)
{
  if (i == INTERRUPT) {
    device_context *dev = &ctx->devices[i];
    int left;

    do {
      left = rtems_termios_dequeue_characters(dev->tty, dev->output_pending);
    } while (left > 0);
  }
}

static void clear_output(test_context *ctx, size_t i)
{
  device_context *dev = &ctx->devices[i];

  flush_output(ctx, i);
  dev->output_count = 0;
  memset(&dev->output_buf, 0, OUTPUT_BUFFER_SIZE);
}

static void test_onlret(test_context *ctx)
{
  tcflag_t oflags = OPOST | ONLRET;
  size_t i;

  for (i = 0; i < DEVICE_COUNT; ++i) {
    device_context *dev = &ctx->devices[i];
    char c;
    ssize_t n;

    dev->tty->column = 0;
    clear_output(ctx, i);

    clear_set_oflag(ctx, i, 0, oflags);

    c = 'a';
    n = write(ctx->fds[i], &c, sizeof(c));
    rtems_test_assert(n == 1);
    rtems_test_assert(dev->tty->column == 1);
    flush_output(ctx, i);
    rtems_test_assert(dev->output_count == 1);
    rtems_test_assert(dev->output_buf[0] == 'a');

    c = '\n';
    n = write(ctx->fds[i], &c, sizeof(c));
    rtems_test_assert(n == 1);
    rtems_test_assert(dev->tty->column == 0);
    flush_output(ctx, i);
    rtems_test_assert(dev->output_count == 2);
    rtems_test_assert(dev->output_buf[1] == '\n');

    clear_set_oflag(ctx, i, oflags, 0);
  }
}

static void test_onlcr(test_context *ctx)
{
  tcflag_t oflags = OPOST | ONLCR;
  size_t i;

  for (i = 0; i < DEVICE_COUNT; ++i) {
    device_context *dev = &ctx->devices[i];
    char c;
    ssize_t n;

    dev->tty->column = 0;
    clear_output(ctx, i);

    clear_set_oflag(ctx, i, 0, oflags);

    c = 'a';
    n = write(ctx->fds[i], &c, sizeof(c));
    rtems_test_assert(n == 1);
    rtems_test_assert(dev->tty->column == 1);
    flush_output(ctx, i);
    rtems_test_assert(dev->output_count == 1);
    rtems_test_assert(dev->output_buf[0] == 'a');

    c = '\n';
    n = write(ctx->fds[i], &c, sizeof(c));
    rtems_test_assert(n == 1);
    rtems_test_assert(dev->tty->column == 0);
    flush_output(ctx, i);
    rtems_test_assert(dev->output_count == 3);
    rtems_test_assert(dev->output_buf[1] == '\r');
    rtems_test_assert(dev->output_buf[2] == '\n');

    clear_set_oflag(ctx, i, oflags, 0);
  }
}

static void test_onocr(test_context *ctx)
{
  tcflag_t oflags = OPOST | ONOCR;
  size_t i;

  for (i = 0; i < DEVICE_COUNT; ++i) {
    device_context *dev = &ctx->devices[i];
    char c;
    ssize_t n;

    dev->tty->column = 0;
    clear_output(ctx, i);

    clear_set_oflag(ctx, i, 0, oflags);

    c = '\r';
    n = write(ctx->fds[i], &c, sizeof(c));
    rtems_test_assert(n == 1);
    rtems_test_assert(dev->tty->column == 0);
    flush_output(ctx, i);
    rtems_test_assert(dev->output_count == 0);

    c = 'a';
    n = write(ctx->fds[i], &c, sizeof(c));
    rtems_test_assert(n == 1);
    rtems_test_assert(dev->tty->column == 1);
    flush_output(ctx, i);
    rtems_test_assert(dev->output_count == 1);
    rtems_test_assert(dev->output_buf[0] == 'a');

    c = '\r';
    n = write(ctx->fds[i], &c, sizeof(c));
    rtems_test_assert(n == 1);
    rtems_test_assert(dev->tty->column == 0);
    flush_output(ctx, i);
    rtems_test_assert(dev->output_count == 2);
    rtems_test_assert(dev->output_buf[1] == '\r');

    clear_set_oflag(ctx, i, oflags, 0);
  }
}

static void test_ocrnl(test_context *ctx)
{
  tcflag_t oflags = OPOST | OCRNL;
  size_t i;

  for (i = 0; i < DEVICE_COUNT; ++i) {
    device_context *dev = &ctx->devices[i];
    char c;
    ssize_t n;

    dev->tty->column = 0;
    clear_output(ctx, i);

    clear_set_oflag(ctx, i, 0, oflags);

    c = '\r';
    n = write(ctx->fds[i], &c, sizeof(c));
    rtems_test_assert(n == 1);
    rtems_test_assert(dev->tty->column == 0);
    flush_output(ctx, i);
    rtems_test_assert(dev->output_count == 1);
    rtems_test_assert(dev->output_buf[0] == '\n');

    clear_set_oflag(ctx, i, oflags, 0);
  }
}

static void test_ocrnl_onlret(test_context *ctx)
{
  tcflag_t oflags = OPOST | OCRNL | ONLRET;
  size_t i;

  for (i = 0; i < DEVICE_COUNT; ++i) {
    device_context *dev = &ctx->devices[i];
    char c;
    ssize_t n;

    dev->tty->column = 0;
    clear_output(ctx, i);

    clear_set_oflag(ctx, i, 0, oflags);

    c = 'a';
    n = write(ctx->fds[i], &c, sizeof(c));
    rtems_test_assert(n == 1);
    rtems_test_assert(dev->tty->column == 1);
    flush_output(ctx, i);
    rtems_test_assert(dev->output_count == 1);
    rtems_test_assert(dev->output_buf[0] == 'a');

    c = '\r';
    n = write(ctx->fds[i], &c, sizeof(c));
    rtems_test_assert(n == 1);
    rtems_test_assert(dev->tty->column == 0);
    flush_output(ctx, i);
    rtems_test_assert(dev->output_count == 2);
    rtems_test_assert(dev->output_buf[1] == '\n');

    clear_set_oflag(ctx, i, oflags, 0);
  }
}

static void test_opost(test_context *ctx)
{
  tcflag_t oflags = OPOST;
  size_t i;

  for (i = 0; i < DEVICE_COUNT; ++i) {
    device_context *dev = &ctx->devices[i];
    char c;
    ssize_t n;

    dev->tty->column = 0;
    clear_output(ctx, i);

    clear_set_oflag(ctx, i, 0, oflags);

    c = 'a';
    n = write(ctx->fds[i], &c, sizeof(c));
    rtems_test_assert(n == 1);
    rtems_test_assert(dev->tty->column == 1);
    flush_output(ctx, i);
    rtems_test_assert(dev->output_count == 1);
    rtems_test_assert(dev->output_buf[0] == 'a');

    c = '\33';
    n = write(ctx->fds[i], &c, sizeof(c));
    rtems_test_assert(n == 1);
    rtems_test_assert(dev->tty->column == 1);
    flush_output(ctx, i);
    rtems_test_assert(dev->output_count == 2);
    rtems_test_assert(dev->output_buf[1] == '\33');

    c = '\t';
    n = write(ctx->fds[i], &c, sizeof(c));
    rtems_test_assert(n == 1);
    rtems_test_assert(dev->tty->column == 8);
    flush_output(ctx, i);
    rtems_test_assert(dev->output_count == 3);
    rtems_test_assert(dev->output_buf[2] == '\t');

    c = '\b';
    n = write(ctx->fds[i], &c, sizeof(c));
    rtems_test_assert(n == 1);
    rtems_test_assert(dev->tty->column == 7);
    flush_output(ctx, i);
    rtems_test_assert(dev->output_count == 4);
    rtems_test_assert(dev->output_buf[3] == '\b');

    c = '\r';
    n = write(ctx->fds[i], &c, sizeof(c));
    rtems_test_assert(n == 1);
    rtems_test_assert(dev->tty->column == 0);
    flush_output(ctx, i);
    rtems_test_assert(dev->output_count == 5);
    rtems_test_assert(dev->output_buf[4] == '\r');

    clear_set_oflag(ctx, i, oflags, 0);
  }
}

static void test_xtabs(test_context *ctx)
{
  tcflag_t oflags = OPOST | OXTABS;
  size_t i;

  for (i = 0; i < DEVICE_COUNT; ++i) {
    device_context *dev = &ctx->devices[i];
    char c;
    ssize_t n;

    dev->tty->column = 0;
    clear_output(ctx, i);

    clear_set_oflag(ctx, i, 0, oflags);

    c = 'a';
    n = write(ctx->fds[i], &c, sizeof(c));
    rtems_test_assert(n == 1);
    rtems_test_assert(dev->tty->column == 1);
    flush_output(ctx, i);
    rtems_test_assert(dev->output_count == 1);
    rtems_test_assert(dev->output_buf[0] == 'a');

    c = '\t';
    n = write(ctx->fds[i], &c, sizeof(c));
    rtems_test_assert(n == 1);
    rtems_test_assert(dev->tty->column == 8);
    flush_output(ctx, i);
    rtems_test_assert(dev->output_count == 8);
    rtems_test_assert(dev->output_buf[1] == ' ');
    rtems_test_assert(dev->output_buf[2] == ' ');
    rtems_test_assert(dev->output_buf[3] == ' ');
    rtems_test_assert(dev->output_buf[4] == ' ');
    rtems_test_assert(dev->output_buf[5] == ' ');
    rtems_test_assert(dev->output_buf[6] == ' ');
    rtems_test_assert(dev->output_buf[7] == ' ');

    clear_set_oflag(ctx, i, oflags, 0);
  }
}

static void test_olcuc(test_context *ctx)
{
  tcflag_t oflags = OPOST | OLCUC;
  size_t i;

  for (i = 0; i < DEVICE_COUNT; ++i) {
    device_context *dev = &ctx->devices[i];
    char c;
    ssize_t n;

    dev->tty->column = 0;
    clear_output(ctx, i);

    clear_set_oflag(ctx, i, 0, oflags);

    c = 'a';
    n = write(ctx->fds[i], &c, sizeof(c));
    rtems_test_assert(n == 1);
    rtems_test_assert(dev->tty->column == 1);
    flush_output(ctx, i);
    rtems_test_assert(dev->output_count == 1);
    rtems_test_assert(dev->output_buf[0] == 'A');

    c = 'B';
    n = write(ctx->fds[i], &c, sizeof(c));
    rtems_test_assert(n == 1);
    rtems_test_assert(dev->tty->column == 2);
    flush_output(ctx, i);
    rtems_test_assert(dev->output_count == 2);
    rtems_test_assert(dev->output_buf[1] == 'B');

    c = '9';
    n = write(ctx->fds[i], &c, sizeof(c));
    rtems_test_assert(n == 1);
    rtems_test_assert(dev->tty->column == 3);
    flush_output(ctx, i);
    rtems_test_assert(dev->output_count == 3);
    rtems_test_assert(dev->output_buf[2] == '9');

    clear_set_oflag(ctx, i, oflags, 0);
  }
}

static void
set_self_prio(rtems_task_priority prio)
{
  rtems_status_code sc;

  sc = rtems_task_set_priority(RTEMS_SELF, prio, &prio);
  rtems_test_assert(sc == RTEMS_SUCCESSFUL);
}

static void flush_task(rtems_task_argument arg)
{
  test_context *ctx = (test_context *) arg;

  while (true) {
    set_self_prio(1);
    flush_output(ctx, INTERRUPT);
    set_self_prio(2);
  }
}

static void test_write(test_context *ctx)
{
  tcflag_t oflags = OPOST | ONLCR | OXTABS;
  rtems_status_code sc;
  size_t i = INTERRUPT;
  device_context *dev = &ctx->devices[i];
  char buf[OUTPUT_BUFFER_SIZE];
  ssize_t n;

  ctx->context_switch_counter = 0;

  sc = rtems_task_create(
    rtems_build_name('F', 'L', 'S', 'H'),
    2,
    RTEMS_MINIMUM_STACK_SIZE,
    RTEMS_DEFAULT_MODES,
    RTEMS_DEFAULT_ATTRIBUTES,
    &ctx->flush_task_id
  );
  rtems_test_assert(sc == RTEMS_SUCCESSFUL);

  sc = rtems_task_start(
    ctx->flush_task_id,
    flush_task,
    (rtems_task_argument) ctx
  );
  rtems_test_assert(sc == RTEMS_SUCCESSFUL);

  clear_output(ctx, i);
  memset(buf, 'a', OUTPUT_BUFFER_SIZE);

  n = write(ctx->fds[i], &buf[0], OUTPUT_BUFFER_SIZE);
  rtems_test_assert(n == OUTPUT_BUFFER_SIZE - 1);

  rtems_test_assert(ctx->context_switch_counter == 0);

  enable_non_blocking(ctx, i, true);
  n = write(ctx->fds[i], &buf[OUTPUT_BUFFER_SIZE - 1], 1);
  rtems_test_assert(n == 0);

  enable_non_blocking(ctx, i, false);
  n = write(ctx->fds[i], &buf[OUTPUT_BUFFER_SIZE - 1], 1);
  rtems_test_assert(n == 1);

  rtems_test_assert(ctx->context_switch_counter == 2);
  rtems_test_assert(dev->output_count == OUTPUT_BUFFER_SIZE);
  rtems_test_assert(memcmp(dev->output_buf, buf, OUTPUT_BUFFER_SIZE) == 0);

  clear_set_oflag(ctx, i, 0, oflags);

  /* Ensure that ONLCR output expansion is taken into account */

  dev->tty->column = 0;
  clear_output(ctx, i);
  memset(buf, 'b', OUTPUT_BUFFER_SIZE - 1);
  buf[OUTPUT_BUFFER_SIZE - 2] = '\n';

  n = write(ctx->fds[i], &buf[0], OUTPUT_BUFFER_SIZE - 3);
  rtems_test_assert(n == OUTPUT_BUFFER_SIZE - 3);

  rtems_test_assert(ctx->context_switch_counter == 2);

  n = write(ctx->fds[i], &buf[OUTPUT_BUFFER_SIZE - 3], 2);
  rtems_test_assert(n == 1);

  rtems_test_assert(ctx->context_switch_counter == 2);

  enable_non_blocking(ctx, i, true);
  n = write(ctx->fds[i], &buf[OUTPUT_BUFFER_SIZE - 2], 1);
  rtems_test_assert(n == 0);

  enable_non_blocking(ctx, i, false);
  n = write(ctx->fds[i], &buf[OUTPUT_BUFFER_SIZE - 2], 1);
  rtems_test_assert(n == 1);

  rtems_test_assert(ctx->context_switch_counter == 4);
  rtems_test_assert(dev->output_count == OUTPUT_BUFFER_SIZE);
  buf[OUTPUT_BUFFER_SIZE - 2] = '\r';
  buf[OUTPUT_BUFFER_SIZE - 1] = '\n';
  rtems_test_assert(memcmp(dev->output_buf, buf, OUTPUT_BUFFER_SIZE) == 0);

  /* Ensure that OXTABS output expansion is taken into account */

  dev->tty->column = 0;
  clear_output(ctx, i);
  memset(buf, 'c', OUTPUT_BUFFER_SIZE - 8);
  buf[OUTPUT_BUFFER_SIZE - 8] = '\t';

  n = write(ctx->fds[i], &buf[0], OUTPUT_BUFFER_SIZE - 9);
  rtems_test_assert(n == OUTPUT_BUFFER_SIZE - 9);

  rtems_test_assert(ctx->context_switch_counter == 4);

  n = write(ctx->fds[i], &buf[OUTPUT_BUFFER_SIZE - 9], 2);
  rtems_test_assert(n == 1);

  rtems_test_assert(ctx->context_switch_counter == 4);

  enable_non_blocking(ctx, i, true);
  n = write(ctx->fds[i], &buf[OUTPUT_BUFFER_SIZE - 8], 1);
  rtems_test_assert(n == 0);

  enable_non_blocking(ctx, i, false);
  n = write(ctx->fds[i], &buf[OUTPUT_BUFFER_SIZE - 8], 1);
  rtems_test_assert(n == 1);

  rtems_test_assert(ctx->context_switch_counter == 6);
  rtems_test_assert(dev->output_count == OUTPUT_BUFFER_SIZE);
  memset(&buf[OUTPUT_BUFFER_SIZE - 8], ' ', 8);
  rtems_test_assert(memcmp(dev->output_buf, buf, OUTPUT_BUFFER_SIZE) == 0);

  clear_set_oflag(ctx, i, oflags, 0);

  sc = rtems_task_delete(ctx->flush_task_id);
  rtems_test_assert(sc == RTEMS_SUCCESSFUL);
}

static void test_tx_callback(test_context *ctx)
{
  size_t i = INTERRUPT;
  device_context *dev = &ctx->devices[i];
  char c;
  ssize_t n;

  clear_output(ctx, i);
  dev->callback_counter = 0;
  dev->tty->tty_snd.sw_pfn = callback;
  dev->tty->tty_snd.sw_arg = dev;

  c = 'a';
  n = write(ctx->fds[i], &c, sizeof(c));
  rtems_test_assert(n == 1);
  rtems_test_assert(dev->callback_counter == 0);
  flush_output(ctx, i);
  rtems_test_assert(dev->callback_counter == 1);
  rtems_test_assert(dev->output_count == 1);
  rtems_test_assert(dev->output_buf[0] == 'a');

  dev->tty->tty_snd.sw_pfn = NULL;
  dev->tty->tty_snd.sw_arg = NULL;
}

static void Init(rtems_task_argument arg)
{
  test_context *ctx = &test_instance;

  TEST_BEGIN();

  setup(ctx);
  test_igncr(ctx);
  test_istrip(ctx);
  test_iuclc(ctx);
  test_icrnl(ctx);
  test_inlcr(ctx);
  test_rx_callback(ctx);
  test_rx_callback_icanon(ctx);
  test_read_icanon(ctx, INTERRUPT);
  test_read_icanon(ctx, POLLED);
  test_onlret(ctx);
  test_onlcr(ctx);
  test_onocr(ctx);
  test_ocrnl(ctx);
  test_ocrnl_onlret(ctx);
  test_opost(ctx);
  test_xtabs(ctx);
  test_olcuc(ctx);
  test_write(ctx);
  test_tx_callback(ctx);

  TEST_END();
  rtems_test_exit(0);
}

static void switch_extension(Thread_Control *executing, Thread_Control *heir)
{
  test_context *ctx = &test_instance;

  ++ctx->context_switch_counter;
}

#define CONFIGURE_APPLICATION_NEEDS_CLOCK_DRIVER
#define CONFIGURE_APPLICATION_NEEDS_SIMPLE_CONSOLE_DRIVER

#define CONFIGURE_MAXIMUM_FILE_DESCRIPTORS 5

#define CONFIGURE_MAXIMUM_TASKS 2

#define CONFIGURE_MAXIMUM_SEMAPHORES 7

#define CONFIGURE_INITIAL_EXTENSIONS \
  { .thread_switch = switch_extension }, \
  RTEMS_TEST_INITIAL_EXTENSION

#define CONFIGURE_RTEMS_INIT_TASKS_TABLE

#define CONFIGURE_INIT

#include <rtems/confdefs.h>