• 🔥 Бесплатный курс от Академии Кодебай: «Анализ защищенности веб-приложений»

    🛡 Научитесь находить и использовать уязвимости веб-приложений.
    🧠 Изучите SQLi, XSS, CSRF, IDOR и другие типовые атаки на практике.
    🧪 Погрузитесь в реальные лаборатории и взломайте свой первый сайт!
    🚀 Подходит новичкам — никаких сложных предварительных знаний не требуется.

    Доступ открыт прямо сейчас Записаться бесплатно

Статья ASM. Программирование ОС [4] – Жёсткие диски: SATA, AHCI, NVMe

LogoOsDev.webp

Как упоминалось в предыдущей части, эта будет полностью посвящена работе с устройствами «Mass-Storage Device» (дословно устройства массового хранения данных). Основную ставку сделаем на современный режим контроллёра AHCI (Advanced Host Controller Interface), который в русско-язычном сегменте сети не освящён от слова «совсем», а в анго – лишь поверхностно, без практических примеров. В таких случаях приходится открывать спецификацию от производителя, и десятки раз штудировать её от чердака и до подвала. В силу того, что интерфейсы обмена данными накопителей эволюционировали аж в пяти разных направлениях, тема просто огромна, а потому обсудим здесь только критически важные моменты.

Предыдущие части:
ASM. Программирование ОС [1] – загрузчик - Форум информационной безопасности - Codeby.net
Ядро ОС на Assembler: PCI, RAM, BIOS
ASM. Программирование ОС [3] – BootUSB и таблица DMI/SMBIOS

В этой:

1. Эволюция накопителей
1.1. Технология SATA​
1.2. Интерфейс NVM​

2. Поиск хост-контроллеров
2.1. Определение термина «Порт»​
2.2. BAR – регистры базовых адресов​

3. Параллельный интерфейс ATA
3.1. Доступ через физические порты ввода-вывода​
3.2. ATA в режиме PCI-IDE​
3.3. Идентификация устройств на шине​

4. Последовательный интерфейс SATA
4.1. Контроллер DPA – Direct Port Access​

5. Усовершенствованный SATA-AHCI
5.1. Программная модель​
5.2. Описание и типы структур FIS​
5.3. ATA Command Set​

6. Заключение



1. Эволюция накопителей информации

Сразу небольшой ликбез..
Изначально контролёр диска был встроен в чипсет мат.платы (как сеть и звук сейчас), а непосредственно внешний накопитель ST506 подключался к нему кабелем SA1000. Сейчас такая схема осталась на гибких дисках Floppy, внутри которого нет сложной электроники, а лишь мотор и подвижные части механики. Проблема в том, что помимо данных, хост должен посылать много и служебной информации, например проверка ошибок, команды позиционирования считывающих головок, и прочее. То-есть чтобы отправить одну команду, нужно было верхом на улитке преодолеть расстояние кабеля, и потом вернуться обратно с результатом. Поэтому инженеры переместили контролёр диска с чипсета, непосредственно в сам внешний накопитель, в результате чего появился термин IDE или «Intergated Device Electronics».

На стороне хоста-же остался только контролёр конфигурации диска и управления шиной HBA «Host Bus Adapter», куда подключается шлейф IDE устройства. В целях совместимости, хост может работать в двух режимах – это унаследованный от дедушки «Legacy ATA», и новый «PCI-IDE». В режиме Legacy хост предоставляет доступ к ATA через строго выделенные архитектурой порты 1F0h для первого, и 170h для второго канала, к каждому из которых можно подключить по 2 девайса Master/Slave. Что касается PCI-IDE, то здесь номера портов заранее не известны, и в зависимости от кол-ва занятых на текущий момент, их назначает биос динамически. Таким образом, основные регистры управления и программная модель у обоих режимов одинакова, а проблема с PCI-IDE только в поиске и определении номера его портов.

1.1. Технология SATA

