免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1395 | 回复: 0
打印 上一主题 下一主题

Linux 设备驱动简析—PC蜂鸣器驱动 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2009-05-05 18:49 |只看该作者 |倒序浏览



/*
*By Neil Chiao (
neilchiao at gmail.com
)
*转载请注明出处:
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])
}

2、定位到内核中tty的ioctl实现(KDMKTONE)
       其实,上述代码中,把/dev/tty10修改成tty2,tty5随便一个都是可以的(我试过的)。
       下面定位到内核的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叫。

3、input 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蜂鸣器的event在pcspkr.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
              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;
}

  


本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u3/91522/showart_1919020.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP