Re: serial.c

From: Steve Hill (sjhill@cotw.com)
Date: Wed May 10 2000 - 14:02:57 EST


Rich Bryant wrote:
>
> This might be slightly off topic since I am actually working on an embedded
> project, but I can't help but ask a few questions.
>
Heh, so am I.

> I have read the policy on commenting code and understand that over
> commentign is a sin in many of your eyes. However, the idea that a person
> should be able to understand the code with only limited effort is much
> different for the low level drivers. When code is working this closely
> with hardware "limited effort" means something completely different if you
> have to dig through hardware specs, assembly definitions, etc just to have
> a clue of what the code is doing. These are very specific actions and I
> don't feel it is reasonable to think that someone can pick up the code and
> understand it when there are a ton of #ifndef statments, scattered code in
> multiple files and very limited comments.
>
I can see your point to a certain degree. However, if you understand the
basics of how a UART works it should not be that difficult. I will offer
you this URL for the register stuff:
    
    http://www.byterunner.com/16550.html

This helped me tremendously while writing my driver for my ARM platform.

> I understand also that the collective probably doesn't want anyone with a
> linux box re-writing all of their drivers. I am torn between that and the
> fact that having the source code freely available is of no advantage to
> anyone to is not spending his life working with it exclusively.
>
True on the first sentence. Ted Ts'o has done a tremendous job of mainting
the serial stuff among others and the base 'serial.c' driver probably does
not need much tampering with. I do disagree with your second sentence. The
point is kernel hacking/development is not meant to be easy. If you're not
willing to spend a few days or a week (maybe longer) learning how it works,
then maybe you should find someone to write the driver for you if you're
not willing to put in the effort of learning. Sorry I cannot be nicer
about that.

> I find it hard to believe that there isn't a commented version of drivers
> such as serial.c out there. If anyone has it and is willing to share I
> would appreciate it. I would also be interested in a simple version that
> only supports the 16550A.
>
Well, I realized this too when writing my driver. I have attached the
beginnings of a document I am going to eventually clean up and make
available, but it is really rough. It should help some. There is also a
good paper entitled "Serial Programming Guide for POSIX Operating Systems,
5th Edition", written by Michael R. Sweet. I don't remember where I
downloaded it from.

-Steve

-- 
 Steven J. Hill - Embedded SW Engineer
 Public Key: 'finger sjhill@mail.cotw.com'
 FPR1: E124 6E1C AF8E 7802 A815
 FPR2: 7D72 829C 3386 4C4A E17D

CREATING AND INITIALIZING THE SERIAL DRIVER *******************************************

If you are taking the time to do a serial console, most likely your are doing an embedded system and need to understand your hardware. After the MMU is up and we have switched to virtual addressing, the first call is to 'start_kernel' located in 'init/main.c'. After setting up the IRQs, scheduling, timers and parsing the command line a call is made to 'console_init'. This is done BEFORE the serial drivers are actually even initialized and register with the system. The call chain looks like this:

start_kernel init/main.c console_init drivers/char/tty_io.c

'start_kernel' calls 'console_init' which in turn calls the hardware specific serial console initialization function. WARNING: If you have your own serial console driver function, be sure that you make the necessary changes in 'drivers/char/tty_io.c' to include your serial console initialization. Also make sure that you do not configure in the default CONFIG_SERIAL_CONSOLE as that will use the default serial console. <A><HREF=show code snippet from tty_io.c</A>.

As you can see the console init function is fairly simple, however, the structure contains function pointers to the serial functions which you must implement for your specific hardware. The 'console' structure used in 'rs_l7200_console_init' is defined in 'include/linux/console.h'.

struct console { char name[8]; void (*write)(struct console *, const char *, unsigned); int (*read)(struct console *, const char *, unsigned); kdev_t (*device)(struct console *); int (*wait_key)(struct console *); void (*unblank)(void); int (*setup)(struct console *, char *); short flags; short index; int cflag; struct console *next; };

If you properly implement these functions, then you should be well on your way to implementing the driver.

CREATING AND INITIALIZING THE SERIAL DRIVER *******************************************

Call chain for initializing the serial drivers:

start_kernel init/main.c - spawns kernel thread calling 'init' function - init in turn calls 'do_basic_setup' - do_basic_setup initializes PCI, networking and then calls 'do_initcalls' - do_initcalls calls all of the functions that were located into the initialization area of memory of which 'partition_setup' is one. If you look at a dissassembly of the kernel you will see the following order

c0008f58 <__initcall_start>: c0008f58: c0032b54 T+..

c0008f5c <__initcall_bdflush_init>: c0008f5c: c003ce30 0...

c0008f60 <__initcall_init_aout_binfmt>: c0008f60: c0050c40 @...

c0008f64 <__initcall_init_script_binfmt>: c0008f64: c0050e98 ....

c0008f68 <__initcall_init_elf_binfmt>: c0008f68: c005315c \1..

c0008f6c <__initcall_partition_setup>: c0008f6c: c00538bc .8..

c0008f70 <__initcall_init_ext2_fs>: c0008f70: c005b368 h...

c0008f74 <__initcall_fpe_init>: c0008f74: c00813c0 ....

c0008f78 <__initcall_end>: ...

