Операционная система ЗОСРВ «Нейтрино» > Руководство разработчика > Основные принципы системной разработки > Запуск системы, быстрая активация устройств > Быстрая активация устройств > Общие сведения > Примеры драйверов для быстрой активации устройств



Примеры драйверов для быстрой активации устройств

Примеры кода драйверов для различных платформ

Минидрайвер для платы FreeScale Media5200b
Функция-обработчик минидрайвера
Добавление минидрайвера в систему
Сборка программы запуска
Тестирование минидрайвера
Временные характеристики работы драйвера
Минидрайвер для платы Renesas Biscayne
Функция-обработчик минидрайвера
Добавление минидрайвера в систему
Сборка программы запуска
Тестирование минидрайвера
Временные характеристики работы драйвера
Минидрайвер OMAP
Добавление минидрайвера в систему
Сборка программы запуска
Тестирование минидрайвера
Временные характеристики работы драйвера

Минидрайвер для платы FreeScale Media5200b

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

Функция-обработчик минидрайвера

Прототип функции-обработчика минидрайвера имеет следующий вид:

int mdriver_handler( int state, void *data );

С помощью параметра state обработчик определяет, на каком этапе процесса загрузки он вызывается. См. описание функции mdriver_add() в главе Быстрая активация устройств настоящего руководства. Параметр data представляет собой виртуальный указатель на область данных, которая выделена минидрайверу. В этом примере драйвера исходный код функции-обработчика выглядит следующим образом:

