IDE обращение к диску через порты
За ops->read в слое Storage сидел настоящий драйвер железа и IDE это первый из двух. Он самый старый, самый простой и самый медленный, но работает практически везде и именно поэтому он запасной вариант когда нет ничего современнее. Здесь мы впервые говорим с железом напрямую.
Что такое IDE
IDE, точнее Parallel ATA (PATA), это способ, при котором процессор говорит с контроллером диска напрямую через порты I/O. Без посредника-драйвера, без очередей, без ускорения. Процессор пишет номер сектора в несколько регистров, даёт команду, а потом сам забирает данные слово за словом. Это "сам забирает" зовётся PIO, Programmed I/O и проходит красной нитью через эту статью.
Порты I/O
К контроллеру IDE обращаются через восемь подряд идущих портов, плюс управляющий порт чуть в стороне. У каждого своя задача. Вот как адресуют сектор в режиме LBA28:
void pata_select_sector_lba28(uint16_t base_port, uint32_t sector, uint8_t n) {
outb(base_port + 2, n); // sector count
outb(base_port + 3, sector & 0xFF); // LBA биты 0.. 7
outb(base_port + 4, (sector >> 8) & 0xFF); // LBA биты 8..15
outb(base_port + 5, (sector >> 16) & 0xFF); // LBA биты 16..23
uint8_t dev = inb(base_port + 6) & 0x10; // сохранить бит выбора привода
dev |= 0xE0 | ((sector >> 24) & 0x0F); // режим LBA + LBA биты 24..27
outb(base_port + 6, dev);
}
Здесь видны две вещи. Во-первых, номер сектора разложен по нескольким регистрам, ведь каждый вмещает лишь байт. Во-вторых, у регистра +6 надо сохранить бит выбора привода, ведь master и slave делят один контроллер и одни порты. Кто здесь будет неосторожен, вдруг начнет говорить не с тем диском :-) .
Порт +7 особый - запись в него, это команда, чтение - это регистр статуса. А управляющий порт +0x206 отдаёт тот же статус ещё раз но без побочного эффекта, который станет важен через миг.
Как происходит чтение
Полное чтение - адресовать, дать команду, а потом каждый сектор подождать готовности и забрать данные.
uint8_t pata_read(uint16_t base_port, uint64_t sector, uint8_t n, uint8_t* memory) {
uint16_t* mem = (uint16_t*)memory;
if (sector < 0x10000000) { // < 137 ГБ → хватает LBA28
pata_select_sector_lba28(base_port, sector, n);
outb(base_port + 7, 0x20); // READ SECTORS (LBA28)
} else {
pata_select_sector_lba48(base_port, sector, n);
outb(base_port + 7, 0x24); // READ SECTORS EXT (LBA48)
}
for (int i = 0; i < n; i++) {
if (!ide_wait_for_data(base_port, IDE_DATA_WAIT_VERY_LONG_MS))
return false; // ждать, пока данные готовы
for (uint32_t j = 0; j < 256; j++)
*mem++ = inw(base_port + 0); // 256 слов = 512 байт = один сектор
}
return true;
}
Внутренний цикл это сердце PIO и причина почему IDE медленный: процессор сам читает каждое из 256 слов сектора из регистра данных и кладёт в память. Нет DMA, который дал бы диску писать в RAM самому, процессор всё время занят переноской слов. При LBA28 хватает 28-битного номера сектора (до 137 ГБ), для больших дисков есть LBA48 с другими номерами команд. Запись зеркальна: команда 0x30 или 0x34, потом 256× outw на сектор, и в конце сброс кэша (0xE7), который заставляет диск действительно записать, а не держать данные лишь в своём буфере.
Читаем регистр статуса
Нет прерывания, которое крикнет "готово", во всяком случае мой драйвер его не использует. Вместо этого он спрашивает регистр статуса, пока не придет статус "данные готовы". Порядок здесь важен.
uint8_t status = inb(base_port + 7);
if (status & ATA_SR_BSY) continue; // 1. ещё занят? ждать дальше
if (status & (ATA_SR_ERR | ATA_SR_DF)) return false; // 2. ошибка?
if (status & ATA_SR_DRQ) return true; // 3. данные готовы!
Пока бит BSY установлен, все прочие биты ненадёжны, поэтому его ждут первым. Лишь когда он падает, можно проверить ошибки и проверка ошибки идёт до проверки данных, ведь хочется знать, не пошло ли что-то не так, прежде чем забирать данные. У всего цикла есть тайм-аут, чтобы проблемный диск не висел вечно.
Выбор привода и поучительный deadlock
Поскольку master и slave делят контроллер, перед каждым доступом надо сказать, какой имеется в виду. И именно здесь завелся ЖУК (один из багов :-) ).
void ide_select_drive(uint16_t base_port, uint8_t drive) {
ide_wait_drive_not_busy(base_port, ...);
outb(base_port + 6, drive); // 0xE0 = master, 0xF0 = slave
// задержка 400нс, НЕ зависит от тика: прочитать alternate status 4 раза
// (control block +0x206 — без побочного эффекта IRQ, как у +7)
for (int i = 0; i < 4; i++) (void)inb(base_port + 0x206);
}
После смены привода контроллеру нужно около 400 наносекунд, прежде чем его статус станет надёжен. Раньше код просто ждал для этого timer_wait_ms(10). Звучит безобидно, но этот таймер висит на тике процессора, а тик поднимается лишь прерыванием таймера. Если тик вдруг вставал, скажем, из-за потерянного IRQ или из-за того, что второе ядро ещё не запустило свой таймер, тогда это ожидание под захваченной блокировкой файловой системы уходило в бесконечный цикл. Блокировка не освобождалась никогда и полсистемы зависало. Фикс мал и красив - вместо ожидания тика читаешь регистр alternate status четыре раза. Это само по себе около 400 нс, не висит на тике и не вызывает прерывания, ведь управляющий порт +0x206, в отличие от обычного порта статуса +7 не квитирует ожидающий IRQ.
ATAPI для оптических приводов
CD и DVD часто висят на том же контроллере IDE но говорят на другом языке ATAPI, то есть команды SCSI, завёрнутые в передачи ATA. Вместо номера сектора в регистрах здесь сперва шлют команду PACKET (0xA0 на +7), а затем двенадцатибайтовый пакет SCSI через регистр данных, например 0xA8 для чтения. Сектор на данных CD это 2048 байт вместо 512. По сути та же игра с портами, только с командой SCSI как промежуточным слоем.
Честнее честного
IDE честно говоря, устарел. PIO занимает процессор, опрос ест вычислительное время, а deadlock с timer_wait показал насколько коварным может стать даже крошечное wait, когда оно сходится с блокировкой и вставшим тиком. Но у IDE есть бесценное достоинство - он прост и работает везде, даже в свежезагруженной VM, где ещё ничего не настроено. Поэтому он остаётся надёжным запасным вариантом, пока настоящую работу делает старший брат. И еще, CDDA и ISO9660 я до ума не довел, столько всего интересного, а я с этим по большому счету никому не нужному возиться буду. Но если станет скучно- допилю
Что дальше
Этот более быстрый сосед чем наш IDE, AHCI, современный драйвер SATA. Вместо того чтобы нести каждое слово самому, он устраивает диску списки команд в памяти и даёт ему указание писать прямо туда через DMA. Процессор отправляет запрос и свободен, пока диск не сообщит, что закончил. Это последний из двух драйверов железа и следующая часть.
Было бы интересно увидеть ваши комментарии и улучшить статьи.
◀ Предыдущая статья Содержание Следующая статья ▶
*Система не стоит на месте, поэтому в дальнейшем тексты могут не совпадать с реальным положением