Далее выяснилось, что теперь уже интерфейс представляет «бутылочное горлышко», поскольку параллельная его природа не позволяет передавать данные на скорости выше 133 МБ/сек. Не долго думая инженеры заменили его на последовательный Serial-ATA. Сейчас имеются 3 поколения SATA: это Gen1 = 150 МБ/сек, Gen2 = 300, и Gen3 = 600 МБ/сек. При этом на стороне хоста тоже появился новый контролёр управления шиной DPA или «Direct Port Access». В отличие от конфигурации Master/Slave (где одновременно может работать только один диск), DPA позволяет осуществлять независимый обмен сразу с несколькими портами.

Не нарушая традиций, и DPA может функционировать в двух режимах, только на этот раз роль унаследованного играет «PCI-IDE», а все прелести SATA раскрывает «AHCI» (Advanced Host Controller Interface). В режиме IDE ничего нового, а вот в AHCI наблюдаем полный разворот на 180 градусов от уже поднадоевших правил, в сторону абсолютно новой модели взаимодействия хоста с устройством. Здесь для повышения скорости все порты и регистры отображаются на системную память, и регистров так много, что занимают они 16 КБ в ОЗУ, в то время как размер всех регистров SATA-IDE не превышает и 128 Байт.

Обмен по такой схеме назвали MMIO, или «Memory Mapped Input-Output» (ввод-вывод с отображением в памяти). Если хост SATA в режиме IDE поддерживает всего 4 независимых порта (нету Master/Slave), то в AHCI уже в 8 раз больше макс 32. Видимо поддержка такого кол-во портов одним AHCI-контроллёром рассчитана на серверные системы, но в любом случае это реально круто, в чём мы убедимся позже.

Ata-Ide.webp

В плотную подобравшись к пределу возможностей передачи данных «SATA Gen3 600 МБ/с», инженеры решили избавиться и от всех механических частей внутри накопителя – так появился ещё один тип устройств хранения данных SSD или «Solid State Drive». Отправив весь лом на свалку (а это магнитные диски, двигатели, головки чтения/записи), инженеры заменили их на обычные флэшки больших объёмов. При этом программная модель осталась прежней, как и была в SATA. Поскольку теперь не нужно тратить время на поиск физ.секторов и позиционирование головок, в SSD увеличилась лишь скорость отклика самого устройства на команды со стороны хоста, хотя на скорость транспорта данных по интерфейсу это никак не повлияло макс 600 МБ/с.

1.2. Интерфейс NVM

Не успели инженеры расслабиться, как «сверху» потребовали скоростей выше Gen3. В первую очередь это было связано с переносом графической подсистемы внутрь процессора CPU. Здесь у инженеров был единственный выбор – избавиться от хост контроллёра в привычном его понимании, и подключить устройство MassStorage напрямую к периферийной шине PCI. Под такую «миграцию», штабу pci-sig пришлось разрабатывать и новую спеку SATA Gen3.2, которая поддерживает скорость до 900 МБ/сек. Теперь в огромных по своим габаритам ATA/SATA HDD потребность отпала, и на смену им пришли мелкие чипы NVM «Non Voltage Memory».

Позже инженеры предложили более усовершенствованный вариант. Раз уж появилась возможность подключить накопитель без посредников к паршине PCI, почему-бы аналогично не присобачить его и к последовательной PCI-Express, которая имеет впечатляющую скорость «SATA Gen3.3 = 2000 MБайт/с» (может быть больше, т.к. зависит от кол-ва линий PCIe-x1,2,4). Сказано-сделано, и всё сообщество IT дружненько пересело на интерфейс NVMe в слотах типа M.2. Это уже не IDE как мы привыкли его называть, а фактически самостоятельное устройство типа «точка-точка» на первичной шине PCI-Express, которое никогда не использует вторичные шины после мостов «PCI-to-PCI Bridge». Контроллёр на стороне хоста называется теперь NVMHCI, по аналогии с SATA-AHCI.

M2_SSD.webp


2. Поиск хост-контроллеров

Если ОС планирует работать с устройствами хранения данных ATA-SATA-NVM, она должна:

1. Обнаружить хост-контролёры запоминающих устройств на мат.плате​
2. Найти подключённые к контролёрам активные устройства​
3. Опознать режимы их работы – Legacy, PCI-IDE или AHCI​
4. Запросить паспорт обнаруженных устройств, чтобы определить размер и прочие свойства​

