Глава 2. Основная технология для PCI драйверов |
Предыдущая Содержание Следующая |
Краткое описаниеМинимальная структура для звуковых карт 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>.
|
Предыдущая Содержание Следующая |