Joel Sherrrill <joel.sherrill@oarcorp.com>

	* bsp_howto/Makefile.am, bsp_howto/console.t: Sebastian improved
	 documentation for termios device drivers.
	* bsp_howto/TERMIOSFlow.eps, bsp_howto/TERMIOSFlow.png: New files.
	Joel added Termios Flow figure from RTEMS Open Class material.
This commit is contained in:
Joel Sherrill
2008-12-11 15:51:13 +00:00
parent 0cbc1501c4
commit 850bad68cc
5 changed files with 26054 additions and 171 deletions

View File

@@ -19,7 +19,8 @@ GENERATED_FILES = intro.texi target.texi makefiles.texi linkcmds.texi \
COMMON_FILES += $(top_srcdir)/common/cpright.texi
PNG_FILES = Developer-User-Timeline.png BSPInitFlowchart-49.png
PNG_FILES = Developer-User-Timeline.png BSPInitFlowchart-49.png \
TERMIOSFlow.png
if USE_HTML
html_project_DATA += $(PNG_FILES)

25574
doc/bsp_howto/TERMIOSFlow.eps Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

@@ -1,5 +1,5 @@
@c
@c COPYRIGHT (c) 1988-2002.
@c COPYRIGHT (c) 1988-2008.
@c On-Line Applications Research Corporation (OAR).
@c All rights reserved.
@c
@@ -36,9 +36,17 @@ of data, but Termios permits having only one driver.
@section Termios
Termios is a standard for terminal management, included in the POSIX 1003.1b
standard. It is commonly provided on UNIX implementations.
Having RTEMS support for Termios is beneficial:
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 @uref{http://opengroup.org/onlinepubs/007908775/xbd/termios.html
,http://opengroup.org/onlinepubs/007908775/xbd/termios.html}.
The requirements for the @code{<termios.h>} file are also provided
and are at @uref{http://opengroup.org/onlinepubs/007908775/xsh/termios.h.html,
http://opengroup.org/onlinepubs/007908775/xsh/termios.h.html}.
Having RTEMS support for Termios is beneficial because:
@itemize @bullet
@@ -51,6 +59,10 @@ developer from dealing with buffer states and mutual exclusions on them.
Early RTEMS console device drivers also did their own special
character processing.
@item it is part of an internationally recognized standard.
@item it makes porting code from other environments easier.
@end itemize
Termios support includes:
@@ -118,9 +130,24 @@ before the first interrupt will occur.
The following Figure shows how a Termios driven serial driver works:
@example
Figure not included in this draft
@end example
@ifset use-ascii
@center Figure not included in ASCII version
@end ifset
@ifset use-tex
@sp1
@center{@image{TERMIOSFlow,,6in}}
@end ifset
@ifset use-html
@html
<P ALIGN="center"><IMG SRC="TERMIOSFlow.png"
WIDTH=800 HEIGHT=610 ALT="Termios Flow"></P>
@end html
@end ifset
The most significant five bits are the object class. The next
three bits indicate the API to which the object class belongs.
The following list describes the basic flow.
@@ -129,249 +156,522 @@ The following list describes the basic flow.
@item the application programmer uses standard C library call (printf,
scanf, read, write, etc.),
@item C library (in fact that's Cygnus Newlib) calls RTEMS
system call interface. This code can be found in the
@code{c/src/lib/libc} directory.
@item C library (e.g. RedHat (formerly Cygnus) Newlib) calls
the RTEMS system call interface. This code can be found in the
@code{cpukit/libcsupport/src} directory.
@item Glue code calls the serial driver entry routines.
@end itemize
@subsection Termios and Polled I/O
@subsection Basics
You need to include the following header files in your Termios device driver
source file:
@example
@group
#include <unistd.h>
#include <termios.h>
#include <rtems.h>
#include <rtems/libio.h>
#include <rtems/console.h>
@end group
@end example
You need to provide a data structure for the Termios driver interface. The
functions are described later in this chapter. The functions should return
zero on succes and minus one in case of an error. Currently the return value
will be not checked from the Termios infrastructure in most cases. One notable
exception is the polled read function, here is the return value important.
If you want to use polled IO it should look like the following. You may also
have a look at @code{c/src/lib/libbsp/shared/console-polled.c} for a shared
implementation of the basic framework. Termios must be told the addresses of
the functions that are to be used for simple character IO, i.e. pointers to the
@code{my_driver_poll_read} and @code{my_driver_poll_write} functions described
later in @ref{Console Driver Termios and Polled IO}.
@example
@group
static const rtems_termios_callbacks my_driver_callbacks_polled = @{
.firstOpen = my_driver_first_open,
.lastClose = my_driver_last_close,
.pollRead = my_driver_poll_read,
.write = my_driver_poll_write,
.setAttributes = my_driver_set_attributes,
.stopRemoteTx = NULL,
.startRemoteTx = NULL,
.outputUsesInterrupts = TERMIOS_POLLED
@};
@end group
@end example
For an interrupt driven implementation you need the following. The driver
functioning is quite different in this mode. There is no device driver read
function to be passed to Termios. Indeed a @code{console_read} call returns
the contents of Termios input buffer. This buffer is filled in the driver
interrupt subroutine, see also
@ref{Console Driver Termios and Interrupt Driven IO}.
The driver is responsible for providing a pointer to the
@code{my_driver_interrupt_write} function.
@example
@group
static const rtems_termios_callbacks my_driver_callbacks_interrupt = @{
.firstOpen = my_driver_first_open,
.lastClose = my_driver_last_close,
.pollRead = NULL,
.write = my_driver_interrupt_write,
.setAttributes = my_driver_set_attributes,
.stopRemoteTx = NULL,
.startRemoteTx = NULL,
.outputUsesInterrupts = TERMIOS_IRQ_DRIVEN
@};
@end group
@end example
You can also provide callback functions for remote transmission control. This
is not covered in this manual, so thay are set to @code{NULL} in the above
examples.
Normally the device specific data structures are stored in a table which is
indexed by the minor number. You may need an entry for the Termios handler
pointer in your data structure. For simplicity of the console initialization
example the device name is also present.
@example
@group
/* Driver specific data structure */
typedef struct @{
const char *device_name;
struct rtems_termios_tty *tty;
@} my_driver_entry;
/*
* This table contains the driver specific data. It is later
* indexed by the minor number.
*/
static my_driver_entry my_driver_table [MY_DRIVER_DEVICE_NUMBER];
@end group
@end example
@subsection Termios and Polled IO
The following functions are provided by the driver and invoked by
Termios for simple character input/output. The specific names of
these routines are not important as Termios invokes them indirectly
via function pointers.
Termios for simple character IO.
@subsubsection pollWrite
The @code{pollWrite} routine is responsible for writing @code{len} characters
from @code{buf} to the serial device specified by @code{minor}.
The @code{my_driver_poll_write} routine is responsible for writing @code{n}
characters from @code{buf} to the serial device specified by @code{minor}.
@example
@group
int pollWrite (int minor, const char *buf, int len)
static int my_driver_poll_write(int minor, const char *buf, int n)
@{
for (i=0; i<len; i++) @{
put buf[i] into the UART channel minor
wait for the character to be transmitted
on the serial line
@}
return 0
my_driver_entry *e = &my_driver_table [minor];
int i = 0;
/*
* There is no need to check the minor number since it is derived
* from a file descriptor. The upper layer takes care that it is
* in a valid range.
*/
/* Write */
for (i = 0; i < n; ++i) @{
my_driver_write_char(e, buf [i]);
@}
return 0;
@}
@end group
@end example
@subsubsection pollRead
The @code{pollRead} routine is responsible for reading a single character
from the serial device specified by @code{minor}. If no character is
available, then the routine should return -1.
The @code{my_driver_poll_read} routine is responsible for reading a single
character from the serial device specified by @code{minor}. If no character is
available, then the routine should return minus one.
@example
@group
int pollRead(int minor)
static int my_driver_poll_read(int minor)
@{
read status of UART
if status indicates a character is available
return character
return -1
my_driver_entry *e = &my_driver_table [minor];
/*
* There is no need to check the minor number since it is derived
* from a file descriptor. The upper layer takes care that it is
* in a valid range.
*/
/* Check if a character is available */
if (my_driver_can_read_char(e)) @{
/* Return the character */
return my_driver_read_char(e);
@} else @{
/* Return an error status */
return -1;
@}
@}
@end group
@end example
@subsection Termios and Interrupt Driven I/O
@subsection 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 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.
@subsubsection InterruptHandler
The @code{InterruptHandler} 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
The @code{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 @code{InterruptHandler} will have to check
the status of the UART and determine what caused the interrupt.
The following describes the operation of an @code{InterruptHandler}
which has to do this:
In the simplest case, the @code{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 @code{my_driver_interrupt_handler} which has to
do this:
@example
@group
rtems_isr InterruptHandler (rtems_vector_number v)
static void my_driver_interrupt_handler(
rtems_vector_number vector,
void *arg
)
@{
check whether there was an error
my_driver_entry *e = (my_driver_entry *) arg;
char buf [N];
int n = 0;
if some characters were received:
Ask Termios to put them on his input buffer
/*
* 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(e, buf, N);
if (n > 0) @{
/* Hand the data over to the Termios infrastructure */
rtems_termios_enqueue_raw_characters(e->tty, buf, n);
@}
if some characters have been transmitted
(i.e. the UART output buffer is empty)
Tell TERMIOS that the characters have been
transmitted. The TERMIOS routine will call
the InterruptWrite function with the number
of characters not transmitted yet if it is
not zero.
/*
* 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(e);
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(e->tty, n);
@}
@}
@end group
@end example
@subsubsection InterruptWrite
The @code{InterruptWrite} is responsible for telling the UART
that the @code{len} characters at @code{buf} are to be transmitted.
The @code{my_driver_interrupt_write} function is responsible for telling the
device that the @code{n} characters at @code{buf} are to be transmitted. The
return value may be arbitrary since it is not checked from Termios.
@example
static int InterruptWrite(int minor, const char *buf, int len)
@group
static int my_driver_interrupt_write(int minor, const char *buf, int n)
@{
tell the UART to transmit len characters from buf
return 0
my_driver_entry *e = &my_driver_table [minor];
/*
* There is no need to check the minor number since it is derived
* from a file descriptor. The upper layer takes care that it is
* in a valid range.
*/
/*
* Tell the device to transmit some characters from buf (less than
* or equal to n). If 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.
*/
return 0;
@}
@end group
@end example
The driver has to put the @i{n} first buf characters in the UART channel minor
buffer (@i{n} is the UART channel size, @i{n}=1 on the MC68640). Generally, an
interrupt is raised after these @i{n} characters being transmitted. So
UART interrupts may have to be enabled after putting the characters in the
UART.
@subsection Initialization
The driver initialization is called once during the RTEMS initialization
process.
The @code{console_initialize} function has to:
@itemize @bullet
@item initialize Termios support: call @code{rtems_termios_initialize()}. If
Termios has already been initialized by another device driver, then
this call will have no effect.
@item Initialize the UART: This procedure should
be described in the UART manual. This procedure @b{MUST} be
followed precisely. This procedure varies but
usually consists of:
@itemize @bullet
@item reinitialize the UART channels
@item set the channels configuration to the Termios default:
9600 bauds, no parity, 1 stop bit, and 8 bits per character
@end itemize
@item If interrupt driven, register the console interrupt routine to RTEMS:
The @code{console_initialize} function may look like this:
@example
rtems_interrupt_catch(
InterruptHandler, CONSOLE_VECTOR, &old_handler);
@group
rtems_device_driver console_initialize(
rtems_device_major_number major,
rtems_device_minor_number minor,
void *arg
)
@{
rtems_status_code sc = RTEMS_SUCCESSFUL;
rtems_device_minor_number i = 0;
/*
* 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 (i = 0; i < MY_DRIVER_DEVICE_NUMBER; ++i) @{
my_driver_entry *e = &my_driver_table [i];
/*
* Register this device in the file system. In order to use the
* console (i.e. being able to do printf, scanf etc. on stdin,
* stdout and stderr), some device must be registered
* as "/dev/console" (CONSOLE_DEVICE_NAME).
*/
sc = rtems_io_register_name (e->device_name, major, i);
RTEMS_CHECK_SC(sc, "Register IO device");
/*
* Initialize this device and install the interrupt handler if
* necessary. You may also initialize the device in the first
* open call.
*/
@}
return RTEMS_SUCCESSFUL;
@}
@end group
@end example
@item enable the UART channels.
@item register the device name: in order to use the console (i.e. being
able to do printf/scanf on stdin, stdout, and stderr), some device
must be registered as "/dev/console":
@example
rtems_io_register_name ("dev/console", major, i);
@end example
@end itemize
@subsection Opening a serial device
The @code{console_open} function is called whenever a serial
device is opened. The device registered as @code{"/dev/console"}
is opened automatically during RTEMS initialization.
For instance, if UART channel 2 is registered as "/dev/tty1",
the @code{console_open} entry point will be called as
the result of an @code{fopen("/dev/tty1", mode)} in the
The @code{console_open} function is called whenever a serial device is opened.
The device registered as @code{"/dev/console"} (@code{CONSOLE_DEVICE_NAME}) is
opened automatically during RTEMS initialization. For instance, if UART
channel 2 is registered as "/dev/tty1", the @code{console_open} entry point
will be called as the result of an @code{fopen("/dev/tty1", mode)} in the
application.
The @code{console_open} function has to inform Termios of the low-level
functions for serial line support; the "callbacks".
functions for serial line support.
The gen68340 BSP defines two sets of callback tables:
@example
@group
rtems_device_driver console_open(
rtems_device_major_number major,
rtems_device_minor_number minor,
void *arg
)
@{
struct rtems_termios_callbacks *callbacks =
&my_driver_callbacks_polled;
@itemize @bullet
/*
* Check the minor number. Termios does currently not check
* the return value of the first open call so the minor
* number must be checked here.
*/
if (MY_DRIVER_IS_MINOR_INVALID(minor)) @{
return RTEMS_INVALID_NUMBER;
@}
@item one with functions for polled input/output
/*
* Depending on the IO mode you need to pass a different set of
* callback functions to Termios.
*/
if (MY_DRIVER_USES_INTERRUPTS(minor)) @{
callbacks = &my_driver_callbacks_interrupt;
@}
@item another with functions for interrupt driven input/output
return rtems_termios_open(major, minor, arg, callbacks);
@}
@end group
@end example
@end itemize
During the first open of the device Termios will call @code{my_driver_first_open}.
This code can be found in the file @code{$BSPROOT/console/console.c}.
@example
@group
static int my_driver_first_open(int major, int minor, void *arg)
@{
my_driver_entry *e = &my_driver_table [minor];
struct rtems_termios_tty *tty =
((rtems_libio_open_close_args_t *) arg)->iop->data1;
/* Check minor number */
if (MY_DRIVER_IS_MINOR_INVALID(minor)) @{
return -1;
@}
@subsubsection Polled I/O
/* Connect the TTY data structure */
e->tty = tty;
Termios must be told the addresses of the functions that are to be
used for simple character input/output, i.e. pointers to the
@code{pollWrite} and @code{pollRead} functions
defined earlier in @ref{Console Driver Termios and Polled I/O}.
/*
* You may add some initialization code here.
*/
@subsubsection Interrupt Driven I/O
Driver functioning is quite different in this mode. There is no
device driver read function to be passed to Termios. Indeed a
@code{console_read} call returns the contents of Termios input buffer.
This buffer is filled in the driver interrupt subroutine
(see @ref{Console Driver Termios and Interrupt Driven I/O}).
The driver is responsible for providing a pointer to the
@code{InterruptWrite} function.
/*
* Sets the inital baud rate. This should be set to the value of
* the boot loader.
*/
return rtems_termios_set_initial_baud(e->tty, MY_DRIVER_BAUD_RATE);
@}
@end group
@end example
@subsection Closing a Serial Device
The @code{console_close} is invoked when the serial device is to
be closed. This entry point corresponds to the device driver
close entry point.
The @code{console_close} is invoked when the serial device is to be closed.
This entry point corresponds to the device driver close entry point.
This routine is responsible for notifying Termios that the serial
device was closed. This is done with a call to @code{rtems_termios_close}.
This routine is responsible for notifying Termios that the serial device was
closed. This is done with a call to @code{rtems_termios_close}.
@subsection Reading Characters From a Serial Device
@example
@group
rtems_device_driver console_close(
rtems_device_major_number major,
rtems_device_minor_number minor,
void *arg
)
@{
return rtems_termios_close(arg);
@}
@end group
@end example
The @code{console_read} is invoked when the serial device is to
be read from. This entry point corresponds to the device driver
read entry point.
Termios will call the @code{my_driver_last_close} function if the last close
happens on the device.
@example
@group
static int my_driver_last_close(int major, int minor, void *arg)
@{
my_driver_entry *e = &my_driver_table [minor];
/*
* There is no need to check the minor number since it is derived
* from a file descriptor. The upper layer takes care that it is
* in a valid range.
*/
This routine is responsible for returning the content of the
Termios input buffer. This is done by invoking the
@code{rtems_termios_read} routine.
/* Disconnect the TTY data structure */
e->tty = NULL;
/*
* The driver may do some cleanup here.
*/
return 0;
@}
@end group
@end example
@subsection Reading Characters from a Serial Device
The @code{console_read} is invoked when the serial device is to be read from.
This entry point corresponds to the device driver read entry point.
This routine is responsible for returning the content of the Termios input
buffer. This is done by invoking the @code{rtems_termios_read} routine.
@example
@group
rtems_device_driver console_read(
rtems_device_major_number major,
rtems_device_minor_number minor,
void *arg
)
@{
return rtems_termios_read(arg);
@}
@end group
@end example
@subsection Writing Characters to a Serial Device
The @code{console_write} is invoked when the serial device is to
be written to. This entry point corresponds to the device driver
write entry point.
The @code{console_write} is invoked when the serial device is to be written to.
This entry point corresponds to the device driver write entry point.
This routine is responsible for adding the requested characters to
the Termios output queue for this device. This is done by
calling the routine @code{rtems_termios_write}
to add the characters at the end of the Termios output
buffer.
This routine is responsible for adding the requested characters to the Termios
output queue for this device. This is done by calling the routine
@code{rtems_termios_write} to add the characters at the end of the Termios
output buffer.
@example
@group
rtems_device_driver console_write(
rtems_device_major_number major,
rtems_device_minor_number minor,
void *arg
)
@{
return rtems_termios_write(arg);
@}
@end group
@end example
@subsection Changing Serial Line Parameters
The @code{console_control} 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 @code{console_control} 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 write is able to control the serial line configuration
with Termios calls (such as the @code{ioctl} command, see
the Termios documentation for
more details). If the driver is to support dynamic configuration, then
is must have the @code{console_control} piece of code. Refer to the gen68340
BSP for an example of how it is done. Basically @code{ioctl}
commands call @code{console_control} with the serial line
configuration in a Termios defined data structure. The driver
is responsible for reinitializing the UART with the correct settings.
The application writer is able to control the serial line configuration with
Termios calls (such as the @code{ioctl} command, see the Termios documentation
for more details). If the driver is to support dynamic configuration, then it
must have the @code{console_control} piece of code. Basically @code{ioctl}
commands call @code{console_control} with the serial line configuration in a
Termios defined data structure.
@example
@group
rtems_device_driver console_control(
rtems_device_major_number major,
rtems_device_minor_number minor,
void *arg
)
@{
return rtems_termios_ioctl(arg);
@}
@end group
@end example
The driver is responsible for reinitializing the device with the correct
settings. For this purpuse Termios calls the @code{my_driver_set_attributes}
function.
@example
@group
static int my_driver_set_attributes(
int minor,
const struct termios *t
)
@{
my_driver_entry *e = &my_driver_table [minor];
/*
* There is no need to check the minor number since it is derived
* from a file descriptor. The upper layer takes care that it is
* in a valid range.
*/
/*
* 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 0;
@}
@end group
@end example