Из части(2) этого цикла мы уже знаем, что характеристики всех контролёров на мат.плате собираются в централизированную базу «PCI Configuration Space». Каждое устройство (в лице контролёров) имеет свой класс, подкласс и интерфейс. В глобальном пространстве Pci-Cfg ему выделяются штатные 256-байт памяти, хотя устройства PCIe могут запрашивать вплоть до 1КБ. Соглассно спецификации на шину PCI, для устройств хранения данных выделен класс(1), по которому мы и сможем найти диски. В таблице ниже я собрал распределение подклассов и интерфейсов для решения задач из пунктов 1,3 списка выше, где под интерфейсом нужно понимать способ программирования «Programming Interface» PI:

class.webp

Программный доступ к Pci-Cfg открывают стандартные порты процессора 0x0CF8 (bus:device:func) и 0x0CFC (data-rw), но есть и более простой метод в виде функций AX=B1xxh прерывания биос int-1Ah (не смотря на то, что указано биос, его эмулирует и efi). Вот описание этих функций, которые передаются в регистре AX:

C-подобный:
;//   AX = 0xB103 – найти устройство по классу (см.таблицу выше)
;//   AX = 0xB108 – прочитать байт из конфиг.пространства PCI
;//   AX = 0xB109 – прочитать слово
;//   AX = 0xB10А – прочитать двойное слово
;//   ------------------------------------------------------------------
      mov    ax,0xB103
      mov   ecx,0x010100     ;//<----- Найти “ATA Legacy”
;//   mov   ecx,0x01018F                      ATA в режиме “PCI-IDE”
;//   mov   ecx,0x010600                     SATA в режиме “PCI-IDE”
;//   mov   ecx,0x010601                     SATA в режиме “AHCI”
;//   mov   ecx,0x010801                      NVM на шине PCI
;//   mov   ecx,0x010802                      NVM на шине PCI-Ex
      xor   si,si            ;// начиная с устройства(0)
      int   0x1a             ;// Поиск !!!
      or    ah,ah            ;// проверим результат
      jnz   @NotFound        ;// если AH=1, значит нет такого контролёра

Таким образом, не затрачивая особых усилий мы получим доступ к конфигурационному пространству нужного нам хост-контроллёра, и сможем узнать его свойства. Не последнюю роль играет здесь байт со значением интерфейса – он позволит определить режим контролёра, и соответственно программный метод работы с ним. Битовая маска значения описана ниже.

ClassInterface.webp

Сейчас все контролёры дисков поддерживают обмен с хостом в режиме DMA (прямой доступ к памяти), поэтому в байте интерфейса старший бит(7) будет всегда взведён, а это в HEX как минимум 80h. Далее биты(3.1) говорят нам, сколько ATA-каналов имеется на мат.плате (как правило это 2 разъёма под шлейфы носителей), и какой из них активен на данный момент. Ну и в пару бит(2.0) биос прописывает текущий режим работы. Для SATA значимым является только младший бит(0), поскольку они по умолчанию всегда обменивается посредством DMA, а активные порты указываются в самом PciCfg. Если теперь посмотреть на лог утилиты «PCIScope» (в удобной форме отображает конфиг-пространство PCI), то по значениям байтов Class/SubClass/Interface можно сделать какие-то выводы.

AtaPciCfg.webp


2.1. Определение термина «Порт»

На программном уровне, доступ к любым физ.устройствам возможен исключительно через его порты ввода-вывода, поскольку иных вариантов в природе не существует. Порт – это компонент электрической схемы самого контролёра, наряду с его шинами адреса и данных. Драйверы Windows/Linux/Mac рано или поздно всё-равно обращаются к портам своих подопечных. Как правило один девайс имеет несколько портов, и в каждом порту свои регистры. У контролёра диска имеется «Порт управления каналом» и «Порт команд» для исполнения. Подобного рода разделение портов позволяет классифицировать все регистры по назначению, чтобы программисту было легче в них ориентироваться.

