В этой статье приведен перевод раздела "Deferred Callback Manager" из документации "VisualDSP++ 5.0 Device Drivers and System Services Manual for Blackfin® Processors" [1]. Описывается менеджер отложенных функций обратного вызова, deferred callback (DCB) manager, используемый разработчиками приложения для управления отложенным выполнением вызовов функции. Имеется также подробное описание интерфейса программирования (API), предоставляемое Менеджером DCB.
Рассматриваются следующие вопросы:
• Использование Менеджера DCB • Взаимодействие с RTOS • Документация API Менеджера DCB • Публичные типы данных и макросы
Функции обратного вызова (callback functions) обычно используются в приложениях, управляемых событиями, где клиент приложения запрашивает от менеджера службы (такой как Менеджер DMA из библиотеки Системных Служб, SSL) оповещение о том, что запрошенная задача выполнена, например завершена передача DMA, через вызов клиентской функции обратного вызова, указанной клиентом приложения в момент инициализации требуемой службы.
Необходимость вызова клиентской callback-функции обычно возникает, когда выполняется обработчик прерывания ISR с относительно высоким приоритетом. Основное правило таких ISR - как можно меньше занимать времени на свою обработку, и сохранить свое время выполнения максимально определенным и минимально возможным. С другой стороны, callback-функции могут быть длительными, и с неопределенным заранее временем выполнения. В большинстве случаев пользователю было бы предпочтительнее отложить выполнение таких callback-ов на планировщик, работающий с приоритетом ниже, который мог бы быть вытесненным более высокоприоритетными прерываниями. Если делать так, то запрошенная обработка ISR может завершиться с минимальной задержкой.
Менеджер отложенных функций обратного вызова (deferred callback, DCB) из состава Системных Служб предоставляет такой сервис путем обслуживания одной или большего количества очередей отложенных функций обратного вызова, так что их обработка их вызова обычно происходит из функции диспетчера, работающей с приоритетом ниже, чем остальные обработчики прерываний приложения. Такие точки входа в функции обратного вызова ставятся в очередь, с предоставлением адреса требуемой callback-функции вместе с тремя значениями (2 указателя и одно 32-битное целое число без знака), которые передаются в callback-функцию при её (отложенном) запуске.
Менеджер DCB разработан как модуль, работающий отдельно, или совместно с операционной системой реального времени (real-time operating system, RTOS). Реализация модуля имеется для систем Express Logic ThreadX, Green Hills Software INTEGRITY, а так же для VDK компании Analog Devices.
Количество доступных очередей и их длина определяется клиентским приложением в момент инициализации модуля и очереди. От того, как реализован Менеджер DCB (как отдельный модуль или совместно с RTOS), также зависит количество и размер очередей. При реализации совместно с VDK Менеджер DCB может поддерживать только одну очередь с фиксированным уровнем приоритета IVG 14.
Хотя разрешена только одна очередь на один уровень IVG, инженеры могут установить приоритеты для отдельных callback-записей в момент их постановки в очередь. Нет ограничений на количество уровней программного приоритета, которые можно использовать (за исключением практического ограничения на значения числа типа unsigned short). Функция диспетчера пытается выполнить высокоприоритетные callback-и перед теми, у кого программный приоритет ниже, и все они будут выполняться на на одном уровне IVG.
Подробное описание работы Менеджера DCB предоставлено в разделе "Использование Менеджера DCB" вместе с кусками кода, иллюстрирующими его использование в отдельном режиме (без RTOS). Как Менеджер DCB использовать совместно с RTOS, показано в разделе "Взаимодействие с RTOS".
Менеджер DCB использует однозначное соглашение об именовании функций, типов и макросов API, чтобы избежать конфликтов в с другими программными библиотеками, предоставленными ADI (это компания Analog Devices) или другими компаниями. В результате все значения перечисления (enum) и операторы определения типа (typedef) используют префикс ADI_DCB_, и соответственно функции и глобальные переменные используют adi_dcb_, эквивалентный префикс в нижнем регистре.
[Использование Менеджера DCB]
Работа Менеджера DCB состоит в выполнении следующих функций.
• Настройка Менеджера DCB:
Инициализация Менеджера DCB Открытие очереди
• Обслуживание очереди:
Постановка callback-ов в требуемую очередь Диспетчеризирование callback-ов по их уровню приоритета (что задается при постановке в очередь)
• Выполнение вспомогательных функций:
Закрытие очереди Завершение Менеджера DCB
Как все это реализовано - зависит от условий работы Менеджера DCB, т. е. в отдельном приложении (без RTOS), или под управлением механизма отложенных вызовов, предоставляемых RTOS. В любом случае API-вызовы к Менеджеру DCB одинаковые: очередь инициализируется вызовом adi_dcb_Open, и callback-и ставятся в очередь вызовом adi_dcb_Post.
Отложенное выполнение callback-ов планируется по программному приоритету с помощью функции adi_dcb_Dispatch_Callbacks. В рабочем окружении обычного приложения (не RTOS) Менеджер DCB регистрирует (в момент инициализации очереди) подпрограмму обработчика прерывания на желаемом уровне IVG, используя модуль Менеджера Прерываний [2] из библиотеки Системных Служб, и прерывание вызывается каждый раз при постановке в очередь callback-а. Поскольку версия без RTOS использует Менеджер Прерываний, то Менеджер Прерываний должен быть инициализирован перед инициализацией Менеджера DCB.
Следующий код демонстрирует использование без RTOS на одной очереди, инициализированной на уровне IVG 14, с самым низким уровнем IVG, доступным на уровне приложения.
Как уже упоминалось, работа без RTOS требует инициализации Менеджера Прерываний перед инициализацией Менеджера DCB. Если предположить, что пример приложения требует только один обработчик прерывания на один уровень IVG, то Менеджер Прерываний инициализируется следующим кодом:
u32 ne;
adi_int_Init(NULL,0,&ne,NULL);
Инициализация Менеджера DCB памятью, достаточной для одной очереди, следующая:
staticchar mjk_dcb_Data[ADI_DCB_QUEUE_SIZE];
:
u32 ns;
:
adi_dcb_Init(
(void*)mjk_dcb_Data, // Адрес используемой памяти.
ADI_DCB_QUEUE_SIZE, // Количество байт, требуемых для// требуемого количества серверов очереди.&ns // На выходе это должно быть таким же,// как требуемое количество очередей.NULL// Не требуется специальной области данных// для критического региона.
);
Далее для использования открывается сервер очереди путем передачи достаточной памяти для требуемой длины очереди (в этом примере очередь на 5 записей) и желаемого уровня IVG, на котором работает очередь. Этот уровень игнорируется приложениями, основанными на VDK. Будет возвращен хендл к серверу очереди p_DCB_handle:
:
adi_dcb_Open(
14, // Требуемый уровень IVG.
(void*) mjk_dcb_QueueData, // Адрес памяти, используемой для5*ADI_DCB_ENTRY_SIZE, // очереди глубиной в 5 callback-ов.&nqe; // На выходе это должно быть таким же,// как требуемое количество записей// в очереди (5 для нашего примера).&p_DCB_handle // Возвращаемый handle к серверу очереди.
);
Теперь Менеджер DCB готов принять постановки в очередь callback-ов для сервера очереди. Обратите внимание, что эта функция обычно выполняется в ISR другой службы. Менеджер DCB передает серверу (идентифицированному по полученному handle) очереди адрес клиентской callback-функции и связанные с ней значения аргументов:
adi_dcb_Post(
p_DCB_handle, // Хендл к требуемому серверу очереди.0, // Уровень приоритета.
ClientCallback, // Адрес callback-функции.
pService, // Адрес экземпляра службы, которая// поставила этот callback в очередь.
event, // Флаг, идентифицирующий событие, которое// вызвало прерывание.
(void*)data // Адрес данных, относящихся к// callback-функции.
);
В примере, приведенном выше, event обычно определяет событие (например, завершение передачи DMA) и data обычно указывают на соответствующее место в памяти, которое имеет значение в контексте callback-функции. В контексте Менеджера DMA, этот аргумент - адрес подходящего дескриптора или буфера данных.
По любой причине сброс записей очереди может произойти одним из 2 способов: прямым вызовом функции adi_dcb_Remove, или косвенно вызовом функции adi_dcb_Control. См. описание функции adi_dcb_Terminate для получения подробной информации и примера использования совместно с другими запросами. Следующий код описывает метод прямого вызова:
adi_dcb_Remove(
p_DCB_handle, // Хендл для требуемого сервера очереди.
ClientCallback // Адрес сбрасываемой callback-функции.
);
И наконец, если требуется, очередь может быть закрыта и Менеджер DCB остановлен:
adi_dcb_Close(
p_DCB_handle, // Хендл к требуемому серверу очереди.
);
adi_dcb_Terminate();
[Взаимодействие с RTOS]
Менеджер DCB реализует 2 функции, adi_dcb_RegisterISR и adi_dcb_Forward, для интерфейса с различными рабочими окружениями RTOS, включая выделенный режим (standalone mode, работа без RTOS). Эти функции предоставляются в отдельном файле исходного кода adi_dcb_xxxx.c для каждой реализации рабочего окружения, где xxxx идентифицирует требуемую RTOS (например, threadx для Express Logic ThreadX, и integrity для Green Hill Software INTEGRITY), либо standalone для использования без RTOS. Поддержка для VDK реализована так, что эти вышеописанные функции встроены напрямую в VDK. В результате для случая использования рабочего окружения VDK нет эквивалентного файла adi_dcb_vdk.c.
Соответствующий файл adi_dcb_xxxx.c встраивается (или не встраивается) в основной файл adi_dcb.c через операторы условной компиляции, управляемые макросом ADI_SSL_XXXX, где вместо XXXX может быть STANDALONE, THREADX, INTEGRITY или VDK.
В нестоящее время реализации Менеджера DCB предоставлены только для ранее описанных рабочих окружений. Чтобы реализовать эти функции для альтернативных операционных систем (например Linux, FreeRTOS и т. п.), разработчики должны предоставить заменяющие определения в эквивалентных файлах.
В этой секции функции API описаны более подробно.
adi_dcb_Forward. Функция adi_dcb_Forward принимает 2 аргумента. Первый это указатель на структуру ADI_DCB_ENTRY_HDR заголовка записи DCB, и второй это уровень IVG соответствующей очереди. Функция adi_dcb_Forward вызывается из тела функции adi_dcb_Post, и имеет следующий прототип:
Указатель на структуру ADI_DCB_ENTRY_HDR. Это совпадает с адресом структуры сервера очереди, с которой callback был поставлен в очередь. Игнорируется standalone-режиме (без RTOS).
IvgLevel
Уровень IVG соответствующей очереди. Этот аргумент игнорируется VDK.
Структура ADI_DCB_ENTRY_HDR используется для передачи информации в нижележащее рабочее окружение RTOS, и она описана так:
Первое поле в этой структуре это pNext, которое будет NULL на входе в функцию adi_dcb_Forward. В то время как это значение обычно используется для указания на следующий элемент в очереди, его требуемая интерпретация в функции adi_dcb_Forward зависит от специфики реализации RTOS. Второе поле pDeferredFunction устанавливается для указания на функцию adi_dcb_DispatchCallbacks, когда инициализируется очередь. Сервер вызовов отложенных процедур в соответствующей RTOS должен передать указатель в функцию adi_dcb_DispatchCallbacks при выполнении отложенной функции.
adi_dcb_RegisterISR. Функция adi_dcb_RegisterISR вызывается из тела функции adi_dcb_Open, и имеет следующий прототип:
Типы данных определены в заголовочном файле < services/services.h >, и аргументы функции следующие.
IvgLevel
Уровень прерывания, на котором диспетчеризируются callback-и.
Dispatcher
Обязательный адрес для функции adi_dcb_DispatchCallbacks.
hServer
Адрес структуры сервера очереди.
В выделенной реализации (без RTOS) эта функция регистрирует функцию adi_dcb_DispatchCallbacks с Менеджером Прерываний на указанном уровне прерываний. В реализации VDK она делает возврат без всякого эффекта.
Обработка критических регионов в callback-функции. Если требуется наличие критического региона в теле функции обратного вызова, Вы должны избегать любых ограничений, которые накладывает применение нижележащей RTOS. Например, для VDK-приложения запрещено вызывать PushCriticalRegion/PopCriticalRegion из уровня прерываний.
Если используется VDK-версия Менеджера DCB, вызовы такого плана можно использовать, поскольку callback-функция выполняется на уровне кода ядра. Однако если используется выделенная (без RTOS) версия библиотеки для запуска очереди DCB с приоритетом выше, чем очередь отложенных вызовов VDK, то такие вызовы недопустимы, поскольку callback выполняется на уровне прерывания. В этих случаях эффект критического региона может быть получен прямым использованием встроенных функций cli() и sti().
[Документация API Менеджера DCB]
В этой секции предоставлено описание API-функций Менеджера DCB.
Функция adi_dcb_Close() закрывает очередь DCB для сервера, идентифицируемого по единственному аргументу (хендл сервера), с освобождением слота для последующего использования. В выделенном режиме (без RTOS) функция Менеджера DCB adi_dcb_DispatchCallbacks отцепляется от цепочки обработчиков прерывания на указанном уровне IVG.
Хендл на сервер, очередь которого должна быть закрыта.
Возвращаемые значения:
ADI_DCB_RESULT_SUCCESS
Очередь успешно закрыта.
ADI_DCB_RESULT_NO_SUCH_QUEUE
Предоставленный хендл не соответствует допустимому серверу очереди.
ADI_DCB_RESULT_QUEUE_IN_USE
На очереди ожидают своего выполнения не завершенные callback-и. Если об их выполнении не нужно беспокоиться, то сначала сбросьте очередь, перед тем как закрыть её.
Функция adi_dcb_Control() используется для конфигурирования/управления сервером очереди callback-функций в соответствии с парами команда-значение (параметры Command и Value). Для дополнительной информации см. описание ADI_DCB_COMMAND_PAIR. Прототип функции:
В настоящий момент имеет значение только одна команда, ADI_DCB_CMD_FLUSH_QUEUE, хотя в будущем могут быть добавлены и другие. Пары команда-значение можно указать тремя способами:
Функция adi_dcb_Init инициализирует Менеджер DCB с предоставлением достаточного объема памяти для очереди из требуемого количества отложенных вызовов функций. Эта функция может быть вызвана один раз на процессорное ядро.
Указатель на область памяти, используемую для хранения данных, связанных с каждым зарегистрированным сервером очереди.
szServer
Размер в байтах области памяти, предоставленной для данных сервера очереди.
NumServers
При возврате из функции ячейка, на которую ссылается указатель в аргументе будет содержать максимальное количество открытых серверов очереди, которое может поддержать предоставленная область памяти.
hCriticalRegionData
Хендл, указывающий на область данных, содержащий критический регион данных. Это передается в adi_int_EnterCriticalRegion, что используется внутри модуля. Подробности см. в документации на Менеджер Прерываний [2].
Возвращаемое значение:
ADI_DCB_RESULT_SUCCESS
Был успешно инициализирован сервер очереди.
ADI_DCB_RESULT_NO_MEMORY
Обнаружена ошибка нехватки памяти для одной записи очереди.
ADI_DCB_RESULT_CALL_IGNORED
Менеджер DCB уже был проинициализирован для этого ядра процессора.
Функция adi_dcb_Open открывает сервер очереди для использования назначенной памяти для её callback-очереди. Дополнительно, в выделенном режиме (без RTOS) очередь назначается на запрашиваемый уровень IVG и функция adi_dcb_DispatchCallbacks Менеджера DCB подцепляется к цепочке обработчиков прерываний с помощью Менеджера Прерываний, для обработки на имеющемся уровне IVG.
! Менеджер Прерываний должен быть инициализирован перед открытием сервера очереди.
Уровень IVG, на котором работает функция диспетчера Менеджера DCB. Это значение игнорируется в VDK-версии библиотеки.
QueueMemData
Указатель на область памяти, используемой для хранения данных, связанных с записью очереди сервера.
szQueue
Размер области памяти, предоставленной для очереди.
NumEntries
На выходе из функции этот указатель ссылается на ячейку памяти, которая хранит максимальное количество записей в очереди, которое может обеспечить предоставленный объем памяти.
hServer
При выходе из функции этот аргумент содержит ссылку на хендл, указывающий на открытый сервер. Это используется для уникальной идентификации сервера очереди в вызовах других функций API библиотеки Системных Служб.
Возвращаемое значение:
ADI_DCB_RESULT_SUCCESS
Сервер очереди был успешно инициализирован.
ADI_DCB_RESULT_NO_MEMORY
Обнаружена ошибка нехватки памяти для одной записи очереди.
Функция adi_dcb_Post() ставит в очередь (для последующей обработки) callback-функцию со связанными аргументами. Сервер очереди идентифицируется по аргументу хендла hServer.
Функция обратного вызова (callback) привязывается к уровню приоритета, так что callback-и с более высоким уровнем приоритета отработают раньше, чем callback-и с низким уровнем приоритета. Чтобы запускать callback-и с одинаковым уровнем приоритета, назначайте параметр Priority одинаковым для каждой постановки в очередь.
Уровень приоритета, на котором работает callback. Чем меньше число, тем выше приоритет.
Callback
Адрес клиентской функции обратного вызова, для которой запрошен отложенный вызов.
pHandle
Адрес void*, передаваемый как первый аргумент для callback-функции при её отложенном вызове. Обычно это адрес (хендл), который становится осмысленным в контексте тела callback-функции. Например, когда этот параметр используется в обработчике прерывания Менеджера DMA, этот аргумент будет значением ClientHandle, определенном, когда был открыт канал DMA.
u32Arg
Значение типа u32 (unsigned int), передаваемое как второй аргумент в callback-функцию при её отложенном вызове (см. описание ADI_DCB_CALLBACK_FN). Обычно это значение обретает смысл в контексте кода callback-функции. Например, когда оно используется в обработчике прерывания Менеджера DMA, этот аргумент описывает природу произошедшего события.
pArg
Значение void*, передаваемое как третий аргумент для callback-функции при её отложенном вызове (см. описание ADI_DCB_CALLBACK_FN). Обычно это адрес блока данных. Например, когда этот параметр используется в обработчике прерывания Менеджера DMA, этот аргумент указывает на начальную ячейку буфера, для которого завершена передача DMA.
Возвращаемое значение:
ADI_DCB_RESULT_SUCCESS
Запись вызова callback-функции успешно добавлена в очередь.
ADI_DCB_RESULT_NO_MEMORY
Нет свободных мест в очереди.
ADI_DCB_RESULT_NO_SUCH_QUEUE
Предоставленный хендл не соответствует допустимому серверу очереди.
Функция adi_dcb_Remove() удаляет записи в указанной очереди, которые совпадают по адресу с указанной callback-функцией. Альтернативно можно передать значение NULL для адреса callback-функции, что задаст полную очистку очереди (удаление из неё всех функций обратного вызова).
Адрес удаляемой клиентской функции обратного вызова. Если здесь задано NULL, то из очереди будут удалены все записи, иначе будут удалены все записи, адрес функций обратного вызова в которых совпадает с указанным.
Возвращаемое значение:
ADI_DCB_RESULT_FLUSHED_OK
Запись (записи) была успешно удалена.
ADI_DCB_RESULT_NONE_FLUSHED
Не было найдено записей для удаления.
ADI_DCB_RESULT_NO_SUCH_QUEUE
Предоставленный хендл не соответствует допустимому серверу очереди.
Функция adi_dcb_Terminate() завершает работу Менеджера DCB путем освобождения предоставленной для него памяти (см. описание функции adi_dcb_Init) и данных критического региона.
Тип данных ADI_DCB_COMMAND_PAIR используется, чтобы разрешить генерацию таблицы управляющих команд для отправки в Менеджер DCB через функцию adi_dcb_Control.
Для допустимых значений полей см. описание ADI_DCB_COMMAND. Например, следующая команда должна быть отправлена Менеджеру DCB, чтобы сбросить все callback-и в очереди:
Команда ADI_DCB_COMMAND используется для управления сервером очереди Менеджера DCB. Этот тип данных используется в ADI_DCB_COMMAND_PAIR, чтобы поменять значение конфигурации с помощью вызова adi_dcb_Control.
ADI_DCB_CMD_END
Определяет конец таблицы пар команда-значение.
ADI_DCB_CMD_PAIR
Говорит функции adi_dcb_Control, что передана одна пара команда-значение.
ADI_DCB_CMD_TABLE
Говорит функции adi_dcb_Control, что передана таблица пар команда-значение.
ADI_DCB_CMD_FLUSH_QUEUE
Адрес callback-функции, для которой будут удалены из очереди все совпадающие по адресу записи, назависимо от их приоритета.
Структура ADI_DCB_ENTRY_HDR предоставляет интерфейс с нижележащей RTOS через функцию adi_dcb_Forward (см. описание функции adi_dcb_Forward):
typedefstruct ADI_DCB_ENTRY_HDR (
struct ADI_DCB_ENTRY *pNext; // Следующий элемент в очереди.
ADI_DCB_DEFERRED_FN pDeferredFunction; // Указывает на процедуру отложенного// вызова (Deferred Callback), здесь// находится указатель на функцию.
} ADI_DCB_ENTRY_HDR;
Здесь pNext указывает на следующий элемент в очереди, pDeferredFunction это адрес отложенной функции, которая всегда указывает на adi_dcb_DispatchCallbacks.
Определение типа ADI_DCB_DEFERRED_FN задает прототип для этой функции: