Monday, October 10, 2011

Serial Consoles and Consoles in General

After dissecting the architecture of serial drivers and how a serial device interacts with PPP and SLIP, this month we are going to look at how a serial device can act as a console. Fortunately, console management is independent of the exact nature of serial ports and an interesting feature by itself. This article, therefore, deals more with console management than with serial ports, and is accompanied by the implementation of a UDP console, to show how in practice console management is organized in Linux-2.4. Sample code has been written and tested using version 2.4.4 of the kernel.

The idea of "console"

Once upon a time, when computers were huge boxes with dozens of terminals, one of those terminals had the special role of ``system console''; it was the only terminal that could be used in single-user-mode for system recovery, and the only one that received system error messages. Recent hardware is usually equipped with keyboard and video hardware and doesn't run a plethora of text terminal, so the ``system console'' is physically embodied by the native keyboard and monitor. When the host computer has no own display
hardware, on the other hand, the role of the console is usually taken by a serial port: both diagnostic messages and the login prompt can be accessed by plugging a serial cable to the computer. While it makes sense to think of the console as the ``controlling terminal'', where both input and output is performed, being able to fire a root shell and delivering kernel messages are completely different facilities. The former feature is handled by the configuration of the init process and is out of scope here; this kernel-oriented column is rather concerned with the latter issue: how kernel messages are delivered to the interactive system administrator. Note that I'm not going to discuss how kernel messages are collected by user programs (like klogd and syslogd, I'd rather concentrate on delivery of messages to the system console device, be it a serial port or other device.

The default console

When you run the typical text-mode GNU/Linux installation on your computer, the console is by default the current virtual terminal. Thus, kernel messages fall in the middle of your shell session. This behavior may be irritating if, for example, you are accessing a damaged floppy disk and the kernel spits a line of complaint every few seconds; we'll see how to fix the problem in a while. When you run X, the current virtual terminal is in graphic mode and delivery of kernel messages is disabled for this reason; even if the kernel printed the messages, you wouldn't see them anyways.
The mechanism for delivering messages to the console is implemented by the printk function, defined in kernel/printk.c. The function uses vsprintf (defined in lib/vsprintf.c) to create a message string, places the string in the circular buffer of kernel messages and passes it to all active console devices if the priority of the message is less than console_loglevel. The circular buffer is out of scope for this article because it is just temporary storage whence user-space programs can retrieve kernel messages.
The variable console_loglevel, used to select which messages are considered to be ``important enough'' to be worth printing on the system console, defaults to DEFAULT_CONSOLE_LOGLEVEL and the system administrator can change it by writing to /proc/sys/kernel/printk. A value of 8, for example, will force all messages down to debug messages to be printed to the console, and a command like "echo 8 > /proc/sys/kernel/printk" would work to enable all messages. Individual priority values (in the range 0 to 7) are defined in  as macros like KERN_ERROR and KERN_DEBUG.
As stated, if the priority of the current message is numerically less than console_loglevel, the message is passed to all active consoles. The actual code for delivering the message scans a list of console devices and reduces to the few lines shown in listing 1.
if (msg_level < console_loglevel && console_drivers) {
    struct console *c = console_drivers;
    while(c) {
        if ((c->flags & CON_ENABLED) && c->write)
            c->write(c, msg, p - msg + line_feed);
        c = c->next;
    }
}
In the most common Linux configuration there is only one console device registered, and it corresponds to the virtual terminal. The relevant code lives in drivers/char/console.c, and actual printing of messages is performed byvt_console_print. If you look in the function, you'll find that it doesn't always print messages to the foreground virtual terminal but may be configured to elect a specific virtual terminal for messaging. If the variable kmsg_redirect is non-zero, its value is used as the number of the virtual terminal elected to receive kernel messages. The variable can be written by invoking ioctl(TIOCLINUX) (a Linux-specific tty ioctl command) on a file descriptor associated to a virtual console. To this aim, I use a simple tool called setconsole, whose source is included in the sample code associated to this article. By issuing ``setconsole 1'', for example, you can force all kernel messages to be printed to virtual terminal number 1 instead of the one you are using when the message is delivered.
If you are running a ``standard'' PC equipped with serial ports, you can also elect one of your serial ports as a console by passing a proper console= command line option to your kernel. The file Documentation/serial-console.txt, part of the kernel source tree, clearly describes both the overall design and the details you may need, so I won't repeat it here. The only point worth noting is that there may be several console devices at the same time (for example, both a serial port and a virtual terminal), thanks to the loop shown in listing 1.

Declaring and selecting a console