Порты всегда физические (внутри устройства), однако биос может отображать их (mapping) на системную память ОЗУ. В первом случае для чтения/записи портов используются инструкции ассемблера in/out, а во-втором – обычная пересылка данных mov. ЦП получает мгновенный доступ к памяти, а потому все скоростные устройства на шине PCI-Express отображают регистры в пространство памяти MMIO. Устаревший и крайне медленный обмен посредством PIO современное оборудование использует лишь в контексте каких-нибудь сервисных мероприятий (например чтение паспорта для инициализации девайса), а в остальных случаях на переднем плане всегда DMA.

2.2. BAR – регистры базовых адресов

В независимости PIO это или MMIO, биос прописывает номера портов устройства в шести регистрах BAR[0:5] его конфиг.пространства (Base Address Register, см.выделенный блок на рис.выше). Каждый BAR имеет размер 32-бита, хотя 2 соседних могут объединяться в один 64-битный. Первый BAR[0] расположен по смещению 10h, а последний BAR[5] по смещению 24h. Если в BAR лежит нуль, значит устройство использует порты по-умолчанию, и для дисков ATA это 1F0h для первого, и 170h для второго каналов. Если-же BAR не пустой, то сначала нужно проверить флаг в младшем его бите: 1=порт I/O, 0=память MMIO. Остальные биты расшифровываются как на рис.ниже. Соответственно, чтобы получить валидный адрес из регистра BAR, нужно сбросить 4 младших бита если это MMIO, или 2 бита если BAR описывает порты ввода-вывода I/O.

BAR_mask.webp


3. Параллельный интерфейс ATA

Самым древним является АТА, и соответственно программировать его проще остальных. Сейчас он уступает место более производительным SATA, однако ОС должна предусмотреть все возможные варианты событий, от устаревших «Legacy» и до реактивных «NVMe».

3.1. Доступ через физические порты ввода-вывода

Программирование АТА в режиме Legacy тривиально. Здесь номера портов заранее известны, и остаётся только записывать в регистры коды требуемых операций. В таблице ниже, в качестве порта для канала(1) выступает 1F0h, и он-же является первым из 8-ми регистров до 1F7h. Аналогично порт управления имеет адрес 3F6h в пространстве I/O, и он-же единственный регистр в данном порту. Обратите внимание на регистр 1F7h – в операциях записи out он играет роль командного регистра, а в операциях чтения in превращается в регистр состояния устройства «Status». Запись в него должна осуществляться в последнюю очередь, т.к. она активирует приём/передачу данных. Это говорит о том, что при оформлении запросов важно соблюдать очерёдность записи в регистры, последовательно от 1F1h и до 1F7h. Если диск примет команду, то в регистре данных 1F0h получим запрашиваемую информацию.

ATA_reg.webp

Ata_Error.webp

1F1h – Характер ошибки, и как правило «мигают» в нём только 2 выделенных цветом бита.
1F2h – Счётчик секторов для RW. Поскольку размер регистра 1-байт, то макс 255 секторов за одну операцию, или 255*512 = 128 КБайт.
1F3h – Сектор, с которого начинается чтение/запись. 48 бит позволяют адресовать диски размером 128 ПетаБайт: 0x0000.FFFF.FFFF.FFFF * 512.
1F6h – Выбор геометрии и устройства. Бит[6]: 1=LBA, 0=CHS. Бит[4]: 1=Slave, 0=Master (только для АТА).
3F6h – Статус канала. Для ATA в режиме Legacy представляет копию регистра 1F7h «Статус устройства».

Внимание! Проверять маску в регистре-статуса 1F7h нужно перед отправкой каждой команды, иначе контролёр может проигнорировать запрос. Достаточно запомнить, что диск принимает команды только, когда его бит BSY=0, а DRDY=1. Любая другая комбинация этой пары анулирует операцию обмена с накопителем.

3.2. ATA в режиме PCI-IDE

Здесь всё аналогично, только номера портов нужно считывать из регистров BAR. Переход от «Legacy» к «PCI-IDE» коснулся в основном протокола обмена между устройством и хостом, повлиять на который программист не может. Если не учитывать мелкие детали, то софтовая часть осталась без изменений – всё те-же порты, регистры и прочее, только теперь они называются «Shadow Command Block». Вот, что можно увидеть в 64-байтном заголовке «PCI-Config-Space» контроллёра ATA, при работе в разных его режимах (важные поля выделены цветом).

ATA_BAR.webp


