Обзор технологии быстрой активации устройств
Быстрая активация устройств (которая также обозначается термином «минидрайвер») повышает степень интеграции оборудования встраиваемых систем. Развитые процессоры тесно интегрированы с периферийными устройствами и напрямую управляют такими интерфейсами, как CAN, J1850 и MOST.
Уменьшение количества микросхем и электронных компонентов сокращает затраты на оборудование, но в то же время ставит дополнительные задачи перед разработчиками программного обеспечения. Например, телематический блок управления должен начинать прием сообщений по шине CAN через 30-100 миллисекунд после включения. Проблема заключается в том, что загрузка сложных программ, которые выполняются на таких телематических устройствах, может занимать сотни миллисекунд и более.
Еще одним примером являются критически важные точки в процессе загрузки автомобильной телематической или информационно-развлекательной системы, который обычно осуществляется в «холодном» режиме (из состояния полного отключения) или после перезагрузки ЦП, при которой отключается память SDRAM. Эта система должна:
Для удовлетворения этих временных требований во многих встраиваемых системах применяется простое, но дорогостоящее решение, в состав которого входит дополнительный процессор связи или внешний модуль питания. Инновационные программные продукты компании «СВД Встраиваемые Системы» позволяют уменьшать количество дополнительного оборудования вплоть до полного отказа от него. Их основу составляют минидрайверы — небольшие высокоэффективные драйверы устройств, которые начинают работать до инициализации ядра ОС.
При обычной загрузке ЗОСРВ «Нейтрино» драйвер не может выполняться до тех пор, пока образ операционной системы не загружен в оперативную память и ядро не инициализировано. В зависимости от конкретного оборудования (процессора, флеш-памяти, архитектуры) и размера образа длительность загрузки ОС может составлять от сотен миллисекунд до нескольких секунд. Минидрайвер запускается на ранних этапах загрузки и может соответствовать временным требованиям протоколов таких шин, как MOST и CAN.
Минидрайвер задается в коде запуска и начинает работать до того, как завершается загрузка операционной системы. Он быстро и своевременно реагирует на сообщения, которые включают оборудование, и гарантирует, что они не теряются к моменту окончания загрузки ОС. По завершении загрузки ОС минидрайвер продолжает выполняться или передает управление полнофункциональному драйверу, который имеет доступ к данным, собранным минидрайвером.
Минидрайвер состоит из двух основных компонентов:
После создания минидрайвера функция-обработчик вызывается в процессе загрузки. Первый вызов функции-обработчика выполняется таймером (опрос). После разрешения прерываний ЦП обработчик вызывается прерываниями, которые генерируются устройствами. Поскольку таймеры также генерируют прерывания, с их помощью можно опрашивать устройства, которые не генерируют прерывания.
Минидрайвер — это функция (фрагмент кода), которую разработчик связывает с программой запуска ЗОСРВ «Нейтрино». Минидрайвер выполняется раньше, чем ядро инициализируется и система становится готовой к работе. Минидрайвер имеет доступ к устройству и может сохранять данные в буфере RAM, который доступен для считывания полнофункциональному драйверу, представляющему собой обычный процесс.
При запуске системы периодически вызывается (опрашивается) функция-обработчик минидрайвера, которая добавлена в код запуска ЗОСРВ «Нейтрино». Можно регулировать интервал опроса в соответствии с временными требованиями устройства путем внесения небольших изменений в программу запуска. После того, как в процессе загрузки системы включаются прерывания, они управляют функцией-обработчиком. При вызове обработчику передается переменная состояния (state).
Прототип функции-обработчика минидрайвера имеет вид:
int mdriver_handler( int state, void *data );
Когда в операционной системе запускается полнофункциональный драйвер, он принимает управление от минидрайвера. Передача управления осуществляется в плавном режиме без прерывания обслуживания устройства. Полнофункциональный драйвер просто подключается к прерыванию устройства, а минидрайвер получает об этом уведомление; Затем минидрайвер может штатно завершать работу, а полнофункциональный драйвер — продолжать выполнение. Полнофункциональный драйвер имеет доступ ко всем буферам данных, которые созданы минидрайвером.
В минидрайвере могут выполняться несколько функций-обработчиков. Если устройство должно совершать какое-либо действие с периодом n миллисекунд, можно присоединить две функции-обработчика:
Поскольку при запуске системы минидрайвер таймера спорадически опрашивается, а не вызывается через постоянные промежутки времени, минидрайверу необходимо измерять время между вызовами и рассчитывать интервалы.
Эта архитектура дает возможность запускать драйверы устройств на самых ранних этапах загрузки системы и обеспечивать функционирование устройства до ее окончания. Если полнофункциональный драйвер не принимает управление устройством, минидрайвер продолжает работать в полностью загруженной системе.
Перед тем, как приступать к написанию минидрайвера, необходимо определить:
Получите BSP аппаратной платформы, который включает в себя исходный код программы запуска платы. Необходимо связать минидрайвер с этой программой.
![]() | Если вы работаете с платформой ARM, необходимо реализовывать функцию-обработчика минидрайвера в виде позиционно-независимого кода (англ. PIC, Position Independent Code). Это означает, что если обработчик находится в состоянии MDRIVER_KERNEL , MDRIVER_PROCESS или MDRIVER_INTR_ATTACH , в нем нельзя использовать глобальные или статические переменные. |
Поскольку при запуске системы и инициализации ядра в процессе загрузки опрашивается код минидрайвера, необходимо знать временные параметры устройства, чтобы обеспечивать выполнение опроса с достаточной частотой. Копирование загрузочного образа из флеш-памяти в RAM занимает большую часть времени выполнения программы запуска; в этот период осуществляется опрос минидрайвера.
Программа запуска содержит глобальную переменную mdriver_max, в которой хранится количество данных (в байтах), копируемых из флеш-памяти в RAM между вызовами минидрайвера. Размер данных по умолчанию составляет 16 Кбайт и должен выбираться с учетом временных требований устройства, быстродействия процессора и характеристик флеш-памяти. Эта переменная содержится в файле mdriver_max.c
, который находится в библиотеке запуска.
Можно изменить значение этой переменной двумя способами:
mdriver_max.c
содержит переменную:
unsigned mdriver_max = KILO( 16 );
Программе минидрайвера необходим буфер для приема данных от оборудования и их передачи полнофункциональному драйверу после загрузки системы. Разработчик определяет объем хранимых данных и выделяет память для них.
Местоположение этой памяти выбирается разработчиком или программой запуска. Память выделяется функцией:
paddr_t alloc_ram( phys_addr, size, alignment );
которая возвращает ее физический адрес. Эта область памяти используется при регистрации минидрайвера в программе запуска. Поскольку вышеуказанная функция резервирует область в RAM, необходимо вызывать ее после инициализации RAM с помощью функции init_raminfo().
Система не управляет этой областью памяти; ваш драйвер должен защищать ее содержимое от перезаписи и предотвращать сбои в программе запуска.
Если драйвер инициализирует оборудование, следует поместить код в обработчик MDRIVER_INIT
минидрайвера. MDRIVER_INIT
является первым состоянием минидрайвера и применяется однократно.
Минидрайверу почти всегда необходимо иметь доступ к оборудованию, чтобы выполнять операции чтения и записи над его регистрами. Для работы с регистрами устройств в библиотеке запуска предусмотрены функции отображения и отмены отображения физической памяти.
При вызове обработчика минидрайвера с параметром MDRIVER_STARTUP_INIT
осуществляются обращения к следующим функциям:
uintptr_t startup_io_map( size, phys_addr );startup_io_unmap( paddr );void * startup_memory_map( size, phys_addr );startup_memory_unmap( paddr );
После вызова минидрайвера с параметром MDRIVER_STARTUP_PREPARE
перечисленные функции становятся недоступными, и вместо них в драйвере необходимо использовать функции:
uintptr_t callout_io_map( size, phys_addr );void * callout_memory_map( size, phys_addr );
Множество доступных вызовов зависит от этапов процесса загрузки. Если драйверу необходим доступ к оборудованию, выполните в нем следующие действия:
После запуска ядра минидрайвер может передавать управление полнофункциональному драйверу и штатно завершать работу. При передаче управления полнофункциональный драйвер выполняет следующие действия:
Минидрайвер вызывается с состоянием MDRIVER_INTR_ATTACH
. На этом этапе минидрайвер должен выполнять все необходимые действия по очистке и отключать прерывание устройства. Затем обработчик минидрайвера может вернуть ненулевое значение, которое указывает, что минидрайвер завершает работу. Передача управления выполнена успешно, если:
В этом разделе представлен простой пример минидрайвера, который можно использовать для отладки. Он подсчитывает количество раз, которое он вызывается на каждом этапе процесса загрузки, и сохраняет эту информацию в области данных. По окончании загрузки системы программа может считывать область данных и извлекать из нее содержимое.
Особенности реализации
Временные данные хранятся в общей памяти. В этом примере размер области данных составляет 64 Кбайт. Если переменной mdriver_max присваивается значение менее 16 Кбайт, может потребоваться область данных большего размера (например, 128 Кбайт), поскольку уменьшение значения mdriver_max приводит к увеличению количества внешних вызовов.
Рекомендуется использовать значение переменной mdriver_max по умолчанию (16 Кбайт), при котором размер области данных равен 64 Кбайт. Предполагается, что рассматриваемомум драйверу не требуется доступ к устройству.
Прототип функции-обработчика минидрайвера имеет следующий вид:
int mdriver_handler( int state, void *data );
Определения переменной state и указателя data см. в описании функции mdriver_add(). В этом примере драйвера исходный код функции-обработчика выглядит следующим образом:
struct mini_data{uint16_t nstartup;uint16_t nstartupp;uint16_t nstartupf;uint16_t nkernel;uint16_t nprocess;uint16_t data_len;};/** Пример функции-обработчика минидрайвера, предназначенного для отладки** Подсчитывает количество вызовов для каждого состояния и записывает текущее состояние* обработчика в область данных*/int mini_data( int state, void *data ){uint8_t *dptr;struct mini_data *mdata;mdata = (struct mini_data *)data;dptr = (uint8_t *)(mdata + 1);/* создание области данных в состоянии MDRIVER_INIT */if ( state == MDRIVER_INIT ){mdata->nstartup = 0;mdata->nstartupf = 0;mdata->nstartupp = 0;mdata->nkernel = 0;mdata->nprocess = 0;mdata->data_len = 0;}/* подсчет количества вызовов для каждого типа */if ( state == MDRIVER_STARTUP ) mdata->nstartup = mdata->nstartup + 1;else if ( state == MDRIVER_STARTUP_PREPARE ) mdata->nstartupp = mdata->nstartupp + 1;else if ( state == MDRIVER_STARTUP_FINI ) mdata->nstartupf = mdata->nstartupf + 1;else if ( state == MDRIVER_KERNEL ) mdata->nkernel = mdata->nkernel + 1;else if ( state == MDRIVER_PROCESS ) mdata->nprocess = mdata->nprocess + 1;else if ( state == MDRIVER_INTR_ATTACH ) return (1); /* обычное запрещение нашего прерывания *//* запись информации о состоянии в область данных после структуры при наличии свободного места */if (mdata->data_len < 60000 ){dptr[mdata->data_len] = (uint8_t)state;mdata->data_len = mdata->data_len + 1;}return (0);}
Поскольку в этом примере функция-обработчик хранит информацию о вызовах, для облегчения доступа к области данных создана специальная структура.
Мы предотвращаем возможность записи за пределы области данных, размер которой составляет 64 Кбайт. Попытка функции-обработчика выполнить запись вне области данных может приводить к сбою и невозможности дальнейшей загрузки операционной системы. Разработчику необходимо тщательно следить за соблюдением границ при чтении и записи в память.
В состоянии MDRIVER_INIT
выполняется инициализация области данных. Обработчик входит в него один раз. В состоянии MDRIVER_INTR_ATTACH
обработчик возвращает значение 1
, запрашивая останов. Тем не менее, из-за асинхронного характера работы системы обработчик может быть вызван еще несколько раз после запроса на останов.
После написания функции-обработчика необходимо зарегистрировать ее в программе запуска и выделить требуемый объем RAM для области данных. Для этого используются функции:
paddr_t alloc_ram( phys_addr, size, alignment );int mdriver_add( name, interrupt, handler, data_paddr, data_size );
Поскольку эти функции выделяют память и используют номер прерывания, перед их вызовом необходимо инициализировать оперативную память с помощью функции init_raminfo() и добавлять информацию о прерываниях на системную страницу с помощью функции init_intrinfo().
Функция main() в файле main.c
программы запуска выглядит следующим образом:
...paddr_t mdrvr_addr;.../* Получение информации обо всей свободной оперативной памяти системы. */init_raminfo();/* В виртуальной системе нам необходимо инициализировать таблицы страниц */if ( shdr->flags1 & STARTUP_HDR_FLAGS1_VIRTUAL )init_mmu();/** Следующие процедуры могут иметь зависимости от оборудования или системы,* которые требуется изменить*/init_intrinfo();mdrvr_addr = alloc_ram( ~0L, 65535, 1 ); /* создание области данных размером 64 Кбайт */mdriver_add( "mini-data", 0, mini_data, mdrvr_addr, 65535 );...
В этом примере мы выделили 64 Кбайт памяти и зарегистрировали функцию-обработчика минидрайвера с именем mini-data.
После написания минидрайвера необходимо пересобрать код запуска и загрузочный образ. Чтобы проверить корректность работы минидрайвера, можно добавить отладочную информацию в функцию-обработчик или написать приложение, которое считывает область данных минидрайвера и отображает ее содержимое.
В этом разделе содержится исходный код тестового приложения mini-peeker.c
, которое отображает в свое адресное пространство область данных минидрайвера и выводит ее содержимое:
#include <stdlib.h>#include <stdio.h>#include <unistd.h>#include <stdint.h>#include <sys/mman.h>#include <sys/neutrino.h>#include <sys/syspage.h>#include <hw/inout.h>#include <inttypes.h>struct mini_data{uint16_t nstartup;uint16_t nstartupp;uint16_t nstartupf;uint16_t nkernel;uint16_t nprocess;uint16_t data_len;};int main( int argc, char *argv[] ){int i,count,dump_data = 0;uint8_t *dptr;struct mini_data *mdata;if ( argv[1] )dump_data = 1;ThreadCtl( _NTO_TCTL_IO, 0 );/* отображение области данных минидрайвера */if ( (dptr = mmap_device_memory( 0, 65535, PROT_READ | PROT_WRITE | PROT_NOCACHE,0, SYSPAGE_ENTRY( mdriver )->data_paddr )) == NULL ){fprintf( stderr, "Unable to get data pointer\n" );return (-1);}mdata = (struct mini_data *)dptr;dptr = dptr + sizeof( struct mini_data );/* вывод данных минидрайвера */printf( "---------------- MDRIVER DATA -------------------\n" );printf( "\tMDRIVER_STARTUP calls = %d\n", mdata->nstartup );printf( "\tMDRIVER_STARTUP_PREPARE calls = %d\n", mdata->nstartupp );printf( "\tMDRIVER_STARTUP_FINI calls = %d\n", mdata->nstartupf );printf( "\tMDRIVER_KERNEL calls = %d\n", mdata->nkernel );printf( "\tMDRIVER_PROCESS calls = %d\n", mdata->nprocess );printf( "\tData Length calls = %d\n", mdata->data_len );count = mdata->data_len;if ( dump_data ){printf( "State information:\n" );for ( i = 0; i < count; i++ )printf( "%d\n", dptr[i] );}printf( "\n---------------------------------\n" );return (EXIT_SUCCESS);}
Скомпилируйте этот код для целевой системы стандартным способом:
qcc -Vgcc_ntoppcbe mini-peeker.c -o mini-peeker
После загрузки системы и запуска полнофункционального драйвера минидрайвер передает ему управление. Полнофункциональный драйвер подключается к прерыванию и считывает область данных, подготовленную минидрайвером.
Когда полнофункциональный драйвер обращается к функции InterruptAttach() или InterruptAttachEvent(), ядро вызывает минидрайвер с состоянием MDRIVER_INTR_ATTACH
. Функция-обработчик минидрайвера должна выполнить все необходимые действия по очистке и завершить работу.
После подключения к прерыванию полнофункциональный драйвер может обрабатывать буферизованные данные и продолжать обслуживать устройство. Пример:
if ( (id == InterruptAttachEvent( intr, event, _NTO_INTR_FLAGS_TRK_MSK )) == -1 ){perror( "InterruptAttachEvent\n" );return (-1);}if ( (dptr = mmap_device_memory( 0, data_size, PROT_READ | PROT_WRITE | PROT_NOCACHE,0, SYSPAGE_ENTRY( mdriver )->data_paddr )) == NULL ){fprintf( stderr, "Unable to get data pointer\n" );return (-1);}/* На этом этапе работа минидрайвера завершена, а полнофункциональный драйвер имеет доступ к прерыванию и области данных *//* Включите прерывание устройства (intr) */
Для обеспечения безопасности в полнофункциональном драйвере следует всегда запрещать прерывание устройства перед вызовом функции InterruptAttach() или InterruptAttachEvent() и разрешать его после успешного завершения функции.
Подробнее рассмотрим этапы разработки, выполнения и отладки минидрайвера.
Разработка минидрайвера состоит из следующих основных этапов:
Этап передачи управления полнофункциональному драйверу не является обязательным. Функция-обработчик минидрайвера принимает прерывания даже после окончания загрузки ЗОСРВ «Нейтрино» до тех пор, пока функция присоединения обычного обработчика прерываний ( InterruptAttach*()) не отключает минидрайвер. Такое поведение может быть желательным для проектируемой системы.
Рассмотрим этапы разработки, а также применимые к ним методы и рекомендации.
Это ключевой этап разработки минидрайвера.
Чтобы реализовать минидрайвер, выполните следующие действия:
mdriver_max.c
в библиотеке libstartup (этот файл находится на инструментальной системе в пакете поддержки аппаратной платформы в каталоге, имя которого заканчивается на libstartup).
По умолчанию это значение составляет 16 Кбайт (в стандартном пакете поддержки аппаратной платформы для ЗОСРВ «Нейтрино»), что соответствует объему данных, которые одновременно копируются при передаче загрузочного образа из флеш-памяти в ОЗУ. После каждого копирования выполняется внешний вызов минидрайвера:
внешний вызов минидрайвера копирование следующих 16 Кбайт внешний вызов минидрайвера копирование следующих 16 Кбайт и т.д.
Иногда необходимо изменять это значение. Например, на микроконтроллере MPC5200 с процессором 396 МГц копирование 16 Кбайт занимает примерно 8 мс. Длительность копирования зависит от скорости процессора и флеш-накопителя. Если присвоить переменной mdriver_max значение 1 Кбайт, копирование займет менее 1 миллисекунды. Следует подбирать значение mdriver_max экспериментальным путем.
После внесения изменений в файл mdriver_max.c
необходимо повторно скомпилировать библиотеку libstartup.a и скомпоновать код запуска с новой библиотекой.
Все файлы, которые требуется изменять, находятся в том же каталоге BSP, что и код startup. Например, при сборке BSP платы Media5200b редактируются следующие файлы:
main.c
— обращение к функции mdriver_add() для внешнего вызова
cpu_mdriver.c
, mdriver.c
— следует скопировать их в каталог запуска, но не вносить в них никаких изменений.
mini-driver.c
— файл, в котором находится остальная часть кода минидрайвера, в том числе основная функция-обработчик. Примеры см. в главе Примеры драйверов для быстрой активации устройств. Выполните следующие основные действия:
main.c
extern int mini_data( int state, void *data );
/* глобальная переменная */paddr_t mdriver_addr; /* выделение 64 Кбайт памяти для использования минидрайвером */mdriver_addr = alloc_ram( ~0L, 65536, 1 );
/* Пример добавления функции минидрайвера с именем mini-data для irq=81 */mdriver_add( "mini-data", 81, mini_data, mdrvr_addr, 65536 );
cpu_mdriver.c
mdriver.c
mini-driver.c
Опробуйте примеры, которые входят в этот комплект, и создайте новую программу запуска (например, startup-mgt5200).
На этом этапе у вас имеется скомпилированная программа запуска с интегрированным минидрайвером. Теперь следует включить эту программу запуска в загрузочный образ ЗОСРВ «Нейтрино» и опробовать минидрайвер.
Несколько важных правил сборки загрузочного образа с минидрайвером:
[virtual=ppcbe,binary] .bootstrap = {
Обратите внимание, что в этой строке отсутствует ключевое слово +compress. Замените параметры ppcbe или binary в соответствии с используемым оборудованием и форматом образа.
${KPDA_TARGET}/ppcbe/boot/sys/startup-mgt5200-mdriver
, а затем указать имя startup-mgt5200-mdriver в файле сборки.
Для отладки минидрайвера применяются следующие методы:
kprintf( "I am the minidriver!\n" );kprintf( "Global variable mcounts=%d\n", mcounts );
mini-peeker.c
.
После загрузки ядра изучите содержимое общей памяти, которая была выделена для минидрайвера с помощью функции alloc_ram(). Передайте указатель paddr_t
в функцию mdriver_add(), чтобы связать минидрайвер с областью общей памяти.
См. пример mini-peeker.c
.
Минидрайвер вызывается следующим образом:
Как правило, возможности минидрайвера слишком ограниченны для полноценной обработки прерываний, поэтому он передает управление полнофункциональному драйверу, который подключается к прерыванию.
Корректная передача управления осуществляется в три этапа:
dptr = mmap_device_memory( 0, 65536, PROT_READ | PROT_WRITE | PROT_NOCACHE,0, SYSPAGE_ENTRY( mdriver )->data_paddr );
Поскольку на этом этапе минидрайвер продолжает работать, он вызывается при каждом прерывании до тех пор, пока полнофункциональный драйвер не присоединяется к этому прерыванию. В зависимости от структуры драйверов перед передачей управления полнофункциональному драйверу может требоваться обработка существующих данных, которые собраны минидрайвером.
Полнофункциональный драйвер вызывает функцию InterruptAttach() или InterruptAttachEvent() (предпочтительнее, чем InterruptAttach()). При выполнении этого вызова минидрайвер получает сообщение типа MDRIVER_INTR_ATTACH
и возвращает значение 1
.
С этого момента при возникновении прерывания вызывается не минидрайвер, а полнофункциональный драйвер. Область общей памяти продолжает существовать до тех пор, пока разработчик не удаляет ее.
Предыдущий раздел: перейти