source: rtems-docs/bsp-howto/console.rst @ 969e60e

5
Last change on this file since 969e60e was cb0f55a, checked in by Sebastian Huber <sebastian.huber@…>, on 04/26/18 at 07:05:20

Update due to BSP source reorganization

This patch is a part of the BSP source reorganization.

Close #3285.

  • Property mode set to 100644
File size: 17.9 KB
Line 
1.. comment SPDX-License-Identifier: CC-BY-SA-4.0
2
3.. COMMENT: COPYRIGHT (c) 1988-2002.
4.. COMMENT: On-Line Applications Research Corporation (OAR).
5.. COMMENT: All rights reserved.
6
7Console Driver
8**************
9
10.. warning::
11    The low-level driver API changed between RTEMS 4.10 and RTEMS 4.11.  The
12    legacy callback API is still supported, but its use is discouraged.  The
13    following functions are deprecated:
14
15    - :c:func:`rtems_termios_open()`
16
17    - :c:func:`rtems_termios_close()`
18
19    This manual describes the new API.
20
21Introduction
22============
23
24This chapter describes the operation of a console driver using the RTEMS POSIX
25Termios support.  Traditionally, RTEMS has referred to all serial device drivers
26as console drivers.
27`Termios <http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap11.html>`_
28is defined by IEEE Std 1003.1-2008 (POSIX.1-2008).  It supports various modes
29of operations at application level.  This chapter focuses on the low-level
30serial device driver.  Additional Termios information can be found in the
31`Linux TERMIOS(3) <http://man7.org/linux/man-pages/man3/termios.3.html>`_
32manpage or the
33`FreeBSD TERMIOS(4) <https://www.freebsd.org/cgi/man.cgi?query=termios&sektion=4>`_
34manpage.
35
36There are the following software layers.
37
38+-------------------------+
39| Application             |
40+-------------------------+
41| Termios                 |
42+-------------------------+
43| Low-Level Device Driver |
44+-------------------------+
45
46In the default application configuration RTEMS opens during system
47initialization a :file:`/dev/console` device file to create the file
48descriptors 0, 1 and 2 used for standard input, output and error, respectively.
49The corresponding device driver is usually a Termios serial device driver
50described here.  The standard file descriptors are used by standard C library
51calls such as :c:func:`printf` or :c:func:`scanf` or directly via the
52:c:func:`read` or :c:func:`write` system calls.
53
54Build System and Files
55======================
56
57A new serial device driver should consist of three parts.
58
59- A section in the BSPs Makefile.am:
60
61.. code-block:: makefile
62
63      [...]
64      libbsp_a_SOURCES += ../../shared/dev/serial/console-termios.c
65      libbsp_a_SOURCES += console/console.c
66      [...]
67
68- A general serial device specific low-level driver providing the handler table
69  and the device context specialization for the Termios
70  :c:func:`rtems_termios_device_install()` function.  This low-level driver
71  could be used for more than one BSP.
72
73- A BSP-specific initialization routine :c:func:`console_initialize()`, that calls
74  :c:func:`rtems_termios_device_install()` providing a low-level driver context for
75  each installed device.  This is usually defined in the file
76  :file:`console/console.c` relative to the BSP base directory.
77
78The low-level driver should provide a specialization of the Termios device
79context.  The initialization routine must provide a context for each installed
80device via :c:func:`rtems_termios_device_install()`.  Here is an example header
81file for a low-level serial device driver.
82
83.. code-block:: c
84
85    #ifndef MY_DRIVER_H
86    #define MY_DRIVER_H
87
88    #include <some-chip/serial.h>
89
90    #include <rtems/termiostypes.h>
91
92    /* My low-level driver specialization of Termios device context */
93    typedef struct {
94      rtems_termios_device_context base;
95      const char *device_name;
96      volatile some_chip_registers *regs;
97      /* More stuff */
98    } my_driver_context;
99
100    extern const rtems_termios_device_handler my_driver_handler_polled;
101
102    extern const rtems_termios_device_handler my_driver_handler_interrupt;
103
104    #endif /* MY_DRIVER_H */
105
106Driver Functioning Modes
107========================
108
109There are four main functioning modes for a Termios serial device driver.  The
110mode must be set during device creation and cannot be changed afterwards.
111
112Polled Mode (`TERMIOS_POLLED`)
113    In polled mode, the processor blocks on sending/receiving characters.  This
114    mode is not the most efficient way to utilize the serial device. But polled
115    mode is usually necessary when one wants to print an error message in the
116    event of a fatal error such as a fatal error in the BSP.  This is also the
117    simplest mode to program.  Polled mode is generally preferred if the serial
118    device is to be used primarily as a debug console.  In a simple polled
119    driver, the software will continuously check the status of the serial
120    device when it is reading or writing to the serial device.  Termios
121    improves on this by delaying the caller for one clock tick between
122    successive checks of the serial device on a read operation.
123
124Interrupt Driven Mode (`TERMIOS_IRQ_DRIVEN`)
125    In interrupt driven mode, the processor does not block on sending/receiving
126    characters.  Data is buffered between the interrupt service routine and
127    application code.  Two buffers are used to insulate the application from
128    the relative slowness of the serial device.  One of the buffers is used for
129    incoming characters, while the other is used for outgoing characters.
130
131    An interrupt is raised when a character is received by the serial device.
132    The interrupt routine places the incoming character at the end of the input
133    buffer.  When an application asks for input, the characters at the front of
134    the buffer are returned.
135
136    When the application prints to the serial device, the outgoing characters
137    are placed at the end of the output buffer.  The driver will place one or
138    more characters in the serial device (the exact number depends on the
139    serial device) An interrupt will be raised when all the characters have
140    been transmitted.  The interrupt service routine has to send the characters
141    remaining in the output buffer the same way.  When the transmitting side of
142    the serial device is idle, it is typically necessary to prime the
143    transmitter before the first interrupt will occur.
144
145Interrupt Server Driven Mode (`TERMIOS_IRQ_SERVER_DRIVEN`)
146    The interrupt server driven mode is identical to the interrupt driven mode,
147    except that a mutex is used to protect the low-level device state instead
148    of an interrupt lock (disabled interrupts).  Use this mode in case the
149    serial device is connected via I2C or SPI and the I2C or SPI framework is
150    used.
151
152Task Driven Mode (`TERMIOS_TASK_DRIVEN`)
153    The task driven mode is similar to interrupt driven mode, but the actual
154    data processing is done in dedicated tasks instead of interrupt routines.
155    This mode is not available in SMP configurations.  It has some
156    implementation flaws and it is not well tested.
157
158Polled Mode
159===========
160
161The handler table for the polled mode should look like the following.
162
163.. code-block:: c
164
165    const rtems_termios_device_handler my_driver_handler_polled = {
166      .first_open = my_driver_first_open,
167      .last_close = my_driver_last_close,
168      .poll_read = my_driver_poll_read,
169      .write = my_driver_poll_write,
170      .set_attributes = my_driver_set_attributes,
171      .ioctl = my_driver_ioctl, /* optional, may be NULL */
172      .mode = TERMIOS_POLLED
173    }
174
175The :c:func:`my_driver_poll_write()` routine is responsible for writing ``n``
176characters from ``buf`` to the serial device specified by ``base``.
177
178.. code-block:: c
179
180    static void my_driver_poll_write(
181      rtems_termios_device_context *base,
182      const char                   *buf,
183      size_t                        n
184    )
185    {
186      my_driver_context *ctx;
187      size_t             i;
188
189      ctx = (my_driver_context *) base;
190
191      for ( i = 0 ; i < n ; ++i ) {
192        my_driver_write_char( ctx, buf[ i ] );
193      }
194    }
195
196The :c:func:`my_driver_poll_read` routine is responsible for reading a single
197character from the serial device specified by ``base``.  If no character is
198available, then the routine should immediately return minus one.
199
200.. code-block:: c
201
202    static int my_driver_poll_read( rtems_termios_device_context *base )
203    {
204      my_driver_context *ctx;
205
206      ctx = (my_driver_context *) base;
207
208      if ( my_driver_can_read_char( ctx ) ) {
209        /* Return the character (must be unsigned) */
210        return my_driver_read_char( ctx );
211      } else {
212        /* Return -1 to indicate that no character is available */
213        return -1;
214      }
215    }
216
217Interrupt Driven Mode
218=====================
219
220The handler table for the interrupt driven mode should look like the following.
221
222.. code-block:: c
223
224    const rtems_termios_device_handler my_driver_handler_interrupt = {
225      .first_open = my_driver_first_open,
226      .last_close = my_driver_last_close,
227      .poll_read = NULL,
228      .write = my_driver_interrupt_write,
229      .set_attributes = my_driver_set_attributes,
230      .ioctl = my_driver_ioctl, /* optional, may be NULL */
231      .mode = TERMIOS_IRQ_DRIVEN
232    };
233
234There is no device driver read handler to be passed to Termios.  Indeed a
235:c:func:`read()` call returns the contents of Termios input buffer.  This
236buffer is filled in the driver interrupt routine.
237
238A serial device generally generates interrupts when it is ready to accept or to
239emit a number of characters.  In this mode, the interrupt routine is the core
240of the driver.
241
242The :c:func:`my_driver_interrupt_handler` is responsible for processing
243asynchronous interrupts from the serial device.  There may be multiple
244interrupt handlers for a single serial device.  Some serial devices can
245generate a unique interrupt vector for each interrupt source such as a
246character has been received or the transmitter is ready for another character.
247
248In the simplest case, the :c:func:`my_driver_interrupt_handler` will have to
249check the status of the serial device and determine what caused the interrupt.
250The following describes the operation of an
251:c:func:`my_driver_interrupt_handler` which has to do this:
252
253.. code-block:: c
254
255    static void my_driver_interrupt_handler( void *arg )
256    {
257      rtems_termios_tty *tty;
258      my_driver_context *ctx;
259      char               buf[N];
260      size_t             n;
261
262      tty = arg;
263      ctx = rtems_termios_get_device_context( tty );
264
265      /*
266       * Check if we have received something.  The function reads the
267       * received characters from the device and stores them in the
268       * buffer.  It returns the number of read characters.
269       */
270      n = my_driver_read_received_chars( ctx, buf, N );
271      if ( n > 0 ) {
272        /* Hand the data over to the Termios infrastructure */
273        rtems_termios_enqueue_raw_characters( tty, buf, n );
274      }
275
276      /*
277       * Check if we have something transmitted.  The functions returns
278       * the number of transmitted characters since the last write to the
279       * device.
280       */
281      n = my_driver_transmitted_chars( ctx );
282      if ( n > 0 ) {
283        /*
284         * Notify Termios that we have transmitted some characters.  It
285         * will call now the interrupt write function if more characters
286         * are ready for transmission.
287         */
288        rtems_termios_dequeue_characters( tty, n );
289      }
290    }
291
292The :c:func:`my_driver_interrupt_write()` handler is responsible for telling
293the device that the ``n`` characters at ``buf`` are to be transmitted.  It the
294value ``n`` is zero to indicate that no more characters are to send.  The
295driver can disable the transmit interrupts now.  This routine is invoked either
296from task context with disabled interrupts to start a new transmission process
297with exactly one character in case of an idle output state or from the
298interrupt handler to refill the transmitter.  If the routine is invoked to
299start the transmit process the output state will become busy and Termios starts
300to fill the output buffer.  If the transmit interrupt arises before Termios was
301able to fill the transmit buffer you will end up with one interrupt per
302character.
303
304.. code-block:: c
305
306    static void my_driver_interrupt_write(
307      rtems_termios_device_context  *base,
308      const char                    *buf,
309      size_t                         n
310    )
311    {
312      my_driver_context *ctx;
313
314      ctx = (my_driver_context *) base;
315
316      if ( n > 0 ) {
317        /*
318         * Tell the device to transmit some characters from buf (less than
319         * or equal to n).  When the device is finished it should raise an
320         * interrupt.  The interrupt handler will notify Termios that these
321         * characters have been transmitted and this may trigger this write
322         * function again.  You may have to store the number of outstanding
323         * characters in the device data structure.
324         */
325      } else {
326        /*
327         * Termios will set n to zero to indicate that the transmitter is
328         * now inactive.  The output buffer is empty in this case.  The
329         * driver may disable the transmit interrupts now.
330         */
331      }
332    }
333
334First Open
335==========
336
337Upon first open of the device, the :c:func:`my_driver_first_open` handler is
338called by Termios.  The device registered as :file:`/dev/console` (or
339``CONSOLE_DEVICE_NAME``) is opened automatically during RTEMS initialization.
340
341.. code-block:: c
342
343    static bool my_driver_first_open(
344      rtems_termios_tty             *tty,
345      rtems_termios_device_context  *base,
346      struct termios                *term,
347      rtems_libio_open_close_args_t *args
348    )
349    {
350      my_driver_context *ctx;
351      rtems_status_code  sc;
352      bool               ok;
353
354      ctx = (my_driver_context *) base;
355
356      /*
357       * You may add some initialization code here.
358       */
359
360      /*
361       * Sets the initial baud rate.  This should be set to the value of
362       * the boot loader.  This function accepts only exact Termios baud
363       * values.
364       */
365      sc = rtems_termios_set_initial_baud( tty, MY_DRIVER_BAUD_RATE );
366      if ( sc != RTEMS_SUCCESSFUL ) {
367        /* Not a valid Termios baud */
368      }
369
370      /*
371       * Alternatively you can set the best baud.
372       */
373      rtems_termios_set_best_baud( term, MY_DRIVER_BAUD_RATE );
374
375      /*
376       * To propagate the initial Termios attributes to the device use
377       * this.
378      */
379      ok = my_driver_set_attributes( base, term );
380      if ( !ok ) {
381        /* This is bad */
382      }
383
384      /*
385       * Return true to indicate a successful set attributes, and false
386       * otherwise.
387       */
388      return true;
389    }
390
391Last Close
392==========
393
394Termios will call the :c:func:`my_driver_last_close` handler if the last close
395happens on the device.
396
397.. code-block:: c
398
399    static void my_driver_last_close(
400      rtems_termios_tty             *tty,
401      rtems_termios_device_context  *base,
402      rtems_libio_open_close_args_t *args
403    )
404    {
405      my_driver_context *ctx;
406
407      ctx = (my_driver_context *) base;
408
409      /*
410       * The driver may do some cleanup here.
411       */
412    }
413
414Set Attributes
415==============
416
417Termios will call the :c:func:`my_driver_set_attributes` handler if a serial
418line configuration parameter changed, e.g. baud, character size, number of stop
419bits, parity, etc.
420
421.. code-block:: c
422
423    static bool my_driver_set_attributes(
424      rtems_termios_device_context *base,
425      const struct termios         *term
426    )
427    {
428      my_driver_context *ctx;
429
430      ctx = (my_driver_context *) base;
431
432      /*
433       * Inspect the termios data structure and configure the device
434       * appropriately.  The driver should only be concerned with the
435       * parts of the structure that specify hardware setting for the
436       * communications channel such as baud, character size, etc.
437       */
438
439      /*
440       * Return true to indicate a successful set attributes, and false
441       * otherwise.
442       */
443      return true;
444    }
445
446IO Control
447==========
448
449Optionally, the :c:func:`my_driver_ioctl()` routine may be provided for
450arbitrary device-specific functions.
451
452.. code-block:: c
453
454    static int my_driver_ioctl(
455      rtems_termios_device_context *base,
456      ioctl_command_t               request,
457      void                         *buffer
458    )
459    {
460      my_driver_context *ctx;
461
462      ctx = (my_driver_context *) base;
463
464      switch ( request ) {
465        case MY_DRIVER_DO_XYZ:
466          my_driver_do_xyz(ctx, buffer);
467          break;
468        default:
469          rtems_set_errno_and_return_minus_one( EINVAL );
470      }
471
472      return 0;
473    }
474
475Flow Control
476============
477
478You can also provide handler for remote transmission control.  This is not
479covered in this manual.
480
481General Initialization
482======================
483
484The BSP-specific driver initialization is called once during the RTEMS
485initialization process.
486
487The :c:func:`console_initialize()` function may look like this:
488
489.. code-block:: c
490
491    #include <my-driver.h>
492
493    #include <rtems/console.h>
494
495    #include <bsp.h>
496    #include <bsp/fatal.h>
497
498    static my_driver_context driver_context_table[] = {
499      { /* Some values for device 0 */ },
500      { /* Some values for device 1 */ }
501    };
502
503    rtems_device_driver console_initialize(
504      rtems_device_major_number  major,
505      rtems_device_minor_number  minor,
506      void                      *arg
507    )
508    {
509      const rtems_termios_device_handler *handler;
510      rtems_status_code                   sc;
511      size_t                              i;
512
513      #ifdef SOME_BSP_USE_INTERRUPTS
514        handler = &my_driver_handler_interrupt;
515      #else
516        handler = &my_driver_handler_polled;
517      #endif
518
519      /*
520       * Initialize the Termios infrastructure.  If Termios has already
521       * been initialized by another device driver, then this call will
522       * have no effect.
523       */
524      rtems_termios_initialize();
525
526      /* Initialize each device */
527      for ( i = 0; i < RTEMS_ARRAY_SIZE( driver_context_table ) ; ++i ) {
528        my_driver_context *ctx;
529
530        ctx = &driver_context_table[ i ];
531
532        /*
533         * Install this device in the file system and Termios.  In order
534         * to use the console (i.e. being able to do printf, scanf etc.
535         * on stdin, stdout and stderr), one device must be registered as
536         * "/dev/console" (CONSOLE_DEVICE_NAME).
537         */
538        sc = rtems_termios_device_install( ctx->device_name, handler, NULL, ctx );
539        if ( sc != RTEMS_SUCCESSFUL ) {
540          bsp_fatal( SOME_BSP_FATAL_CONSOLE_DEVICE_INSTALL );
541        }
542      }
543
544      return RTEMS_SUCCESSFUL;
545    }
Note: See TracBrowser for help on using the repository browser.