From b35050917272ab536c8f4158e5c002f98a092796 Mon Sep 17 00:00:00 2001 From: Amar Takhar Date: Sun, 17 Jan 2016 00:47:50 -0500 Subject: Split document into seperate files by section. --- bsp_howto/console.rst | 552 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 552 insertions(+) create mode 100644 bsp_howto/console.rst (limited to 'bsp_howto/console.rst') diff --git a/bsp_howto/console.rst b/bsp_howto/console.rst new file mode 100644 index 0000000..13f7a6c --- /dev/null +++ b/bsp_howto/console.rst @@ -0,0 +1,552 @@ +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 ```` 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:: c + + [...] + 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:: 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:: 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. Her is an example header file. +.. code:: c + + #ifndef MY_DRIVER_H + #define MY_DRIVER_H + #include + #include + /* 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:: 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:: 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:: 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:: 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:: c + + #include + #include + #include + #include + 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:: 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:: 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:: 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; + } + +.. COMMENT: COPYRIGHT (c) 1988-2002. + +.. COMMENT: On-Line Applications Research Corporation (OAR). + +.. COMMENT: All rights reserved. + -- cgit v1.2.3