Chinaunix首页 | 论坛 | 博客
  • 博客访问: 286321
  • 博文数量: 65
  • 博客积分: 1490
  • 博客等级: 上尉
  • 技术积分: 835
  • 用 户 组: 普通用户
  • 注册时间: 2009-01-16 20:00
文章分类

全部博文(65)

文章存档

2015年(1)

2012年(3)

2010年(7)

2009年(54)

我的朋友

分类: LINUX

2009-05-05 18:49:24

 

 

/*

*By Neil Chiao ()

*转载请注明出处:neilengineer.cublog.cn

*欢迎到“新星湾()”指导

*/

 

       X86平台的主板上一般都有一个蜂鸣器,有人可能认为这么简单的东西,根本不需要驱动吧?  但是其实Linux内核中专门有一个这样的驱动pcspkr.c

(注意:本文分析的代码来自linux-2.6.28

 

1、从用户空间代码开始

       先看下面这个小程序,此程序的作用是让PC蜂鸣器叫,它open/dev/tty10这个设备,难道PC蜂鸣器设备节点是/dev/tty10??

#include

#include

#include

 

int main(int argc, char *argv[])

{

    int fd = open("/dev/tty10", O_RDONLY);

    if (fd == -1 || argc != 3) return -1;

    return ioctl(fd, KDMKTONE, (atoi(argv[2])<<16)+(1193180/atoi(argv[1])));

}

 

2、定位到内核中ttyioctl实现(KDMKTONE

       其实,上述代码中,把/dev/tty10修改成tty2tty5随便一个都是可以的(我试过的)。

       下面定位到内核的tty实现,发现在vt_ioctl.c中有如下代码:

vt_ioctl()

{

......

       case KDMKTONE:

              if (!perm)

                     goto eperm;

       {

              unsigned int ticks, count;

              ticks = HZ * ((arg >> 16) & 0xffff) / 1000;

              count = ticks ? (arg & 0xffff) : 0;

              if (count)

                     count = CLOCK_TICK_RATE / count;

              kd_mksound(count, ticks);

              break;

       }

......

}

 

       上述代码最终调用kd_mksound函数来发声。 kd_mksound函数实现如下:

void kd_mksound(unsigned int hz, unsigned int ticks)

{

       struct list_head *node;

       del_timer(&kd_mksound_timer);

       if (hz) {

              list_for_each_prev(node, &kbd_handler.h_list) {

                     struct input_handle *handle = to_handle_h(node);

                     if (test_bit(EV_SND, handle->dev->evbit)) {

                            if (test_bit(SND_TONE, handle->dev->sndbit)) {

                                   input_inject_event(handle, EV_SND, SND_TONE, hz);

                                   break;

                            }

                            if (test_bit(SND_BELL, handle->dev->sndbit)) {

                                   input_inject_event(handle, EV_SND, SND_BELL, 1);

                                   break;

                            }

                     }

              }

              if (ticks)

                     mod_timer(&kd_mksound_timer, jiffies + ticks);

       } else

              kd_nosound(0);

}

 

由代码,我们知道,kd_mksound函数实质上使用input_inject_event(handle, EV_SND, SND_TONE, hz)来触发了一个input事件,来使pc speaker叫。

 

3input event机制

 

       这里就涉及到了input子系统了,这个子系统还是比较复杂的,呵。

上述代码中的input_inject_event按下面顺序调用:

input_inject_eventà

input_handle_event(dev, type, code, value);à

input_pass_event

       其中,input_handle_event如下:

 

static void input_handle_event(struct input_dev *dev,

                            unsigned int type, unsigned int code, int value)

{

       int disposition = INPUT_IGNORE_EVENT;

 

       switch (type) {

......

//PC

       case EV_SND:

              if (is_event_supported(code, dev->sndbit, SND_MAX)) {

 

                     if (!!test_bit(code, dev->snd) != !!value)

                            __change_bit(code, dev->snd);

                     disposition = INPUT_PASS_TO_ALL;

              }

              break;

......

       }

......

       if (disposition & INPUT_PASS_TO_HANDLERS)

              input_pass_event(dev, type, code, value);

}

 

       input_pass_event函数实现如下:

static void input_pass_event(struct input_dev *dev,

                          unsigned int type, unsigned int code, int value)

{

       struct input_handle *handle;

       rcu_read_lock();

       handle = rcu_dereference(dev->grab);

       if (handle)

              handle->handler->event(handle, type, code, value);

       else

              list_for_each_entry_rcu(handle, &dev->h_list, d_node)

                     if (handle->open)

                            handle->handler->event(handle,

                                                 type, code, value);

       rcu_read_unlock();

}

 

 

PC蜂鸣器的event实现

input子系统根据input event的类型(PC蜂鸣器是EV_SND),最终调用相应的event处理,PC蜂鸣器的eventpcspkr.c中实现:

 

static int pcspkr_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)

{

       unsigned int count = 0;

       unsigned long flags;

       if (type != EV_SND)

              return -1;

       switch (code) {

              case SND_BELL: if (value) value = 1000;

              case SND_TONE: break;

              default: return -1;

       }

       if (value > 20 && value < 32767)

              count = PIT_TICK_RATE / value;

       spin_lock_irqsave(&i8253_lock, flags);

       printk("count = %d\n",count);

//下面一段是让PC蜂鸣器叫最实质的实现,想看懂的话,请自己找ICH8南桥芯片手册看

       if (count) {

              outb_p(inb_p(0x61) | 3, 0x61);

              outb_p(0xB6, 0x43);

              outb_p(count & 0xff, 0x42);

              outb((count >> 8) & 0xff, 0x42);

       } else {

              outb(inb_p(0x61) & 0xFC, 0x61);

       }

       spin_unlock_irqrestore(&i8253_lock, flags);

       return 0;

}

 

  

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