struct mserial_data
{
psc_regs *psc;
psc_regs *psc_k;
uint16_t intr_calls;
uint16_t ncalls;
uint16_t errors;
uint16_t last_count;
uint16_t data_len;
};
/*
* Функция инициализации оборудования, которая вызывается только при первом выполнении
* минидрайвера. В этом примере значения Baud, Clk и port жестко закодированы
* baud = 14400
* clk = 132000000
* port = PSC1
*/
static psc_regs * init_hw()
{
/* жесткое кодирование параметров PSC1, baud и clk */
int baud = 14400;
int clk = 132000000;
paddr64_t psc_base = MGT5200_MBAR_BASE + 0x2000;
psc_regs *psc;
gpio_std_regs *gpio;
if ( (psc = (psc_regs *)(startup_memory_map( 0xA0, psc_base,
PROT_READ | PROT_WRITE | PROT_NOCACHE ))) == 0 )
return (0);
if ( (gpio = (gpio_std_regs *)(startup_memory_map( 0x40, (MGT5200_MBAR_BASE + 0x0b00),
PROT_READ | PROT_WRITE | PROT_NOCACHE ))) == 0 )
{
startup_memory_unmap( (void *)psc );
return (0);
}
baud = ((clk / baud) + 16) / 32; /* вычисление скорости приема */
psc->CR = PSC_CR_TXD | PSC_CR_RXD;
psc->SICR = 0x00000000; /* запись в SICR: SIM2 = режим uart,dcd не влияет на передачу */
psc->SR_CSR = 0xdd00; /* запись в CSR: скорость приема/передачи от таймеров */
psc->CTUR = (baud >> 8) & 0xff; /* запись в CTUR: старший байт делителя частоты */
psc->CTLR = baud & 0xff; /* запись в CTLR: младший байт делителя частоты */
psc->CR = 0x20; /* запись в CR: сброс RX и RXFIFO */
psc->CR = 0x30; /* запись в CR: сброс TX и TXFIFO */
psc->CR = 0x40; /* запись в CR: сброс состояния ошибки */
psc->CR = 0x50; /* запись в CR: сброс прерывания по изменению состояния или состоянию break */
psc->CR = 0x10; /* запись в CR: сброс указателя MR */
psc->IPCR_ACR = 0x00; /* запись в ACR: запрет прерываний по изменению состояний CTS/DCD */
psc->ModeReg = 0x73; /* запись в MR1: 8 бит данных, без контроля четности */
psc->ModeReg = 0x07; /* запись в MR2: обычный режим, один стоп-бит */
psc->OP1 = 1; /* запись в OP1: установка RTS для отправки */
psc->ISR_IMR = 0x0000; /* запись в IMR: маскирование прерывания RxRDY и TxRDY */
psc->RFALARM = 511;
psc->TFALARM = 0xF8;
psc->RFCNTL = 0x04;
gpio->port_config = (gpio->port_config & GPIO_PSC1_MASK) | GPIO_PSC1_UART_ENABLE;
/* включение передатчиков и приемников */
psc->CR = 0x05; /* запись в CR: включение передачи и приема. */
psc->ISR_IMR = PSC_ISR_RxRDY;
startup_memory_unmap( (void *)gpio );
/* возврат назначенного указателя на регистры */
return (psc);
}
int mini_serial( int state, void *data )
{
uint8_t *bp = 0;
paddr64_t psc_base = MGT5200_MBAR_BASE + 0x2000;
uint16_t count;
uint8_t *dptr;
struct mserial_data *mdata;
mdata = (struct mserial_data *)data;
dptr = (uint8_t *)(mdata + 1);
if ( state == MDRIVER_INTR_ATTACH )
{
/* запрещение прерываний от последовательного порта */
mdata->psc->ISR_IMR = 0x0000;
/* очистка аппаратного буфера */
count = 0;
while ( mdata->psc->SR_CSR & PSC_SR_RxRDY )
{
bp = (uint8_t *)&mdata->psc->RB_TB;
dptr[mdata->data_len+count] = *bp;
count++;
}
mdata->last_count = count;
count = count + mdata->data_len;
mdata->data_len = count;
/* сброс ошибок */
if ( (mdata->psc->SR_CSR & PSC_SR_ORERR) || (mdata->psc->SR_CSR & PSC_SR_RB) ||
(mdata->psc->SR_CSR & PSC_SR_FE) || (mdata->psc->SR_CSR & PSC_SR_PE) )
{
mdata->psc->CR = PSC_CR_RESET_ERR;
count = mdata->errors + 1;
mdata->errors = count;
}
return (1);
} else if ( state == MDRIVER_INIT )
{
/* при первом вызове - инициализация оборудования и настройка данных */
mdata->psc = init_hw();
if ( mdata->psc == 0 )
return (1);
mdata->intr_calls = 0;
mdata->ncalls = 0;
mdata->errors = 0;
mdata->data_len = 0;
} else if ( state == MDRIVER_STARTUP_PREPARE )
{
/* по окончании запуска применяется функция callout_io_map */
if ( (mdata->psc_k = (psc_regs *)(callout_memory_map( 0xA0, psc_base, PROT_READ |
PROT_WRITE | PROT_NOCACHE ))) == 0 )
{
/* серьезная ошибка, отключение драйвера */
mdata->psc->ISR_IMR = 0x0000;
return (1);
}
}
/* подсчет количества вызовов драйвера */
count = mdata->ncalls + 1;
mdata->ncalls = count;
if ( state == MDRIVER_PROCESS )
{
/* вызывается при прерывании */
count = mdata->intr_calls + 1;
mdata->intr_calls = count;
}
count = 0;
while ( mdata->psc->SR_CSR & PSC_SR_RxRDY )
{
bp = (uint8_t *)&mdata->psc->RB_TB;
dptr[mdata->data_len+count] = *bp;
count++;
}
mdata->last_count = count;
count = count + mdata->data_len;
mdata->data_len = count;
/* сброс ошибок */
if ( (mdata->psc->SR_CSR & PSC_SR_ORERR) || (mdata->psc->SR_CSR & PSC_SR_RB) ||
(mdata->psc->SR_CSR & PSC_SR_FE) || (mdata->psc->SR_CSR & PSC_SR_PE) )
{
mdata->psc->CR = PSC_CR_RESET_ERR;
count = mdata->errors + 1;
mdata->errors = count;
}
if ( state == MDRIVER_STARTUP_FINI )
mdata->psc = mdata->psc_k;
return (0);
}

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

Драйверу необходим доступ к регистрам последовательного порта. В ходе инициализации функция startup_memory_map() отображает регистры, а полученный указатель сохраняется в области данных на этапе запуска. Функция startup_memory_map() отображает регистры. По окончании этапа запуска обработчик должен получить доступ к регистрам с помощью функции callout_memory_map().

В состоянии MDRIVER_INIT инициализируется область данных и настраивается последовательный порт; обработчик входит в это состояние только один раз. Здесь вызывается функция startup_memory_map(), которая отображает аппаратные регистры, а указатель сохраняется в области данных и используется для доступа к последовательному порту, когда обработчик вызывается с состоянием STARTUP_MDRIVER_PREPARE. На этом этапе можно вызвать функцию callout_memory_map() и сохранить указатель в области данных, однако следует по-прежнему использовать указатель, который возвращен функцией startup_memory_map(). После вызова с состоянием MDRIVER_STARTUP_FINI обработчик выполняет все операции над оборудованием с использованием указателя, который возвращен функцией callout_memory_map(). Этот указатель применяется во всех последующих вызовах обработчика минидрайвера.

В состоянии MDRIVER_INTR_ATTACH обработчик запрещает прерывание устройства и возвращает значение 1, запрашивая останов.

Добавление минидрайвера в систему

После написания функции-обработчика необходимо зарегистрировать ее в программе запуска и выделить требуемый объем оперативной памяти для области данных. Для этого используются следующие функции:

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, 65536, 1 ); /* получение 64 Кбайт */
mdriver_add( "mini-serial", 0, mini_serial, mdrvr_addr, 65536 );
...

В этом примере мы выделили 64 Кбайт памяти и зарегистрировали функцию-обработчика минидрайвера с именем mini-serial.

Сборка программы запуска

Минидрайвер готов, и теперь необходимо пересобрать код запуска и загрузочный образ. Чтобы проверить корректность работы минидрайвера, можно добавить отладочную информацию в функцию-обработчика или написать приложение, которое считывает область данных минидрайвера и отображает ее содержимое.

Тестирование минидрайвера

Плата Media5200b включает в себя один последовательный порт, через который минидрайвер получает данные. Для тестирования этого драйвера следует создать загрузочный образ, который настраивает протокол TCP/IP и позволяет подключаться к плате с помощью telnet. Поскольку последовательный порт используется в качестве источника данных, не следует запускать его драйвер из образа, а также выполнять отладочную печать и выводить подробную информацию в модуле procnto. Запишите созданный образ во встроенную флеш-память, чтобы загрузить из него систему.

Соедините плату Media5200b с главным компьютером с помощью нуль-модемного кабеля. Минидрайвер работает со скоростью 14400 бит/с без управления потоком передачи данных. Необходимо таким же образом настроить последовательный порт главного компьютера. Начните передавать символы в последовательный порт на главном компьютере:

...
uint8_t test_data[20] = {"012345678987654321"};
...
if ( (fd = open( "/dev/ser1", O_RDWR )) == -1 )
{
fprintf( stderr, "Unable to open device: %s (errno=%d\n", device, errno );
return (-1);
}
for ( ;; )
{
write( fd, test_data + index, 1 );
index++;
if ( index == 19 )
index = 0;
}

Этот код отправляет набор символов в последовательный порт, чтобы минидрайвер принял их.

Теперь загрузите плату Media5200b и подключитесь к ней по протоколу telnet.

После установления соединения запустите пример полнофункционального драйвера mdrvr-serpsc, который выполняет следующие действия:

Поскольку mdrvr-serpsc является примером драйвера последовательного порта, нет необходимости запускать драйвер devc-serpsc.

Временные характеристики работы драйвера

Эти временные характеристики указаны для загрузочного образа со следующими свойствами:

mdriver_max Количество вызовов Средний интервал между вызовами
1 Кбайт 855 514 мкс
4 Кбайт 231 2.04 мс
16 Кбайт 75 8.13 мс

Время загрузки Время первого вызова
453.2 мс 1 мс


Note: Время загрузки — интервал между запуском системы и началом работы первого приложения. Время первого вызова — интервал между запуском начального загрузчика (IPL) и первым вызовом минидрайвера (MDRIVER_INIT).

Минидрайвер для платы Renesas Biscayne

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

Особенности реализации драйвера:

Функция-обработчик минидрайвера

Прототип функции-обработчика минидрайвера имеет следующий вид:

int mdriver_handler( int state, void *data );

С помощью параметра state обработчик определяет, на каком этапе процесса загрузки он вызывается. См. описание функции mdriver_add() в главе Быстрая активация устройств настоящего руководства. Параметр data представляет собой виртуальный указатель на область данных, которая выделена минидрайверу. В этом примере драйвера исходный код функции-обработчика выглядит следующим образом:

