Chinaunix首页 | 论坛 | 博客
  • 博客访问: 15340137
  • 博文数量: 2005
  • 博客积分: 11986
  • 博客等级: 上将
  • 技术积分: 22535
  • 用 户 组: 普通用户
  • 注册时间: 2007-05-17 13:56
文章分类

全部博文(2005)

文章存档

2014年(2)

2013年(2)

2012年(16)

2011年(66)

2010年(368)

2009年(743)

2008年(491)

2007年(317)

分类: LINUX

2009-12-11 16:08:45

浅析ac97声卡intel8x0的pci总线DMA物理地址填充和音频数据发送流程

DEVICE_INTEL_ICH4一共有6级缓冲[6个功能,一个pci卡最多有8个功能],每一级缓冲大小为ichdev->fragsize,即8192字节.
snd_intel8x0_probe
==> snd_intel8x0_create
    static unsigned int bdbars[] = {
        3, /* DEVICE_INTEL */
        6, /* DEVICE_INTEL_ICH4 */
        3, /* DEVICE_SIS */
        6, /* DEVICE_ALI */
        4, /* DEVICE_NFORCE */
    };
    chip->addr = pci_iomap(pci, 2, 0);  // 映射pci总线bank2的物理地址到内核空间,该pci-bank2内部含有6级声音缓冲[对应6个功能,一个pci卡最多有8个功能]
    chip->bdbars_count = bdbars[device_type]; // intel8x0每种不同ac97声卡型号,并不是对应的pci-bank数目,而是对应声卡中音频缓冲区bar数目,我的是DEVICE_INTEL_ICH4,一共有6个声音缓冲[luther.gliethttp]
    for (i = 0; i < chip->bdbars_count; i++) {
        ichdev = &chip->ichd[i];
        ichdev->ichd = i;
        ichdev->reg_offset = tbl[i].offset;
        ichdev->int_sta_mask = tbl[i].int_sta_mask;
        if (device_type == DEVICE_SIS) {
            /* SiS 7012 swaps the registers */
            ichdev->roff_sr = ICH_REG_OFF_PICB;
            ichdev->roff_picb = ICH_REG_OFF_SR;
        } else {
            ichdev->roff_sr = ICH_REG_OFF_SR;
            ichdev->roff_picb = ICH_REG_OFF_PICB;
        }
        if (device_type == DEVICE_ALI)
            ichdev->ali_slot = (ichdev->reg_offset - 0x40) / 0x10;
        /* SIS7012 handles the pcm data in bytes, others are in samples */
        ichdev->pos_shift = (device_type == DEVICE_SIS) ? 0 : 1;
    }
    for (i = 0; i < chip->bdbars_count; i++) {
        ichdev = &chip->ichd[i];
        ichdev->bdbar = ((u32 *)chip->bdbars.area) + // 本bar声音缓冲对应的内核线性地址[luther.gliethttp]
            (i * ICH_MAX_FRAGS * 2);
        ichdev->bdbar_addr = chip->bdbars.addr + // 本bar声音缓冲对应的pci上的物理地址[luther.gliethttp]
            (i * sizeof(u32) * ICH_MAX_FRAGS * 2);
        int_sta_masks |= ichdev->int_sta_mask;
    }
[22753.679940] +++++snd_dma_alloc_pages++size=1536,vaddr=c971e000,paddr=0971e000, type=2+++++
[22753.679947] bdbars[0].bdbar=c971e000  // 一共6级bar缓冲[6个功能,一个pci卡最多有8个功能],每个bar功能占用256字节内存空间
[22753.679949] bdbars[0].bdbar_addr=0971e000
[22753.679954] bdbars[1].bdbar=c971e100
[22753.679956] bdbars[1].bdbar_addr=0971e100
[22753.679961] bdbars[2].bdbar=c971e200
[22753.679962] bdbars[2].bdbar_addr=0971e200
[22753.679967] bdbars[3].bdbar=c971e300
[22753.679969] bdbars[3].bdbar_addr=0971e300
[22753.679973] bdbars[4].bdbar=c971e400
[22753.679975] bdbars[4].bdbar_addr=0971e400
[22753.679980] bdbars[5].bdbar=c971e500
[22753.679982] bdbars[5].bdbar_addr=0971e500
ok,上面已经将bar声卡上的音频缓冲区地址进行了获取和存储,接下来使用这些缓冲来传输音频数据[luther.gliethttp]

