diff options
author | Chris Johns <chrisj@rtems.org> | 2016-11-03 16:58:08 +1100 |
---|---|---|
committer | Chris Johns <chrisj@rtems.org> | 2016-11-03 16:58:08 +1100 |
commit | 72a62ad88f82fe1ffee50024db4dd0f3fa5806f7 (patch) | |
tree | 6b0e527e67141f8126ba56b8a3c1eb90aeed5849 /bsp-howto/console.rst | |
parent | waf: Use separate doctrees so avoid sphinx clashes. (diff) | |
download | rtems-docs-72a62ad88f82fe1ffee50024db4dd0f3fa5806f7.tar.bz2 |
Rename all manuals with an _ to have a -. It helps released naming of files.
Diffstat (limited to 'bsp-howto/console.rst')
-rw-r--r-- | bsp-howto/console.rst | 579 |
1 files changed, 579 insertions, 0 deletions
diff --git a/bsp-howto/console.rst b/bsp-howto/console.rst new file mode 100644 index 0000000..bcca519 --- /dev/null +++ b/bsp-howto/console.rst @@ -0,0 +1,579 @@ +.. comment SPDX-License-Identifier: CC-BY-SA-4.0 + +.. COMMENT: COPYRIGHT (c) 1988-2002. +.. COMMENT: On-Line Applications Research Corporation (OAR). +.. COMMENT: All rights reserved. + +Console Driver +############## + +Introduction +============ + +This chapter describes the operation of a console driver using the RTEMS POSIX +Termios support. Traditionally RTEMS has referred to all serial device drivers +as console device drivers. A console driver can be used to do raw data +processing in addition to the "normal" standard input and output device +functions required of a console. + +The serial driver may be called as the consequence of a C Library call such as +``printf`` or ``scanf`` or directly via the``read`` or ``write`` system calls. +There are two main functioning modes: + +- console: formatted input/output, with special characters (end of line, + tabulations, etc.) recognition and processing, + +- raw: permits raw data processing. + +One may think that two serial drivers are needed to handle these two types of +data, but Termios permits having only one driver. + +Termios +======= + +Termios is a standard for terminal management, included in the POSIX 1003.1b +standard. As part of the POSIX and Open Group Single UNIX Specification, is +commonly provided on UNIX implementations. The Open Group has the termios +portion of the POSIX standard online at +http://opengroup.org/onlinepubs/007908775/xbd/termios.html. The requirements +for the ``<termios.h>`` file are also provided and are at +http://opengroup.org/onlinepubs/007908775/xsh/termios.h.html. + +Having RTEMS support for Termios is beneficial because: + +- from the user's side because it provides standard primitive operations to + access the terminal and change configuration settings. These operations are + the same under UNIX and RTEMS. + +- from the BSP developer's side because it frees the developer from dealing + with buffer states and mutual exclusions on them. Early RTEMS console device + drivers also did their own special character processing. + +- it is part of an internationally recognized standard. + +- it makes porting code from other environments easier. + +Termios support includes: + +- raw and console handling, + +- blocking or non-blocking characters receive, with or without Timeout. + +At this time, RTEMS documentation does not include a thorough discussion of the +Termios functionality. For more information on Termios, type ``man termios`` +on a Unix box or point a web browser athttp://www.freebsd.org/cgi/man.cgi. + +Driver Functioning Modes +======================== + +There are generally three main functioning modes for an UART (Universal +Asynchronous Receiver-Transmitter, i.e. the serial chip): + +- polled mode + +- interrupt driven mode + +- task driven mode + +In polled mode, the processor blocks on sending/receiving characters. This +mode is not the most efficient way to utilize the UART. But polled mode is +usually necessary when one wants to print an error message in the event of a +fatal error such as a fatal error in the BSP. This is also the simplest mode +to program. Polled mode is generally preferred if the serial port is to be +used primarily as a debug console. In a simple polled driver, the software +will continuously check the status of the UART when it is reading or writing to +the UART. Termios improves on this by delaying the caller for 1 clock tick +between successive checks of the UART on a read operation. + +In interrupt driven mode, the processor does not block on sending/receiving +characters. Data is buffered between the interrupt service routine and +application code. Two buffers are used to insulate the application from the +relative slowness of the serial device. One of the buffers is used for +incoming characters, while the other is used for outgoing characters. + +An interrupt is raised when a character is received by the UART. The interrupt +subroutine places the incoming character at the end of the input buffer. When +an application asks for input, the characters at the front of the buffer are +returned. + +When the application prints to the serial device, the outgoing characters are +placed at the end of the output buffer. The driver will place one or more +characters in the UART (the exact number depends on the UART) An interrupt will +be raised when all the characters have been transmitted. The interrupt service +routine has to send the characters remaining in the output buffer the same way. +When the transmitting side of the UART is idle, it is typically necessary to +prime the transmitter before the first interrupt will occur. + +The task driven mode is similar to interrupt driven mode, but the actual data +processing is done in dedicated tasks instead of interrupt routines. + +Serial Driver Functioning Overview +================================== + +The following Figure shows how a Termios driven serial driver works: Figure not +included in ASCII version + +The following list describes the basic flow. + +- the application programmer uses standard C library call (printf, scanf, read, + write, etc.), + +- C library (ctx.g. RedHat (formerly Cygnus) Newlib) calls the RTEMS system + call interface. This code can be found in the:file:`cpukit/libcsupport/src` + directory. + +- Glue code calls the serial driver entry routines. + +Basics +------ + +The low-level driver API changed between RTEMS 4.10 and RTEMS 4.11. The legacy +callback API is still supported, but its use is discouraged. The following +functions are deprecated: + +- ``rtems_termios_open()`` - use ``rtems_termios_device_open()`` in combination + with ``rtems_termios_device_install()`` instead. + +- ``rtems_termios_close()`` - use ``rtems_termios_device_close()`` instead. + +This manual describes the new API. A new console driver should consist of +three parts. + +- The basic console driver functions using the Termios support. Add this the + BSPs Makefile.am: + +.. code-block:: makefile + + [...] + libbsp_a_SOURCES += ../../shared/console-termios.c + [...] + +- A general serial module specific low-level driver providing the handler table + for the Termios ``rtems_termios_device_install()`` function. This low-level + driver could be used for more than one BSP. + +- A BSP specific initialization routine ``console_initialize()``, that calls + ``rtems_termios_device_install()`` providing a low-level driver context for + each installed device. + +You need to provide a device handler structure for the Termios device +interface. The functions are described later in this chapter. The first open +and set attributes handler return a boolean status to indicate success (true) +or failure (false). The polled read function returns an unsigned character in +case one is available or minus one otherwise. + +If you want to use polled IO it should look like the following. Termios must +be told the addresses of the handler that are to be used for simple character +IO, i.e. pointers to the ``my_driver_poll_read()`` and +``my_driver_poll_write()`` functions described later in `Termios and Polled +IO`_. + +.. code-block:: c + + const rtems_termios_handler my_driver_handler_polled = { + .first_open = my_driver_first_open, + .last_close = my_driver_last_close, + .poll_read = my_driver_poll_read, + .write = my_driver_poll_write, + .set_attributes = my_driver_set_attributes, + .stop_remote_tx = NULL, + .start_remote_tx = NULL, + .mode = TERMIOS_POLLED + } + +For an interrupt driven implementation you need the following. The driver +functioning is quite different in this mode. There is no device driver read +handler to be passed to Termios. Indeed a ``console_read()`` call returns the +contents of Termios input buffer. This buffer is filled in the driver +interrupt subroutine, see also `Termios and Interrupt Driven IO`_. The driver +is responsible for providing a pointer to the``my_driver_interrupt_write()`` +function. + +.. code-block:: c + + const rtems_termios_handler my_driver_handler_interrupt = { + .first_open = my_driver_first_open, + .last_close = my_driver_last_close, + .poll_read = NULL, + .write = my_driver_interrupt_write, + .set_attributes = my_driver_set_attributes, + .stopRemoteTx = NULL, + .stop_remote_tx = NULL, + .start_remote_tx = NULL, + .mode = TERMIOS_IRQ_DRIVEN + }; + +You can also provide hander for remote transmission control. This is not +covered in this manual, so they are set to ``NULL`` in the above examples. + +The low-level driver should provide a data structure for its device context. +The initialization routine must provide a context for each installed device via +``rtems_termios_device_install()``. For simplicity of the console +initialization example the device name is also present. Here is an example +header file. + +.. code-block:: c + + #ifndef MY_DRIVER_H + #define MY_DRIVER_H + + #include <rtems/termiostypes.h> + #include <some-chip-header.h> + + /* Low-level driver specific data structure */ + typedef struct { + rtems_termios_device_context base; + const char *device_name; + volatile module_register_block *regs; + /* More stuff */ + } my_driver_context; + + extern const rtems_termios_handler my_driver_handler_polled; + extern const rtems_termios_handler my_driver_handler_interrupt; + + #endif /* MY_DRIVER_H */ + +Termios and Polled IO +--------------------- + +The following handler are provided by the low-level driver and invoked by +Termios for simple character IO. + +The ``my_driver_poll_write()`` routine is responsible for writing ``n`` +characters from ``buf`` to the serial device specified by ``tty``. + +.. code-block:: c + + static void my_driver_poll_write( + rtems_termios_device_context *base, + const char *buf, + size_t n + ) + { + my_driver_context *ctx = (my_driver_context *) base; + size_t i; + /* Write */ + for (i = 0; i < n; ++i) { + my_driver_write_char(ctx, buf[i]); + } + } + +The ``my_driver_poll_read`` routine is responsible for reading a single +character from the serial device specified by ``tty``. If no character is +available, then the routine should return minus one. + +.. code-block:: c + + static int my_driver_poll_read(rtems_termios_device_context *base) + { + my_driver_context *ctx = (my_driver_context *) base; + /* Check if a character is available */ + if (my_driver_can_read_char(ctx)) { + /* Return the character */ + return my_driver_read_char(ctx); + } else { + /* Return an error status */ + return -1; + } + } + +Termios and Interrupt Driven IO +------------------------------- + +The UART generally generates interrupts when it is ready to accept or to emit a +number of characters. In this mode, the interrupt subroutine is the core of +the driver. + +The ``my_driver_interrupt_handler()`` is responsible for processing +asynchronous interrupts from the UART. There may be multiple interrupt +handlers for a single UART. Some UARTs can generate a unique interrupt vector +for each interrupt source such as a character has been received or the +transmitter is ready for another character. + +In the simplest case, the ``my_driver_interrupt_handler()`` will have to check +the status of the UART and determine what caused the interrupt. The following +describes the operation of an ``my_driver_interrupt_handler`` which has to do +this: + +.. code-block:: c + + static void my_driver_interrupt_handler( + rtems_vector_number vector, + void *arg + ) + { + rtems_termios_tty *tty = arg; + my_driver_context *ctx = rtems_termios_get_device_context(tty); + char buf[N]; + size_t n; + + /* + * Check if we have received something. The function reads the + * received characters from the device and stores them in the + * buffer. It returns the number of read characters. + */ + n = my_driver_read_received_chars(ctx, buf, N); + if (n > 0) { + /* Hand the data over to the Termios infrastructure */ + rtems_termios_enqueue_raw_characters(tty, buf, n); + } + + /* + * Check if we have something transmitted. The functions returns + * the number of transmitted characters since the last write to the + * device. + */ + n = my_driver_transmitted_chars(ctx); + if (n > 0) { + /* + * Notify Termios that we have transmitted some characters. It + * will call now the interrupt write function if more characters + * are ready for transmission. + */ + rtems_termios_dequeue_characters(tty, n); + } + } + +The ``my_driver_interrupt_write()`` function is responsible for telling the +device that the ``n`` characters at ``buf`` are to be transmitted. It the +value ``n`` is zero to indicate that no more characters are to send. The +driver can disable the transmit interrupts now. This routine is invoked either +from task context with disabled interrupts to start a new transmission process +with exactly one character in case of an idle output state or from the +interrupt handler to refill the transmitter. If the routine is invoked to +start the transmit process the output state will become busy and Termios starts +to fill the output buffer. If the transmit interrupt arises before Termios was +able to fill the transmit buffer you will end up with one interrupt per +character. + +.. code-block:: c + + static void my_driver_interrupt_write( + rtems_termios_device_context *base, + const char *buf, + size_t n + ) + { + my_driver_context *ctx = (my_driver_context *) base; + + /* + * Tell the device to transmit some characters from buf (less than + * or equal to n). When the device is finished it should raise an + * interrupt. The interrupt handler will notify Termios that these + * characters have been transmitted and this may trigger this write + * function again. You may have to store the number of outstanding + * characters in the device data structure. + */ + /* + * Termios will set n to zero to indicate that the transmitter is + * now inactive. The output buffer is empty in this case. The + * driver may disable the transmit interrupts now. + */ + } + +Initialization +-------------- + +The BSP specific driver initialization is called once during the RTEMS +initialization process. + +The ``console_initialize()`` function may look like this: + +.. code-block:: c + + #include <my-driver.h> + #include <rtems/console.h> + #include <bsp.h> + #include <bsp/fatal.h> + + static my_driver_context driver_context_table[M] = { /* Some values */ }; + + rtems_device_driver console_initialize( + rtems_device_major_number major, + rtems_device_minor_number minor, + void *arg + ) + { + rtems_status_code sc; + #ifdef SOME_BSP_USE_INTERRUPTS + const rtems_termios_handler *handler = &my_driver_handler_interrupt; + #else + const rtems_termios_handler *handler = &my_driver_handler_polled; + #endif + + /* + * Initialize the Termios infrastructure. If Termios has already + * been initialized by another device driver, then this call will + * have no effect. + */ + rtems_termios_initialize(); + + /* Initialize each device */ + for ( + minor = 0; + minor < RTEMS_ARRAY_SIZE(driver_context_table); + ++minor + ) { + my_driver_context *ctx = &driver_context_table[minor]; + + /* + * Install this device in the file system and Termios. In order + * to use the console (i.e. being able to do printf, scanf etc. + * on stdin, stdout and stderr), one device must be registered as + * "/dev/console" (CONSOLE_DEVICE_NAME). + */ + sc = rtems_termios_device_install( + ctx->device_name, + major, + minor, + handler, + NULL, + ctx + ); + if (sc != RTEMS_SUCCESSFUL) { + bsp_fatal(SOME_BSP_FATAL_CONSOLE_DEVICE_INSTALL); + } + } + + return RTEMS_SUCCESSFUL; + } + +Opening a serial device +----------------------- + +The ``console_open()`` function provided by :file:`console-termios.c` is called +whenever a serial device is opened. The device registered as +``"/dev/console"`` (``CONSOLE_DEVICE_NAME``) is opened automatically during +RTEMS initialization. For instance, if UART channel 2 is registered as +``"/dev/tty1"``, the ``console_open()`` entry point will be called as the +result of an ``fopen("/dev/tty1", mode)`` in the application. + +During the first open of the device Termios will call the +``my_driver_first_open()`` handler. + +.. code-block:: c + + static bool my_driver_first_open( + rtems_termios_tty *tty, + rtems_termios_device_context *base, + struct termios *term, + rtems_libio_open_close_args_t *args + ) + { + my_driver_context *ctx = (my_driver_context *) base; + rtems_status_code sc; + bool ok; + + /* + * You may add some initialization code here. + */ + + /* + * Sets the initial baud rate. This should be set to the value of + * the boot loader. This function accepts only exact Termios baud + * values. + */ + sc = rtems_termios_set_initial_baud(tty, MY_DRIVER_BAUD_RATE); + if (sc != RTEMS_SUCCESSFUL) { + /* Not a valid Termios baud */ + } + + /* + * Alternatively you can set the best baud. + */ + rtems_termios_set_best_baud(term, MY_DRIVER_BAUD_RATE); + + /* + * To propagate the initial Termios attributes to the device use + * this. + */ + ok = my_driver_set_attributes(base, term); + if (!ok) { + /* This is bad */ + } + + /* + * Return true to indicate a successful set attributes, and false + * otherwise. + */ + return true; + } + +Closing a Serial Device +----------------------- + +The ``console_close()`` provided by :file:`console-termios.c` is invoked when +the serial device is to be closed. This entry point corresponds to the device +driver close entry point. + +Termios will call the ``my_driver_last_close()`` handler if the last close +happens on the device. + +.. code-block:: c + + static void my_driver_last_close( + rtems_termios_tty *tty, + rtems_termios_device_context *base, + rtems_libio_open_close_args_t *args + ) + { + my_driver_context *ctx = (my_driver_context *) base; + + /* + * The driver may do some cleanup here. + */ + } + +Reading Characters from a Serial Device +--------------------------------------- + +The ``console_read()`` provided by :file:`console-termios.c` is invoked when +the serial device is to be read from. This entry point corresponds to the +device driver read entry point. + +Writing Characters to a Serial Device +------------------------------------- + +The ``console_write()`` provided by :file:`console-termios.c` is invoked when +the serial device is to be written to. This entry point corresponds to the +device driver write entry point. + +Changing Serial Line Parameters +------------------------------- + +The ``console_control()`` provided by :file:`console-termios.c` is invoked when +the line parameters for a particular serial device are to be changed. This +entry point corresponds to the device driver IO control entry point. + +The application writer is able to control the serial line configuration with +Termios calls (such as the ``ioctl()`` command, see the Termios documentation +for more details). If the driver is to support dynamic configuration, then it +must have the ``console_control()`` piece of code. Basically ``ioctl()`` +commands call ``console_control()`` with the serial line configuration in a +Termios defined data structure. + +The driver is responsible for reinitializing the device with the correct +settings. For this purpose Termios calls the ``my_driver_set_attributes()`` +handler. + +.. code-block:: c + + static bool my_driver_set_attributes( + rtems_termios_device_context *base, + const struct termios *term + ) + { + my_driver_context *ctx = (my_driver_context *) base; + + /* + * Inspect the termios data structure and configure the device + * appropriately. The driver should only be concerned with the + * parts of the structure that specify hardware setting for the + * communications channel such as baud, character size, etc. + */ + /* + * Return true to indicate a successful set attributes, and false + * otherwise. + */ + return true; + } |