struct mserial_data
{
uintptr_t port;
uintptr_t port_k;
uint16_t intr_calls;
uint16_t ncalls;
uint16_t errors;
uint16_t err;
uint16_t last_count;
uint16_t data_len;
};
void Delay( int n )
{
for ( unsigned int i = 0; i < n; i++ )
;
}
uintptr_t init_hw()
{
int cnt;
uint64_t base = SH_SCIF_BASE;
int baud = 14400;
int clk = 33333333;
unsigned port;
if ( (port = startup_io_map( 120, base )) == NULL )
return (NULL);
cnt = clk / (baud * 64 / 2) - 1; /* предполагается, что SCSMR1.CKS = 0 */
/* выбор часов */
out16( port + SH_SCIF_SCSCR_OFF, 0x0 );
Delay( 1 );
/* включение состояния сброса регистра FIFO */
out16( port + SH_SCIF_SCFCR_OFF, SH_SCIF_SCFCR_TFRST | SH_SCIF_SCFCR_RFRST );
/*
* формат передачи данных
* 8 бит, без контроля четности
* CKS = 0
*/
out16( port + SH_SCIF_SCSMR_OFF, 0 );
Delay( 1 );
/* скорость передачи */
out8( port + SH_SCIF_SCBRR_OFF, cnt );
Delay( 1 );
/* отключение состояния сброса FIFO */
set_port16( port + SH_SCIF_SCFCR_OFF, SH_SCIF_SCFCR_TFRST | SH_SCIF_SCFCR_RFRST, 0 );
/* управление FIFO */
out16( port + SH_SCIF_SCFCR_OFF + 4, 0x0 );
Delay( 1 );
out8( port + SH_SCIF_SCFTDR_OFF, 0 );
Delay( 1 );
out16( port + SH_SCIF_SCSPTR_OFF + 4, 0x41 );
out16( port + SH_SCIF_SCFCR_OFF, 0x30 | 0x200 | SH_SCIF_SCFCR_MCE );
out16( port + SH_SCIF_SCSCR_OFF, SH_SCIF_SCSCR_RE | SH_SCIF_SCSCR_TE |
SH_SCIF_SCSCR_TIE | SH_SCIF_SCSCR_RIE );
Delay( 1 );
out16( port + SH7760_SCIF_SCSPTR_OFF, SH_SCIF_SCSPTR_RTSIO );
out16( port + SH_SCIF_SCSCR_OFF, SH_SCIF_SCSCR_RE | SH_SCIF_SCSCR_RIE );
return (port);
}
int mini_serial( int state, void *data )
{
uint8_t *bp = 0;
uint16_t count;
uint8_t *dptr, c;
struct mserial_data *mdata;
int num, i;
int pending_interrupts;
mdata = (struct mserial_data *)data;
dptr = (uint8_t *)(mdata + 1);
if ( state == MDRIVER_INTR_ATTACH )
{
/* запрещение прерываний от последовательного порта */
set_port16( mdata->port + SH_SCIF_SCSCR_OFF, SH_SCIF_SCSCR_RE | SH_SCIF_SCSCR_RIE, 0 );
return (1);
} else if ( state == MDRIVER_INIT )
{
/* При первом вызове - инициализация оборудования и настройка данных */
mdata->port = init_hw();
if ( mdata->port == 0 )
return (1);
mdata->intr_calls = 0;
mdata->ncalls = 0;
mdata->errors = 0;
mdata->data_len = 0;
} else if ( state == MDRIVER_STARTUP_PREPARE )
{
/* по окончании запуска применяется функция callout_io_map */
if ( (mdata->port_k = callout_io_map( 0x120, SH_SCIF_BASE )) == 0 )
{
/* серьезная ошибка, отключение драйвера */
set_port16( mdata->port + SH_SCIF_SCSCR_OFF, SH_SCIF_SCSCR_RE | SH_SCIF_SCSCR_RIE, 0 );
return ( 1 );
}
}
/* подсчет количества вызовов драйвера */
count = mdata->ncalls + 1;
mdata->ncalls = count;
if ( state == MDRIVER_PROCESS )
{
/* вызывается при прерывании */
count = mdata->intr_calls + 1;
mdata->intr_calls = count;
}
/* считывание данных из последовательного порта и сброс всех ошибок */
/* обработка сообщений об ошибках: переполнение, синхронизация, четность */
pending_interrupts = in16( mdata->port + SH_SCIF_SCFSR_OFF );
if ( in8( mdata->port + SH7760_SCIF_SCLSR_OFF ) & SH_SCIF_SCLSR_ORER )
{
set_port16( mdata->port + SH7760_SCIF_SCLSR_OFF, SH_SCIF_SCLSR_ORER, 0 );
}
if ( pending_interrupts & SH_SCIF_SCFSR_PER )
{
set_port16( mdata->port + SH_SCIF_SCFSR_OFF, SH_SCIF_SCFSR_PER | SH_SCIF_SCFSR_ER |
SH_SCIF_SCFSR_DR, 0 );
}
if ( pending_interrupts & SH_SCIF_SCFSR_FER )
{
set_port16( mdata->port + SH_SCIF_SCFSR_OFF, SH_SCIF_SCFSR_FER | SH_SCIF_SCFSR_ER |
SH_SCIF_SCFSR_DR, 0 );
}
if ( pending_interrupts & SH_SCIF_SCFSR_BRK )
{
set_port16( mdata->port + SH_SCIF_SCFSR_OFF, SH_SCIF_SCFSR_BRK | SH_SCIF_SCFSR_DR, 0 );
}
/* считывание данных */
if ( in16( mdata->port + SH7760_SCIF_SCRFDR_OFF ) )
{
c = in8( mdata->port + SH_SCIF_SCFRDR_OFF );
set_port16( mdata->port + SH_SCIF_SCFSR_OFF, SH_SCIF_SCFSR_RDF, 0 );
dptr[mdata->data_len] = c;
mdata->data_len = mdata->data_len + 1;
}
if ( pending_interrupts & (SH_SCIF_SCFSR_TDFE|SH_SCIF_SCFSR_TEND) )
{
set_port16( mdata->port + SH_SCIF_SCSCR_OFF, SH_SCIF_SCSCR_TIE, 0 );
}
/* сброс всех прерываний */
set_port16( mdata->port + SH7760_SCIF_SCLSR_OFF, SH_SCIF_SCLSR_ORER, 0 );
set_port16( mdata->port + SH_SCIF_SCFSR_OFF, 0xff, 0 );
if ( state == MDRIVER_STARTUP_FINI )
mdata->port = mdata->port_k;
return (0);
}

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

