Архитектура драйвера
Драйвер, таким образом, состоит из основной нити, обработчика прерывания и, возможно, одной или нескольких высокоприоритетных нитей, создаваемых обработчиком. Все эти нити совместно (и, как правило, гарантируя взаимное исключение) исполняют более или менее сложный конечный автомат, состояния которого соответствуют этапам выполнения очередного запроса к устройству.
Как правило, первое состояние автомата обрабатывается основной нитью драйвера, а последующие — обработчиком прерываний. В финальном состоянии автомата мы сообщаем процессу, породившему запрос, что запрос отработан. В зависимости от протокола взаимодействия этого процесса основная нить драйвера может отправлять такое сообщение как fork-процессом, так и пробуждением основной нити.
Драйвер IDE/ATA для Linux
В примере 10.5 приведена основная функция обработки запроса и функция обработки прерывания, используемая при записи нескольких секторов. Обе эти функции вызываются драйвером контроллера IDE/ATA, который представляет собой диспетчер запросов к подключённым к контроллеру устройствам.
Структура *hwgroup
представляет собой блок переменных состояний контроллера устройства. Эта структура также содержит указатель на текущий запрос к устройству. Информации, содержащейся в этих структурах, достаточно, чтобы очередная функция конечного автомата драйвера узнала всё необходимое для выполнения очередного этапа запроса. В данном случае конечный автомат весьма прост и состоит из многократного вызова функции ide_multiwrite
, копирующей в контроллер очередной блок данных. Условием завершения автомата служат ошибка контроллера либо завершение запроса. Функции ide_dma_read
, ide_dma_write
, ide_read
и ide_write
, исполняемые машиной состояний при обработке других запросов, не приводятся.
Пример 10.5. Фрагменты драйвера диска IDE/ATA ОС Linux 2.2, перевод комментариев автора
/*
* ide_multwrite() передаёт приводу блок из не более, чем mcount
* секторов как часть многосекторной операции записи.
* Возвращает 0 при успехе.
* Обратите внимание, что мы можем быть вызваны из двух контекстов -
* контекста do_rw и контекста IRQ. IRQ (Interrupt Request,
* запрос прерывания) может произойти в любой
* момент после того, как мы выведем полное количество секторов,
* поэтому мы должны обновлять состояние _до_ того, как мы выведем
* последнюю часть данных!
*/
int ide_multwrite (ide_drive__t *drive, unsigned int mcount) {
ide_hwgroup_t *hwgroup = HWGROUP(drive);
struct request *rq = &hwgroup->wrq;
do {
char *buffer;
int nsect = rq->current_nr_sectors;
if (nsect > mcount)
nsect = mcount;
mcount -= nsect;
buffer = rq->buffer;
rq->sector += nsect;
rq->buffer += nsect << 9;
rq->nr_sectors -= nsect;
rq->current_nr_sectors -= nsect;
/* Переходим ли мы к следующему bh после этого? */
if (!rq->current_nr_sectors) {
struct buffer_head *bh = rq->bh->b_reqnext;
/* Завершиться, если у нас кончились запросы */
if (!bh) {
mcount = 0;
} else {
rq->bh = bh;
rq->current_nr_sectors = bh->b_size >> 9;
rq->buffer = bh->b_data;
}
}
/*
* Теперь мы все настроили, чтобы прерывание
* снова вызвало нас после последней передачи.
*/
idedisk_output_data(drive, buffer, nsect << 7);
} while (mcount);
return 0;
}
/*
* multwrite_intr() — обработчик прерывания многосекторной записи
*/
static ide_startstop_t multwrite_intr (ide_drive_t *drive) {
byte stat;
int i;
ide_hwgroup_t *hwgroup = HWGROUP(drive);
struct request *rq = &hwgroup->wrq;
if (OK_STAT(stat=GET_STAT(), DRIVE_READY, drive->bad_wstat)) {
if (stat & DRQ_STAT) {
/*
* Привод требует данных. Помним, что rq -
* копия запроса.
*/
if (rq->nr_sectors) {
if (ide_multwrite(drive, drive->mult_count))
return ide_stopped;
ide_set_handler (drive, &multwrite_intr, WAIT_CMD, NULL);
return ide_started;
}
} else {
/*
* Если копирование всех блоков завершилось,
* мы можем завершить исходный запрос.
*/
if (!rq->nr_sectors) { /* all done? */
rq = hwgroup->rq;
for (i = rq->nr_sectors; i > 0;) {
i -= rq->current_nr_sectors;
ide_end_request(1, hwgroup);
}
return ide_stopped;
}
}
}
return ide_errcr(drive, "multwrite_intr", stat);
}
/*
* do_rw_disk() передаёт команды READ и WRITE приводу,
* используя LBA, если поддерживается, или CHS, если нет, для адресации
* секторов. Функция do_rw_disk также передаёт специальные запросы.
*/
static ide_startstop_t do_rw_disk (ide_drive_t *drive, struct request *rq, unsigned long block) {
if (IDE_CONTROL_REG) {
OUT_BYTE (drive->ctl, IDE_CONTROL_REG);
OUT_BYTE (rq->nr_sectors, IDE_NSECTOR_REG);
if (drive->select.b.lba) {
OUT_BYTE (block, IDE_SECTOR_REG);
OUT_BYTE (block >>= 8, IDE_LCYL_REG);
OUT_BYTE (block >>= 8, IDE_HCYL_REG);
OUT_BYTE(((block >> 8) & 0x0f) | drive->select.all, IDE_SELECT_REG);
} else {
unsigned int sect, head, cyl, track;
track = block / drive->sect;
sect = block % drive->sect + 1;
OUT_BYTE (sect, IDE_SECTOR_REG);
head = track % drive->head;
cyl = track / drive->head;
OUT_BYTE (cyl, IDE_LCYL_REG);
OUT_BYTE (cyl >> 8, IDE_HCYL_REG);
OUT_BYTE (head | drive->select.all, IDE_SELECT_REG);
if (rq->cmd == READ) {
#ifdef CONFIG_BLK_DEV_IDEDMA
if (drive->using_dma && !(HWIF(drive)->dmaproc(ide_dma_read, drive)))
return ide_started;
#endif /* CONFIG_BLK_DEV_IDEDMA */
ide_set_handler(drive, iread_intr, WAIT_CMD, NULL);
OUT_BYTE(drive->mult_count ? WIN_MULTREAD : WIN_READ, IDE_COMMAND_REG);
return ide_started;
}
if (rq->cmd == WRITE) {
ide_startstop_t startstop;
#ifdef CONFIG_BLK_DEV_IDEDMA
if (drive->using_dma && !(HWIF(drive)->dmaproc(ide_dma_write, drive)))
return ide_started;
#endif /* CONFIG_BLK_DEV_IDEDMA */
OUT_BYTE(drive->mult_count ? WIN_MULTWRITE : WIN_WRITE, IDE_COMMAND_REG);
if (ide_wait_stat(startstop, drive, DATA_READY, drive->bad_wstat, WAIT_DRQ)) {
printk(KERN_ERR "%s: no DRQ after issuing %s\n", drive->name, drive->mult_count ? "MULTWRITE" : "WRITE");
return startstop;
}
if (!drive->unmask)
__cli(); /* только локальное ЦПУ */
if (drive->mult_count) {
ide_hwgroup_t *hwgroup = HWGROUP(drive);
/*
* Эта часть выглядит некрасиво, потому что мы ДОЛЖНЫ установить
* обработчик перед выводом первого блока данных.
* Если мы обнаруживаем ошибку (испорченный список буферов)
* в ide_multwrite(),
* нам необходимо удалить обработчик и таймер перед возвратом.
* К счастью, это НИКОГДА не происходит (правильно?).
* Кажется, кроме случаев, когда мы получаем ошибку...
*/
hwgroup->wrq = *rq; /* scratchpad */
ide_set_handler(drive, &multwrite_intr, WAIT_CMD, NULL);
if (ide_multwrite(drive, drive->mult_count)) {
unsigned long flags;
spin_lock_irqsave(&io_request_lock, flags);
hwgroup->handler = NULL;
del_timer(&hwgroup->timer);
spin_unlock_irqrestore(&io_request_lock, flags);
return ide_stopped;
} else {
ide_set_handler(drive, &write_intr, WAIT_CMD, NULL);
idedisk_output_data(drive, rq->buffer, SECTOR_WORDS);
}
}
return ide_started;
} else {
printk(KERN_ERR "%s: bad command: %d\n", drive->name, rq->cmd);
ide_end_request(0, HWGROUP(drive));
return ide_stopped;
}
}
}