Глава 11. Управление буфером и памятью

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

Типы буферов

ALSA предоставляет несколько разных функций для выделения буфера в зависимости от шины и архитектуры. Все они имеют соответствующее API. Выделение физически непрерывных страниц выполняется с помощью функции snd_malloc_xxx_pages(), где xxx является типом шины.

 

Выделение страниц с альтернативой выполняется snd_malloc_xxx_pages_fallback(). Эта функция пытается выделить указанные страницы, но если страницы не доступны, она пытается уменьшить размеры страниц, пока не будет найдено достаточное пространство.

 

Для освобождения страниц вызовите функцию snd_free_xxx_pages().

 

Как правило, в момент загрузки модуля драйверы ALSA пытаются выделить и зарезервировать большое непрерывное физическое пространство для дальнейшего использования. Это называется "предварительное выделение". Как уже писалось, во время создания экземпляра PCM можно вызвать следующую функцию (в случае шины PCI):

 

snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,

                                      snd_dma_pci_data(pci), size, max);

 

где size - размер в байтах для предварительного выделения, а max - максимальный размер, изменяемый с помощью файла prealloc интерфейса proc. Функция выделения памяти будет пытаться получить как можно большую область в пределах заданного размера.

 

Второй аргумент (тип) и третий аргумент (указатель устройства) зависят от шины. В случае шины ISA в качестве третьего аргумента передаётся snd_dma_isa_data(), а в качестве типа - SNDRV_DMA_TYPE_DEV. Непрерывный буфер не связанный с шиной может предварительно выделяется с помощью типа SNDRV_DMA_TYPE_CONTINUOUS и указателем устройства snd_dma_continuous_data(GFP_KERNEL), где GFP_KERNEL является используемым для выделения флагом ядра. Для PCI буферов со сборкой/разборкой используйте SNDRV_DMA_TYPE_DEV_SG с snd_dma_pci_data(pci) (смотрите раздел Буферы, состоящие из несмежных участков).

 

После предварительного выделения буфера в обратном вызове hw_params можно использовать следующий распределитель памяти:

 

snd_pcm_lib_malloc_pages(substream, size);

 

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

Внешние аппаратные буферы

Некоторые чипы имеют свои собственные аппаратные буферы и передача DMA из основной памяти не доступна. В таком случае, вам необходимо либо 1) копировать/помещать аудио-данные непосредственно во внешний аппаратный буфер, либо 2) сделать промежуточный буфер и копировать/помещать данные из него во внешний аппаратный буфер в прерываниях (или, что предпочтительнее, в тасклетах).

 

Первый вариант отлично работает, если внешний аппаратный буфер достаточно велик. Этот метод не требует каких-либо дополнительных буферов и, следовательно, является более эффективным. Для передачи данных необходимо определить обратные вызовы copy и silence. Однако, есть недостаток: он не может быть отображён через mmap. Примерами являются GF1 PCM от GUS или таблица волнового синтеза PCM в emu8000.

 

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

 

Ещё одним случаем является использование чипом для буфера отображённой области памяти PCI, а не основной памяти. В этом случае mmap доступна только на определённых архитектурах, например, Intel. В режиме без mmap данные не могут быть переданы, как в обычном способе. Таким образом, необходимо определить обратные вызовы copy и silence также, как и в случаях выше. Примеры можно найти в rme32.c и rme96.c.

 

Реализация обратных вызовов copy и silence зависит от того, поддерживает оборудование данные с чередованием (interleaved) или без чередования (non-interleaved). Обратный вызов copy определяется, как показано ниже, и немного отличается в зависимости от используемого направления, воспроизведение или захват:

 

static int playback_copy(struct snd_pcm_substream *substream, int channel,

            snd_pcm_uframes_t pos, void *src, snd_pcm_uframes_t count);

static int capture_copy(struct snd_pcm_substream *substream, int channel,

            snd_pcm_uframes_t pos, void *dst, snd_pcm_uframes_t count);

 

В случае с данных с чередованием, второй аргумент (channel, канал) не используется. Третий аргумент (pos) указывает текущее положение смещения в кадрах.

 

Смысл четвёртого аргумента отличается для воспроизведения и захвата. Для воспроизведения он хранит указатель данных источника, а для захвата - это указатель данных назначения.

 

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

 

То, что вы должны сделать в этом обратном вызове снова зависит от направления, воспроизведение и захват. В случае воспроизведения, копируется заданное количество данных (count) по заданному указателю (src) с указанным смещением (pos) в аппаратный буфер. Код в стиле memcpy будет выглядеть так:

 

my_memcpy(my_buffer + frames_to_bytes(runtime, pos), src,

          frames_to_bytes(runtime, count));

 

В случае захвата копируется заданное количество данных (count) с указанным смещением (pos) в аппаратном буфере по указанному указателю (dst).

 

my_memcpy(dst, my_buffer + frames_to_bytes(runtime, pos),

          frames_to_bytes(runtime, count));

 

Отметим, что и позиция, и объём данных указаны в кадрах.

 

В случае данных без чередования реализация будет немного сложнее.

 

Вы должны проверить аргумент channel, и если он -1, скопировать все каналы. В противном случае, вы должны скопировать только указанный канал. Пожалуйста, используйте в качестве примера isa/gus/gus_pcm.c.

 

Обратный вызов silence также реализован аналогично.

 

static int silence(struct snd_pcm_substream *substream, int channel,

                   snd_pcm_uframes_t pos, snd_pcm_uframes_t count);

 

Значения аргументов такие же, как в обратном вызове copy, хотя нет аргумента src/dst. В случае данных с чередованием аргумент channel не имеет смысла, также как в обратном вызове copy.

 

Ролью обратного вызова silence является установка определенного количества (count) данных тишины по указанному смещению (pos) в аппаратном буфере. Предположим, что формат данных знаковый (то есть, данными для тишины является 0), тогда реализация в стиле memset была бы такой:

 

my_memcpy(my_buffer + frames_to_bytes(runtime, pos), 0,

          frames_to_bytes(runtime, count));

 

Аналогично, в случае данных без чередования реализация становится немного сложнее. В качестве примера смотрите isa/gus/gus_pcm.

Буферы, состоящие из несмежных участков

Если ваше оборудование поддерживает таблицы страниц, как emu10k1, или дескрипторы буферов, как via82xx, можно использовать DMA с разборкой/сборкой (scatter-gather, SG). ALSA предоставляет интерфейс для обработки SG-буферов. API предоставляется в <sound/pcm.h>.

 

Для создания обработчик SG-буфера, вызовите snd_pcm_lib_preallocate_pages() или snd_pcm_lib_preallocate_pages_for_all() с SNDRV_DMA_TYPE_DEV_SG в конструкторе PCM, как и при другом предварительном выделении памяти для PCI. Вам необходимо передать snd_dma_pci_data(pci), где pci также является указателем struct pci_dev чипа. Экземпляр структуры snd_sg_buf создаётся как substream->dma_private. Вы можете привести указатель следующим образом:

 

struct snd_sg_buf *sgbuf = (struct snd_sg_buf *)substream->dma_private;

 

Затем, так же как случае обычного буфера PCI вызовите в обратном вызове hw_params функцию snd_pcm_lib_malloc_pages(). Обработчик SG-буфера выделит несмежные страницы ядра заданного размера и отобразит их на виртуально непрерывную память. Виртуальный указатель доступен в runtime->dma_area. Физический адрес (runtime->dma_addr) равен нулю, так как буфер является физически несмежным. Таблица физических адресов создана в sgbuf->table. Вы можете получить физический адрес для определённого смещения через snd_pcm_sgbuf_get_addr().

 

Когда используется SG-обработчик, необходимо в качестве обратного вызова page установить snd_pcm_sgbuf_ops_page. (Смотрите раздел Обратный вызов page.)

 

Для освобождения данных в обратном вызове hw_free вызовите snd_pcm_lib_free_pages(), как обычно.

Буферы, выделенные с помощью vmalloc

Возможно использование буфера, выделенного через vmalloc, например, для промежуточного буфера. Так как выделенные страницы не являются смежными, для получения физического адреса для каждого смещения вам необходимо указать обратный вызов page.

 

Реализация обратного вызова page была бы такой:

 

#include <linux/vmalloc.h>

 

/* get the physical page pointer on the given offset */

static struct page *mychip_page(struct snd_pcm_substream *substream,

                                unsigned long offset)

{

    void *pageptr = substream->runtime->dma_area + offset;

    return vmalloc_to_page(pageptr);

}

 

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