嵌入式linux中文站在线图书

Previous Page Next Page

Misc Drivers

Misc (or miscellaneous) drivers are simple char drivers that share certain common characteristics. The kernel abstracts these commonalities into an API (implemented in drivers/char/misc.c), and this simplifies the way these drivers are initialized. All misc devices are assigned a major number of 10, but each can choose a single minor number. So, if a char driver needs to drive multiple devices as in the CMOS example discussed earlier, it's probably not a candidate for being a misc driver.

Consider the sequence of initialization steps that a char driver performs:

A misc driver accomplishes all this with a single call to misc_register():

static struct miscdevice mydrv_dev = {
  MYDRV_MINOR,
  "mydrv",
  &mydrv_fops
};

misc_register(&mydrv_dev);

In the preceding example, MYDRV_MINOR is the minor number that you want to statically assign to your misc driver. You may also request a minor number to be dynamically assigned by specifying MISC_DYNAMIC_MINOR rather than MYDRV_MINOR in the mydrv_dev structure.

Each misc driver automatically appears under /sys/class/misc/ without explicit effort from the driver writer. Because misc drivers are char drivers, the earlier discussion on char driver entry points hold for misc drivers, too. Let's now look at an example misc driver.

Device Example: Watchdog Timer

A watchdog's function is to return an unresponsive system to operational state. It does this by periodically checking the system's pulse and issuing a reset[4] if it can't detect any. Application software is responsible for registering this pulse (or "heartbeat") by periodically strobing (or "petting") the watchdog using the services of a watchdog device driver. Most embedded controllers support internal watchdog modules. External watchdog chips are also available. An example is the Netwinder W83977AF chip.

[4] A watchdog may issue audible beeps rather than a system reset. An example scenario is when a timeout occurs due to a power supply problem, assuming that the watchdog circuit is backed up using a battery or a super capacitor.

Linux watchdog drivers are implemented as misc drivers and live inside drivers/char/watchdog/. Watchdog drivers, like RTC drivers, export a standard device interface to user land, so conforming applications are rendered independent of the internals of watchdog hardware. This API is specified in Documentation/watchdog/watchdog-api.txt in the kernel source tree. Programs that desire the services of a watchdog operate on /dev/watchdog, a device node having a misc minor number of 130.

Listing 5.9 implements a device driver for a fictitious watchdog module built in to an embedded controller. The example watchdog contains two main registers as shown in Table 5.2: a service register (WD_SERVICE_REGISTER) and a control register (WD_CONTROL_REGISTER). To pet the watchdog, the driver writes a specific sequence (0xABCD in this case) to the service register. To program watchdog timeout, the driver writes to specified bit positions in the control register.

Table 5.2. Register Layout on the Watchdog Module
Register NameDescription
WD_SERVICE_REGISTERWrite a specific sequence to this register to pet the watchdog.
WD_CONTROL_REGISTERWrite the watchdog timeout to this register.


Strobing the watchdog is usually done from user space because the goal of having a watchdog is to detect and respond to both application and kernel hangs. A critical application[5] such as the graphics engine in Listing 5.10 opens the watchdog driver in Listing 5.9 and periodically writes to it. If no write occurs within the watchdog timeout due to an application hang or a kernel crash, the watchdog triggers a system reset. In the case of Listing 5.10, the watchdog will reboot the system if

[5] If you need to monitor the health of several applications, you may implement a multiplexer in the watchdog device driver. If any one of the processes that open the driver becomes unresponsive, the watchdog attempts to self-correct the system.

The watchdog starts ticking when an application opens /dev/watchdog. Closing this device node stops the watchdog unless you set CONFIG_WATCHDOG_NOWAYOUT during kernel configuration. Setting this option helps you tide over the possibility that the watchdog monitoring process (such as Listing 5.10) gets killed by a signal while the system continues running.

Listing 5.9. An Example Watchdog Driver

#include 
#include 

#define DEFAULT_WATCHDOG_TIMEOUT 10  /* 10-second timeout */
#define TIMEOUT_SHIFT             5  /* To get to the timeout field
                                        in WD_CONTROL_REGISTER */
#define WENABLE_SHIFT             3  /* To get to the
                                        watchdog-enable field in
                                        WD_CONTROL_REGISTER */

/* Misc structure */
static struct miscdevice my_wdt_dev = {
 .minor = WATCHDOG_MINOR, /* defined as 130 in
                             include/linux/miscdevice.h*/
 .name = "watchdog",      /* /dev/watchdog */
 .fops = &my_wdt_dog  /* Watchdog driver entry points */
};

/* Driver methods */
struct file_operations my_wdt_dog = {
.owner = THIS_MODULE,
.open = my_wdt_open,
.release = my_wdt_close,
.write = my_wdt_write,
.ioctl = my_wdt_ioctl
}

/* Module Initialization */
static int __init
my_wdt_init(void)
{
  /* ... */
  misc_register(&my_wdt_dev);
  /* ... */
}
/* Open watchdog */
static void
my_wdt_open(struct inode *inode, struct file *file)
{
  /* Set the timeout and enable the watchdog */
  WD_CONTROL_REGISTER |= DEFAULT_WATCHDOG_TIMEOUT << TIMEOUT_SHIFT;
  WD_CONTROL_REGISTER |= 1 << WENABLE_SHIFT;
}

/* Close watchdog */
static int
my_wdt_close(struct inode *inode, struct file *file)
{
  /* If CONFIG_WATCHDOG_NOWAYOUT is chosen during kernel
     configuration, do not disable the watchdog even if the
     application desires to close it */
#ifndef CONFIG_WATCHDOG_NOWAYOUT
  /* Disable watchdog */
  WD_CONTROL_REGISTER &= ~(1 << WENABLE_SHIFT);
#endif
  return 0;
}

/* Pet the dog */
static ssize_t
my_wdt_write(struct file *file, const char *data,
             size_t len, loff_t *ppose)
{
  /* Pet the dog by writing a specified sequence of bytes to the
     watchdog service register */
  WD_SERVICE_REGISTER = 0xABCD;
}

/* Ioctl method. Look at Documentation/watchdog/watchdog-api.txt
   for the full list of ioctl commands. This is standard across
   watchdog drivers, so conforming applications are rendered
   hardware-independent */
static int
my_wdt_ioctl(struct inode *inode, struct file *file,
             unsigned int cmd, unsigned long arg)
{
   /* ... */
   switch (cmd) {
     case WDIOC_KEEPALIVE:
       /* Write to the watchdog. Applications can invoke
          this ioctl instead of writing to the device */
       WD_SERVICE_REGISTER = 0xABCD;
       break;
     case WDIOC_SETTIMEOUT:
        copy_from_user(&timeout, (int *)arg, sizeof(int));

       /* Set the timeout that defines unresponsiveness by
          writing to the watchdog control register */
        WD_CONTROL_REGISTER = timeout << TIMEOUT_BITS;
       break;
     case WDIOC_GETTIMEOUT:
       /* Get the currently set timeout from the watchdog */
       /* ... */
       break;
     default:
       return 鈥揈NOTTY;
   }
}

/* Module Exit */
static void __exit
my_wdt_exit(void)
{
  /* ... */
  misc_deregister(&my_wdt_dev);
  /* ... */
}

module_init(my_wdt_init);
module_exit(my_wdt_exit);

					  

 

Listing 5.10. A Watchdog User

#include 
#include 
#include 

int
main()
{
  int new_timeout;

  int wfd = open("/dev/watchdog", O_WRONLY);

  /* Set the watchdog timeout to 20 seconds */
  new_timeout = 20;
  ioctl(fd, WDIOC_SETTIMEOUT, &new_timeout);

  while (1) {
    /* Graphics processing */
    process_graphics();
    /* Pet the watchdog */
    ioctl(fd, WDIOC_KEEPALIVE, 0);
    /* Or instead do: write(wfd, "\0", 1); */
    fsync(wfd);
  }
}

External Watchdogs

To ensure that the system attempts to recover even in the face of processor failures, some regulatory bodies stipulate the use of an external watchdog chip, even if the main processor has a sophisticated built-in watchdog module such as the one in our example. Because of this requirement, embedded devices sometimes use an inexpensive no-frill watchdog chip (such as MAX6730 from Maxim) that is based on simple hard-wired logic rather than a register interface. The watchdog asserts a reset pin if no voltage pulse is detected on an input pin within a fixed reset timeout. The reset pin is connected to the reset logic of the processor, and the input pin is wired to a processor GPIO port. All that software has to do to prevent reset is to periodically pulse the watchdog's input pin within the chip's reset timeout. If you are writing a driver for such a device, the ioctl() method is not relevant. The driver's write() method pulses the watchdog's input pin whenever application software writes to the associated device node. To aid manufacturing and field diagnostics, the watchdog is wired such that it can be disabled by wiggling a processor GPIO pin.

Such chips usually allow a large initial timeout to account for boot time, followed by shorter reset timeouts.


For platforms that do not support a hardware watchdog module, the kernel implements a software watchdog, also called a softdog. The softdog driver, drivers/char/watchdog/softdog.c, is a pseudo misc driver because it does not operate on real hardware. The softdog driver has to perform two tasks that a watchdog driver doesn't have to do, which the latter accomplishes in hardware:

This is done by delaying the execution of a timer handler whenever an application writes to the softdog. If no write occurs to the softdog within a timeout, the timer handler fires and reboots the system.

A related support in 2.6 kernels is the sensing of soft lockups, which are instances when scheduling does not occur for 10 or more seconds. A kernel thread watchdog/N, where N is the CPU number, touches a per-CPU timestamp every second. If the thread doesn't touch the timestamp for more than 10 seconds, the system is deemed to have locked up. Soft lockup detection (implemented in kernel/softlockup.c) will aid us while debugging a kernel crash in the section "Kdump" in Chapter 21, "Debugging Device Drivers."

There are several more misc drivers in the kernel. The Qtronix infrared keyboard driver, drivers/char/qtronix.c, is another example of a char driver that has a misc form factor. Do a grep on misc_register() in the drivers/char/ directory to find other misc device drivers present in the kernel.

Previous Page Next Page