4.2.3 azx_probe_work

回顾一下azx_create中最后部分的一行代码:

INIT_DELAYED_WORK(&hda->probe_work, azx_probe_work);

初始化了一个延时work。

再回到azx_probe的最后部分:

if (schedule_probe)schedule_delayed_work(&hda->probe_work, 0);

azx_probe_work被放到了延时处理的消息列表中去执行。

static void azx_probe_work(struct work_struct *work)
{struct hda_intel *hda = container_of(work, struct hda_intel, probe_work.work);azx_probe_continue(&hda->chip);
}

azx_probe_work是通过azx_probe_continue来完成任务的。

static int azx_probe_continue(struct azx *chip)
{struct hda_intel *hda = container_of(chip, struct hda_intel, chip);struct hdac_bus *bus = azx_bus(chip);struct pci_dev *pci = chip->pci;int dev = chip->dev_index;int err;.......err = azx_first_init(chip);if (err < 0)goto out_free;....../* create codec instances */if (bus->codec_mask) {err = azx_probe_codecs(chip, azx_max_codecs[chip->driver_type]);if (err < 0)goto out_free;}......complete_all(&hda->probe_wait);to_hda_bus(bus)->bus_probing = 0;hda->probe_retry = 0;return 0;
}

这段代码中我去除了一些针对特殊设备的特殊处理,以及在初始化失败后尝试再次启动延时work的相关代码。只是想照着主线,推进我们的理解。

这时候我们要面对的就是两个函数azx_first_init和azx_probe_codecs。

4.2.3.1 azx_first_init