首先填充6个功能声音缓冲区DMA传输使用到的DDR音频数据内存地址[luther.gliethttp]
ioctl(SNDRV_PCM_IOCTL_PREPARE)
==> snd_pcm_playback_ioctl
==> snd_pcm_playback_ioctl1
==> snd_pcm_common_ioctl1
==> snd_pcm_prepare // prepare the PCM substream to be triggerable
==> snd_pcm_action_nonatomic(&snd_pcm_action_prepare,
                           substream, f_flags);
==> snd_pcm_action_single(ops, substream, state);
    ops->pre_action(substream, state);
    ops->do_action(substream, state);
    ops->post_action(substream, state);
    上面ops就是之前提到的snd_pcm_action_prepare
==> snd_pcm_do_prepare调用snd_pcm_do_reset(substream, 0);复位
    substream->ops->prepare(substream);即snd_intel8x0_playback_ops.prepare
==> snd_intel8x0_pcm_prepare
static int snd_intel8x0_pcm_prepare(struct snd_pcm_substream *substream)
{
    struct intel8x0 *chip = snd_pcm_substream_chip(substream);
    struct snd_pcm_runtime *runtime = substream->runtime;
    struct ichdev *ichdev = get_ichdev(substream);

    ichdev->physbuf = runtime->dma_addr;    // dma缓冲区地址
    ichdev->size = snd_pcm_lib_buffer_bytes(substream); // 将帧缓冲大小转为字节空间大小[luther.gliethttp]
    ichdev->fragsize = snd_pcm_lib_period_bytes(substream);
    if (ichdev->ichd == ICHD_PCMOUT) {
        snd_intel8x0_setup_pcm_out(chip, runtime); // 为play模式设置ac97寄存器[luther.gliethttp]
        if (chip->device_type == DEVICE_INTEL_ICH4)
            ichdev->pos_shift = (runtime->sample_bits > 16) ? 2 : 1;
    }
    snd_intel8x0_setup_periods(chip, ichdev); // 设置PCI总线ac97的bank地址空间[luther.gliethttp]
    return 0;
}
==> snd_intel8x0_setup_periods
/*
 * DMA I/O
 */
static void snd_intel8x0_setup_periods(struct intel8x0 *chip, struct ichdev *ichdev)
{
    int idx;
    u32 *bdbar = ichdev->bdbar; // 使用bar功能对应的内核线性地址,来操作声卡pci地址[luther.gliethttp]
    unsigned long port = ichdev->reg_offset;

    iputdword(chip, port + ICH_REG_OFF_BDBAR, ichdev->bdbar_addr);
    if (ichdev->size == ichdev->fragsize) {
        ichdev->ack_reload = ichdev->ack = 2;
        ichdev->fragsize1 = ichdev->fragsize >> 1;
        for (idx = 0; idx < (ICH_REG_LVI_MASK + 1) * 2; idx += 4) {
《浅析ac97声卡intel8x0的runtime->dma_area是怎么获取的》
            bdbar[idx + 0] = cpu_to_le32(ichdev->physbuf);
            bdbar[idx + 1] = cpu_to_le32(0x80000000 | /* interrupt on completion */
                             ichdev->fragsize1 >> ichdev->pos_shift);
            bdbar[idx + 2] = cpu_to_le32(ichdev->physbuf + (ichdev->size >> 1));
            bdbar[idx + 3] = cpu_to_le32(0x80000000 | /* interrupt on completion */
                             ichdev->fragsize1 >> ichdev->pos_shift);
        }
        ichdev->frags = 2;
    } else {
        // 对于DEVICE_INTEL_ICH4声卡
        ichdev->ack_reload = ichdev->ack = 1;
        ichdev->fragsize1 = ichdev->fragsize;
        for (idx = 0; idx < (ICH_REG_LVI_MASK + 1) * 2; idx += 2) {
            // 将音频申请到的DMA内存地址作为pci声卡进行DMA传输的数据源地址[luther.gliethttp]
            bdbar[idx + 0] = cpu_to_le32(ichdev->physbuf +
                             (((idx >> 1) * ichdev->fragsize) %
                              ichdev->size));
            bdbar[idx + 1] = cpu_to_le32(0x80000000 | /* interrupt on completion */
                             ichdev->fragsize >> ichdev->pos_shift);
            // 使能中断,本级音频缓冲区bar功能音频数据发送完毕之后,声卡将通过pci触发snd_intel8x0_interrupt中断[luther.gliethttp]
#if 0
            printk(KERN_DEBUG "bdbar[%i] = 0x%x [0x%x]\n",
                   idx + 0, bdbar[idx + 0], bdbar[idx + 1]);
#endif
        }
        ichdev->frags = ichdev->size / ichdev->fragsize;
    }
    iputbyte(chip, port + ICH_REG_OFF_LVI, ichdev->lvi = ICH_REG_LVI_MASK);
    ichdev->civ = 0;
    iputbyte(chip, port + ICH_REG_OFF_CIV, 0);
    ichdev->lvi_frag = ICH_REG_LVI_MASK % ichdev->frags;
    ichdev->position = 0;
#if 0
    printk(KERN_DEBUG "lvi_frag = %i, frags = %i, period_size = 0x%x, "
           "period_size1 = 0x%x\n",
           ichdev->lvi_frag, ichdev->frags, ichdev->fragsize,
           ichdev->fragsize1);
#endif
    /* clear interrupts */
    iputbyte(chip, port + ichdev->roff_sr, ICH_FIFOE | ICH_BCIS | ICH_LVBCI);
}
启动pci声卡的DMA传输
ioctl(SNDRV_PCM_IOCTL_START)
==> snd_pcm_playback_ioctl
==> snd_pcm_playback_ioctl1
==> snd_pcm_common_ioctl1
==> snd_pcm_action_lock_irq(&snd_pcm_action_start, substream, SNDRV_PCM_STATE_RUNNING);
==> snd_pcm_action_single // state等于SNDRV_PCM_STATE_RUNNING
static struct action_ops snd_pcm_action_start = {
    .pre_action = snd_pcm_pre_start,
    .do_action = snd_pcm_do_start,
    .undo_action = snd_pcm_undo_start,
    .post_action = snd_pcm_post_start
};
    ops->pre_action(substream, state);
    ops->do_action(substream, state);
    ops->post_action(substream, state);
    上面ops就是之前提到的snd_pcm_action_start