partition_setup fs/partitions/check.c device_init drivers/block/genhd.c chr_dev_init drivers/char/mem.c tty_init drivers/char/tty_io.c (Main fncns for all TTY operations) rs_init drivers/char/serial.c (or your own file)

Initialization and registration of the serial driver begins

In tty_init the '/dev/tty' and '/dev/console' devices are registered. '/dev/tty' is the current auxilary/alternate TTY device and /dev/console is where all system messages and such are sent to. In addition to these, the virtual terminals and the System V/Unix98 pseudoterminals (PTYs) are also registered. See 'Documentation/devices.txt' for more information.

Below is the code for registering the current TTY device and system console:

*********************************** void __init tty_init(void) { if (sizeof(struct tty_struct) > PAGE_SIZE) panic("size of tty structure > PAGE_SIZE!");

/* * dev_tty_driver and dev_console_driver are actually magic * devices which get redirected at open time. Nevertheless, * we register them so that register_chrdev is called * appropriately. */ memset(&dev_tty_driver, 0, sizeof(struct tty_driver)); dev_tty_driver.magic = TTY_DRIVER_MAGIC; dev_tty_driver.driver_name = "/dev/tty"; dev_tty_driver.name = dev_tty_driver.driver_name + 5; dev_tty_driver.name_base = 0; dev_tty_driver.major = TTYAUX_MAJOR; dev_tty_driver.minor_start = 0; dev_tty_driver.num = 1; dev_tty_driver.type = TTY_DRIVER_TYPE_SYSTEM; dev_tty_driver.subtype = SYSTEM_TYPE_TTY;

if (tty_register_driver(&dev_tty_driver)) panic("Couldn't register /dev/tty driver\n");

dev_syscons_driver = dev_tty_driver; dev_syscons_driver.driver_name = "/dev/console"; dev_syscons_driver.name = dev_syscons_driver.driver_name + 5; dev_syscons_driver.major = TTYAUX_MAJOR; dev_syscons_driver.minor_start = 1; dev_syscons_driver.type = TTY_DRIVER_TYPE_SYSTEM; dev_syscons_driver.subtype = SYSTEM_TYPE_SYSCONS;

if (tty_register_driver(&dev_syscons_driver)) panic("Couldn't register /dev/console driver\n"); ***********************************

The values in the structure <A><HREF=link to dev_tty_driver structure></A> should be somewhat self explanatory. The call to tty_register_driver registers the device in the 'devfs' and 'proc' filesystems which makes the device visible to the kernel and the user. Note that NO actual hardware initialization has taken place yet. This does not occur until further down in 'tty_init' when the call to 'rs_init' is made. The remaining calls initialize the virtual terminals and UNIX98 PTY devices. For more information on what VTs and 98PTY devices are, see links <A><HREF>links to VT and 98PTY stuff></A>.

After all the terminal devices have registered with the kernel, the real work of initializing the hardware takes place. Due to the fact that a common kernel tree is used for a whole bunch of different CPU architectures, the same serial driver obviously will not work for all architectures. If you were to take a look at the main Makefile in 'drivers/char' <A><HREF=Makefile</A> you would see that a few of the architectures have do not use the 'serial.c' file and define their own serial driver based on the kernel configuration. In addition to this, we also have to take into account multiport serial cards which take care of their own serial ports. Given this information, we see the next lines of code initialize the appropriate serial driver depending on our configured hardware. WARNING! If you're creating your own serial driver as we are in this case, you will very likely NOT configure in the CONFIG_SERIAL option as that will use the default serial driver. We now move on to the actual hardware initialization routine.

INITIALIZATION OF SERIAL PORTS 'rs_init'

'rs_init' contains the guts of setting up the serial ports. In the main 'serial.c' The first few lines are concerned with registering the timers and bottom half interrupt handlers for the serial port(s). Next we print a version string for the user and then fill in the tty_driver structures. There is one for both the normal serial driver and for the callout device. The callout device is for when you have a modem or another DCE device. If your serial port does not have the CTS, DCD or DSR lines, then you most likely will not be configuring a callout device.

We then register these drivers by calling 'tty_register_driver' which simply registers the device names in the proc and devfs (if configured) filesystems and adds the driver to the linked list of terminal IO drivers. 'tty_register_driver' is located in 'drivers/char/tty_io.c'. An error code is return if anything fails.

The next step is to discover the serial ports available in the system. This involves actual detection of the port, discovering what type it is, its memory location and desired IRQ. For the stock serial driver, there is a table of all the standard serial port definitions containing the base IO address, IRQ and standard flags. This table is located in 'include/asm-i386/serial.h'. We loop through every entry in the table and make a call to 'autoconfig' for every serial port checking to see if it exists. We also zero out the counter for various things like framing errors, overrun errors, number of bytes received and transmitted, etc. All this information is stored in an array of 'serial_state' structures named the 'rs_table'. An entry exists for every device listed in the table from 'include/asm-i386/serial.h'. The 'serial_state' structure is located in 'include/linux/serial.h'. This structure is then used by all the other serial driver functions to communicate with the serial ports. A message is printed out for each serial port discovered and then each device is optionally registered with the 'devfs' filesystem if configured. 'rs_init' finishes off by probing for any PCI serial cards or Plug-N-Play serial devices.

- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.rutgers.edu Please read the FAQ at http://www.tux.org/lkml/



This archive was generated by hypermail 2b29 : Mon May 15 2000 - 21:00:16 EST