static int azx_first_init(struct azx *chip)
{int dev = chip->dev_index;struct pci_dev *pci = chip->pci;struct snd_card *card = chip->card;struct hdac_bus *bus = azx_bus(chip);int err;unsigned short gcap;unsigned int dma_bits = 64;#if BITS_PER_LONG != 64/* Fix up base address on ULI M5461 */if (chip->driver_type == AZX_DRIVER_ULI) {u16 tmp3;pci_read_config_word(pci, 0x40, &tmp3);pci_write_config_word(pci, 0x40, tmp3 | 0x10);pci_write_config_dword(pci, PCI_BASE_ADDRESS_1, 0);}
#endif// 以下三个函数用来分配和获取IO空间err = pcim_iomap_regions(pci, 1 << 0, "ICH HD audio");if (err < 0)return err;bus->addr = pci_resource_start(pci, 0);bus->remap_addr = pcim_iomap_table(pci)[0];if (chip->driver_type == AZX_DRIVER_SKL)snd_hdac_bus_parse_capabilities(bus);/** Some Intel CPUs has always running timer (ART) feature and* controller may have Global time sync reporting capability, so* check both of these before declaring synchronized time reporting* capability SNDRV_PCM_INFO_HAS_LINK_SYNCHRONIZED_ATIME*/
// gts_present是针对get_time_info的特殊处理,在中断的时候同步硬件信息,后续讲到读取音频的时候会用到。chip->gts_present = false;#ifdef CONFIG_X86if (bus->ppcap && boot_cpu_has(X86_FEATURE_ART))chip->gts_present = true;
#endifif (chip->msi) {if (chip->driver_caps & AZX_DCAPS_NO_MSI64) {dev_dbg(card->dev, "Disabling 64bit MSI\n");pci->no_64bit_msi = true;}if (pci_enable_msi(pci) < 0)chip->msi = 0;}//设置过后,才能从pci总线发送消息到设备pci_set_master(pci);// 读取GCAP 注存器信息gcap = azx_readw(chip, GCAP);dev_dbg(card->dev, "chipset global capabilities = 0x%x\n", gcap);/* AMD devices support 40 or 48bit DMA, take the safe one */if (chip->pci->vendor == PCI_VENDOR_ID_AMD)dma_bits = 40;/* disable SB600 64bit support for safety */if (chip->pci->vendor == PCI_VENDOR_ID_ATI) {struct pci_dev *p_smbus;dma_bits = 40;p_smbus = pci_get_device(PCI_VENDOR_ID_ATI,PCI_DEVICE_ID_ATI_SBX00_SMBUS,NULL);if (p_smbus) {if (p_smbus->revision < 0x30)gcap &= ~AZX_GCAP_64OK;pci_dev_put(p_smbus);}}/* NVidia hardware normally only supports up to 40 bits of DMA */if (chip->pci->vendor == PCI_VENDOR_ID_NVIDIA)dma_bits = 40;/* disable 64bit DMA address on some devices */if (chip->driver_caps & AZX_DCAPS_NO_64BIT) {dev_dbg(card->dev, "Disabling 64bit DMA\n");gcap &= ~AZX_GCAP_64OK;}/* disable buffer size rounding to 128-byte multiples if supported */if (align_buffer_size >= 0)chip->align_buffer_size = !!align_buffer_size;else {if (chip->driver_caps & AZX_DCAPS_NO_ALIGN_BUFSIZE)chip->align_buffer_size = 0;elsechip->align_buffer_size = 1;}/* allow 64bit DMA address if supported by H/W */if (!(gcap & AZX_GCAP_64OK))dma_bits = 32;if (dma_set_mask_and_coherent(&pci->dev, DMA_BIT_MASK(dma_bits)))dma_set_mask_and_coherent(&pci->dev, DMA_BIT_MASK(32));dma_set_max_seg_size(&pci->dev, UINT_MAX);/* read number of streams from GCAP register instead of using* hardcoded value*/chip->capture_streams = (gcap >> 8) & 0x0f;chip->playback_streams = (gcap >> 12) & 0x0f;if (!chip->playback_streams && !chip->capture_streams) {/* gcap didn't give any info, switching to old method */switch (chip->driver_type) {case AZX_DRIVER_ULI:chip->playback_streams = ULI_NUM_PLAYBACK;chip->capture_streams = ULI_NUM_CAPTURE;break;case AZX_DRIVER_ATIHDMI:case AZX_DRIVER_ATIHDMI_NS:chip->playback_streams = ATIHDMI_NUM_PLAYBACK;chip->capture_streams = ATIHDMI_NUM_CAPTURE;break;case AZX_DRIVER_GENERIC:default:chip->playback_streams = ICH6_NUM_PLAYBACK;chip->capture_streams = ICH6_NUM_CAPTURE;break;}}chip->capture_index_offset = 0;chip->playback_index_offset = chip->capture_streams;chip->num_streams = chip->playback_streams + chip->capture_streams;/* sanity check for the SDxCTL.STRM field overflow */if (chip->num_streams > 15 &&(chip->driver_caps & AZX_DCAPS_SEPARATE_STREAM_TAG) == 0) {dev_warn(chip->card->dev, "number of I/O streams is %d, ""forcing separate stream tags", chip->num_streams);chip->driver_caps |= AZX_DCAPS_SEPARATE_STREAM_TAG;}/* initialize streams */err = azx_init_streams(chip);if (err < 0)return err;err = azx_alloc_stream_pages(chip);if (err < 0)return err;/* initialize chip */azx_init_pci(chip);snd_hdac_i915_set_bclk(bus);hda_intel_init_chip(chip, (probe_only[dev] & 2) == 0);/* codec detection */if (!azx_bus(chip)->codec_mask) {dev_err(card->dev, "no codecs found!\n");/* keep running the rest for the runtime PM */}//申请中断号if (azx_acquire_irq(chip, 0) < 0)return -EBUSY;strcpy(card->driver, "HDA-Intel");strscpy(card->shortname, driver_short_names[chip->driver_type],sizeof(card->shortname));snprintf(card->longname, sizeof(card->longname),"%s at 0x%lx irq %i",card->shortname, bus->addr, bus->irq);return 0;
}

这部分要解释的内容比较多,分开来介绍。

4.2.3.1.1 IO空间

一般设备都支持一定数量的指令,用来获取设备的相关信息,或者是驱动设备进行一些特定的处理。早期的计算机系统中,实现的方式是IO端口,x86 的汇编指令中通过in/out指令来完成相应的操作。后来希望通过与读取内存相似的方式来操控设备,发展出了IO空间的概念。可以理解为设备上自己也有一段内存,这段内存也可以读写。读取可以获取设备的一些信息,写入可以命令设备进行特定的操作。这段内存也需要映射到系统的虚拟地址空间上,然后只要读写这段地址空间,就可以达到操控设备的目的。一般通过ioremap函数来完成设备IO空间的配置,不过对于pci设备有专门的函数。

err = pcim_iomap_regions(pci, 1 << 0, "ICH HD audio");if (err < 0)return err;bus->addr = pci_resource_start(pci, 0);bus->remap_addr = pcim_iomap_table(pci)[0];

pcim_iomap_regions函数将通过ioremap来为设备分配虚拟内存空间。

pci_resource_start返回的是物理地址的开始位置。

pcim_iomap_table返回的虚拟地址的开始位置,其实针对设备的操作,都是通过remap_addr来完成的。而物理地址addr,并不会被用到,这里只起到一个记录的作用。

