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 | |
---|
7 | Console 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 | |
---|
21 | Introduction |
---|
22 | ============ |
---|
23 | |
---|
24 | This chapter describes the operation of a console driver using the RTEMS POSIX |
---|
25 | Termios support. Traditionally, RTEMS has referred to all serial device drivers |
---|
26 | as console drivers. |
---|
27 | `Termios <http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap11.html>`_ |
---|
28 | is defined by IEEE Std 1003.1-2008 (POSIX.1-2008). It supports various modes |
---|
29 | of operations at application level. This chapter focuses on the low-level |
---|
30 | serial device driver. Additional Termios information can be found in the |
---|
31 | `Linux TERMIOS(3) <http://man7.org/linux/man-pages/man3/termios.3.html>`_ |
---|
32 | manpage or the |
---|
33 | `FreeBSD TERMIOS(4) <https://www.freebsd.org/cgi/man.cgi?query=termios&sektion=4>`_ |
---|
34 | manpage. |
---|
35 | |
---|
36 | There are the following software layers. |
---|
37 | |
---|
38 | +-------------------------+ |
---|
39 | | Application | |
---|
40 | +-------------------------+ |
---|
41 | | Termios | |
---|
42 | +-------------------------+ |
---|
43 | | Low-Level Device Driver | |
---|
44 | +-------------------------+ |
---|
45 | |
---|
46 | In the default application configuration RTEMS opens during system |
---|
47 | initialization a :file:`/dev/console` device file to create the file |
---|
48 | descriptors 0, 1 and 2 used for standard input, output and error, respectively. |
---|
49 | The corresponding device driver is usually a Termios serial device driver |
---|
50 | described here. The standard file descriptors are used by standard C library |
---|
51 | calls such as :c:func:`printf` or :c:func:`scanf` or directly via the |
---|
52 | :c:func:`read` or :c:func:`write` system calls. |
---|
53 | |
---|
54 | Build System and Files |
---|
55 | ====================== |
---|
56 | |
---|
57 | A 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 | |
---|
78 | The low-level driver should provide a specialization of the Termios device |
---|
79 | context. The initialization routine must provide a context for each installed |
---|
80 | device via :c:func:`rtems_termios_device_install()`. Here is an example header |
---|
81 | file 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 | |
---|
106 | Driver Functioning Modes |
---|
107 | ======================== |
---|
108 | |
---|
109 | There are four main functioning modes for a Termios serial device driver. The |
---|
110 | mode must be set during device creation and cannot be changed afterwards. |
---|
111 | |
---|
112 | Polled 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 | |
---|
124 | Interrupt 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 | |
---|
145 | Interrupt 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 | |
---|
152 | Task 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 | |
---|
158 | Polled Mode |
---|
159 | =========== |
---|
160 | |
---|
161 | The 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 | |
---|
175 | The :c:func:`my_driver_poll_write()` routine is responsible for writing ``n`` |
---|
176 | characters 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 | |
---|
196 | The :c:func:`my_driver_poll_read` routine is responsible for reading a single |
---|
197 | character from the serial device specified by ``base``. If no character is |
---|
198 | available, 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 | |
---|
217 | Interrupt Driven Mode |
---|
218 | ===================== |
---|
219 | |
---|
220 | The 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 | |
---|
234 | There 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 |
---|
236 | buffer is filled in the driver interrupt routine. |
---|
237 | |
---|
238 | A serial device generally generates interrupts when it is ready to accept or to |
---|
239 | emit a number of characters. In this mode, the interrupt routine is the core |
---|
240 | of the driver. |
---|
241 | |
---|
242 | The :c:func:`my_driver_interrupt_handler` is responsible for processing |
---|
243 | asynchronous interrupts from the serial device. There may be multiple |
---|
244 | interrupt handlers for a single serial device. Some serial devices can |
---|
245 | generate a unique interrupt vector for each interrupt source such as a |
---|
246 | character has been received or the transmitter is ready for another character. |
---|
247 | |
---|
248 | In the simplest case, the :c:func:`my_driver_interrupt_handler` will have to |
---|
249 | check the status of the serial device and determine what caused the interrupt. |
---|
250 | The 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 | |
---|
292 | The :c:func:`my_driver_interrupt_write()` handler is responsible for telling |
---|
293 | the device that the ``n`` characters at ``buf`` are to be transmitted. It the |
---|
294 | value ``n`` is zero to indicate that no more characters are to send. The |
---|
295 | driver can disable the transmit interrupts now. This routine is invoked either |
---|
296 | from task context with disabled interrupts to start a new transmission process |
---|
297 | with exactly one character in case of an idle output state or from the |
---|
298 | interrupt handler to refill the transmitter. If the routine is invoked to |
---|
299 | start the transmit process the output state will become busy and Termios starts |
---|
300 | to fill the output buffer. If the transmit interrupt arises before Termios was |
---|
301 | able to fill the transmit buffer you will end up with one interrupt per |
---|
302 | character. |
---|
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 | |
---|
334 | First Open |
---|
335 | ========== |
---|
336 | |
---|
337 | Upon first open of the device, the :c:func:`my_driver_first_open` handler is |
---|
338 | called 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 | |
---|
391 | Last Close |
---|
392 | ========== |
---|
393 | |
---|
394 | Termios will call the :c:func:`my_driver_last_close` handler if the last close |
---|
395 | happens 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 | |
---|
414 | Set Attributes |
---|
415 | ============== |
---|
416 | |
---|
417 | Termios will call the :c:func:`my_driver_set_attributes` handler if a serial |
---|
418 | line configuration parameter changed, e.g. baud, character size, number of stop |
---|
419 | bits, 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 | |
---|
446 | IO Control |
---|
447 | ========== |
---|
448 | |
---|
449 | Optionally, the :c:func:`my_driver_ioctl()` routine may be provided for |
---|
450 | arbitrary 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 | |
---|
475 | Flow Control |
---|
476 | ============ |
---|
477 | |
---|
478 | You can also provide handler for remote transmission control. This is not |
---|
479 | covered in this manual. |
---|
480 | |
---|
481 | General Initialization |
---|
482 | ====================== |
---|
483 | |
---|
484 | The BSP-specific driver initialization is called once during the RTEMS |
---|
485 | initialization process. |
---|
486 | |
---|
487 | The :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 | } |
---|