09-巧用类似芯片的驱动程序
23.2.2 巧用类似芯片的驱动程序
任何驱动工程师都没有必要在面对新设备驱动编写需求的时候一切从头开始,因为内核源代码drivers目录(音频设备的驱动在sound目录)中已经包含了大量现成的类似芯片驱动的源代码,是极好的参考模板,我们不需要“re-invent the wheel”。实际上,在内核源代码许多后期编写的驱动程序中,就直接参考了之前的驱动源码,所以同类设备的驱动往往呈现出非常相似的架构和数据结构定义。我们来看看sound/oss/ au1550_ac97.c文件最开始的一段注释:
/*
-
au1550_ac97.c -- Sound driver for Alchemy Au1550 MIPS Internet Edge
-
Processor.
-
Copyright 2004 Embedded Edge, LLC
-
Mostly copied from the au1000.c driver and some from the
-
PowerMac dbdma driver.
-
We assume the processor can do memory coherent DMA.
...
*/
这段注释很清楚地说明其绝大多数代码都来自au1000.c驱动,还有一些来自PowerMac dbdma驱动。
打开sound/oss目录下的au1550_ac97.c(Alchemy Au1550 MIPS处理器的音频驱动)、es1370.c (Ensoniq ES1370/Asahi Kasei AK4531声卡驱动)、es1371.c(reative Ensoniq ES1371声卡驱动)、cs46xx.c(Crystal SoundFusion CS46xx声卡驱动),发现如下相似之处。
● 它们全都自定义了全局的xxx_state结构体实例用于封装音频设备的锁、信号量、缓冲区、ID等信息,这几个结构体分别是:au1550_state、es1370_state、es1371_state、cs_state。
● 它们的核心函数都使用了完全相同的实现方法,au1550_ac97.c的au1550_read()和es1370.c 的es1370_read()的处理流程是一致的,下面列表的左右两列对等地给出了au1550_read()和es1370_read()函数的源代码(为了进行横向比较,适当地增加了源代码的换行,以达到类似WinMerge等源码比较软件的效果)。
static ssize_t u1550_read(struct file
file, char buffer, size_t count,
loff_t*ppos)
{
struct au1550_state *s = (struct
au1550_state*)file->private_data;
struct dmabuf *db = &s->dma_adc;
DECLARE_WAITQUEUE(wait, current);
ssize_t ret;
unsigned long flags;
int cnt, usercnt, avail;
Static ssize_t es1370_read(struct file
file, char __user buffer, size_t
count, loff_t *ppos)
{
struct es1370_state *s = (struct
es1370_state*)file->private_data;
if (db->mapped)
return - ENXIO;
if (!access_ok(VERIFY_WRITE, buffer,
count))
return - EFAULT;
ret = 0;
DECLARE_WAITQUEUE(wait, current);
ssize_t ret = 0;
unsigned long flags;
unsigned swptr;
int cnt;
VALIDATE_STATE(s);
if (s->dma_adc.mapped)
return - ENXIO;
if (!access_ok(VERIFY_WRITE, buffer,
count))
return - EFAULT;
count *= db->cnt_factor;
down(&s->sem);
add_wait_queue(&db->wait, &wait);
while (count > 0)
{
/* wait for samples in ADC dma buffer
*/
do
{
spin_lock_irqsave(&s->lock, flags) ;
if (db->stopped)
start_adc(s);
avail = db->count;
if (avail <= 0)
_ _set_current_state
(TASK_INTERRUPTIBLE);
spin_unlock_irqrestore(&s->lock,
flags);
if (avail <= 0)
{
if (file->f_flags &O_NONBLOCK)
{
if (!ret)
ret = - EAGAIN;
goto out;
}
up(&s->sem);
schedule();
if (signal_pending(current))
{
if (!ret)
ret = - ERESTARTSYS;
goto out2;
}
down(&s->sem);
}
}
while (avail <= 0);
/* copy from nextOut to user
*/
if ((cnt = copy_dmabuf_user(db,
down(&s->sem);
if (!s->dma_adc.ready && (ret =
prog_dmabuf_adc(s)))
goto out;
add_wait_queue(&s->dma_adc.wait, &wait);
while (count > 0)
{
spin_lock_irqsave(&s->lock, flags);
swptr = s->dma_adc.swptr;
cnt = s->dma_adc.dmasize - swptr;
if (s->dma_adc.count < cnt)
cnt = s->dma_adc.count;
if (cnt <= 0)
_ _set_current_state
(TASK_INTERRUPTIBLE);
spin_unlock_irqrestore(&s->lock,
flags);
if (cnt > count)
cnt = count;
if (cnt <= 0)
{
if (s->dma_adc.enabled)
start_adc(s);
if (file->f_flags &O_NONBLOCK)
{
if (!ret)
ret = - EAGAIN;
goto out;
}
up(&s->sem);
schedule();
if (signal_pending(current))
{
if (!ret)
ret = - ERESTARTSYS;
goto out;
}
down(&s->sem);
if (s->dma_adc.mapped)
{
ret = - ENXIO;
goto out;
}
continue;
}
if (copy_to_user(buffer, s
buffer, count > avail ? avail :
count, 1)) < 0)
{
if (!ret)
ret = - EFAULT;
goto out;
}
->dma_adc.rawbuf + swptr, cnt))
{
if (!ret)
ret = - EFAULT;
goto out;
}
swptr = (swptr + cnt) % s
->dma_adc.dmasize;
spin_lock_irqsave(&s->lock, flags);
db->count -= cnt;
db->nextOut += cnt;
if (db->nextOut >= db->rawbuf + db
->dmasize)
db->nextOut -= db->dmasize;
spin_unlock_irqrestore(&s->lock,
flags);
spin_lock_irqsave(&s->lock, flags);
s->dma_adc.swptr = swptr;
s->dma_adc.count -= cnt;
spin_unlock_irqrestore(&s->lock,
flags);
count -= cnt;
usercnt = cnt / db->cnt_factor;
buffer += usercnt;
ret += usercnt;
} / while (count > 0) /
count -= cnt;
buffer += cnt;
ret += cnt;
if (s->dma_adc.enabled)
start_adc(s);
}
out: up(&s->sem);
out2: remove_wait_queue(&db->wait,
&wait);
set_current_state(TASK_RUNNING);
return ret;
}
out: up(&s->sem);
remove_wait_queue(&s->dma_adc.wait,
&wait);
set_current_state(TASK_RUNNING);
return ret;
}
可以看出,内核中看似神秘的、庞大的设备驱动源码也是互相学习、互相借鉴的结果。