В состоянии MDRIVER_INIT инициализируется область данных и настраивается последовательный порт; обработчик входит в это состояние только один раз. Здесь вызывается функция startup_memory_map(), которая отображает аппаратные регистры, а указатель сохраняется в области данных и используется для доступа к последовательному порту, когда обработчик вызывается с состоянием STARTUP_MDRIVER_PREPARE.

На этом этапе также вызывается функция callout_io_map() и указатель сохраняется в области данных, однако следует по-прежнему использовать указатель, который возвращен функцией startup_io_map(). После вызова с состоянием MDRIVER_STARTUP_FINI обработчик выполняет все операции над оборудованием с использованием указателя, который возвращен функцией callout_io_map(). Этот указатель применяется во всех последующих вызовах обработчика минидрайвера.

В состоянии MDRIVER_INTR_ATTACH обработчик запрещает прерывание устройства и возвращает значение 1, запрашивая останов.

Добавление минидрайвера в систему

После написания функции-обработчика необходимо зарегистрировать ее в программе запуска и выделить требуемый объем оперативной памяти для области данных. Для этого используются следующие функции:

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, 65536, 1 ); /* grab 64k */
mdriver_add( "mini-serial", 0, mini_serial, mdrvr_addr, 65536 );
...

В этом примере мы выделили 64 Кбайт памяти и зарегистрировали функцию-обработчика минидрайвера с именем mini-serial.

Сборка программы запуска

Минидрайвер готов, и теперь необходимо пересобрать программу запуска и загрузочный образ. Чтобы проверить корректность работы минидрайвера, можно добавить отладочную информацию в функцию-обработчика или написать приложение, которое считывает область данных минидрайвера и отображает ее содержимое.

Тестирование минидрайвера

Плата Biscayne включает в себя отладочный последовательный порт, через который минидрайвер получает данные. Для тестирования этого драйвера следует создать загрузочный образ, который настраивает протокол TCP/IP и позволяет подключаться к плате с помощью telnet. Поскольку последовательный порт используется в качестве источника данных, не следует запускать его драйвер из образа, а также выполнять отладочную печать и выводить подробную информацию в модуле procnto. Запишите созданный образ во встроенную флеш-память, чтобы загрузить из него систему.

Соедините плату Biscayne с главным компьютером с помощью нуль-модемного кабеля. Поскольку минидрайвер работает на скорости передачи 14400 бит/с, необходимо таким же образом настроить последовательный порт главного компьютера. Начните передавать символы в последовательный порт на главном компьютере:

