Глава 2. Основная технология для PCI драйверов

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

Краткое описание

Минимальная структура для звуковых карт PCI выглядит следующим образом:

 

определение таблицы идентификаторов PCI (см. раздел Регистрация PCI).

создание обратного вызова probe().

создание обратного вызова remove().

создание структуры pci_driver, содержащую три вышеописанные указателя.

создание функции init(), просто вызывающей pci_register_driver() для регистрации таблицы pci_driver, определённой до этого.

создание функции exit() для вызова функции pci_unregister_driver().

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

Ниже приведён пример кода. Некоторые части на данный момент оставлены нереализованными, но пробелы будут заполнены в следующих разделах. Номера в строках комментариев функции snd_mychip_probe() относятся к деталям, объясняемым в следующем разделе.

 

Пример 2.1. Основная технология для PCI драйверов - пример

 

#include <linux/init.h>

#include <linux/pci.h>

#include <linux/slab.h>

#include <sound/core.h>

#include <sound/initval.h>

 

/* параметры модуля (смотрите "Параметры модуля") */

/* SNDRV_CARDS: максимальное число карт, поддерживаемых этим модулем */

static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;

static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;

static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;

 

/* определение объекта, зависящего от используемой микросхемы */

struct mychip {

    struct snd_card *card;

    /* остальная часть реализации будет в разделе

     * "Управление ресурсами PCI"

     */

};

 

/* деструктор, зависящий от используемой микросхемы

 * (смотрите "Управление ресурсами PCI")

 */

static int snd_mychip_free(struct mychip *chip)

{

    .... /* будет реализовано позже... */

}

 

/* деструктор компонента

 * (смотрите "Управление картами и компонентами")

 */

static int snd_mychip_dev_free(struct snd_device *device)

{

    return snd_mychip_free(device->device_data);

}

 

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

 * (смотрите "Управление картами и компонентами")

 */

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

     * (смотрите "Управление ресурсами PCI")

     */

    ....

 

    /* выделяем обнулённую память для зависимых от микросхемы данных */

    chip = kzalloc(sizeof(*chip), GFP_KERNEL);

    if (chip == NULL)

        return -ENOMEM;

 

    chip->card = card;

 

    /* здесь остальная инициализация; будет реализована

     * позже, смотрите "Управление ресурсами PCI"

     */

    ....

 

    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;

}

 

/* конструктор -- смотрите подраздел "Конструктор" */

static int __devinit snd_mychip_probe(struct pci_dev *pci,

                        const struct pci_device_id *pci_id)

{

    static int dev;

    struct snd_card *card;

    struct mychip *chip;

    int err;

    /* (1) */

    if (dev >= SNDRV_CARDS)

        return -ENODEV;

    if (!enable[dev]) {

        dev++;

        return -ENOENT;

    }

    /* (2) */

    err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card);

    if (err < 0)

        return err;

    /* (3) */

    err = snd_mychip_create(card, pci, &chip);

    if (err < 0) {

        snd_card_free(card);

        return err;

    }

    /* (4) */

    strcpy(card->driver, "My Chip");

    strcpy(card->shortname, "My Own Chip 123");

    sprintf(card->longname, "%s at 0x%lx irq %i",

            card->shortname, chip->ioport, chip->irq);

    /* (5) */

    .... /* реализовано позже */

    /* (6) */

    err = snd_card_register(card);

    if (err < 0) {

        snd_card_free(card);

        return err;

    }

    /* (7) */

    pci_set_drvdata(pci, card);

    dev++;

    return 0;

}

 

/* деструктор -- смотрите подраздел "Деструктор" */

static void __devexit snd_mychip_remove(struct pci_dev *pci)

{

    snd_card_free(pci_get_drvdata(pci));

    pci_set_drvdata(pci, NULL);

}

 

Конструктор

Настоящим конструктором драйверов PCI является обратный вызов probe. Обратный вызов probe и другие конструкторы компонентов, вызывающиеся из обратного вызова probe, должны быть определены с префиксом __devinit. Для них нельзя использовать префикс __init, потому что любое устройство PCI может быть автоопределяемым устройством.

 

