分类: LINUX
2008-04-07 09:01:22
Serial Driver and Console
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:
Contents |
Most of the parameter settings are rather obvious. Here is a list of some less obvious ones:
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.
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!
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.
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.
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#include #include #include #include
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 , 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);
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 . 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
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.
See also