3.3. Идентификация устройств на шине

Если мы хотим послать команды контролёру диска, в засаде нас поджидает небольшая проблема. Во-первых нужно определить канал, на котором висит АТА-накопитель, а во-вторых Master он, или Slave. Поскольку каналов на мат.плате обычно 2, то нужно будет выбрать 1 из 4-х возможных вариантов. Забегая вперёд скажу, что в SATA уже упростили данный процесс и активные устройства там указываются явно, но в АТА придётся перебирать все варианты вслепую.

Прежде чем рваться в бой запустим утилиту «RW», которая способна отображать содержимое портов ввода-вывода. В качестве адреса вводим наименьший 170h, и на 80h байт ниже сразу получим порт первого канала 1F0h. Здесь видно, что диск у меня висит на первом канале, а все регистры второго забиты значением 7Fh, которые можно использовать в качестве сигнатуры отсутствующего устройства. Однако даже по регистрам активного порта 1F0h мы не в состоянии определить, кем именно является девайс на шине, Master или Slave. Остаётся сначала отправить любую команду мастеру (см.регистр 1F6h в таблице выше), и если он не ответит, то ведомому Slave.

По сути значение регистра 1F6h у нас уже есть на скрине утилиты «RW» – это смещение 86h от начала. В данном случае там лежит байт со-значением F0h = 1[B]1[/B]1[B]1[/B].0000b, а значит перед нами LBA/Slave. Но если контролёр находится в режиме PCI-IDE, этот финт уже не сработает и нужен только слепой перебор.

Ata_Port_RW.webp


4. Последовательный интерфейс SATA

SATA принёс с собой много новых фишек, и в первую очередь это последовательный интерфейс соединения с хостом. Правда первая его версия мало отличалась от предшественника АТА – всего 150 МБ/сек, вместо 133. Зато во-второй подняли частоту и скорость увеличилась сразу вдвое 300 МБ/сек, а в третьей вообще до 600. Одного только этого бонуса нам хватало-бы вполне, но инженеры пошли дальше.

Хост SATA может работать в трёх режимах:

1. Режим эмуляции АТА . класс 01:01:8А. Программная модель полностью соответствует АТА (2 канала с эмуляцией двух устройств Master/Slave).​
2. Режим PCI-IDE . класс 01:06:01. Здесь уже 4 независимых канала на мат.плате, и возможен одновременный обмен между устройствами в разных направлениях.​
3. Режим AHCI . класс 01:06:02. Число каналов увеличено до 32, вместо обычных прерываний используются MSI, команды передаются посредством структур FIS, а не в регистрах.​

4.1. Контроллер DPA – Direct Port Access

Как упоминалось выше, порты SATA нужно считывать из BAR конфиг.пространства. В режиме эмуляции, регистры BAR[0:1] открывают доступ к портам первого канала АТА, а BAR[2:3] ко второму каналу. Если-же в настройках биос выставлен режим PCI-IDE, тогда регистры BAR[0:3] превращаются в 4 регистра «Command» (каждому каналу свой), а через BAR[5] можно будет получить доступ к регистрам управления «Control», от куда считывается статус и возможные ошибки при передачи данных.

Регистр по смещению 92h в пространстве PCI содержит информацию о 4-х каналах на мат.плате – младшие 4 бита хранят их состояние (вкл/выкл), а в старших указывается, к какому из этих каналов подключёно на текущий момент устройство SATA. На рис.ниже видно, что у меня включены все 4 канала, а девайс висит на первом. C завода все контроллёры DPA выходят 4 канальными, но на некоторых платах вендор может реализовать например всего 2 SATA-порта. Именно для таких случаев и были введены 4 младших бита в регистре PCS.92h. Обратите внимание, что они доступны на запись R/W, а значит мы можем сами отключать не нужные каналы контролёра (факт, но делать этого не рекомендуется).

SATA_Port.webp