之前提到IO空间是一段空间,也就是说它可以支持众多的指令,但支持多少指令其实是硬件设备设定好的。一般设备的说明上,都会给出这些命令的固定格式,参考以下controller说明书上的指令格式:

这里的偏移开始和结束对应的是以字节为单位的,比如GCAP开始位置是00,结束是01,代表的是2个字节。而VMIN开始结束都是02,也就是说它占用一个字节。这里的偏移可以理解为相对于bus->remap_addr的位置。

这里没有给出所有的指令集,可以自行参考hda的说明书。

在设定regmap_addr后,下面的代码就开始读取GCAP的信息:

gcap = azx_readw(chip, GCAP);

我们顺着理解以下它的操作:

#define azx_readw(chip, reg) \

snd_hdac_chip_readw(azx_bus(chip), reg)

azx_readw扩展成了对snd_hdac_chip_readw的调用,而azx_bus(chip)用来将azx对象转化为hdac_bus对象。差别之前介绍过。

#define snd_hdac_chip_readw(chip, reg) \

_snd_hdac_chip_readw(chip, AZX_REG_ ## reg)

这段操作的目的是给GCAP前面加上 AZX_REG_,也就是变成AZX_REG_GCAP。这是因为这些偏移值在代码中被统一定义过,如:

#define AZX_REG_GCAP 0x00

#define AZX_REG_VMIN 0x02

#define AZX_REG_VMAJ 0x03

#define _snd_hdac_chip_readw(chip, reg) \

snd_hdac_reg_readw(chip, (chip)->remap_addr + (reg))

remap_addr + (reg) 用来确定真实要访问的地址。

static inline u16 snd_hdac_reg_readw(struct hdac_bus *bus, void __iomem *addr)
{return snd_hdac_aligned_mmio(bus) ?snd_hdac_aligned_read(addr, 0xffff) : readw(addr);
}

对于对齐的操作不考虑,只了解一下readw。在不同的平台上实现的方式并不一样,这里只讲一下简单的理解。其实这行代码等价于 return *addr,就是将addr地址中的内容返回去。

这里的azx_readw用来读取两个字节,而azx_readb读一字节,还有azx_readl和azx_readq只是读取的字节数不同。另外就是azx_writew等函数,用来写相应的字节数到设备。

再来看看读取到内容:

返回的内容其实有固定的格式,如这里的GCAP:第一bit代表是否支持64bit的地址,而后的信息用来表示它支持的SDO数量等信息。

通过读取GCAP,可以得到支持的streams的数量:

chip->capture_streams = (gcap >> 8) & 0x0f;

chip->playback_streams = (gcap >> 12) & 0x0f;

然后进行下一步的处理。

4.2.3.1.2 azx_init_streams

我们先来理解一下stream的相关概念。

在之前的介绍中,也简单提到过。当要播放音频的时候,会将内存中的数据通过DMA传送到controller的缓冲中,然后controller通过ac link再传送到codec。这样一条数据通路可以理解为一条stream。一个controller可以支持多条stream。比如上图中Stream 1的数据发送到Codec-b,解码后是发送到电话的。Stream3的数据可以发送到CodecA 和CodecC,解码后发送到耳机或者音响。需要注意的是一个Stream上可以连接多个Codec,它们得到的数是相同的。录音的过程只是方向反了过来,也属于一条Stream。

int azx_init_streams(struct azx *chip)
{int i;int stream_tags[2] = { 0, 0 };/* initialize each stream (aka device)* assign the starting bdl address to each stream (device)* and initialize*/for (i = 0; i < chip->num_streams; i++) {struct azx_dev *azx_dev = kzalloc(sizeof(*azx_dev), GFP_KERNEL);int dir, tag;if (!azx_dev)return -ENOMEM;// 就是通过处理的顺序,来确定当前生成播放还是录音的streamdir = stream_direction(chip, i);/* stream tag must be unique throughout* the stream direction group,* valid values 1...15* use separate stream tag if the flag* AZX_DCAPS_SEPARATE_STREAM_TAG is used*/if (chip->driver_caps & AZX_DCAPS_SEPARATE_STREAM_TAG)tag = ++stream_tags[dir];elsetag = i + 1;snd_hdac_stream_init(azx_bus(chip), azx_stream(azx_dev),i, dir, tag);}return 0;
}
void snd_hdac_stream_init(struct hdac_bus *bus, struct hdac_stream *azx_dev,int idx, int direction, int tag)
{azx_dev->bus = bus;/* offset: SDI0=0x80, SDI1=0xa0, ... SDO3=0x160 */azx_dev->sd_addr = bus->remap_addr + (0x20 * idx + 0x80);/* int mask: SDI0=0x01, SDI1=0x02, ... SDO3=0x80 */azx_dev->sd_int_sta_mask = 1 << idx;azx_dev->index = idx;azx_dev->direction = direction;azx_dev->stream_tag = tag;snd_hdac_dsp_lock_init(azx_dev);list_add_tail(&azx_dev->list, &bus->stream_list);
}

这两段函数其实就是根据从GCAP中得到的stream数量来初始化相应个数的azx_dev,这些对象现在还都是初始值,都还没进行相应的处理。这些对象会被保存到hdac_bus的stream_list中。可以参考snd_card结构框图,理解它们之间的关系。

4.2.3.1.3 azx_alloc_stream_pages

#define azx_alloc_stream_pages(chip) \snd_hdac_bus_alloc_stream_pages(azx_bus(chip))

其实azx_alloc_stream_pages的目的只是调用snd_hdac_bus_alloc_stream_pages。

int snd_hdac_bus_alloc_stream_pages(struct hdac_bus *bus)
{struct hdac_stream *s;int num_streams = 0;int dma_type = bus->dma_type ? bus->dma_type : SNDRV_DMA_TYPE_DEV;int err;list_for_each_entry(s, &bus->stream_list, list) {/* allocate memory for the BDL for each stream */err = snd_dma_alloc_pages(dma_type, bus->dev,BDL_SIZE, &s->bdl);num_streams++;if (err < 0)return -ENOMEM;}if (WARN_ON(!num_streams))return -EINVAL;/* allocate memory for the position buffer */err = snd_dma_alloc_pages(dma_type, bus->dev,num_streams * 8, &bus->posbuf);if (err < 0)return -ENOMEM;list_for_each_entry(s, &bus->stream_list, list)s->posbuf = (__le32 *)(bus->posbuf.area + s->index * 8);/* single page (at least 4096 bytes) must suffice for both ringbuffes */return snd_dma_alloc_pages(dma_type, bus->dev, PAGE_SIZE, &bus->rb);
}

这段代码不长,但是要想真正理解需要点时间了。

4.2.3.1.3.1 关于DMA

DMA的相关概念,应该都接触过。这里简单的描述一下我的理解,未必精确,但是足够帮助理解代码。

这里以音频播放为例子,在前面的描述中,我们也大致了解。需要做的工作是将音频数据从内存中传输到controller上,在没有DMA的情况下怎么做呢?大概如此,在controller上IO空间上分配一段空间用来接收音频数据。受限于总线的长度,32位机器每次最多传输的数据量是32bit,也就是4字节。这样的处理一个明显的缺点就是慢,另外每次穿输都需要占用cpu周期。在DMA参与的情况下,一段内存中的字节可以批次传送到controller中,而且传输过程中不需要CPU的参与,CPU可以进行其它处理。

但是由于一些系统的原因,不是所有内存空间的数据都可以通过DMA传送。这时候就需要分配一些特殊区域的内存,这里是通过snd_dma_alloc_pages来完成。后续要做的就是将要播放的内容写到这块内存,触发controller读取这部分内存。具体的指令,后面可以看到。

4.2.3.1.3.2 Controller中的DMA

代码中看,首先是帮每个stream分配了一段BDL内存:

snd_dma_alloc_pages(dma_type, bus->dev,

BDL_SIZE, &s->bdl);

BDL(Buffer Descriptor List),名字起的可能不是太贴切。读写音频的时候数据是通过DMA来传输的,实现上会将内存分成多个块,每个块叫做一个period。controller每处理好一个块,都会触发一个中断,通知软件有块空闲了,软件就可以接着处理空闲的块。这样的块每个流最少需要两块,才能保证播放录制过程中没有卡顿。BDL其实是用来记录这些块的位置的,不过它本身也是DMA实现的。这部分的内容讲播放音频的内容时,会详细展开。

第二段DMA空间是位置缓冲(posbuf)。读写数据的时候一方写数据,一方读数据,涉及到了数据同步的问题。软件需要知道controller的处理位置,这段内存就是干这个用的。也是在讲到播放音频的内容时,会详细展开。

第三段是rb,用来给codec发送和接收消息。 写命令是CORB(Command Outbound Ring Buffer),读命令是RIRB(Response Inbound Ring Buffer)。下一节会详细展开这部分内容。

4. hda设备中的pcm文件 (第三部分)相关推荐

  1. 4. hda设备中的pcm文件(第二部分)

    4.2 snd_card 的构建 上一小节,我们看了硬件的简图,现在要看它们如何被组织成软件视图.snd_card的框架简图基本就是一个软件的视图.下面我们去了解它们的组织过程,以及各个模块的作用. ...

  2. 4. hda设备中的pcm文件 (第六部分)

    4.5  codec驱动 hda_codec对象构建成功后,并没有直接为它构建pcm文件,而是放到codec的驱动中去完成.这样做的目的,可能是为了能方便的替换不同的驱动程序,就可以少一些在代码中的硬 ...

  3. 4.hda设备中的pcm文件 (第七部分)

    4.6 构建pcm 接着上节的内容,现在来看看构建pcm的过程. 4.6.1 snd_hda_codec_build_pcms int snd_hda_codec_build_pcms(struct ...

  4. 4. hda设备中的pcm文件(第四部分)

    4.3 Codec命令 之前介绍过,codec支持一定数量的命令,在每个帧周期中SDO中的前40bit都会保留下来作为发送给codec的命令,而SDI中也保留了36bit作为codec对命令的响应. ...

  5. 苹果隐藏应用_使用iMazing导出苹果设备中的录音文件

    iMazing是一款功能强大的苹果设备管理软件,能为用户提供便捷的录音文件导出功能.用户可以直接将录音文件从苹果设备中导出,接下来,就让小编为大家演示一下如何操作吧. 图1:iMazing界面 1.打 ...

  6. 同步推软件:查看ios设备中persistentDataPath下文件,安装ipa

    1.使用Unity开发的app,有可能会用到PersistentDataPath,在ios设备中可以借助同步推软件来查看(mac上也有,不过更新不是太频繁,经常下载后和mac os系统不兼容,不可用) ...

  7. flash air中读取本地文件的三种方法

    actionscript中读取本地文件操作有两种代码如下 1.使用File和FileStream两个类,FileStream负责读取数据的所以操作:(同步操作) ? 1 2 3 4 5 var str ...

  8. python中readlines_python读文件的三个方法read()、readline()、readlines()详解

    文件 runoob.txt 的内容如下:1:www.runoob.com 2:www.runoob.com 3:www.runoob.com 4:www.runoob.com 5:www.runoob ...

  9. CAD中的dxf文件解析(三):多段线篇

    1.前言 在前面的CAD中的dxf文件解析(二)中讲到了一些CAD的dxf文件解析点.线.圆弧.圆.块等的思路.下面提供链接: (二): CAD中的dxf文件解析(二):dxflib的使用_不爱学习 ...

最新文章

  1. mediawiki java api_维基百科 MediaWiki API 解析
  2. Oracle已从2019年1月起收取Java费用
  3. 编程入门python语言是多大孩子学的-Python 适合初学编程的人学吗?
  4. 一行代码求两个数的最大公约数
  5. 可以搜python编程答案的软件_python实现百万答题自动百度搜索答案
  6. 移动webAPP前端开发技巧汇总
  7. HTML 5--Grouping and Nesting Styles
  8. vscode怎样打开终端 使用命令行
  9. 极客大学产品经理训练营 认识产品经理 作业1
  10. 工资软件测试白盒测试报告,白盒测试测试报告模板.doc
  11. EndnoteX7插入文献时,提示“访问未命名的文件时尝试越过其结尾”的解决方法
  12. EnableQ在线问卷调查引擎在学校教学教评中的作用
  13. 计算机系统的软件有,计算机系统软件有哪些
  14. openCV利用航拍相机从底部向上扫描物体拼接全景图
  15. 【HTML5 基础】HTML5重要内容
  16. Mac Mojave10.14安装vmvare Fusion 11.0.0 win8 镜像
  17. 第二十九章 OOTV杯超级模式大赛-模式总结(读书笔记)
  18. 互联网金融四大暴富机会:P2P 征信 支付 供应链金融
  19. 量化交易——布林带策略
  20. linux下gcc版本切换

热门文章

  1. 手游平台游戏源码为什么要选择PHP作为后端语言
  2. 读Hean first jQuery笔记2(常用方法)
  3. 常用音频接口:TDM,PDM,I2S,PCM
  4. uniapp - 实现 H5 网站使用腾讯地图,附带地图使用教程 / 当前用户 IP 定位获取位置信息教程(详细配置教程及运行示例源代码,保证新手小白 100% 成功)
  5. 小d课堂-海量数据处理商用短链平台大课-课程资料xiaoecf
  6. Cannot find table rule and default data source with logic tables: '[]'
  7. 滑动平均滤波c语言_11种经典软件滤波算法及其波形效果图(附C语言程序)
  8. 《小白H5成长之路50》js与PHP配合完成图片上传功能
  9. 【板栗糖GIS】——如何使用插件将微信读书笔记同步到notion
  10. 查找——索引顺序表和倒排表