In order to declare a new console device, your kernel code must first include . The header defines struct console and a few flags used therein.
Once equipped with a struct console, your code can simply call register_console to get inserted in the list of active consoles. The structure being registered is made up of the following fields:
  • name: the name of the console device is used to parse the console= command line option.
  • write(): the function used to print kernel messages
  • wait_key(): the function that may be called at system boot to force a delay until the user presses a key.
  • device(): a function that returns the device number for the underlying tty device that is currently acting as a console
  • unblank(): the function, if defined, is used to unblank the screen.
  • setup(): the function is called when the console= command-line argument matches the name for this console structure.
  • flags: various console flags
  • index: the number of the device acting as a console in an array of devices.
Listing 2 shows how the device driver for PC serial ports (drivers/char/serial.c) declares and registers the serial console.
static struct console sercons = {
 name:  "ttyS",
 write:  serial_console_write,
 device:  serial_console_device,
 wait_key: serial_console_wait_key,
 setup:  serial_console_setup,
 flags:  CON_PRINTBUFFER,
 index:  -1,
};

void __init serial_console_init(void)
{
 register_console(&sercons);
}
Listing 2a shows how the device driver for PC parallel ports (drivers/char/lp.c) declares and registers the parallel console (if enabled). As you can see, the code for registering a parallel console is a bit more complicated, as the kernel first checks if the line printer that is being attached is the first one (!nr) and refuses to use it as a console otherwise.
static struct console lpcons = {
 name:  "lp",
 write:  lp_console_write,
 device:  lp_console_device,
 flags:  CON_PRINTBUFFER,
};

static int lp_register(int nr, struct parport *port)
{

[...]

#ifdef CONFIG_LP_CONSOLE
 if (!nr) {
  if (port->modes & PARPORT_MODE_SAFEININT) {
   register_console (&lpcons);
   console_registered = port;
   printk (KERN_INFO "lp%d: console ready\n", CONSOLE_LP);
  } else
   printk (KERN_ERR "lp%d: cannot run console on %s\n",
    CONSOLE_LP, port->name);
 }
#endif

 return 0;
}
It's interesting to note that the console.h header is also concerned with frame-buffer-consoles; that kind of functionality (based on the struct consw data structure) is out of scope in this column as it deals with mapping the virtual-terminal text modes to different video hardware implementations.

Writing a console driver

