writing-an-alsa-driver(编写一个ALSA驱动)翻译稿 第四章

1631阅读 0评论2012-07-13 zhou991
分类:

翻译:creator

第四章 PCI资源管理

代码示例

本节我们会完成一个chip-specific的构造函数,析构函数和PCI entries。先来看代码。

1.Example4-1.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*/

....//这篇文档没有实现


/*release the irq*/

if (chip->irq >= 0)

free_irq(chip->irq,chip);

/*释放iomemory*/

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 __devinit snd_mychip_create(struct snd_card *card,

struct pci_dev *pci,

struct mychip **rchip)

{

struct mychip *chip;

int err;

static struct snd_device_ops ops ={

.dev_free = snd_mychip_dev_free,

};

*rchip = NULL;

/*initialize the PCI entry*/

if ((err = pci_enable_device(pci)) < 0)

return err;

/*check PCI availability (28bit DMA)*/

if (pci_set_dma_mask(pci, DMA_28BIT_MASK) < 0 ||

pci_set_consistent_dma_mask(pci,DMA_28BIT_MASK) < 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 资源分配*/

if ((err = pci_request_regions(pci, “My Chip”)) < 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, “My Chip”,chip){

printk(KERN_ERR “Cannot grab irq %d\n”,pci->irq);

snd_mychip_free(chip);

return -EBUSY;

}

chip->irq = pci->irq;

/*(2)chip hardware的初始化*/

....//本文未实现

if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL,

chip, &ops)) < 0){

snd_mychip_free(chip);

return err;

}

snd_card_set_dev(card,&pci->dev);

*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定义*/

static struct pci_drvier driver={

.name = “My Own Chip”,

.id_table = snd_mychip_ids,

.probe = snd_mychip_probe,

.remove = __devexit_p(snd_mychip_remove),

};

/*module的初始化*/

static int __init alsa_card_mychip_init(void)

{

return pci_register_driver(&driver);

}

/*clean up the module*/

static void __exit alsa_card_mychip_exit(void)

{

pci_unregister_drvier(&drvier);

}

module_init(alsa_card_mychip_init);

module_exit(alsa_card_mychip_eixt);

EXPORT_NO_SYMBOLS /*为了和老版本的内核兼容*/


一些必须做的事情

一般在probe()函数中分配PCI资源,通常采用一个xxx_create()函数来完成上述功能。 在PCI设备驱动中,在分配资源之前先要调用pci_enable_device().同样,你也要设定合适的PCI DMA mask限制i/o接口的范围。在一些情况下,你也需要设定pci_set_master().


资源分配

利用标准的内核函数来分配I/O和中断。不像ALSA ver0.5.x那样没有什么帮助。同时那些资源必须在析构函数中被释放(如下)。在ALSA 0.9.x,你不需要像0.5.x那样还要为PCI分配DMA

现在假定PCI设备拥有8直接的I/O口和中断,mychip的结构体可以如下所示:

struct mychip{

struct snd_card *card;

unsigned long port;

int irq;

}

对于一个I/O端口(或者也有内存区域),你需要得到可以进行标准的资源管理的资源的指针。对于中断,你必须保存它的中断号。但是你必须实际分配之前先把初始化中断号为

-1,因为中断号为0也是被允许的。端口地址和它的资源指针可以通过kzalloc()来分配初始化为null,所以你不用非要重新设定他们。

I/O端口的分配可以采用如下方式:

if ((err = pci_request_region(pci, “My Chip”)) < 0){

kfree(chip);

pci_disable_device(pci);

return err;

}

chip->port = pci_resource_start(pci, 0);

它将会保留给定PCI设备的8字节的I/O端口区域。返回值chip->res_portrequest_region函数中通过kmalloc分配。这个分配的指针必须通过kfree()函数进行释放,但是这个部分有些问题,具体将会在下面详细分析。

分配一个中断源如下所示:

if (request_irq(pci->irq, snd_mychip_interrupt,

IRQF_DISABLED | IRQF_SHARED, “My Chip”,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 bus上,中断是共享的。因此,申请中断的函数request_irq要加入IRQF_SHARED标志位。

request_irq的最后一个参数是被传递给中断处理函数的数据指针。通常来说,chip-specific记录会用到它,你也可以按你喜欢的方式用它。

我不想在这时候详细解释中断向量函数,但是至少这里出现的会解释一下。中断向量如下所示:

static irqreturn_t snd_mychip_interrup(int irq, void *dev_id)

{

struct mychip *chip = dev_id;

return IRQ_HANDLED;

}

现在,让我们为上述的资源写一个相应的析构函数。析构函数是非常简单的:关闭硬件(假如它被激活)同时释放它的资源。到目前为止,我们没有硬件,所以关闭硬件的部分就不写了。

为了释放资源,“check-and-release”(检查然后释放)的方式是比较安全的。对于中断,采用如下的方式:

if (chip->irq >= 0)

free_irq(chip->irq,chip);

因为irq号是从0开始的,所以你必须初始化chip->irq为一个负数(例如-1),所以你可以按上面的方式检查irq的有效性。

通过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_region()来释放。假定你把request_region返回的resource pointer保存在chip->res_port,释放的程序如下所示:

release_and_free_resource(chip->res_port);

在所有都结束的时候不要忘记了调用pci_disable_device().最后释放chip-specific记录。

kfree(chip);


再提醒一下,不能在析构函数前面放__devexit

我们在上面没有实现关闭硬件的功能部分。假如你需要做这些,请注意析构函数可能会在chip的初始化完成之前被调用。最好设定一个标志位确定是否硬件已经初始化,来决定是否略过这部分。

如果chip-data放置在在含有SNDRV_DEV_LOWLEVEL标志的snd_device_new()函数申请的设备中,它的析构函数将会最后被调用。那是因为,它要确认像PCM和一些控制组件已经被释放。你不必显式调用停止PCM的函数,只要在low-level中停止这些硬件。

mamory-mapped的区域的管理和i/o端口管理一样。需要如下3个结构变量:

struct mychip{

....

unsigned long iobase_phys;

void __iomem *iobase_virt;

};

通过如下方式来申请:

if ((err = pci_request_regions(pci, “My Chip”)) < 0){

kfree(chip);

return err;

}

chip->iobase_phys = pci_resource_start(pci, 0);

chip->iobase_virt = ioremap_nocache(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);

....

}


注册设备结构体

在一些地方,典型的是在调用snd_device_new(),假如你想通过udev或者ALSA提供的一些老版本内核的兼容的宏来控制设备,你需要注册chip的设备结构体。很简单如下所示:

snd_card_set_dev(card,&pci->dev);

所以它保存了cardPCI设备指针。它将会在后续设备注册的时候被ALSA内核功能调用。

假如是非PCI设备,就需要传递一个合适的设备结构指针。(假如是不可以热拔插的ISA设备,你就不需要这么做了。)


PCI Entries

到目前为止已经做得非常好了,下面让我们完成PCI的剩余的一些工作。首先,我们需要一个这个芯片组的pci_device_id表。它是一个含有PCI制造商和设备ID的表,还有一些mask

例如:

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_device_id结构体的第一个和第二个结构变量是制造商和设备的ID。假如你没有关于设备的特别选择,你可以采用上述的。pci_device_id结构体的最后一个结构变量是一个私有变量。你可以放一些可以区别其他的值,如:可以区分每个设备ID的不同操作类型。这些例子出现在intel的驱动里面。最后一个table元素是代表结束符。必须把所有成员设定为0.

然后,我们来准备pci_driver记录:

static struct pci_drvier driver = {

.name = “My Own Chip”,

.id_table = snd_mychip_ids,

.probe = snd_mychip_probe,

.remove = __devexit_p(snd_mychip_remove),

};

proberemove函数在以前的章节已经介绍过。remove应该用一个__devexit_p()宏来定义。所以,它不是为那些固定的或不支持热拔插的设备定义的。name结构变量是标识设备的名字。注意你不能用“/”字符。

最后,module入口如下:

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);

注意module入口函数被标识为__init__exit前缀,而不是__devinit或者__devexit.

哦,忘记了一件事,假如你没有export标号,如果是2.22.4内核你必须显式声明r如下:(当然,2.6内核已经不需要了。)

EXPORT_NO_SYMBOLS;

就这些了。

上一篇:writing-an-alsa-driver(编写一个ALSA驱动)翻译稿 第三章
下一篇:writing-an-alsa-driver(编写一个ALSA驱动)翻译稿 第六章