/*
*By Neil Chiao ()
*欢迎到“新星湾()”指导
*/
在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、定位到内核中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 < 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;
}