Chinaunix首页 | 论坛 | 博客

分类: LINUX

2009-11-09 10:05:01

杂项驱动
杂项驱动是指那些共享一定共同特征的简单字符驱动。 内核抽象这些共同性到API(实现在drivers/char/misc.c)中,也简化了驱动初始化的方式. 所有杂项驱动都被赋予一个主设备后10,但各自都可以选择单个的次设备号. 因此如果一个字符驱动要驱动多个设备时, 它不可能是杂项驱动.

考虑下一个字符驱动在初始化时所做的工作:
1. 通过alloc_chrdev_region()分配主次设备号.
2. 使用class_device_create()创建/dev和/sys结点.
3. 使用cdev_init()和cdev_add()注册自身为字符驱动.

杂项驱动调用misc_register()完成上面所有步骤:
static struct miscdevice mydrv_dev = {
  MYDRV_MINOR,
  "mydrv",
  &mydrv_fops
};

misc_register(&mydrv_dev);
MYDRV_MINOR是赋予杂项驱动的次设备号. 你也可以使用MISC_DYNAMIC_MINOR而不是MYDRV_MINOR来动态赋予次设备号.

驱动作者不需额外工作就可以让每个杂项驱动自动出现在/sys/class/misc/下面.

设备例子:看门狗定时器
看门狗的功能是让跑飞的系统回复到操作状态. 它通过周期性的检查系统脉冲,如果检测不到的话就会发出复位信号.  大多数嵌入式控制器都支持内部看门狗模块. 也有外部看门狗芯片,例如Netwinder W83977AF芯片.

Linux看门狗驱动是作为杂项驱动实现的,代码位于drivers/char/watchdog/. 像RTC驱动,看门狗驱动也导出标准设备接口到用户空间. API的详情在Documentation/watchdog/watchdog-api.txt. 需要看门狗服务的程序都是在对/dev/watchdog操作的,设备结点的此设备号是130.

下列代码实现了一个假想的嵌入式控制器内置的看门狗设备驱动.例子中的看门狗包含两个主寄存器:一个服务寄存器(WD_SERVICE_REGISTER)和一个控制寄存器(WD_CONTROL_REGISTER). 要启动看门狗驱动要写一个特定序列数(0xABCD)给服务寄存器. 要对看门狗超时编程驱动要写控制寄存器的特定比特位.
#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 –ENOTTY;
   }
}

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

module_init(my_wdt_init);
module_exit(my_wdt_exit);
选通看门狗通常在用户空间中做,因为看门狗的目标是检测并响应应用程序和内核挂起. 关键程序如图形引擎打开看门狗驱动并周期对其写.如果在看门狗超时时由于应用程序挂起或者内核崩溃而没有写操作, 看门狗将触发系统复位. 下列程序中的看门狗将重启系统如果: 应用程序在process_graphics()里面挂起或内核死掉并导致应用程序死掉.

#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);
  }
}
当应用程序打开/dev/watchdog时看门狗启动滴答. 关闭设备结点就会停止看门狗除非你在配置内核时设置了CONFIG_WATCHDOG_NOWAYOUT. 设置改选项可以帮助你克服看门狗监控进程在系统正在运行时被信号杀死的可能.

外部看门狗
为确保系统在处理器发生故障时仍可以恢复, 调整策略仍可保证可以使用外部看门狗芯片. 由于这种需要,嵌入式设备有时使用一个便宜的基于硬连线而不是寄存器接口的看门狗芯片(如Maxim的MAX6730).看门狗在固定复位超时时如果检测到输入引脚没有电压脉冲就声明一个复位引脚. 复位引脚接向处理器的复位逻辑, 输入引脚连向处理器的GPIO口. 如果你要为这样的设备写驱动,则和ioctl()函数无关. 通过改动处理器GPIO引脚来禁止看门狗. 这些芯片通常允许更大的初始化超时值来说明是引导时间,接着是更短的复位超时值.
对于不支持硬件看门狗模块的平台,内核实现了软件看门狗,称为softdog。softdog驱动在drivers/char/watchdog/softdog.c中,是一个伪杂项驱动因为它不对真实硬件操作. softdog驱动做了两件看门狗驱动不做的事,其中后者是在硬件里面完成的:
1. 实现超时机制.
2. 如果系统出故障了就初始化软件重启.
这个是通过延迟执行定时器处理函数做到的,无论应用程序何时向softdog写数据. 如果在超时时间内对softdog没有写发生,定时器处理函数生效并重启系统.

2.6内核里面的相关支持是软件锁,它是调度没有在10秒或10秒开外发生时的实例. 一个内核线程watchdog/N,N为CPU数目,每秒都为每个CPU创建时间戳. 如果线程在10秒之后没有创建时间戳,系统肯定是锁了它. 软件锁检测(实现在kernel/softlockup.c)可以帮助我们调试内核崩溃.

内核中有很多杂项驱动, Qtronix红外线键盘驱动drivers/qtronix.c是另外一个有杂项成分的字符驱动. 在drivers/char/下面做grep misc_register()由此可以发现其他的杂项驱动.

阅读(1115) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~