mirror of
https://git.rtems.org/rtems-docs/
synced 2025-06-01 01:03:29 +08:00
553 lines
20 KiB
ReStructuredText
553 lines
20 KiB
ReStructuredText
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:: 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 <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:: 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 <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:: 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.
|
||
|