...
uint8_t test_data[20] = {"012345678987654321"};
...
if ( (fd = open( "/dev/ser1", O_RDWR )) == -1 )
{
fprintf( stderr, "Unable to open device: %s (errno=%d\n", device, errno );
return (-1);
}
for ( ;; )
{
write( fd, test_data + index, 1 );
index++;
if ( index == 19 )
index = 0;
}

Этот код отправляет набор символов в последовательный порт, чтобы минидрайвер принял их.

Теперь загрузите плату Biscayne и подключитесь к ней по протоколу telnet. После установления соединения запустите пример полнофункционального драйвера mdrvr-sescif, который выполняет следующие действия:

Поскольку mdrvr-serscif является примером драйвера последовательного порта, нет необходимости запускать драйвер devc-sersci.

Временные характеристики работы драйвера

Эти временные характеристики указаны для загрузочного образа со следующими свойствами:

mdriver_max Количество вызовов Средний интервал между вызовами
1 Кбайт 715 59 мкс
4 Кбайт 139 194 мкс
16 Кбайт 65 934 мкс

Время загрузки время первого вызова
636 мс 1 мс


Note: Время загрузки — интервал между запуском системы и началом работы первого приложения. Время первого вызова — интервал между запуском начального загрузчика (IPL) и первым вызовом минидрайвера (MDRIVER_INIT).

Минидрайвер OMAP

В этом примере минидрайвер для платы OMAP 5912 инициализирует последовательный порт с определенной скоростью передачи и сохраняет символы в области данных. По окончании загрузки системы программа может считывать область данных и извлекать из нее буферизованные символы. Особенности реализации драйвера:

Прототип функции-обработчика минидрайвера имеет следующий вид:

int mdriver_handler( int state, void *data );

С помощью параметра state обработчик определяет, на каком этапе процесса загрузки он вызывается. См. описание функции mdriver_add() в главе Быстрая активация устройств настоящего руководства. Параметр data представляет собой виртуальный указатель на область данных, которая выделена минидрайверу. В этом примере драйвера исходный код функции-обработчика выглядит следующим образом:

struct mserial_data
{
uintptr_t port;
uintptr_t port_k;
uint16_t intr_calls;
uint16_t ncalls;
uint16_t errors;
uint16_t err;
uint16_t last_count;
uint16_t data_len;
};
#ifndef write_omap
#define write_omap( __port,__val ) out8( __port, __val )
#endif
#ifndef read_omap
#define read_omap( __port ) in8( __port )
#endif
/*
* инициализация интерфейса UART
* базовый адрес = 0xfffb0000
* скорость передачи = 14400
* данные = 8 n 1
*/
uintptr_t init_hw()
{
unsigned value = 0;
unsigned msr, c;
uintptr_t port;
uint64_t base = 0xfffb0000;
int baud = 14400;
if ( (port = startup_io_map( OMAP_UART_SIZE, base )) == 0 )
return (0);
/* установка в LCR специального байта, который разрешает доступ к регистру
* расширенных функций (Enhanced Feature Register, EFR) */
write_omap( port + OMAP_UART_LCR, 0xbf );
/* отключение программного управления потоком передачи, включение записи в
* MCR[7:5], FCR[5:4] и IER[7:4] */
write_omap( port + OMAP_UART_EFR, 0x10 );
write_omap( port + OMAP_UART_LCR, 0 );
/* установка бита 6 регистра MCR для разрешения доступа к регистрам TCR и TLR */
write_omap( port + OMAP_UART_MCR, 0x40 );
value = 0x0;
/* настройка приемного FIFO-буфера в TCR - прием начинается с позиции 0 и завершается
* в позиции, соответствующей размеру буфера */
write_omap( port + OMAP_UART_TCR,value >> 4 );
write_omap( port + OMAP_UART_TLR, value );
/* запрет доступа к TCR и TLR */
write_omap( port + OMAP_UART_MCR, 0x00 );
write_omap( port + OMAP_UART_SCR, 0x00 );
write_omap( port + OMAP_UART_LCR, 0xbf );
/* запрет доступа к MCR[7:5], FCR[5:4] и IER[7:4],
* отключение автоматического и программного управления потоком передачи */
write_omap( port + OMAP_UART_EFR, 0x00 );
/* включение защелки делителя - при записи в регистр LCR значения,
* отличного от 0xbf, он возвращается в "обычный" режим */
write_omap( port + OMAP_UART_LCR, 0x80 );
/* baud and clk */
value = 48000000 / (16 * baud);
write_omap( port + OMAP_UART_DLL, value );
write_omap( port + OMAP_UART_DLH, (value >> 8) & 0xff );
write_omap( port + OMAP_UART_LCR, 0x13 );
/* включение DTR, RTS */
c = read_omap( port + OMAP_UART_MCR );
write_omap( port + OMAP_UART_MCR, (c & ~OMAP_MCR_DTR | OMAP_MCR_RTS) |
OMAP_MCR_DTR | OMAP_MCR_RTS );
/* в соответствии с Национальным перечнем ошибок необходимо ждать
* опустошения регистра хранения передаваемых данных */
do {
} while ((read_omap( port + OMAP_UART_LSR ) & OMAP_LSR_TXRDY) == 0);
/* сброс устройства, чтобы получить перепад уровня на линии прерывания шины
* включение out2 (передача прерывания на шину) */
c = read_omap( port + OMAP_UART_MCR );
write_omap( port + OMAP_UART_MCR, (c & ~OMAP_MCR_OUT2) | OMAP_MCR_OUT2 );
write_omap( port + OMAP_UART_IER, 0 ); /* запрещение всех прерываний */
read_omap( port + OMAP_UART_LSR ); /* сброс прерывания состояния линии */
read_omap( port + OMAP_UART_RHR ); /* сброс прерывания приема */
read_omap( port + OMAP_UART_THR ); /* сброс прерывания передачи */
read_omap( port + OMAP_UART_MSR ); /* сброс прерывания модема */
/* разрешение источников прерываний */
write_omap( port + OMAP_UART_IER, 0x01 );
/* считывание текущего состояния MSR */
msr = read_omap( port + OMAP_UART_MSR );
return (port);
}
int mini_serial( int state, void *data )
{
uint16_t count;
uint8_t *dptr, c;
struct mserial_data *mdata;
unsigned lsr, iir;
mdata = (struct mserial_data *)data;
dptr = (uint8_t *)(mdata + 1);
if ( state == MDRIVER_INTR_ATTACH )
{
/* запрещение прерываний от последовательного порта */
write_omap( mdata->port + OMAP_UART_IER, 0x00 );
return (1);
} else if ( state == MDRIVER_INIT )
{
if ( (mdata->port = init_hw()) == 0 )
return (1);
/* сброс счетчиков области данных */
mdata->intr_calls = 0;
mdata->ncalls = 0;
mdata->errors = 0;
mdata->data_len = 0;
} else if ( state == MDRIVER_STARTUP_PREPARE )
{
/* по окончании запуска применяется функция callout_io_map */
if ( (mdata->port_k = callout_io_map( OMAP_UART_SIZE, 0xfffb0000 )) == 0 )
{
/* серьезная ошибка, отключение драйвера */
write_omap( mdata->port + OMAP_UART_IER, 0x00 );
return (1);
}
}
/* подсчет количества вызовов драйвера */
count = mdata->ncalls + 1;
mdata->ncalls = count;
if ( state == MDRIVER_PROCESS )
{
/* вызывается при прерывании */
count = mdata->intr_calls + 1;
mdata->intr_calls = count;
iir = read_omap( mdata->port + OMAP_UART_IIR ) & 0x07;
if ( iir == OMAP_II_RX ) /* прерывание приема */
{
do {
dptr[mdata->data_len] = read_omap( mdata->port + OMAP_UART_RHR ) & 0xff;
mdata->data_len = mdata->data_len + 1;
lsr = read_omap( mdata->port + OMAP_UART_LSR );
/* проверка ошибок */
if ( (lsr & (OMAP_LSR_RCV_FIFO | OMAP_LSR_BI | OMAP_LSR_OE |
OMAP_LSR_FE | OMAP_LSR_PE)) != 0 )
{
/* считывание любых входных данных, которые находятся в буфере, чтобы "проглотить"
* побочные данные, связанные с состоянием break, ошибкой четности и др. */
c = read_omap( mdata->port + OMAP_UART_RHR );
c = c;
}
} while (lsr & OMAP_LSR_RXRDY);
}
} else {
/* опрос данных */
while ( read_omap( mdata->port + OMAP_UART_LSR ) & OMAP_LSR_RXRDY )
{
dptr[mdata->data_len] = read_omap( mdata->port + OMAP_UART_RHR ) & 0xff;
mdata->data_len = mdata->data_len + 1;
}
}
if ( state == MDRIVER_STARTUP_FINI )
mdata->port = mdata->port_k;
return (0);
}

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