Наиболее информативный – это BAR[5].SIDPR, что подразумевает «SATA Index/Data Pair Register». Если перейти по его содержимому, то попадём в связанную пару регистров как на схеме ниже. Первый Index настраивает фокус на подчинённые регистры, а через второй Data можно будет оперировать данными в них. Например если нам нужен статус второго канала (отсчёт с нуля), мы должны записать в биты[15:08] регистра SINDEX значение(1), а в биты[7:0] значение нуль. Теперь через SDATA откроется доступ к регистру SSTS с битовой маской ниже. Здесь можем узнать в каком состоянии находится девайс (активен или спит), а так-же скорость его обмена с хостом GenX. Аналогично и с открытым на запись регистром управления SINDEX=xx01h.

SATA_IDE.webp


5. Усовершенствованный SATA-AHCI

AHCI после IDE – это как пересесть с Запорожца на Феррари. В двигателе 32 цилиндра вместо четырёх, кнопочное управление на руле (вместо педалей с рычагом коробки передач), и т.д. В общем впечатляет всё, от фейса и до огромного числа регистров. На первый взгляд огранизация выглядит сложно, но по мере чтения спецификации вырисовывается вполне понятная картина. Как и следует из его названия «Advanced Host Controller Interface», это абсолютно новая модель устаревшего уже 4-канального DPA. В пространстве PCI, первые пять регистров BAR[0:4] отправлены на пенсию по выслуге лет, а действителен только последний BAR[5].

SATA_BAR.webp

Прописанный в BAR указатель отправляет нас прямиком к регистрам в памяти AHCI, которые делятся на 2 типа – общее управление хостом, и управление 32 возможными портами SATA на мат.плате. Регистры управления хостом (т.е. самим AHCI) существуют в единственном экземпляре, а вот регистры управления портами одинаковы для всех портов, и существует столько их ксерокопий, сколько реализовано портов на мат.плате. Один контролёр AHCI поддерживает макс 32 порта, однако на практике это редкость и вендор задействует всего 4-8 линий из них, а остальные лежат в резерве.

Рассмотрим схему ниже, где представлены 4 блока регистров SATA[0:3] (далее могут следовать 4:31 с шагом 80h байт). Первые 100h пространства выделено для «Регистров управлением AHCI», хотя задокументировано из них только 5 двордов, а остальные отданы на растерзание вендору (может заполнять произвольно). Здесь интерес представляют CAP = текущая скорость интерфейса, а так-же PI = сколько из 32-х портов реально задействовано. Если у вас 2 устройства SATA с разными скоростями Gen2-3, то девайс Gen3 нужно подключать в красный порт на мат.плате, а Gen2 в порт чёрного цвета (см.описание регистра cmd ниже). Иначе Gen3 не сможет реализовать свой потенциал.

AHCI_Schema.webp

Таким образом, регистры портов SATA начинаются с адреса BAR[5]+100h, и каждый блок имеет размер 80h=128 байт. Тогда получается, что если реализованы все 32 порта, то общий размер будет составлять 128*32=4096 байт, а реальное кол-во портов можно прочитать из регистра «Port Implemented». Каждый бит в этом 32-битном регистре описывает 1 порт, т.е. для 9 портов маска будет 0000.0001.1111.1111. В свою очередь биты[0:3] в регистре «Status» каждого порта указывают на его состояние – есть на нём подключённое устройство, или нет.

Биты в регистре «Command» отвечают на соответствующий вопрос в виде 1=да, 0=нет. После описанных далее настроек, нужно будет включить в нём биты[0.4], и прежде чем послать команду, всегда проверять на нуль биты[15.14]. А вот бит[24] не внушает доверия, и чтобы безошибочно определить дисковод SATAPI, нужно прочитать константу в регистре «Signature»: 0x00000101 = жёсткий диск, 0xEB140101 = дисковод DVD.

AHCI_Reg.webp


5.1. Программная модель AHCI

Самое трудное в программировании AHCI – это правильно расположить служебные структуры в памяти ОЗУ.
Значит у каждого порта имеется свой буфер из 32 команд, что позволяет не дожидаясь окончания одной, посылать сразу другую команду контролёру диска. Этот буфер описывает массив из 32 структур «Command Header» называемых «Слот». Точное кол-во слотов в порту указывается в битах[12:8] регистра BAR[5].CAP. Размер каждого слота =32 байта, 16 из которых в резерве (дворды 4:7).