==> snd_pcm_do_start
==> substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_START);即snd_intel8x0_playback_ops.trigger
==> snd_intel8x0_pcm_trigger启动ac97数据传输
    case SNDRV_PCM_TRIGGER_START:
    case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
        val = ICH_IOCE | ICH_STARTBM; // 使能pci总线DMA音频数据传输[luther.gliethttp]
        ichdev->last_pos = ichdev->position;
        break;
当pci声卡ac97将本bar缓冲数据[一个有6级缓冲,对应pci的6个功能]传输完毕之后,pci声卡将出发中断,
执行snd_intel8x0_interrupt中断处理函数[luther.gliethttp]
static irqreturn_t snd_intel8x0_interrupt(int irq, void *dev_id)
{
    struct intel8x0 *chip = dev_id;
    struct ichdev *ichdev;
    unsigned int status;
    unsigned int i;

    status = igetdword(chip, chip->int_sta_reg);
    if (status == 0xffffffff)    /* we are not yet resumed */
        return IRQ_NONE;

    if ((status & chip->int_sta_mask) == 0) {
        if (status) {
            /* ack */
            iputdword(chip, chip->int_sta_reg, status);
            if (! chip->buggy_irq)
                status = 0;
        }
        return IRQ_RETVAL(status);
    }

    for (i = 0; i < chip->bdbars_count; i++) {
        ichdev = &chip->ichd[i];
        if (status & ichdev->int_sta_mask)
            snd_intel8x0_update(chip, ichdev);
    }

    /* ack them */
    iputdword(chip, chip->int_sta_reg, status & chip->int_sta_mask);
    
    return IRQ_HANDLED;
}
阅读(1964) | 评论(1) | 转发(0) |
给主人留下些什么吧!~~

mcmibob2010-06-07 17:42:20

博主,你好!我初涉驱动,有个问题想请教下。 关于音频数据发送过程我是这么理解的: IO端口用于控制命令 bar缓冲存放数据发送的具体信息:数据DMA缓冲地址,数据长度等 发送音频数据的时候先写信息到bar缓冲,再往IO端口写命令,然后声卡通过DMA传输音频数据 不知道这么理解正确否?我不太明白bar缓冲区这个东东是干嘛的。 希望楼主可以解答下。谢谢了!