Serial Driver and Console

From LinuxMIPS
Revision as of 19:17, 16 June 2006 by John bougs (Talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

While early printk is rather useful, you still need to get the real serial driver working.

Assuming you have a standard serial port, there are two ways to add serial support: static defines and run-time setup.

With static defines, you modify the 'include/asm-mips/serial.h' file. Looking through the code, it is not difficult to figure out how to add support for your board's serial port(s).

As more boards are supported by Linux/MIPS, the 'serial.h' file gets crowded. One potential solution is to do run-time serial setup. Sometimes run-time serial setup is necessary if any of the parameters can only be detected at the run-time where settings are read from a non-volatile memory device or an option is passed on the kernel command line.

There are two elements to consider for doing run-time serial setup:

  • Reserve the 'rs_table[]' size, see the 'drivers/char/serial.c' file. Unfortunately there is not a clean way to accomplish this yet. A temporary workaround is to define CONFIG_SERIAL_MANY_PORTS in 'arch/mips/config-shared.in' for your board. This configuration reserves up to 64 serial port entries for your board!
  • Call the 'early_serial_setup()' routine in your board setup routine. Here is a piece of sample run-time initialization code.

Serial parameters

Most of the parameter settings are rather obvious. Here is a list of some less obvious ones:

line 
Only used for run-time serial configuration. It is the index into the 'rs_table[] array'.
io_type 
io_type determines how your serial registers are accessed. Two common types are SERIAL_IO_PORT and SERIAL_IO_MEM.
A SERIAL_IO_PORT type driver uses the inb/outb macros to access registers whose base address is at port. In other words, if you specify SERIAL_IO_PORT as the io_type, you should also specify the port parameter.
For SERIAL_IO_MEM, the driver uses readb/writeb macros to access regsiters whose address is at iomem_base plus a shifted offset. The number of digits shifted is specified by iomem_reg_shift. For example, all the serial registers are placed at 4-byte boundary, then you have an iomem_reg_shift of 2.

Generally SERIAL_IO_PORT is for serial ports on an ISA bus and SERIAL_IO_MEM is for memory-mapped serial ports. There are also SERIAL_IO_HUB6 and SERIAL_IO_GSC. The HUB6 was a multi-port serial card produced by Bell Technologies. The GSC is a special bus found on PA-RISC systems. These options will not be used on any MIPS boards.

Non-standard serial ports

If you have a non-standard serial port, you will have to write your own serial driver.

Some people derive their code from the standard serial driver. Unfortunately this is a very daunting task. The 'drivers/char/serial.c' file has over 6000 lines of code!

The generic serial way

In Linux 2.4 there is an easier alternative. There is a generic serial driver, 'drivers/char/generic_serial.c'. This file provides a set of serial routines that are hardware independent. Then your task is to provide only the routines that are hardware dependent such as the interrupt handler routine and low-level I/O functions. There are plenty of examples. Just look inside the 'drivers/char/Makefile' and look for drivers that link with 'generic_serial.o' file.

The drivers/serial way

In Linux 2.6 there is an other, even simpler alternative to the generic serial way described above. In fact, Linux 2.6 deprecates serial drivers in drivers/char and the only ones still left there are old drivers that haven't been rewritten yet. The new way of doing things is using the drivers/serial/serial_core.c infrastructure, which allows to write a very simple serial driver.

Writing the kernel console driver

First of all, the kernel needs a very simple serial driver that only allows to output characters. It should not use interrupts nor DMA to be as simple as possible. It is used to display all kernel messages printed with printk().

Let's say our strange chipset is named //foo// in the rest of the article. Start by creating the file drivers/serial/foo-console.c. You should at least include the following files :

#include <linux/console.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/serial.h>
#include <linux/serial_core.h>

You need to write two functions :

- static void foo_console_write(struct console *co, const char *s, unsigned count), which should write the count characters from the string s to the serial port. For example, it can simply loop on each character of the string and call an internal function which aims at writing a single character to the serial port. With most chipsets, outputting a character is simply a matter of writing the ASCII value of this character to a specific hardware register, and then wait for a busy bit to clear in another register before leaving the function. It is recommended to use polling here instead of interrupts.

- static __init int foo_console_setup(struct console *co, char *options), which should initialize the serial port hardware according to the given options. If your serial hardware is already initialized by the Firmware, then you don't need to do anything in this function.

Now, write a simple structure that describes the kernel console :

static struct console sercons = {
       .name     = "ttyS",
       .write    = foo_console_write,
       .device   = uart_console_device,
       .setup    = foo_console_setup,
       .flags    = CON_PRINTBUFFER,
       .index    = -1,
       .data     = &foo_reg
};

Where foo_reg is a struct uart_driver structure, for example :

struct uart_driver mv64340_reg = {
        .owner        = THIS_MODULE,
        .driver_name  = FOO_SERIAL_NAME,
        .dev_name     = "ttyS",
        .major        = TTY_MAJOR,
        .minor        = 64,
        .nr           = FOO_SERIAL_NR,
};

Then, write a simple function that will initialize the kernel console serial driver :

static int __init foo_console_init(void)
{
        register_console(&sercons);
        return 0;
}

And make sure this function gets called during initialization :

console_initcall(mv64340_console_init);


Writing the real serial driver

The code and informations of this section are based on a Linux Journal article "The Serial Driver Layer" by Greg Kroah-Hartman, which is available online at http://www.linuxjournal.com/article/6331. The code of the article, a Tiny TTY driver is available at http://www.ibiblio.org/pub/linux/docs/linux-journal/listings/issue104/6331l1.txt

Now, you want to use /dev/ttySx devices, for example to run shells on your platform. You need a real serial driver. Let's create the file drivers/serial/foo.c

To implement a driver using the //serial_core// architecture, we need to implement several functions, which are listed in the uart_ops structure, so you can start by defining such a structure :

static struct uart_ops foo_ops = {
 .tx_empty     = foo_tx_empty,
 .set_mctrl    = foo_set_mctrl,
 .get_mctrl    = foo_get_mctrl,
 .stop_tx      = foo_stop_tx,
 .start_tx     = foo_start_tx,
 .stop_rx      = foo_stop_rx,
 .enable_ms    = foo_enable_ms,
 .break_ctl    = foo_break_ctl,
 .startup      = foo_startup,
 .shutdown     = foo_shutdown,
 .type         = foo_type,
 .release_port = foo_release_port,
 .request_port = foo_request_port,
 .config_port  = foo_config_port,
 .verify_port  = foo_verify_port,
 .set_termios  = foo_set_termios,
};

There is quite a lot of functions that you have to implement if you want all functionnalities. But if you only want a simple serial console, only a subset of them is really needed. Let's define empty functions for the one that are useless :

static unsigned int foo_tx_empty(struct uart_port *port) { return 0; }
static void foo_set_mctrl(struct uart_port *port, unsigned int mctrl) { }
static unsigned int foo_get_mctrl(struct uart_port *port) { return 0; }
static void foo_break_ctl(struct uart_port *port, int break_state) { }
static void foo_enable_ms(struct uart_port *port) { }
static void foo_release_port(struct uart_port *port) { }
static int foo_request_port(struct uart_port *port) { return 0; }
static void foo_config_port(struct uart_port *port, int flags) { }
static int foo_verify_port(struct uart_port *port, struct serial_struct *ser) { return 0; }
static void foo_set_termios(struct uart_port *port, struct termios *new, 
                            struct termios *old) { }

We still have to implement foo_type, foo_start_tx, foo_stop_tx, foo_stop_rx, foo_startup and foo_shutdown. You might wonder why you don't see any //write//, //read//, //input// or //output// function. This is because the //serial_core// infrastructure rely on interrupts.

To transmit data, the //serial_core// infrastructure calls foo_start_tx, which should enable transmission on the serial port. As there's nothing to transmit, the serial port will issue an interrupt that acknowledge the end of a transmission. In the interrupt handler, you can now fetch the data to be sent, and send them to the serial port. If there is a lot of data, you won't be able to send it at once. So, you will wait the next interrupt that acknowledge the end of a transmission to send more data. During this process, you inform the //serial_core// infrastructure how many bytes have already been sent. When //serial_core// is happy with what you did, it will call foo_stop_tx to stop transmission.

When a character is received by the serial device, an interrupt is issued. The corresponding interrupt handler has to forward the characters to the //serial_core// infrastructure. The foo_stop_rx function is not used in the reception process.


Next page: KGDB

See also