forked from Imagelibrary/rtems
doc: Update console driver documentation
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
|
||||
@section Introduction
|
||||
|
||||
This chapter describes the operation of a console driver using
|
||||
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
|
||||
@@ -16,20 +16,20 @@ of a console.
|
||||
|
||||
The serial driver may be called as the consequence of a C Library
|
||||
call such as @code{printf} or @code{scanf} or directly via the
|
||||
@code{read} or @code{write} system calls.
|
||||
There are two main functioning modes:
|
||||
@code{read} or @code{write} system calls.
|
||||
There are two main functioning modes:
|
||||
|
||||
@itemize @bullet
|
||||
|
||||
@item console: formatted input/output, with special characters (end of
|
||||
line, tabulations, etc.) recognition and processing,
|
||||
|
||||
@item raw: permits raw data processing.
|
||||
@item raw: permits raw data processing.
|
||||
|
||||
@end itemize
|
||||
|
||||
One may think that two serial drivers are needed to handle these two types
|
||||
of data, but Termios permits having only one driver.
|
||||
of data, but Termios permits having only one driver.
|
||||
|
||||
@section Termios
|
||||
|
||||
@@ -38,7 +38,7 @@ Termios is a standard for terminal management, included in the POSIX
|
||||
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}.
|
||||
,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}.
|
||||
@@ -49,10 +49,10 @@ Having RTEMS support for Termios is beneficial because:
|
||||
|
||||
@item 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.
|
||||
are the same under UNIX and RTEMS.
|
||||
|
||||
@item from the BSP developer's side because it frees the
|
||||
developer from dealing with buffer states and mutual exclusions on them.
|
||||
developer from dealing with buffer states and mutual exclusions on them.
|
||||
Early RTEMS console device drivers also did their own special
|
||||
character processing.
|
||||
|
||||
@@ -62,14 +62,14 @@ character processing.
|
||||
|
||||
@end itemize
|
||||
|
||||
Termios support includes:
|
||||
Termios support includes:
|
||||
|
||||
@itemize @bullet
|
||||
|
||||
@item raw and console handling,
|
||||
|
||||
@item blocking or non-blocking characters receive, with or without
|
||||
Timeout.
|
||||
Timeout.
|
||||
|
||||
@end itemize
|
||||
|
||||
@@ -81,18 +81,19 @@ at
|
||||
|
||||
@section Driver Functioning Modes
|
||||
|
||||
There are generally two main functioning modes for an UART (Universal
|
||||
Asynchronous Receiver-Transmitter, i.e. the serial chip):
|
||||
There are generally three main functioning modes for an UART (Universal
|
||||
Asynchronous Receiver-Transmitter, i.e. the serial chip):
|
||||
|
||||
@itemize @bullet
|
||||
|
||||
@item polled mode
|
||||
@item interrupt driven mode
|
||||
@item task driven mode
|
||||
|
||||
@end itemize
|
||||
|
||||
In polled mode, the processor blocks on sending/receiving characters.
|
||||
This mode is not the most efficient way to utilize the UART. But
|
||||
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
|
||||
@@ -120,12 +121,15 @@ 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
|
||||
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.
|
||||
|
||||
@section Serial Driver Functioning Overview
|
||||
|
||||
The following Figure shows how a Termios driven serial driver works:
|
||||
The following Figure shows how a Termios driven serial driver works:
|
||||
|
||||
@ifset use-ascii
|
||||
@center Figure not included in ASCII version
|
||||
@@ -149,167 +153,185 @@ The following list describes the basic flow.
|
||||
@item the application programmer uses standard C library call (printf,
|
||||
scanf, read, write, etc.),
|
||||
|
||||
@item C library (e.g. RedHat (formerly Cygnus) Newlib) calls
|
||||
@item C library (ctx.g. RedHat (formerly Cygnus) Newlib) calls
|
||||
the RTEMS system call interface. This code can be found in the
|
||||
@code{cpukit/libcsupport/src} directory.
|
||||
@file{cpukit/libcsupport/src} directory.
|
||||
|
||||
@item Glue code calls the serial driver entry routines.
|
||||
@item Glue code calls the serial driver entry routines.
|
||||
|
||||
@end itemize
|
||||
|
||||
@subsection Basics
|
||||
|
||||
You need to include the following header files in your Termios device driver
|
||||
source file:
|
||||
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:
|
||||
|
||||
@itemize @bullet
|
||||
|
||||
@item @code{rtems_termios_open()} - use @code{rtems_termios_device_open()} in
|
||||
combination with @code{rtems_termios_device_install()} instead.
|
||||
|
||||
@item @code{rtems_termios_close()} - use @code{rtems_termios_device_close()}
|
||||
instead.
|
||||
|
||||
@end itemize
|
||||
|
||||
This manual describes the new API. A new console driver should consist of
|
||||
three parts.
|
||||
|
||||
@enumerate
|
||||
|
||||
@item The basic console driver functions using the Termios support. Add this
|
||||
the BSPs Makefile.am:
|
||||
|
||||
@example
|
||||
@group
|
||||
#include <unistd.h>
|
||||
#include <termios.h>
|
||||
|
||||
#include <rtems.h>
|
||||
#include <rtems/libio.h>
|
||||
#include <rtems/console.h>
|
||||
[...]
|
||||
libbsp_a_SOURCES += ../../shared/console-termios.c
|
||||
[...]
|
||||
@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.
|
||||
@item A general serial module specific low-level driver providing the handler
|
||||
table for the Termios @code{rtems_termios_device_install()} function. This
|
||||
low-level driver could be used for more than one BSP.
|
||||
|
||||
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}.
|
||||
@item A BSP specific initialization routine @code{console_initialize()}, that
|
||||
calls @code{rtems_termios_device_install()} providing a low-level driver
|
||||
context for each installed device.
|
||||
|
||||
@end enumerate
|
||||
|
||||
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 @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
|
||||
@};
|
||||
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
|
||||
@}
|
||||
@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.
|
||||
handler 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
|
||||
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
|
||||
@};
|
||||
@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
|
||||
You can also provide hander for remote transmission control. This
|
||||
is not covered in this manual, so they 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.
|
||||
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
|
||||
@code{rtems_termios_device_install()}. For simplicity of the console
|
||||
initialization example the device name is also present. Her is an example header file.
|
||||
|
||||
@example
|
||||
@group
|
||||
/* Driver specific data structure */
|
||||
typedef struct @{
|
||||
const char *device_name;
|
||||
struct rtems_termios_tty *tty;
|
||||
@} my_driver_entry;
|
||||
#ifndef MY_DRIVER_H
|
||||
#define MY_DRIVER_H
|
||||
|
||||
/*
|
||||
* 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];
|
||||
#include <rtems/termiostypes.h>
|
||||
|
||||
#include <some-chip-header.h>
|
||||
|
||||
/* Low-level driver specific data structure */
|
||||
typedef struct @{
|
||||
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 */
|
||||
@end group
|
||||
@end example
|
||||
|
||||
@subsection Termios and Polled IO
|
||||
|
||||
The following functions are provided by the driver and invoked by
|
||||
The following handler are provided by the low-level driver and invoked by
|
||||
Termios for simple character IO.
|
||||
|
||||
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}.
|
||||
|
||||
On success, the number of bytes written is returned (zero indicates nothing
|
||||
was written). On error, @code{-1} is returned.
|
||||
|
||||
NOTE: Due to the current implementation of termios, any data passed into
|
||||
the write function will be lost.
|
||||
The @code{my_driver_poll_write()} routine is responsible for writing @code{n}
|
||||
characters from @code{buf} to the serial device specified by @code{tty}.
|
||||
|
||||
@example
|
||||
@group
|
||||
static ssize_t my_driver_poll_write(int minor, const char *buf, size_t n)
|
||||
static void my_driver_poll_write(
|
||||
rtems_termios_tty *tty,
|
||||
const char *buf,
|
||||
size_t n
|
||||
)
|
||||
@{
|
||||
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 n;
|
||||
my_driver_context *ctx = rtems_termios_get_device_context(tty);
|
||||
size_t i;
|
||||
|
||||
/* Write */
|
||||
for (i = 0; i < n; ++i) @{
|
||||
my_driver_write_char(ctx, buf[i]);
|
||||
@}
|
||||
@}
|
||||
@end group
|
||||
@end example
|
||||
|
||||
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
|
||||
character from the serial device specified by @code{tty}. If no character is
|
||||
available, then the routine should return minus one.
|
||||
|
||||
@example
|
||||
@group
|
||||
static int my_driver_poll_read(int minor)
|
||||
static int my_driver_poll_read(rtems_termios_tty *tty)
|
||||
@{
|
||||
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.
|
||||
*/
|
||||
my_driver_context *ctx = rtems_termios_get_device_context(tty);
|
||||
|
||||
/* 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;
|
||||
@}
|
||||
/* 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;
|
||||
@}
|
||||
@}
|
||||
@end group
|
||||
@end example
|
||||
@@ -320,13 +342,13 @@ 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 @code{my_driver_interrupt_handler} is responsible for processing
|
||||
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{my_driver_interrupt_handler} will have to check
|
||||
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:
|
||||
@@ -334,361 +356,285 @@ do this:
|
||||
@example
|
||||
@group
|
||||
static void my_driver_interrupt_handler(
|
||||
rtems_vector_number vector,
|
||||
void *arg
|
||||
rtems_vector_number vector,
|
||||
void *arg
|
||||
)
|
||||
@{
|
||||
my_driver_entry *e = (my_driver_entry *) arg;
|
||||
char buf [N];
|
||||
int n = 0;
|
||||
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(e, buf, N);
|
||||
if (n > 0) @{
|
||||
/* Hand the data over to the Termios infrastructure */
|
||||
rtems_termios_enqueue_raw_characters(e->tty, buf, 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) @{
|
||||
/*
|
||||
* Check if we have something transmitted. The functions returns
|
||||
* the number of transmitted characters since the last write to the
|
||||
* device.
|
||||
* Notify Termios that we have transmitted some characters. It
|
||||
* will call now the interrupt write function if more characters
|
||||
* are ready for transmission.
|
||||
*/
|
||||
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);
|
||||
@}
|
||||
rtems_termios_dequeue_characters(tty, n);
|
||||
@}
|
||||
@}
|
||||
@end group
|
||||
@end example
|
||||
|
||||
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. It is
|
||||
guaranteed that @code{n} is greater than zero. 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
|
||||
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. It
|
||||
the value @code{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.
|
||||
|
||||
On error, the function should return @code{-1}. On success, it should return
|
||||
@code{0}, since it the interrupt handler will report the actual number of
|
||||
characters transmitted.
|
||||
|
||||
@example
|
||||
@group
|
||||
static ssize_t my_driver_interrupt_write(int minor, const char *buf, size_t n)
|
||||
static void my_driver_interrupt_write(
|
||||
rtems_termios_tty *tty,
|
||||
const char *buf,
|
||||
size_t n
|
||||
)
|
||||
@{
|
||||
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.
|
||||
*/
|
||||
my_driver_context *ctx = rtems_termios_get_device_context(tty);
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
return 0;
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
@}
|
||||
@end group
|
||||
@end example
|
||||
|
||||
@subsection Initialization
|
||||
|
||||
The driver initialization is called once during the RTEMS initialization
|
||||
process.
|
||||
The BSP specific driver initialization is called once during the RTEMS
|
||||
initialization process.
|
||||
|
||||
The @code{console_initialize} function may look like this:
|
||||
The @code{console_initialize()} function may look like this:
|
||||
|
||||
@example
|
||||
@group
|
||||
#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_device_major_number major,
|
||||
rtems_device_minor_number minor,
|
||||
void *arg
|
||||
)
|
||||
@{
|
||||
rtems_status_code sc = RTEMS_SUCCESSFUL;
|
||||
rtems_device_minor_number i = 0;
|
||||
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];
|
||||
|
||||
/*
|
||||
* Initialize the Termios infrastructure. If Termios has already
|
||||
* been initialized by another device driver, then this call will
|
||||
* have no effect.
|
||||
* 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).
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
sc = rtems_termios_device_install(
|
||||
ctx->device_name,
|
||||
major,
|
||||
minor,
|
||||
handler,
|
||||
ctx
|
||||
);
|
||||
if (sc != RTEMS_SUCCESSFUL) @{
|
||||
bsp_fatal(SOME_BSP_FATAL_CONSOLE_DEVICE_INSTALL);
|
||||
@}
|
||||
@}
|
||||
|
||||
return RTEMS_SUCCESSFUL;
|
||||
return RTEMS_SUCCESSFUL;
|
||||
@}
|
||||
@end group
|
||||
@end example
|
||||
|
||||
@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"} (@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 provided by @file{console-termios.c} 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
|
||||
@code{"/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.
|
||||
During the first open of the device Termios will call the
|
||||
@code{my_driver_first_open()} handler.
|
||||
|
||||
@example
|
||||
@group
|
||||
rtems_device_driver console_open(
|
||||
rtems_device_major_number major,
|
||||
rtems_device_minor_number minor,
|
||||
void *arg
|
||||
static bool my_driver_first_open(
|
||||
rtems_termios_tty *tty,
|
||||
rtems_libio_open_close_args_t *args
|
||||
)
|
||||
@{
|
||||
struct rtems_termios_callbacks *callbacks =
|
||||
&my_driver_callbacks_polled;
|
||||
my_driver_context *ctx = rtems_termios_get_device_context(tty);
|
||||
rtems_status_code sc;
|
||||
bool ok;
|
||||
|
||||
/*
|
||||
* 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;
|
||||
@}
|
||||
/*
|
||||
* You may add some initialization code here.
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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;
|
||||
@}
|
||||
/*
|
||||
* 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 */
|
||||
@}
|
||||
|
||||
return rtems_termios_open(major, minor, arg, callbacks);
|
||||
@}
|
||||
@end group
|
||||
@end example
|
||||
/*
|
||||
* Alternatively you can set the best baud.
|
||||
*/
|
||||
rtems_termios_set_best_baud(tty, MY_DRIVER_BAUD_RATE);
|
||||
|
||||
During the first open of the device Termios will call @code{my_driver_first_open}.
|
||||
/*
|
||||
* To propagate the initial Termios attributes to the device use
|
||||
* this.
|
||||
*/
|
||||
ok = my_driver_set_attributes(tty, rtems_termios_get_termios(tty));
|
||||
if (!ok) @{
|
||||
/* This is bad */
|
||||
@}
|
||||
|
||||
@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;
|
||||
@}
|
||||
|
||||
/* Connect the TTY data structure */
|
||||
e->tty = tty;
|
||||
|
||||
/*
|
||||
* You may add some initialization code here.
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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);
|
||||
/*
|
||||
* Return true to indicate a successful set attributes, and false
|
||||
* otherwise.
|
||||
*/
|
||||
return true;
|
||||
@}
|
||||
@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()} 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.
|
||||
|
||||
This routine is responsible for notifying Termios that the serial device was
|
||||
closed. This is done with a call to @code{rtems_termios_close}.
|
||||
|
||||
@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
|
||||
|
||||
Termios will call the @code{my_driver_last_close} function if the last close
|
||||
Termios will call the @code{my_driver_last_close()} handler if the last close
|
||||
happens on the device.
|
||||
@example
|
||||
@group
|
||||
static int my_driver_last_close(int major, int minor, void *arg)
|
||||
static void my_driver_last_close(
|
||||
rtems_termios_tty *tty,
|
||||
rtems_libio_open_close_args_t *args
|
||||
)
|
||||
@{
|
||||
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.
|
||||
*/
|
||||
my_driver_context *ctx = rtems_termios_get_device_context(tty);
|
||||
|
||||
/* Disconnect the TTY data structure */
|
||||
e->tty = NULL;
|
||||
|
||||
/*
|
||||
* The driver may do some cleanup here.
|
||||
*/
|
||||
|
||||
return 0;
|
||||
/*
|
||||
* The driver may do some cleanup here.
|
||||
*/
|
||||
@}
|
||||
@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
|
||||
The @code{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.
|
||||
|
||||
@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.
|
||||
|
||||
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
|
||||
The @code{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.
|
||||
|
||||
@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()} 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 @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
|
||||
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.
|
||||
|
||||
The driver is responsible for reinitializing the device with the correct
|
||||
settings. For this purpuse Termios calls the @code{my_driver_set_attributes}
|
||||
function.
|
||||
settings. For this purpose Termios calls the @code{my_driver_set_attributes()}
|
||||
handler.
|
||||
|
||||
@example
|
||||
@group
|
||||
static int my_driver_set_attributes(
|
||||
int minor,
|
||||
const struct termios *t
|
||||
static bool my_driver_set_attributes(
|
||||
rtems_termios_tty *tty,
|
||||
const struct termios *term
|
||||
)
|
||||
@{
|
||||
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.
|
||||
*/
|
||||
my_driver_context *ctx = rtems_termios_get_device_context(tty);
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
/*
|
||||
* 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;
|
||||
/*
|
||||
* Return true to indicate a successful set attributes, and false
|
||||
* otherwise.
|
||||
*/
|
||||
return true;
|
||||
@}
|
||||
@end group
|
||||
@end example
|
||||
|
||||
Reference in New Issue
Block a user