source: rtems-docs/bsp-howto/console.rst @ 12dccfe

5
Last change on this file since 12dccfe was 12dccfe, checked in by Sebastian Huber <sebastian.huber@…>, on 01/09/19 at 15:14:05

Remove superfluous "All rights reserved."

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