summaryrefslogtreecommitdiffstats
path: root/doc/bsp_howto/console.t
diff options
context:
space:
mode:
authorJoel Sherrill <joel.sherrill@OARcorp.com>2008-12-11 15:51:13 +0000
committerJoel Sherrill <joel.sherrill@OARcorp.com>2008-12-11 15:51:13 +0000
commit850bad68cc3ffa9a83912ba9ecf804a0b2642adf (patch)
tree886c0d3f031b7ea92b36c41098c0f5bde31df005 /doc/bsp_howto/console.t
parent2008-12-11 Ralf Corsepius <ralf.corsepius@rtems.org> (diff)
downloadrtems-850bad68cc3ffa9a83912ba9ecf804a0b2642adf.tar.bz2
2008-12-11 Sebastian Huber <sebastian.huber@embedded-brains.de>
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.
Diffstat (limited to '')
-rw-r--r--doc/bsp_howto/console.t648
1 files changed, 474 insertions, 174 deletions
diff --git a/doc/bsp_howto/console.t b/doc/bsp_howto/console.t
index 95a0916ec1..4422221dc5 100644
--- a/doc/bsp_howto/console.t
+++ b/doc/bsp_howto/console.t
@@ -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
-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.
+You need to include the following header files in your Termios device driver
+source file:
+@example
+@group
+#include <unistd.h>
+#include <termios.h>
-@subsubsection pollWrite
+#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.
-The @code{pollWrite} routine is responsible for writing @code{len} characters
-from @code{buf} to the serial device specified by @code{minor}.
+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
-int pollWrite (int minor, const char *buf, int len)
-@{
- 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
-@}
+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
-@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.
+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
-int pollRead(int minor)
-@{
- read status of UART
- if status indicates a character is available
- return character
- return -1
-@}
+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
-@subsection Termios and Interrupt Driven I/O
+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.
-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.
+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.
-@subsubsection InterruptHandler
+@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
-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
-transmitter is ready for another character.
+@subsection Termios and Polled IO
+
+The following functions are provided by the driver and invoked by
+Termios for simple character IO.
-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:
+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
-rtems_isr InterruptHandler (rtems_vector_number v)
+static int my_driver_poll_write(int minor, const char *buf, int n)
@{
- check whether there was an error
-
- if some characters were received:
- Ask Termios to put them on his input buffer
-
- 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.
+ 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 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_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
-static int InterruptWrite(int minor, const char *buf, int len)
+@group
+static int my_driver_poll_read(int minor)
@{
- 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.
+ */
+
+ /* 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
-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 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.
-@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:
+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.
-@itemize @bullet
-@item reinitialize the UART channels
+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:
-@item set the channels configuration to the Termios default:
-9600 bauds, no parity, 1 stop bit, and 8 bits per character
-@end itemize
+@example
+@group
+static void my_driver_interrupt_handler(
+ rtems_vector_number vector,
+ void *arg
+)
+@{
+ my_driver_entry *e = (my_driver_entry *) arg;
+ char buf [N];
+ int n = 0;
+
+ /*
+ * 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 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
-@item If interrupt driven, register the console interrupt routine to RTEMS:
+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
-rtems_interrupt_catch(
- InterruptHandler, CONSOLE_VECTOR, &old_handler);
+@group
+static int my_driver_interrupt_write(int minor, const char *buf, int 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.
+ */
+
+ /*
+ * 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
-@item enable the UART channels.
+@subsection Initialization
+
+The driver initialization is called once during the RTEMS initialization
+process.
-@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":
+The @code{console_initialize} function may look like this:
@example
-rtems_io_register_name ("dev/console", major, i);
+@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
-@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;
+
+ /*
+ * 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;
+ @}
+
+ /*
+ * 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;
+ @}
+
+ return rtems_termios_open(major, minor, arg, callbacks);
+@}
+@end group
+@end example
-@itemize @bullet
+During the first open of the device Termios will call @code{my_driver_first_open}.
-@item one with functions for polled input/output
+@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);
+@}
+@end group
+@end example
-@item another with functions for interrupt driven input/output
+@subsection Closing a Serial Device
-@end itemize
+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 code can be found in the file @code{$BSPROOT/console/console.c}.
+This routine is responsible for notifying Termios that the serial device was
+closed. This is done with a call to @code{rtems_termios_close}.
-@subsubsection Polled I/O
+@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 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}.
+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.
+ */
+
+ /* Disconnect the TTY data structure */
+ e->tty = NULL;
+
+ /*
+ * The driver may do some cleanup here.
+ */
+
+ return 0;
+@}
+@end group
+@end example
-@subsubsection Interrupt Driven I/O
+@subsection Reading Characters from a Serial Device
-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 @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.
-The driver is responsible for providing a pointer to the
-@code{InterruptWrite} function.
+This routine is responsible for returning the content of the Termios input
+buffer. This is done by invoking the @code{rtems_termios_read} routine.
-@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.
+@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
-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 Writing Characters to a Serial Device
-@subsection Reading Characters From 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_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 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 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_write(
+ rtems_device_major_number major,
+ rtems_device_minor_number minor,
+ void *arg
+)
+@{
+ return rtems_termios_write(arg);
+@}
+@end group
+@end example
-@subsection Writing Characters to a Serial Device
+@subsection Changing Serial Line Parameters
-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_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.
-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.
+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.
-@subsection Changing Serial Line Parameters
+@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 @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 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