В состоянии MDRIVER_INIT инициализируется область данных и настраивается последовательный порт; обработчик входит в это состояние только один раз. Здесь вызывается функция startup_io_map(), которая отображает аппаратные регистры, а указатель сохраняется в области данных и используется для доступа к последовательному порту, когда обработчик вызывается с состоянием STARTUP_MDRIVER_PREPARE.

Здесь вызывается функция callout_io_map() и указатель сохраняется в области данных, однако следует по-прежнему использовать указатель, который возвращен функцией startup_io_map(). После вызова с состоянием MDRIVER_STARTUP_FINI обработчик выполняет все операции над оборудованием с использованием указателя, который возвращен функцией callout_io_map().

Этот указатель применяется во всех последующих вызовах обработчика минидрайвера. В состоянии MDRIVER_INTR_ATTACH обработчик запрещает прерывание устройства и возвращает значение 1, запрашивая останов.

Добавление минидрайвера в систему

После написания функции-обработчика необходимо зарегистрировать ее в программе запуска и выделить требуемый объем оперативной памяти для области данных. Для этого используются следующие функции:

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_intrinfo();
init_qtime();
/* выделение минидрайверу 64 Кбайт оперативной памяти */
mdrvr_addr = alloc_ram( ~0L, 64*1024, 1 );
/* добавление примера минидрайвера, который только собирает данные */
mdriver_add( "mini-data", 0, mini_data, mdrvr_addr, 64 * 1024 );
/* добавление примера минидрайвера последовательного порта */
/* mdriver_add( "mini-serial", 46, mini_serial, mdrvr_addr, 64 * 1024 ); */
...

В этом примере мы выделили 64 Кбайт памяти и зарегистрировали функцию-обработчика минидрайвера с именем mini-serial.

Сборка программы запуска

Минидрайвер готов, и теперь необходимо пересобрать программу запуска и загрузочный образ. Чтобы проверить корректность работы минидрайвера, можно добавить отладочную информацию в функцию-обработчика или написать приложение, которое считывает область данных минидрайвера и отображает ее содержимое.

Тестирование минидрайвера

Плата OMAP 5912 включает в себя отладочный последовательный порт, через который минидрайвер получает данные. Для тестирования этого драйвера следует создать загрузочный образ, который настраивает протокол TCP/IP и позволяет подключаться к плате с помощью telnet. Поскольку последовательный порт используется в качестве источника данных, не следует запускать его драйвер из образа, а также выполнять отладочную печать и выводить подробную информацию в модуле procnto. Запишите созданный образ во встроенную флеш-память, чтобы загрузить из него систему.

Соедините плату OMAP с главным компьютером с помощью нуль-модемного кабеля. Поскольку минидрайвер работает на скорости передачи 14400 бит/с, необходимо настроить последовательный порт главного компьютера аналогичным образом. Начните передавать символы в последовательный порт на главном компьютере:

...
uint8_t test_data[20] = {"012345678987654321"};
...
if ( (fd = open( "/dev/ser1", O_RDWR )) == -1 )
{
fprintf( stderr, "Unable to open device: %s (errno=%d\n", device, errno );
return (-1);
}
for ( ;; )
{
write( fd, test_data + index, 1 );
index++;
if ( index == 19 )
index = 0;
}

Этот код отправляет набор символов в последовательный порт, чтобы минидрайвер принял их.

Теперь загрузите плату OMAP 5912 и подключитесь к ней по протоколу telnet. После установления соединения запустите пример полнофункционального драйвера mdrvr-seromap, который выполняет следующие действия:

Поскольку mdrvr-seromap является примером драйвера последовательного порта, нет необходимости запускать драйвер devc-seromap.

Временные характеристики работы драйвера

Эти временные характеристики указаны для загрузочного образа со следующими свойствами:

mdriver_max Количество вызовов Средний интервал между вызовами
1 Кбайт 782 93 мкс
4 Кбайт 229 352 мкс
16 Кбайт 91 1.38 мс

Время загрузки Время первого вызова
170 мс 1 мс


Note: Время загрузки — интервал между запуском системы и началом работы первого приложения. Время первого вызова — интервал между запуском начального загрузчика (IPL) и первым вызовом минидрайвера (MDRIVER_INIT).




Предыдущий раздел: перейти