Глава 3. Управление картами и компонентами

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

Экземпляр карты

Для каждой звуковой карты должна быть выделена память для объекта "card".

 

Объект card является основой звуковой карты. Он управляет целым списком устройств (компонентов) звуковой карты, такими как PCM, микшеры, MIDI, синтезатор и так далее. Кроме того, объект card хранит ID и строку названия карты, управляет корнем файлов proc и контролирует состояния управлением питания и отключения устройств с автоопределением. Список компонентов в объекте card используется для управления правильным освобождением ресурсов при уничтожении.

 

Как упоминалось выше, для создания экземпляра карты вызывается snd_card_create().

 

struct snd_card *card;

int err;

err = snd_card_create(index, id, module, extra_size, &card);

 

Функция принимает пять аргументов, число индекса карты, строку идентификации, указатель модуля (обычно THIS_MODULE), размер пространства для дополнительных данных и указатель для получения экземпляра карты. Аргумент extra_size используется для выделения памяти card->private_data для данных, зависящих от используемого чипа. Обратите внимание, что память для этих данных выделяется с помощью snd_card_create().

Компоненты

После того, как карта создана, можно подключить компоненты (устройства) к данному экземпляру карты. В драйвере ALSA компонент представлен в виде объекта структуры snd_device. Компонент может быть экземпляром PCM, интерфейсом управления, интерфейсом raw MIDI и так далее. Каждый такой экземпляр имеет одну запись компонента.

 

Компонент может быть создан с помощью функции snd_device_new().

 

snd_device_new(card, SNDRV_DEV_XXX, chip, &ops);

 

Она получает указатель на карту, уровень устройства (SNDRV_DEV_XXX), указатель на данные и указатели обратных вызовов (&ops). Уровень устройства определяет тип компонентов и порядок регистрации и разрегистрации. Для большинства компонентов уровень устройства уже определён. Для определяемых пользователем компонентов можно использовать SNDRV_DEV_LOWLEVEL.

 

Эта функция сама по себе не выделяет память для данных. Память для данных должна быть выделена вручную заранее и указатель на неё передаётся в качестве аргумента. Этот указатель используется в качестве (идентификатор chip в вышеприведённом примере) данного экземпляра.

 

Каждый предопределённый компонент ALSA, такой как ac97 и PCM, вызывает внутри своего конструктора snd_device_new(). Деструктор для каждого компонента определяется в указателях обратного вызова. Таким образом нет необходимости заботиться о вызове деструктора для таких компонентов.

 

Если вы хотите создать свой собственный компонент, необходимо указать функцию деструктора в обратном вызове dev_free в ops, так что он может быть освобожден автоматически через snd_card_free(). Следующий пример покажет реализацию работы с зависимыми от чипа данными.

Данные, зависимые от используемого чипа

Зависимая от чипа информация, например, адрес порта ввода/вывода, указатель на его ресурсы, или номер прерывания, хранится в объекте, зависящем от чипа.

 

struct mychip {

    ....

};

 

В общем, есть два способа выделения памяти для объекта чипа.

1. Создание с помощью snd_card_create().

Как уже упоминалось выше, можно передать размер дополнительных данных в 4-м аргументе snd_card_create(), то есть

 

err = snd_card_create(index[dev], id[dev], THIS_MODULE,

                      sizeof(struct mychip), &card);

 

Структура mychip представляет собой тип объекта чипа.

 

По возвращении, выделенная память для объекта может быть доступна как

 

struct mychip *chip = card->private_data;

 

С помощью этого метода, не требуется выделять память два раза. Объект освобождается вместе с экземпляром карты.

2. Создание дополнительного устройства.

После создания экземпляра карты через snd_card_create() (с 0 в 4-м аргументе) вызываем kzalloc().

 

struct snd_card *card;

struct mychip *chip;

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

.....

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

 

Объект чипа должна иметь по крайней мере поле для хранения указателя на карту,

 

struct mychip {

    struct snd_card *card;

    ....

};

 

Затем устанавливаем указатель на карту, возвращаемый экземпляром чипа.

 

chip->card = card;

 

Далее, инициализируем поля и регистрируем этот объект чипа как низкоуровневое устройство с указанными ops,

 

static struct snd_device_ops ops = {

    .dev_free = snd_mychip_dev_free,

};

....

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

 

snd_mychip_dev_free() является функцией деструктора устройства, которая будет вызывать настоящий деструктор.

 

static int snd_mychip_dev_free(struct snd_device *device)

{

    return snd_mychip_free(device->device_data);

}

 

где snd_mychip_free() является настоящим деструктором.

Регистрация и освобождение памяти

После определения всех компонентов зарегистрируем экземпляр карты, вызывая snd_card_register(). В этой точке разрешается доступ к файлам устройства. То есть до вызова snd_card_register() компоненты гарантировано недоступны снаружи. Если этот вызов не удался, выходим из функции probe после освобождения карты через snd_card_free().

 

Для освобождения экземпляра карты можно просто вызвать snd_card_free(). Как упоминалось ранее, этим вызовом  автоматически освобождаются все компоненты.

 

Обратите внимание, что деструкторы (как snd_mychip_dev_free, так и snd_mychip_free) не могут быть определены с префиксом __devexit, потому что они могут быть вызваны также из конструктора при обработке ошибок.

 

Для устройства, которое позволяет горячее подключение, можно использовать snd_card_free_when_closed. Она отложит уничтожение до момента, пока не будут закрыты все устройств.

 

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