一、codec command and control
这是一个controller和codec之间发送和接受command的机制。分别是CORB和RIRB,软件上分别通过两个control 寄存器来配置。
1.1、command outband ring buffer
controller 通过CORB来向codec发送commands。这个CORB是一个circular buffer,位于系统内存中,用来从software pass命令到和HDA link链接的codec上面。controller 使用DMA来从CORB中抓取数据,然后command放置在每一个link frame 开始的Command/Control位。
CORB的大小一般被设置为256 entries(1kb)
这里会涉及到两个指针:
WP(write pointer): used by sofetware to indicate to the hardware the last valid command in the CORB, 最后一条已发送的有效指令
RP(write pointer): the hardware uses RP to indicate to the sofeware the last command that has been fetched,硬件那边已经接收到的最后一条有效指令
WP & RP both measure the offset into the buffer in terms of commands
因为command都是4 byte长,所以在CORB buffer中的字节偏移都是WP/RP*4。往CORB中添加commands的时候,就是往offset (WP+1)*4byte处写入command,然后更新WP的值,WP+1。当software结束往里面写command的时候,它更新WP的值等于buffer中最后一条有效command的offset的值。刚初始化的时候,WP=0,所以第一条command被放置在offset(0+1)*4=4byte处,WP的值更新为1。
hardware sequence for pointer usage
Loop:
If (RP != WP) && ('run' bit is set) && ('link is running')
RP++
Get DWORD from buffer offset RP*4bytes
Send DWORD (at beginning of next new frame)
goto Loop
当CORB RUN位被设置以后,controller里面的DMA会持续的比较RP和WP的值,看是否有新的命令可以使用。当两个pointer不相等的时候,MDA engine就会run到他们相等,DMA engine从CORB中读取commands并通过link将他们send到codec。
1.1.1、CORB buffer allocation
内存中的CORBbuffer是一个128字节大小开始的空间,大小由软件分配,并将location写道controller的CORB address Upper base和 Lower base register中,低7位为0来强制使用128byte的分配需求。
1.1.2、CORB entry format
从controller到codec的verbs为32 bit长度
1.1.3、 初始化CORB
首先得确认在CORBCTL register中的CORBRUN bit的值为0,正确的寄存器大小是使用corb size寄存器确定的,corb内存是从适当的堆和内存类型分配的。CORBBASE寄存器被定义为分配内存的基址,并使用CORBRPRST位将读取指针重置为0。软件必须向写入指针写入0h才能清除写入指针。如果需要,可以通过设CMEIE位来启用corb错误报告。最后,将CORBRUN位设置为1以启用corb操作。
1.1.4 Transmitting commands via the CORB
通过CORB进行的commands传输,首先是要确认CORB上有足够的空间。CORBWP & CORBRP的差别可以用来确定CORB中的可用空间。当第一条command被放置到CORB buffer中,因为command为32位占据4bytes,所以CORBWP+1表示CORB中4bytes的offset。
CORBWP然后被software更新为指向最后一条被写进CORB的command的index。Hardware然后就开始通过link transfer command,每从memory中抓取一条command就更新CORBRP的值。当RP=WP的时候,所有的command都被sent完,这个时候controller就会停止transfer。
在传送的过程中,software需要确认最新被添加进来的commands不会overwrite the buffer。
1.1.5 notice in corb program
如果一次有大量的command被sent到codec,这时候softwore需要不时的往这些commands中插入breaks,不然会造成拥堵
1.2 Response inbound ring buffer
Codec通过RIRB机制往controller发送responses。RIRB也是一个位于系统memeory中的用于存储codec发送来的responses的ring buffer。responses可以是请求(solicited)(响应来自控制器的命令)或主动请求(unsolicited)(由编解码器发送以发出事件信号)。
RIRB的分配和CORB差不多。
任何一个codec都可以往controller发送Response,controller中的DMA engine会往RIRB中来写这些responses。请求的响应由单个codec在随后的帧中返回,与此同时将提示命令(prompting commonds)发送到该codec。codec可以在不存在请求响应的任何帧中注入(injected)非请求响应。controller将把这个响应流写入rirb缓冲区。软件负责将响应与单个codec 分离,并将未经请求的响应与请求的响应分离。
同样在RIRB中也有WP & RP:
RP: sofrware控制,用来记录software从response buffer中读取的最后一条response,RP没有硬件的表示
WP: controller在hardware中使用WP来表示最后一个被写入到 response buffer中的response的offset。
不管是什么样的请求来了,controller都会往RIRB中写,与CORB一样,wp指示响应缓冲区单元中的偏移量。因为每一个response是8byte,所以buffer中的字节偏移值为:WP*8bytes
Example hardware sequence for pointer usage:
Loop:
If (‘new response received’) && (‘run’ bit is set) && (‘Valid bit’ is set)
WP++
Write response + flags (QWORD) into buffer offset WP*8bytes
Goto loop
controller有两种方法去提醒software RIRB已经被读取了:
1、中断
2、poll(计数)
1.2.1 RIRB entry format
codec发过来的response是32位的,然后controller在RIRB中为每个response加了一个32位的信息,这个信息包括两个:一个是表示是具体哪个codec发过来的,还有一个位表示这个response是un or solicited的。
1.2.2 RIRB初始化
rirb初始化与corb初始化非常相似。必须根据rirbsize寄存器和适合系统基础结构的堆正确分配内存。然后适当地更新rirbubase、rirblbase和中断生成控制寄存器。
二、codec相关函数分析
/* create codec instances */
err = azx_codec_create(chip, model[dev],
azx_max_codecs[chip->driver_type],
power_save_addr);
这里涉及到几个重要的结构体:
hda_bus、hda_bus_ops
hda_codec、hda_codec_ops
azx_rb
下面是codec的初始化工作
/* Codec initialization */
{
struct hda_bus_template bus_temp;
int c, codecs, err;
bus_temp.ops.command = azx_send_cmd;
bus_temp.ops.get_response = azx_get_response;
bus_temp.ops.attach_pcm = azx_attach_pcm_stream;
bus_temp.ops.bus_reset = azx_bus_reset;
azx_send_cmd();azx_get_response();两个函数的内容,应该就是上面所介绍的CORB\RIRB的具体实现。下面来具体分析:
2.1 azx_send_cmd()
这里主要调用函数 azx_corb_send_cmd() 来send a command
unsigned int addr = azx_command_addr(val);
这一句,通过查看azx_command_addr函数可知,应该是用来获得codec的位置信息,也就是具体往哪一个codec上发送commands
/* add command to corb */
wp = azx_readw(chip, CORBWP);
if (wp == 0xffff) {
/* something wrong, controller likely turned to D3 */
spin_unlock_irq(&chip->reg_lock);
return -EIO;
}
wp++;
wp %= ICH6_MAX_CORB_ENTRIES;
CORBWP在HDA spec上有说明,位于offset48h的位置,一个16位的寄存器。表示就是CORB write pointer的值。chip就是具体的声卡芯片,基地址,然后加偏移,这就是azx_readw()的操作。
ICH6_MAX_CORB_ENTRIES == 256,在spec中有说CORBWP支持最多256 CORB entries(256 x 4B = 1KB),所以这里取余是获得wp的具体指向的command的位置数
rp = azx_readw(chip, CORBRP);
if (wp == rp) {
/* oops, it's full */
spin_unlock_irq(&chip->reg_lock);
return -EAGAIN;
}
当wp=rp的时候,DMA就不从CORB中fetch command了
chip->rirb.cmds[addr]++;
chip->corb.buf[wp] = cpu_to_le32(val);
azx_writew(chip, CORBWP, wp);
wp++以后,RIRB中待处理的cmd就得++,然后将这个wp写入到chip上的CORBWP中去
2.2 azx_get_response()
static unsigned int azx_rirb_get_response(struct hda_bus *bus,
unsigned int addr)
{
struct azx *chip = bus->private_data;
unsigned long timeout;
unsigned long loopcounter;
int do_poll = 0;
again:
timeout = jiffies + msecs_to_jiffies(1000);
for (loopcounter = 0;; loopcounter++) {
if (chip->polling_mode || do_poll) {
spin_lock_irq(&chip->reg_lock);
azx_update_rirb(chip);
spin_unlock_irq(&chip->reg_lock);
}
这里使用的是poll模式来更新这个RIRB
在这个函数中主要调用 azx_rirb_get_response() 函数,用于receive a response,然后在这里面首先会=调用下面这个函数来更新RIRB
/* retrieve RIRB entry - called from interrupt handler */
static void azx_update_rirb(struct azx *chip)
unsigned int rp, wp;
unsigned int addr;
u32 res, res_ex;
上面这个addr就是表示codec的具体哪个,res表示codec返回的32位的response,res_ex表示controller为这个res添加的4位的info信息,所以在buffer中要多加一个4byte来表示。
while (chip->rirb.rp != wp) {
chip->rirb.rp++;
chip->rirb.rp %= ICH6_MAX_RIRB_ENTRIES;
rp = chip->rirb.rp << 1; /* an RIRB entry is 8-bytes */
res_ex = le32_to_cpu(chip->rirb.buf[rp + 1]);
res = le32_to_cpu(chip->rirb.buf[rp]);
addr = res_ex & 0xf;
首先要明确几点:因为RIRB中的每个entry是8byte,但在azx_rb中定义的CORB/RIRB buffer为*u32 buf,是4byte的形式存储,所以RIRB buffer中的一个entry的存储占两个位置。
将rp的值取余256可以得到rp在buffer中具体指向的entry(因为rp一直是++的)这时的rp是指向每一条entry的,也就是每隔8byte指向一个;
然后将rp的值左移一位,相当于x2,就是rp的位置上向下挪了一位,指向了低4byte,也就是这个entry的response部分,rp+1指向的就是这个entry的expend部分。
addr就是通过和0x1111与一下就可以得到具体是哪一个codec,因为最多15个。
else if (chip->rirb.cmds[addr]) {
chip->rirb.res[addr] = res;
smp_wmb();
chip->rirb.cmds[addr]--;
这里就表示如果有待处理的command,就赋值写到RIRB中,相应的标志位–。
然后继续回到函数 azx_rirb_get_response() 中;
后面就是一些错误处理,最后释放掉cmd_io,然后查看了一下,发现cmd_io 的初始化在 azx_init_chip()函数中就调用了
if (!chip->single_cmd)
azx_init_cmd_io(chip);
在这里面是CORB\RIRB的一些初始化工作,包括一些关键位和buffer的分配。
2.3 azx_attach_pcm_stream()
这里涉及到三个关于pcm的结构体:
struct snd_pcm pcm
struct azx_pcm apcm
struct hda_pcm cpcm
struct hda_pcm {
char *name;
struct hda_pcm_stream stream[2];
unsigned int pcm_type; /* HDA_PCM_TYPE_XXX */
int device; /* device number to assign */
struct snd_pcm *pcm; /* assigned PCM instance */
bool own_chmap; /* codec driver provides own channel maps */
};
struct azx_pcm {
struct azx *chip;
struct snd_pcm *pcm;
struct hda_codec *codec;
struct hda_pcm_stream *hinfo[2];
struct list_head list;
};
struct snd_pcm {
struct snd_card *card;
struct list_head list;
int device; /* device number */
unsigned int info_flags;
unsigned short dev_class;
unsigned short dev_subclass;
char id[64];
char name[80];
struct snd_pcm_str streams[2];
struct mutex open_mutex;
wait_queue_head_t open_wait;
void *private_data;
void (*private_free) (struct snd_pcm *pcm);
struct device *dev; /* actual hw device this belongs to */
bool internal; /* pcm is for internal use only */
#if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE)
struct snd_pcm_oss oss;
#endif
};
然后创建一个PCM 实例:
err = snd_pcm_new(chip->card, cpcm->name, pcm_dev,
cpcm->stream[SNDRV_PCM_STREAM_PLAYBACK].substreams,
cpcm->stream[SNDRV_PCM_STREAM_CAPTURE].substreams,
&pcm);
上面的最后一个参数 &pcm , 在函数原型上的传参是:struct snd_pcm **rpcm ,rpcm是一个用来存储pcm instace的指针。
apcm = kzalloc(sizeof(*apcm), GFP_KERNEL);
然后为apcm分配一块内存
cpcm->pcm = pcm;
for (s = 0; s < 2; s++) {
apcm->hinfo[s] = &cpcm->stream[s];
if (cpcm->stream[s].substreams)
snd_pcm_set_ops(pcm, s, &azx_pcm_ops);
}
这时为stream设置azx_pcm_ops;
然后接下来就是给buffer pre-allocation, 这里的buffer分配是为给定pcm下的所有的stream pre-allocation一个连续的内存空间,最大是#define MAX_PREALLOC_SIZE (32 * 1024 * 1024),DMA类型是SG
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV_SG,
chip->card->dev,
size, MAX_PREALLOC_SIZE);
然后每个stream的具体的buffer分配是通过下面ALSA 内核函数
/**
* snd_pcm_lib_preallocate_pages - pre-allocation for the given DMA type
* @substream: the pcm substream instance
* @type: DMA type (SNDRV_DMA_TYPE_*)
* @data: DMA type dependent data
* @size: the requested pre-allocation size in bytes
* @max: the max. allowed pre-allocation size
*
* Do pre-allocation for the given DMA buffer type.
*
* Return: Zero if successful, or a negative error code on failure.
*/
int snd_pcm_lib_preallocate_pages(struct snd_pcm_substream *substream,
int type, struct device *data,
size_t size, size_t max)
{
substream->dma_buffer.dev.type = type;
substream->dma_buffer.dev.dev = data;
return snd_pcm_lib_preallocate_pages1(substream, size, max);
}
最后将这个pcm dev 链接到codec上,这样就完成了将pcm和这个codec相关联的过程。
/* link to codec */
pcm->dev = &codec->dev;
2.4、azx_bus_reset()
struct azx *chip = bus->private_data;
bus->in_reset = 1;
azx_stop_chip(chip);
azx_init_chip(chip, true);
首先在这个 stop 、init chip两个函数中,初始化了controller、interrupts、codec command I/O、和 position buffer。
snd_hda_suspend(chip->bus);
snd_hda_resume(chip->bus);
挂起,然后重启 the codecs
err = snd_hda_bus_new(chip->card, &bus_temp, &chip->bus);
创建一个HDA bus,
err = snd_device_new(card, SNDRV_DEV_BUS, bus, &dev_ops);
给bus分配内存
err = snd_device_new(card, SNDRV_DEV_BUS, bus, &dev_ops);
这里把bus也当作一个device来创建
2.5、Create codec instance
int snd_hda_codec_new(struct hda_bus *bus,
unsigned int codec_addr,
struct hda_codec **codecp)
{
struct hda_codec *codec;
char component[31];
hda_nid_t fg;
每个线程都有一个nid。
codec = kzalloc(sizeof(*codec), GFP_KERNEL);
为codec这个设备文件分配一块内存,
:
:
:
:
太多了中间,省略
最后就是把codec当作一个dev来创建
err = snd_device_new(bus->card, SNDRV_DEV_CODEC, codec, &dev_ops);