В обратном вызове probe часто используется следующая схема.

1) Проверяем и увеличиваем индекс устройства.

static int dev;

....

if (dev >= SNDRV_CARDS)

    return -ENODEV;

if (!enable[dev]) {

    dev++;

    return -ENOENT;

}

 

где enable[dev] является параметром модуля.

 

Каждый раз, когда выполняется обратный вызов probe, проверяется наличие устройства. Если оно не доступно, просто увеличиваем индекс устройства и возвращаемся. dev будет увеличиваться и позднее (на шаге 7).

2) Создаём экземпляр карты.

struct snd_card *card;

int err;

....

err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card);

 

Подробности будут объяснены в разделе Управление картами и компонентами.

3) Создаём основной компонент.

В этой части выделяется память для ресурсов PCI.

 

struct mychip *chip;

....

err = snd_mychip_create(card, pci, &chip);

if (err < 0) {

    snd_card_free(card);

    return err;

}

 

Подробности будут объяснены в разделе Управления ресурсами PCI.

4) Устанавливаем идентификатор драйвера и строки названий.

strcpy(card->driver, "My Chip");

strcpy(card->shortname, "My Own Chip 123");

sprintf(card->longname, "%s at 0x%lx irq %i",

        card->shortname, chip->ioport, chip->irq);

 

Поле driver содержит минимальную строку идентификатора чипа. Это используется конфигуратором ALSA-lib, поэтому делайте её простой, но уникальной. Даже один и тот же драйвер может иметь разные идентификаторы драйвера, чтобы различать функциональность чипов разных типов.

 

Поле shortname содержит строку, показывающуюся, как более подробное название. Поле longname содержит информацию, показываемую в /proc/asound/cards.

5) Создаём другие компоненты, такие как микшер, MIDI, и так далее.

Здесь определяются основные компоненты, такие как PCM, микшер (например, AC97), MIDI (например, MPU-401), и другие интерфейсы. Кроме того, если необходим файл proc, определите здесь и его.

6) Регистрируем экземпляр карты.

err = snd_card_register(card);

if (err < 0) {

    snd_card_free(card);

    return err;

}

 

Будет тоже объяснено в разделе Управление картами и компонентами.

7) Устанавливаем данные PCI драйвера и возвращаем ноль.

pci_set_drvdata(pci, card);

dev++;

return 0;

 

Как говорилось выше, это сохранение объекта, описывающего карту. Этот указатель также используется в обратных вызовах удаления и управления питанием.

Деструктор

Деструктор, обратный вызов remove, просто освобождает экземпляр карты. Затем центральный уровень ALSA автоматически освободит все подключенные компоненты.

 

Это будет выглядеть, как правило, следующим образом:

 

static void __devexit snd_mychip_remove(struct pci_dev *pci)

{

    snd_card_free(pci_get_drvdata(pci));

    pci_set_drvdata(pci, NULL);

}

 

Вышеприведённый код предполагает, что указатель карты указывает на закрытые данные PCI драйвера.

Заголовочные файлы

Для приведенного выше примера необходимы по крайней мере следующие файлы заголовков.

 

#include <linux/init.h>

#include <linux/pci.h>

#include <linux/slab.h>

#include <sound/core.h>

#include <sound/initval.h>

 

где последний необходим только тогда, когда параметры модуля определены в исходном файле. Если код разделён на несколько файлов, файлам без параметров модуля он не нужен.

 

В дополнение к этим заголовкам необходим <linux/interrupt.h> для обработки прерываний и <asm/io.h> для доступа к вводу/выводу. Если используются функции mdelay() или udelay(), необходимо также подключить <linux/delay.h>.

 

Интерфейсы ALSA, такие как API PCM и управления, определены в других файлах заголовков <sound/xxx.h>. Они должны быть подключены после <sound/core.h>.

 

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