从零写一个ALSA声卡驱动学习(2)

前言:

大家好,我是txp,今天我们继续来学习从零写一个ALSA声卡驱动系列文章内容!

PCI资源管理:

在本节中,我们将完成与芯片相关的构造函数、析构函数以及 PCI 项的实现。下面首先展示示例代码:

struct mychip {
        struct snd_card *card;
        struct pci_dev *pci;

        unsigned long port;
        int irq;
};

static int snd_mychip_free(struct mychip *chip)
{
        /* disable hardware here if any */
        .... /* (not implemented in this document) */

        /* release the irq */
        if (chip->irq >= 0)
                free_irq(chip->irq, chip);
        /* release the I/O ports & memory */
        pci_release_regions(chip->pci);
        /* disable the PCI entry */
        pci_disable_device(chip->pci);
        /* release the data */
        kfree(chip);
        return 0;
}

/* chip-specific constructor */
static int snd_mychip_create(struct snd_card *card,
                             struct pci_dev *pci,
                             struct mychip **rchip)
{
        struct mychip *chip;
        int err;
        static const struct snd_device_ops ops = {
               .dev_free = snd_mychip_dev_free,
        };

        *rchip = NULL;

        /* initialize the PCI entry */
        err = pci_enable_device(pci);
        if (err < 0)
                return err;
        /* check PCI availability (28bit DMA) */
        if (pci_set_dma_mask(pci, DMA_BIT_MASK(28)) < 0 ||
            pci_set_consistent_dma_mask(pci, DMA_BIT_MASK(28)) < 0) {
                printk(KERN_ERR "error to set 28bit mask DMA\n");
                pci_disable_device(pci);
                return -ENXIO;
        }

        chip = kzalloc(sizeof(*chip), GFP_KERNEL);
        if (chip == NULL) {
                pci_disable_device(pci);
                return -ENOMEM;
        }

        /* initialize the stuff */
        chip->card = card;
        chip->pci = pci;
        chip->irq = -1;

        /* (1) PCI resource allocation */
        err = pci_request_regions(pci, "My Chip");
        if (err < 0) {
                kfree(chip);
                pci_disable_device(pci);
                return err;
        }
        chip->port = pci_resource_start(pci, 0);
        if (request_irq(pci->irq, snd_mychip_interrupt,
                        IRQF_SHARED, KBUILD_MODNAME, chip)) {
                printk(KERN_ERR "cannot grab irq %d\n", pci->irq);
                snd_mychip_free(chip);
                return -EBUSY;
        }
        chip->irq = pci->irq;
        card->sync_irq = chip->irq;

        /* (2) initialization of the chip hardware */
        .... /*   (not implemented in this document) */

        err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
        if (err < 0) {
                snd_mychip_free(chip);
                return err;
        }

        *rchip = chip;
        return 0;
}

/* PCI IDs */
static struct pci_device_id snd_mychip_ids[] = {
        { PCI_VENDOR_ID_FOO, PCI_DEVICE_ID_BAR,
          PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0, },
        ....
        { 0, }
};
MODULE_DEVICE_TABLE(pci, snd_mychip_ids);

/* pci_driver definition */
static struct pci_driver driver = {
        .name = KBUILD_MODNAME,
        .id_table = snd_mychip_ids,
        .probe = snd_mychip_probe,
        .remove = snd_mychip_remove,
};

/* module initialization */
static int __init alsa_card_mychip_init(void)
{
        return pci_register_driver(&driver);
}

/* module clean up */
static void __exit alsa_card_mychip_exit(void)
{
        pci_unregister_driver(&driver);
}

module_init(alsa_card_mychip_init)
module_exit(alsa_card_mychip_exit)

EXPORT_NO_SYMBOLS; /* for old kernels only */

PCI 资源的分配是在 probe 函数中完成的,通常会专门编写一个额外的 xxx_create() 函数来实现这一目的。

对于 PCI 设备,在分配资源之前,首先需要调用 pci_enable_device() 函数。同时,还需要设置合适的 PCI DMA 掩码(mask),以限制可访问的 I/O 范围。在某些情况下,还可能需要调用 pci_set_master() 函数。

假设使用 28 位的掩码,需要添加的代码如下所示:

err = pci_enable_device(pci);
if (err < 0)
        return err;
if (pci_set_dma_mask(pci, DMA_BIT_MASK(28)) < 0 ||
    pci_set_consistent_dma_mask(pci, DMA_BIT_MASK(28)) < 0) {
        printk(KERN_ERR "error to set 28bit mask DMA\n");
        pci_disable_device(pci);
        return -ENXIO;
}

资源分配:

I/O 端口和中断(IRQ)的分配是通过标准内核函数完成的。这些资源必须在析构函数中释放(见下文)。

现在假设该 PCI 设备具有一个 8 字节的 I/O 端口和一个中断,那么 struct mychip 将包含以下字段:

struct mychip {
        struct snd_card *card;

        unsigned long port;
        int irq;
};

对于一个 I/O 端口(以及内存区域),你需要保存其资源指针,以便进行标准的资源管理。

而对于中断(irq),你只需要保存中断号(一个整数)。但在实际分配之前,需要将该中断号初始化为 -1,因为中断号 0 是合法的。

I/O 端口地址及其资源指针可以通过 kzalloc() 自动初始化为 NULL,因此你无需手动重置它们。

I/O 端口的分配方式如下所示:

err = pci_request_regions(pci, "My Chip");
if (err < 0) {
        kfree(chip);
        pci_disable_device(pci);
        return err;
}
chip->port = pci_resource_start(pci, 0);

它将为指定的 PCI 设备保留一个 8 字节的 I/O 端口区域。返回值 chip->res_port 是通过 request_region() 使用 kmalloc() 分配的。该指针必须通过 kfree() 释放,但这里存在一个问题,这个问题稍后会进行说明。

中断源的分配方式如下所示:

if (request_irq(pci->irq, snd_mychip_interrupt,
                IRQF_SHARED, KBUILD_MODNAME, chip)) {
        printk(KERN_ERR "cannot grab irq %d\n", pci->irq);
        snd_mychip_free(chip);
        return -EBUSY;
}
chip->irq = pci->irq;

snd_mychip_interrupt() 是稍后定义的中断处理函数。需要注意的是,只有当 request_irq() 调用成功后,才应设置 chip->irq 的值。

在 PCI 总线上,中断是可以共享的,因此在调用 request_irq() 时需要使用 IRQF_SHARED 作为中断标志。

request_irq() 的最后一个参数是传递给中断处理函数的数据指针。通常会传入与芯片相关的结构体记录,但你也可以传入其他你需要的数据。

此处不会详细介绍中断处理函数的具体内容,但至少可以说明它的大致结构。中断处理函数通常如下所示:

static irqreturn_t snd_mychip_interrupt(int irq, void *dev_id)
{
        struct mychip *chip = dev_id;
        ....
        return IRQ_HANDLED;
}

在申请中断(IRQ)之后,可以将其传递给 card->sync_irq 字段:

card->irq = chip->irq;

这样做可以让 PCM 核心在适当的时机(例如在 hw_free 之前)自动调用 synchronize_irq()。具体细节请参见后文的 sync_stop 回调章节。

现在我们来编写上面这些资源对应的析构函数。析构函数的作用很简单:禁用硬件(如果已经启用)并释放资源。

目前我们还没有涉及硬件部分,因此这里不编写禁用相关的代码。

在释放资源时,采用“检查并释放(check-and-release)”的方法更加安全。对于中断,可以按如下方式处理:

if (chip->irq >= 0)
        free_irq(chip->irq, chip);

由于中断号(irq number)可能从 0 开始,因此你应该将 chip->irq 初始化为一个负值(例如 -1),这样就可以像上面那样判断中断号是否有效。

当你像本示例中那样通过 pci_request_region() 或 pci_request_regions() 申请了 I/O 端口或内存区域时,应使用对应的函数 pci_release_region() 或 pci_release_regions() 来释放资源:

pci_release_regions(chip->pci);

如果你是通过 request_region() 或 request_mem_region() 手动申请的资源,则可以通过 release_resource() 来释放。

假设你将 request_region() 返回的资源指针保存在 chip->res_port 中,那么释放过程如下所示:

release_and_free_resource(chip->res_port);

别忘了在结束前调用 pci_disable_device()。 最后,释放与芯片相关的结构体记录:

kfree(chip);

我们在上文中并未实现硬件禁用(hardware disabling)的部分。如果你需要实现这一功能,请注意:析构函数可能会在芯片初始化尚未完成之前就被调用。因此,建议设置一个标志位,用于在硬件尚未初始化时跳过硬件禁用操作。

当使用 snd_device_new() 并以 SNDRV_DEV_LOWLEVEL 类型将芯片数据(chip-data)绑定到 card 时,其析构函数会最后被调用。也就是说,可以确保此时其他组件(如 PCM 和控制接口)都已被释放。你无需显式停止 PCM 等模块,只需调用底层的硬件停止操作即可。

对内存映射区域(memory-mapped region)的管理方式几乎与 I/O 端口的管理相同。你需要如下两个字段:

struct mychip {
        ....
        unsigned long iobase_phys;
        void __iomem *iobase_virt;
};

分配操作如下所示:

err = pci_request_regions(pci, "My Chip");
if (err < 0) {
        kfree(chip);
        return err;
}
chip->iobase_phys = pci_resource_start(pci, 0);
chip->iobase_virt = ioremap(chip->iobase_phys,
                                    pci_resource_len(pci, 0));

相应的释放函数如下所示:

static int snd_mychip_free(struct mychip *chip)
{
        ....
        if (chip->iobase_virt)
                iounmap(chip->iobase_virt);
        ....
        pci_release_regions(chip->pci);
        ....
}

当然,使用现代的 pci_iomap() 方法也会让处理变得更简单一些:

err = pci_request_regions(pci, "My Chip");
if (err < 0) {
        kfree(chip);
        return err;
}
chip->iobase_virt = pci_iomap(pci, 0, 0);

它需要在析构函数中与 pci_iounmap() 配对使用。

PCI 条目:

到目前为止一切顺利。现在我们来完成剩下的 PCI 相关内容。首先,我们需要为该芯片组定义一个 struct pci_device_id 表。

这个表用于列出 PCI 的厂商/设备 ID 以及一些掩码信息。

例如:

static struct pci_device_id snd_mychip_ids[] = {
        { PCI_VENDOR_ID_FOO, PCI_DEVICE_ID_BAR,
          PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0, },
        ....
        { 0, }
};
MODULE_DEVICE_TABLE(pci, snd_mychip_ids);

struct pci_device_id 的前两个字段分别是厂商 ID 和设备 ID。如果你没有特别的需求去过滤匹配的设备,那么其余字段可以像上面那样保持默认。

结构体中最后一个字段用于存放该条目的私有数据。你可以在这里指定任意值,例如用于为不同的设备 ID 定义特定的操作。类似的用法可以在 intel8x0 驱动中找到。

该列表的最后一项必须是终止项(terminator),也就是全为 0 的条目。

接下来,准备 struct pci_driver 结构体记录:

static struct pci_driver driver = {
        .name = KBUILD_MODNAME,
        .id_table = snd_mychip_ids,
        .probe = snd_mychip_probe,
        .remove = snd_mychip_remove,
};

probe 和 remove 函数已经在前面的章节中定义过。

name 字段是该设备的名称字符串,注意不要在该字符串中使用斜杠(“/”)。 最后是模块入口部分:

static int __init alsa_card_mychip_init(void)
{
        return pci_register_driver(&driver);
}

static void __exit alsa_card_mychip_exit(void)
{
        pci_unregister_driver(&driver);
}

module_init(alsa_card_mychip_init)
module_exit(alsa_card_mychip_exit)

请注意,这些模块入口函数带有 __init 和 __exit 前缀标签。就是这样!

文章学习参考:

https://kernel.org/doc/html/latest/sound/kernel-api/writing-an-alsa-driver.html#header-files

你可能感兴趣的:(音频ALSA驱动,ALSA)