Serial Driver and Console

From LinuxMIPS
Revision as of 12:17, 9 November 2004 by Ralf (Talk | contribs)

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/' 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:

Only used for run-time serial configuration. It is the index into the 'rs_table[] array'.
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

There is an other, even simpler alternative to the generic serial way described above. It consists in 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 mv64340_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)
  return 0;

And make sure this function gets called during initialization :


Writing the real serial driver

The code and informations of this section are based on a Linux Journal article, which is not available online anymore. The code of the article, a Tiny TTY driver is available at

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

Next page: KGDB