Глава 4. Управление ресурсами PCI

Предыдущая  Содержание  Следующая V*D*V

Полный пример кода

В этом разделе мы завершим зависящий от используемого чипа конструктор, деструктор и регистрацию PCI. Сначала покажем пример кода.

 

Пример 4.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)

{

    /* отключаем оборудование, если необходимо */

    .... /* (в этом документе не реализовано) */

 

    /* освобождаем прерывание */

    if (chip->irq >= 0)

        free_irq(chip->irq, chip);

    /* освобождаем порты ввода/вывода и память */

    pci_release_regions(chip->pci);

    /* отключаем регистрацию PCI */

    pci_disable_device(chip->pci);

    /* освобождаем данные */

    kfree(chip);

    return 0;

}

 

/* конструктор, зависящий от используемого чипа */

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;

 

    /* инициализируем регистрацию PCI */

    err = pci_enable_device(pci);

    if (err < 0)

        return err;

    /* проверяем доступность PCI (28-х разрядный 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;

    }

 

    /* инициализируем параметры */

    chip->card = card;

    chip->pci = pci;

    chip->irq = -1;

 

    /* (1) выделение ресурсов PCI */

    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, "My Chip", chip)) {

        printk(KERN_ERR "cannot grab irq %d\n", pci->irq);

        snd_mychip_free(chip);

        return -EBUSY;

    }

    chip->irq = pci->irq;

 

    /* (2) инициализируем оборудование чипа */

    .... /* (в этом документе не реализовано) */

 

    err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);

    if (err < 0) {

        snd_mychip_free(chip);

        return err;

    }

 

    snd_card_set_dev(card, &pci->dev);

 

    *rchip = chip;

    return 0;

}        

 

/* идентификаторы PCI */

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_driver driver = {

    .name = "My Own Chip",

    .id_table = snd_mychip_ids,

    .probe = snd_mychip_probe,

    .remove = __devexit_p(snd_mychip_remove),

};

 

/* инициализация модуля */

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)

 

EXPORT_NO_SYMBOLS; /* только для старых версий ядра */

Кое-что из того, что требуется сделать

Выделение ресурсов PCI выполняется в функции probe(), и, как правило, в дополнительной функции xxx_create(), написанной для этой цели.

 

В случае устройств PCI, необходимо перед выделением ресурсов сначала вызвать функцию pci_enable_device(). Кроме того, необходимо установить корректную маску PCI DMA для ограничения доступного диапазона ввода-вывода. В некоторых случаях также будет необходимо вызвать функцию pci_set_master().

 

Пусть маска будет 28-ми разрядной, тогда код для добавления был бы таким:

 

err = pci_enable_device(pci);

if (err < 0)

    return err;

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;

}

Выделение ресурсов

Выделение портов ввода/вывода и прерываний осуществляется через стандартные функции ядра. В отличие от ALSA версии 0.5.x. помощников для этого нет. И эти ресурсы должны быть освобождены в функции деструктора (смотрите ниже). Кроме того, в ALSA 0.9.x не требуется выделять (псевдо-) DMA для PCI, как в ALSA 0.5.x.

 

Теперь предположим, что устройство PCI имеет 8-ми байтовый порт ввода/вывода и прерывание. Тогда структура mychip будет иметь следующие поля:

 

struct mychip {

    struct snd_card *card;

 

    unsigned long port;

    int irq;

};

 

Для порта ввода/вывода (а также области памяти) необходимо иметь указатель ресурсов для стандартного управления ресурсами. Для прерывания необходимо хранить только номер прерывания (целое число). Но необходимо проинициализировать этот номер как -1 до фактического выделения, так как прерывание 0 правомерно. Адрес порта и его указатель ресурсов можно проинициализировать нулями автоматически с помощью kzalloc(), поэтому не придётся заботиться об их обнулении.

 

Выделение порта ввода/вывода осуществляется следующим образом:

 

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

 

Это будет резервировать 8-ми байтовый порт ввода/вывода данного устройства PCI. Возвращаемое значение, chip->res_port, выделяется через kmalloc() с помощью request_region(). Указатель должен быть освобождён через kfree(), но с этим есть проблема. Эта проблема будет объяснятся позже.

 

Выделение источника прерывания выполняется примерно так:

 

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;

 

где snd_mychip_interrupt() является обработчиком прерывания, определяемым позже. Заметим, что chip->irq должен быть определён только если вызов request_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;

}

 

Теперь давайте напишем соответствующий деструктор для вышеописанных ресурсов. Роль деструктора проста: отключить оборудование (если оно уже активировано) и освободить ресурсы. Пока у нас нет аппаратной части, так что код отключения здесь не написан.

 

Для освобождения ресурсов метод "проверить и освободить" является безопасным способом. Для прерывания сделаем так:

 

if (chip->irq >= 0)

    free_irq(chip->irq, chip);

 

Так как номер прерывания может начинаться с 0, необходимо инициализировать chip->irq отрицательным значением (например, -1), так что можно проверять достоверность номер прерывания, как показано выше.

 

Когда порты ввода/вывода или области памяти запрашиваются с помощью pci_request_region() или pci_request_regions(), как в этом примере, освобождайте ресурс(ы) с помощью соответствующей функции, 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);

 

Опять же, помните, что для этого деструктора нельзя использовать префикс __devexit.

 

Выше мы не реализовали часть отключения оборудования. Если необходимо сделать это, обратите внимание, что деструктор может быть вызван ещё до завершения инициализации чипа. Было бы лучше иметь флаг, чтобы пропускать отключение оборудования, если оборудование ещё не было проинициализировано.

 

Если данные для чипа выделяются для карты используя snd_device_new() с SNDRV_DEV_LOWLELVEL, их деструктор вызывается последним. То есть гарантируется, что все другие компоненты, такие как PCM-ы и элементы управления, уже были освобождены. Вы не должны останавливать PCM-ы и другие явно, необходимо просто вызвать остановку низкоуровневого оборудования.

 

Управление областью отображаемой памяти почти такое же, как управление портом ввода/вывода. Вы будете нуждаться в трёх полях, как показано ниже:

 

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 предоставляет макрос для совместимости со старыми ядрами.

Просто сделайте следующий вызов:

 

snd_card_set_dev(card, &pci->dev);

 

так что он сохраняет указатель устройства PCI в объекте карты. Он будет передан функциям ядра ALSA позже, когда устройства зарегистрированы.

 

В случае не PCI вместо шины передаётся надлежащий указатель на структуру устройства. (В случае устаревшей ISA без PnP, вы не должны ничего делать.)

Регистрация PCI

Пока всё идет хорошо. Давайте завершим с отсутствующим материалом PCI. Сначала для этого чипсета необходима таблица pci_device_id. Это таблица с идентификационными номерами поставщика/устройства PCI и некоторые маски.

 

Например,

 

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 являются идентификаторами поставщика и устройства. Если нет причин для фильтрации совпадающих устройств, можно оставить остальные поля, как показано выше. Последнее поле структуры pci_device_id содержит закрытые данные для этой записи. Здесь можно задать любое значение, например, для определения  специфических операций для идентификаторов поддерживаемых устройств. Такой пример можно найти в драйвере intel8x0.

 

Последняя запись в этом списке является признаком завершения. Вы должны указать этот нулевой элемент.

 

Затем подготовим объект pci_driver:

 

static struct pci_driver driver = {

    .name = "My Own Chip",

    .id_table = snd_mychip_ids,

    .probe = snd_mychip_probe,

    .remove = __devexit_p(snd_mychip_remove),

};

 

Функции probe и remove уже определены в предыдущих разделах. Функция remove должна быть определена с помощью  макроса __devexit_p(), так что она не определена для встроенного (и без горячей замены) случая. Поля 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, а не __devinit или __devexit.

 

Ах да, была забыта ещё одна вещь. Если у вас нет экспортируемых символов, необходимо задекларировать это в ядрах версии 2.2 или 2.4 (это не обязательно в ядрах версии 2.6).

 

EXPORT_NO_SYMBOLS;

 

Это всё!

 

Предыдущая  Содержание  Следующая