4. hda设备中的pcm文件 (第三部分)
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文件 (第三部分)相关推荐
- 4. hda设备中的pcm文件(第二部分)
4.2 snd_card 的构建 上一小节,我们看了硬件的简图,现在要看它们如何被组织成软件视图.snd_card的框架简图基本就是一个软件的视图.下面我们去了解它们的组织过程,以及各个模块的作用. ...
- 4. hda设备中的pcm文件 (第六部分)
4.5 codec驱动 hda_codec对象构建成功后,并没有直接为它构建pcm文件,而是放到codec的驱动中去完成.这样做的目的,可能是为了能方便的替换不同的驱动程序,就可以少一些在代码中的硬 ...
- 4.hda设备中的pcm文件 (第七部分)
4.6 构建pcm 接着上节的内容,现在来看看构建pcm的过程. 4.6.1 snd_hda_codec_build_pcms int snd_hda_codec_build_pcms(struct ...
- 4. hda设备中的pcm文件(第四部分)
4.3 Codec命令 之前介绍过,codec支持一定数量的命令,在每个帧周期中SDO中的前40bit都会保留下来作为发送给codec的命令,而SDI中也保留了36bit作为codec对命令的响应. ...
- 苹果隐藏应用_使用iMazing导出苹果设备中的录音文件
iMazing是一款功能强大的苹果设备管理软件,能为用户提供便捷的录音文件导出功能.用户可以直接将录音文件从苹果设备中导出,接下来,就让小编为大家演示一下如何操作吧. 图1:iMazing界面 1.打 ...
- 同步推软件:查看ios设备中persistentDataPath下文件,安装ipa
1.使用Unity开发的app,有可能会用到PersistentDataPath,在ios设备中可以借助同步推软件来查看(mac上也有,不过更新不是太频繁,经常下载后和mac os系统不兼容,不可用) ...
- flash air中读取本地文件的三种方法
actionscript中读取本地文件操作有两种代码如下 1.使用File和FileStream两个类,FileStream负责读取数据的所以操作:(同步操作) ? 1 2 3 4 5 var str ...
- 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 ...
- CAD中的dxf文件解析(三):多段线篇
1.前言 在前面的CAD中的dxf文件解析(二)中讲到了一些CAD的dxf文件解析点.线.圆弧.圆.块等的思路.下面提供链接: (二): CAD中的dxf文件解析(二):dxflib的使用_不爱学习 ...
最新文章
- mediawiki java api_维基百科 MediaWiki API 解析
- Oracle已从2019年1月起收取Java费用
- 编程入门python语言是多大孩子学的-Python 适合初学编程的人学吗?
- 一行代码求两个数的最大公约数
- 可以搜python编程答案的软件_python实现百万答题自动百度搜索答案
- 移动webAPP前端开发技巧汇总
- HTML 5--Grouping and Nesting Styles
- vscode怎样打开终端 使用命令行
- 极客大学产品经理训练营 认识产品经理 作业1
- 工资软件测试白盒测试报告,白盒测试测试报告模板.doc
- EndnoteX7插入文献时,提示“访问未命名的文件时尝试越过其结尾”的解决方法
- EnableQ在线问卷调查引擎在学校教学教评中的作用
- 计算机系统的软件有,计算机系统软件有哪些
- openCV利用航拍相机从底部向上扫描物体拼接全景图
- 【HTML5 基础】HTML5重要内容
- Mac Mojave10.14安装vmvare Fusion 11.0.0 win8 镜像
- 第二十九章 OOTV杯超级模式大赛-模式总结(读书笔记)
- 互联网金融四大暴富机会:P2P 征信 支付 供应链金融
- 量化交易——布林带策略
- linux下gcc版本切换
热门文章
- 手游平台游戏源码为什么要选择PHP作为后端语言
- 读Hean first jQuery笔记2(常用方法)
- 常用音频接口:TDM,PDM,I2S,PCM
- uniapp - 实现 H5 网站使用腾讯地图,附带地图使用教程 / 当前用户 IP 定位获取位置信息教程(详细配置教程及运行示例源代码,保证新手小白 100% 成功)
- 小d课堂-海量数据处理商用短链平台大课-课程资料xiaoecf
- Cannot find table rule and default data source with logic tables: '[]'
- 滑动平均滤波c语言_11种经典软件滤波算法及其波形效果图(附C语言程序)
- 《小白H5成长之路50》js与PHP配合完成图片上传功能
- 【板栗糖GIS】——如何使用插件将微信读书笔记同步到notion
- 查找——索引顺序表和倒排表