In order to understand the mechanisms that make up a system console in Linux, we'll see them on work in an implementation that sends kernel messages to the network using UDP. In my opinion, bringing the discussion of serial consoles outside of the realm of serial communication helps in better understanding the ``console'' abstraction; any doubts on how to bring this discussion to the serial device can be solved by looking at drivers/char/serial.c.
This column doesn't show all the details of the UDP console, as that would make the discussion too heavy to be interesting. Ingo Molnar's netconsole patches (http://people.redhat.com/mingo/netconsole-patches/) implement a full-featured UDP console. My skeletal UDP console, which could be a good starting point to grasp the whole mechanism and will be discussed throughout the rest of this article, is available from my own FTP and HTTP space (http://arcana.linux.it/docs/sercons.tar.gz). Both Ingo Molnar's patches and my code are licensed according to the GNU GPL.
Sample code availability

All sample code referenced in this column and previous ones is
available from http://www.linux.it/~rubini/linux-mag/ and
ftp://ftp.linux.it/pub/People/rubini/linux-mag .

I owe an apology to the readership because last time I promised sample
code I haven't been able to deliver it in time. Due to high workload,
I usually complete sample code after the deadline for the column
(although I already have a proof of the idea and I compile and test
everything that's printed in the column itself).  Last time I offered
sample code (ktftpd.tar.gz) I haven't been able to complete it in time
(nor to write any more columns for a while). Now everything is in
place and I hope not to so late with deadlines any more.

---> I can't make it shorter than that. I would promise to at least
upload a skeleton as "beta", but that would make it longer.
The definition of the UDP console device is, as you may expect, reminiscent of the serial console just shown. Listing 3 shows the incantation of struct console for the UDP console.
struct console udpcons = {
    name:     "udp",
    write:    udpcons_write,
    wait_key: udpcons_waitkey,
    setup:    udpcons_setup,
    device:   NULL, /* no tty device associated to UDP */
    flags:    CON_ENABLED, /* always enabled */
    index:    -1, /* unspecified */
};
The purpose of the UDP console device is sending kernel messages through the network. Unlike serial or vt consoles, which are associated to real tty devices, this console has no tty associated, and that's why the device function is not defined.
In a real console device, the device function is used to return the device number associated to this console as a kdev_t value. Only one serial port can be elected as a console, for example, and the device function defined by the serial driver is used to tell the caller which one is. The function is used in drivers/char/tty_io.c to redirect any access to /dev/console. Thus, a process that opens /dev/console will actually open a different device, provided at least one of the active console drivers has a device function and the device returned is known to the tty layer.

Console Initialization

A few fields in struct console exist just to simplify console initialization and customization.
The name field is used in command line parsing, together with setup. The UDP console is designed to be a module, so accessing the kernel command line may not make too much sense; however, to better exemplify how add-on consoles work I chose to implement both fields. The name of this console is "udp", so you could use a kernel command line argument of "console=udp...".
At system boot, console_setup (in kernel/printk.c) is called once for each console= kernel command line argument. The function saves all of these directives in an array. When register_console is later called, if one of the options is found to match with the name field, the setup function is called.
A "console" command-line option can be used to specify the name (and number) of the console as well as an optional argument separated by a comma. For serial ports, for example, "console=ttyS2,9600n8" will select the third serial port at a speed of 9600 baud. For the UDP console, you can specify the UDP port like it was a device number and an optional destination IP; the device number is saved to the index field of struct console and everything after the command is passed as options to the setup function. For example you can tell your kernel "console=udp4000,10.0.0.1" so that when the module is later loaded it will use port 4000 and will transmit to host 10.0.0.1. The default UDP port is 2222 and the default destination IP address is the broadcast address ("255.255.255.255"); if you use those default values, the route taken by generated packets depend on your configuration.
int udpcons_setup(struct console *co, char *options)
{
    if (co->index > 0)
 udpcons_port = co->index;

    if (options)
 udpcons_addr = in_aton(options);

    return 0; /* success */
}
Listing 4 shows the core of the setup function (the real one is slightly more flexible, and you can see it in the file udpcons.c).

Console Flags

The flags field is a bitmask, and linux-2.4 defines three flags:
  • CON_ENABLED: the flag is used to tell whether or not this console device is enabled by default. As shown in listing 1, only enabled console devices receive kernel messages. The flag is set automatically by the system if one of the kernel command line options selects this console and setup and is successful. Moreover, the flag is set for the first console device that registers itself. That's why neither the serial nor the parallel consoles don't set the flag by default in struct console (see listing 2 and 2a): no serial or parallel port acts as a console by default unless the user asks for a serial or parallel console on the kernel command line or there exists no virtual-terminal device that can act as a default console.
  • CON_PRINTBUFFER: the flags requests buffered messages to be dumped to this console device. The serial and parallel consoles, for example, display all kernel boot messages because of this flag: when a serial or parallel console is registered the flag is set, and the kernel immediately dumps all kernel messages that were printed earlier.
  • CON_CONSDEV: the console asks to be the preferred console device. The preferred console device is placed first in the console list.
Our UDP console sets CON_ENABLED in order to run even if no kernel command line option requests an UDP console, while other flags are not interesting in this context.

Output and Input

The main role of a console device, as seen from the kernel, is reporting kernel messages to the user, and the write function as defined in struct console is the engine of such reporting. As shown in listing 1, it is called directly from printk. Since printk can be called at any time, even at interrupt time, the write function of a console should not sleep for any noticeable amount of time. To prevent data corruption due to reentrancy, printk is protected by a spinlock and runs with interrupts disabled; this unfortunately means that any delay in the console output function can be detrimental to system operation.
On the other hand, you may want to see all messages that are printed before a system panic, so for maximum reliability the console print function should not return before data is actually output. For this reason, the serial console driver operates in a busy loop and its write function doesn't return before the last byte has been transmitted. The same applies to the parallel console driver, with the caveat that our line printer could get out of paper. In this case, we have the possibility of either blocking until the printer is feeded some more paper or losing messages; we can choose the preferred behaviour by setting CONSOLE_LP_STRICT to 1 or 0, respectively (see drivers/char/lp.c). Our sample UDP console can't wait for data transmission because it can't manipulate the network device in a busy loop; it therefore just builds a network frame and enqueues it in the kernel's transmit queue. For this reason, you won't be able see UDP packets for any kernel message that is immediately followed by a kernel panic. Fortunately, most Linux ports are very conservative in calling panic (while, for example, the PPC port is not), and you'll be able to use the UDP console in debugging your own device drivers like I do.
As far as input is concerned, struct console also includes a wait_key function (and an unused read function). wait_key is only used at system boot time, for example after asking the user to insert a root floppy. There is no such feature implemented in the sample UDP console, but the comment in the placeholder function udpcons_wait_key describes how you could implement it.

Collecting console data from the UDP console

After reading this description of how a console works, you may want to try out the sample UDP console. To this aim, you'll need a way to collect UDP data packets from the network. The udp-get program included in the UDP console tarball is designed to collect packets from an UDP port and dumping them to stdout. To collect all packets sent to port 2222 you just need to run "udp-get 2222".
For more info follow this link:
http://www.linux.it/~rubini/docs/sercons/sercons.html

No comments: