The Serial Driver Layer

Greg explains the interface to the new serial driver layer and how to register a serival driver and an individual serial port.

In the last two installments of Driving Me Nuts [LJ August 2002 and October 2002], we covered the tty layer, explaining how to create a minimal tty driver. We also explained some of the various ioctls and how to interpret the termios structure. Those articles are a great start if you have to implement a new tty-type device for your embedded system, such as a serial port. For every new system that is designed, the hardware engineers always like to place the serial port at a different address, or use a different UART, or sometimes just forget the serial port and use a USB port. So most developers want to create a whole new tty driver for their new devices in order for the hardware to work properly on Linux. Fortunately, there are some layers above the tty layer that help buffer its complexities and provide the developer with a lot of common functions that are needed for a serial driver and that fit the UART or USB model better. These layers are the serial and USB to serial driver layers. We cover the serial driver layer in this article and will cover the USB to serial layer in a future article.

Serial Mess

If you look at the code for the generic PC serial driver in the 2.2 and 2.4 kernels (available at drivers/char/serial.c), you will see a very complex driver that has numerous #ifdef lines, depending on the type of hardware you are using. This file has been copied numerous times in order to provide serial support for devices that do not fit the typical PC UART device (such as the serial_amba.c and serial_21285.c drivers). Thankfully, a number of developers, led by ARM Linux maintainer Russell King, restructured the serial driver into a generic serial core and a number of smaller, hardware-specific drivers. That code showed up in the main kernel tree sometime around the 2.5.28 release. The examples in this article are from version 2.5.35, so check that things have not changed for the kernel version you are using.

Registering a Serial Driver

The serial layer requires your driver to do two things: register the driver itself with the serial core and then register the individual serial ports as they are found in the system (through PCI enumeration or some other sort of device discovery mechanism). To register your driver, call uart_register_driver() with a pointer to a struct uart_driver structure. This function takes the information provided in the uart_driver structure and sets up the tty layer based on this.

The fields in the uart_driver structure that the serial driver needs to provide are the following:

struct module           *owner;
const char              *driver_name;
const char              *dev_name;
int                      major;
int                      minor;
int                      nr;
struct console          *cons;

The owner field is a pointer to the module owning the serial driver. This is usually set to the macro THIS_MODULE.

The driver_name field is a pointer to the string that describes this driver, which is usually the same as the dev_name field. However, when describing the dev_name field, devfs needs to be taken into account, and the characters “%d” must be added to the end of this field if devfs is selected. This is because of the way devfs creates the device nodes. As an example, the amba.c driver sets the driver_name and dev_name fields as such:

        .driver_name            = "ttyAM",
#ifdef CONFIG_DEVFS_FS
        .dev_name               = "ttyAM%d",
#else
        .dev_name               = "ttyAM",
#endif

The major and minor fields specify the major number for the driver and the starting minor number, respectively.

The nr field specifies the maximum number of serial ports this driver supports.

The cons field is a pointer to the struct console structure that is used if this driver can support a serial console. If the driver does not support serial console mode, this field should be set to NULL.

Registering a Serial Port

Now that the serial driver has been registered with the serial driver layer, each of the serial ports needs to be registered individually with a call to uart_add_one_port(). This function takes a pointer to the original uart_driver structure that was passed to uart_register_driver and a pointer to the uart_port structure. The uart_port structure is defined as the following:

struct uart_port {
  spinlock_t       lock;      /* port lock */
  unsigned int     iobase;    /* in/out[bwl] */
  char             *membase;  /* read/write[bwl] */
  unsigned int     irq;       /* irq number */
  unsigned int     uartclk;   /* base uart clock */
  unsigned char    fifosize;  /* tx fifo size */
  unsigned char    x_char;    /* xon/xoff char */
  unsigned char    regshift;  /* reg offset shift */
  unsigned char    iotype;    /* io access style */
  unsigned int     read_status_mask;
                              /* driver specific */
  unsigned int     ignore_status_mask;
                              /* driver specific */
  struct uart_info *info;
                        /* pointer to parent info */
  struct uart_icount icount; /* statistics */
  struct console   *cons;
                      /* struct console, if any */
#ifdef CONFIG_SERIAL_CORE_CONSOLE
  unsigned long sysrq;       /* sysrq timeout */
#endif
  unsigned int     flags;
  unsigned int     mctrl;
                 /* current modem ctrl settings */
  unsigned int     timeout;
                 /* character-based timeout */
  unsigned int     type;      /* port type */
  struct uart_ops  *ops;
  unsigned int     line;      /* port index */
  unsigned long    mapbase;   /* for ioremap */
  unsigned char    hub6;
           /* this should be in the 8250 driver */
  unsigned char unused[3];
    };

A majority of these fields are used by the individual serial drivers during their operation to define how the specific port is hooked up to the processor (through the hub6, iobase, membase, mapbase and iotype variables).

One of the more interesting variables in this structure is the struct uart_ops pointer, which defines a list of functions that the serial core uses to call back into the port-specific serial driver. This structure is defined as:

struct uart_ops {
  unsigned int    (*tx_empty)(struct uart_port *);
  void   (*set_mctrl)(struct uart_port *,
                      unsigned int mctrl);
  unsigned int    (*get_mctrl)(struct uart_port *);
  void   (*stop_tx)(struct uart_port *,
                    unsigned int tty_stop);
  void   (*start_tx)(struct uart_port *,
                     unsigned int tty_start);
  void   (*send_xchar)(struct uart_port *, char ch);
  void   (*stop_rx)(struct uart_port *);
  void   (*enable_ms)(struct uart_port *);
  void   (*break_ctl)(struct uart_port *, int ctl);
  int    (*startup)(struct uart_port *);
  void   (*shutdown)(struct uart_port *);
  void   (*change_speed)(struct uart_port *,
                         unsigned int cflag,
                         unsigned int iflag,
                         unsigned int quot);
  void        (*pm)(struct uart_port *,
                    unsigned int state,
                unsigned int oldstate);
  int        (*set_wake)(struct uart_port *,
                         unsigned int state);
  /*
   * Return a string describing the port type
   */
  const char *(*type)(struct uart_port *);
  /*
   * Release IO and memory resources used by
   * the port. This includes iounmap if necessary.
   */
  void   (*release_port)(struct uart_port *);
  /*
   * Request IO and memory resources used by the
   * port.  This includes iomapping the port if
   * necessary.
   */
  int    (*request_port)(struct uart_port *);
  void   (*config_port)(struct uart_port *, int);
  int    (*verify_port)(struct uart_port *,
                        struct serial_struct *);
  int    (*ioctl)(struct uart_port *, unsigned int,
                  unsigned long);
};

This is a very large structure, with a lot of different function pointers, looking almost as bad as the tty_driver structure.

The startup function is called once each time the open(2) call happens. It is only called after the serial core has done a lot of resource checking and is sure the port needs to be opened. The serial driver usually does some hardware-specific setup to allow the port to be used in this function.

The shutdown function is the opposite of the startup function. It is called when the port is closed and all data has stopped flowing though it. This is where the hardware is told to stop, and any resources that were allocated in the startup function should be freed.

The request_port and release_port functions are used to reserve memory and other hardware resources that are related to the serial port. The config_port function is much like request_port, but it is called when the hardware can autoprobe for any serial ports connected to it and is also responsible for doing the same hardware reservations that request_port does.

The change_speed function is called whenever the port line settings need to be modified. The values passed to this function already have been cleaned up from the original termios structure that was passed through the tty layer, making the logic in the serial driver much simpler.

There are a number of functions that are used to get and set the serial line state and port status, these are:

  • set_mctrl: sets a new value for the MCR UART register.

  • get_mctrl: gets the current MCR UART register value.

  • stop_tx: stops the port from sending data.

  • start_tx: starts the port sending data.

  • tx_empty: returns if the port transmitter is empty or not.

  • send_xchar: tells the port to send the XOFF character to the host.

  • stop_rx: stops receiving data.

  • break_ctl: sends the BREAK value over the port.

  • enable_ms: enables the modem status interrupts.

There are two functions related to power management issues with the serial port: pm and set_wake. If your hardware platform supports power management, use these functions to handle powering the hardware up and down.

verify_port is called to verify that the settings passed to it are valid settings for the specific serial port and is called when the user makes a TIOCSSERIAL ioctl(2) call on the port (see “The tty Layer, Part II”, LJ October 2002 for more on TIOCSSERIAL).

The serial driver layer handles many of the common serial ioctls, such as TIOCMGET, TIOCMBIS, TIOCMBIC, TIOCMSET, TIOCGSERIAL, TIOCSSERIAL, TIOCSERCONFIG, TIOCSERGETLSR, TIOCMIWAIT, TIOCGICOUNT, TIOCSERGWILD and TIOCSERSWILD. If any other ioctl is called on the serial port, it is passed down to the specific port through the ioctl callback in the uart_ops structure.

The last remaining function is type, which is used to return a string describing the type of serial port. This is used in the proc file that resides in /proc/tty/driver/ and in the initial boot message when the port is discovered.

______________________

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

serial device driver

gopu's picture

I require driver that transmit and recieve data in other end of the system connected Via null modem. Now I want to recieve data from the hyper terminal of that system.

Visit

Anonymous's picture

link

taco's picture

You can find the source by entering
ftp://ftp.ssc.com/pub/lj/listings/issue104/
The file is 6331|1.txt

The link to listing 1 is

nate's picture

The link to listing 1 is broken.

Webinar
One Click, Universal Protection: Implementing Centralized Security Policies on Linux Systems

As Linux continues to play an ever increasing role in corporate data centers and institutions, ensuring the integrity and protection of these systems must be a priority. With 60% of the world's websites and an increasing share of organization's mission-critical workloads running on Linux, failing to stop malware and other advanced threats on Linux can increasingly impact an organization's reputation and bottom line.

Learn More

Sponsored by Bit9

Webinar
Linux Backup and Recovery Webinar

Most companies incorporate backup procedures for critical data, which can be restored quickly if a loss occurs. However, fewer companies are prepared for catastrophic system failures, in which they lose all data, the entire operating system, applications, settings, patches and more, reducing their system(s) to “bare metal.” After all, before data can be restored to a system, there must be a system to restore it to.

In this one hour webinar, learn how to enhance your existing backup strategies for better disaster recovery preparedness using Storix System Backup Administrator (SBAdmin), a highly flexible bare-metal recovery solution for UNIX and Linux systems.

Learn More

Sponsored by Storix