Таким образом первое, что нужно сделать нам на этапе инициализации AHCI, это выделить 1 КБайт памяти для массива слотов, и прописать физ.адрес выделенной памяти в регистр BAR[5].Cmd_List_Address активного порта. У следующего порта SATA свои 32 слота и если он активен, то и ему как под копирку выделяем очередной КБайт, и т.д. Теперь в двордах[2.3] слота прописываем 64-битный указатель на массив дескрипторов памяти «Physical Region Descriptor», в которых указывается уже фактический адрес буфера с данными для приёма/передачи диску.

Общее число дескрипторов для всех 32 портов AHCI может достигать макс 65.536, и каждый указывает на свой буфер с данными. Если учесть, что размер одного дескриптора равен 16-байт, то весь пул требует 65536*16=1 МБ памяти. Слот может использовать любое кол-во дескрипторов из общего пула, и эту информацию нужно указать в его поле PRDTL. Поскольку под размер данных в дескрипторе выделено всего 24-бита, значит макс.размер буфера равен 16 МБайт. Если данных для приёма/передачи больше, то придётся передавать их через 2 последовательных буфа, и соответственно в PRDTL записываем 2.

На финишной прямой необходимо выделить ещё один блок памяти в ОЗУ, на этот раз для небольшой таблицы «Command Table» размером 128 байт, и прописать её адрес в регистр порта BAR[5].FIS_Base_Address. Основное отличие от устаревшего DPA в том, что для оформления команд AHCI использует блоки «Frame Information Structures» FIS. Они чем-то напоминают используемые в базах датасеты – набор структурированных данных для решения различного рода задач. Как правило где заканчивается «Command Table», там начинается массив «Physical Region Descriptor», о чём свидетельствуют смещения 00-40-60-80h на схеме выше. Это всё, что требуется для инициализации портов контролёра AHCI.

Внимание! Описанную выше инициализацию портов AHCI нужно проводить только,
предварительно сбросив биты[4.0] ST-FRE в регистре BAR[5].Command, и дождавшись сброса контролёром своих битов[15:14] CR-FR.
По окончании всех сервисных мероприятий, биты[4.0] нужно обратно включить в единичное состояние.

Ну и в последнюю очередь, 32 бита в регистре BAR[5].Command_Issue, бинарной маской активируют соответствующий слот, что равносильно включению рубильника приёма/передачи данных. Программист должен только включать биты в этом регистре, а по окончанию операции обмена, хост AHCI сам их сбрасывает опять в дефолтный нуль. Таким образом, в любой момент времени мы можем проверить, какой из 32 слотов порта свободен, а какой занят. Например, чтобы активировать слот(4), в регистре «Issue» требуется указать двоичное значение 0000.1000. Ясное дело, что оперировать этими битами мы должны в самую последнюю очередь, когда вся информация о приёме/передачи данных уже будет готова к употреблению.

5.2. Описание и типы структур FIS

В широком обиходе используются всего 4 типа структур FIS. Минимальный их размер составляет 5 двордов (или 20 байт), а макс 7 двойных слов (28 байт), хотя на схеме выше видно, что в таблице «Command Table» имеется выравнивание на 64-байтную границу, т.е. 64-28=38 байт в хвосте лежат в резерве. У каждого порта собственная структура FIS в единственном экземпляре, и при оформлении транзакаций она постоянно перезаписывается новыми значениями. Для отправки команд дисководу SATAPI выделена своя память, после региона SATA-FIS по смещению 40h от начала. Под FIS выделяется память, на которую указывает регистр порта BAR[5].FisBaseAddress.

Тип структуры указывается в её поле «FIS Type». Помимо непосредственно АТА-команды, сектора и счётчика байт, в структуре имеется и поле с направлением передачи (см.второй байт #1), которое требует к себе особого внимания. FIS может описывать или передачу от хоста к устройству (Host-to-Device, взведён старший бит=80h, т.е. команда), или наоборот приём служебной инфы от устройства. Например, чтобы запросить режим обмена PIO или DMA, устройство первым отправляет FIS=5Fh/41h хосту, на которую AHCI должен будет ответить Ok или Fuck. Поскольку абоненты используют в этом контексте один и тот-же тип FIS, то бит[5] определяет направление «Direction»: 1=D2H, 0=H2D. В прикреплённом к статье архиве есть спека на AHCI, где каждый бит описан подробно.

C-подобный:
;// typedef enum
;//-------------------
  FIS_TYPE_REG_H2D    = 0x27     ;//  5 двордов. Передача с хоста на устройство
  FIS_TYPE_REG_D2H    = 0x34     ;//  5 двордов. Передача с устройства на хост
  FIS_TYPE_DATA       = 0x46     ;//  8 кБайт.   Передача данных - двунаправленный
  FIS_TYPE_DEV_BITS   = 0xA1     ;//  2 дворда.  Установка битов устройства - с устройства на хост
  FIS_TYPE_BIST       = 0x58     ;//  3 дворда.  Активация теста BIST - двунаправленный
  FIS_TYPE_DMA_ACT    = 0x39     ;//  1 дворд.   Активация DMA - с устройства на хост
  FIS_TYPE_PIO_SETUP  = 0x5F     ;//  5 двордов. Настройка PIO - с устройства на хост
  FIS_TYPE_DMA_SETUP  = 0x41     ;//  7 двордов. Настройка DMA - двунаправленный

FisType.webp

5.3. ATA Command Set

Посылаемые хостом команды стандартизированы организацией Т13 (см. FIS-type=27h). Документ называется «ATA Command Set» или просто ACS (текущая версия вроде 5). В каком-то смысле стандартом это назвать трудно, поскольку у каждого интерфейса опкоды команд в корень отличаются. Так, если для ATA и SATA наблюдается связь, то для дисководов с пакетным интерфейсом ATAPI команды уже другие, а для вроде-бы родственных NVM-NVMe нет ничего общего от слова вообще. Такой винегрет сбивает с толку не подетски, и приходится таскать с собой целый ворох документации. В таблицах ниже я собрал список опкодов для разных интерфейсов, который можно использовать в качестве шпаргалки:

Command-Set.webp

Чтобы опознать устройство хранения данных, нужно послать ему команду «Identify Device», опкод которой пляшет от интерфейса к интерфейсу. На запрос девайс возвращает как минимум 512 байт информации (один сектор) – этакий паспорт, с полной характеристикой от вендора. Поля в структуре FIS для опкода ECh (т.е. для ATA/SATA) описаны ниже:

FisID.webp


6. Заключение

Исходного кода получилось много, а потому я прикрепил его в архив. Среди прочих файлов в папке BIN имеется «Sata.inc», где можно будет найти примеры инициализации дисков в режиме AHCI и PCI-IDE. Если ко всему описанному выше добавить ещё и программную модель NVM + NVMe, то «в тетради» не хватит клеток, поскольку это совсем из другой области, хотя общие черты с AHCI всё-же просматриваются. Оба интерфейса NVM для передачи команд отказались от структур FIS, т.к. в логической организации пространства Nand-Flash вместо секторов используются уже страницы Page. Может в другой раз мы ещё вернёмся к NVM, а пока ограничимся только SATA.

Результат работы кода представлен на двух скринах ниже – первый с вирт.машины «VirtualBox» (поддерживает SATA исключительно в режиме AHCI), а второй с реальной моей машины. Обнаружив контроллёры по их классам 0x010601=AHCI, и 0x010180/8A/8F=ATA/SATA, я запрашивал у них паспорт командой «Identify Device», после чего печатал наиболее интересную инфу из выхлопа на дисплей. Информация в виде общего кол-ва секторов в накопителе нужна ОС для того, чтобы была возможность отформатировать его, ведь на сырой «RAW» поверхности крайне неудобно хранить файлы.

ViBox_Result.webp


h61_Result.webp

В архив положил 4 спецификации на AHCI + NVM, включая доку на чипсет PCH9 с описанием регистров SATA на стороне хоста. Этого будет вполне достаточно, чтобы детально изучить тему самому. В заключительной следующей части рассмотрим ещё один важный аспект операционных систем – защищённый режим процессора, с 64-битной моделью памяти LONG. Всем удачи, пока!
 

Вложения

Мы в соцсетях:

Взломай свой первый сервер и прокачай скилл — Начни игру на HackerLab