Опыт запуска AHCI в VxWorks653

Введение


Я занимаюсь разработкой приложений и драйверов для различных устройств авиационного применения. В Авиации используются ОС с более жесткими требованиями к надежности (ARINC 653), такие как VxWorks653, PikeOS или LynkOS. Разрабатывая приложения для авионики возникла проблема медленного доступа к данным на твердотельных накопителях подключенным по интерфейсу ATA. Это происходило из-за использования медленного программного интерфейса ATA. Я решил эту проблему реализацией драйвера AHCI.

В этой статье Я хочу кратко описать работу AHCI.

49eb570164c54b748c3875bba6da6f17.PNG
Архитектура устройства с AHCI контроллером.

Описание AHCI


Advanced Host Controller Interface (AHCI) — механизм, используемый для подключения накопителей информации по протоколу Serial ATA.

Для работы с устройством в режиме AHCI следует выполнить следующие действия:

  • Настроить контроллер AHCI
  • Заполнить структуры управления
  • Выставить флаг запуска в контроллере
  • Дождаться пока контроллер через DMA прочитает из памяти структуры со списком команд и после этого вернет данные в буферы, указанный в структурах. После выполнения всех действий выставляется флаг завершения транзакции.
  • Profit!

Все взаимодействие с регистрами AHCI происходит через окно PCIe BAR5. Он называется ABAR (AHCI Base Address Region).

5cd8517379cd464da2a2d09c47106d7a.PNG
Распределение адресного пространства ABAR.

Настройка контроллера AHCI


Спецификация протокола находится в открытом доступе по адресу: www.intel.com/content/www/us/en/io/serial-ata/ahci.html
Текущая последняя версия AHCI — 1.3.1

Сначала ищем все контроллеры массовой памяти на шине PCIe. Нас интересуют устройства имеющие class id 0×01 (mass storage device) и subclass id 0×06 (serial ATA).

Настройка контроллера заключается в следующих действиях:

  • Сбросить контроллер;
  • Включить AHCI
  • Записать физические адреса списков команд и FIS в ABAR
  • Установить необходимые флаги
  • Настроить прерывания

Через регистры AHCI — ABAR можно сделать ограниченное количество действий — настроить работу AHCI, прочитать ее версию, и т.д. Доступ к конфигурации носителя данных и к самим данным через эти регистры не возможен.

Для получения доступа используются специальные списки команд. Списки команд хранятся в ОЗУ вычислителя. Физический адрес этих списков записывается в контроллер AHCI. AHCI контроллер сам обращается по DMA к ОЗУ вычислителя, считывает списки команд, выполняет их и считывает записывает блоки памяти из ОЗУ. Архитектура распределения памяти показана на рисунке ниже. пр инициализации ОС должна распределить необходимые массивы в памяти, вычислить их физический адрес и на этапе инициализации их адреса записываются в соответствующие регистры ABAR.

3c8587ebcd4e413f83a206faad8f645c.PNG
Распределение памяти в ABAR — HBA

Заполнение структур управления


Для каждого порта в ABAR должны быть указаны 2 структуры — список команд и FIS.

Список команд состоит из 2 частей — одного заголовка списка команд.
В заголовке указывается размер списка команд, размер FIS и ссылка на физический адрес самого списка команд.
В списке команд содержится адреса буферов, которые понадобятся при выполнении команды, указанной в FIS, и из размеры.

dc8847d6394741ed837edf3f01d6e07b.PNG
Структуры порта AHCI

Формирование Command header:
opts = (20 >> 2) | (sg_count << 16);
pp->cmd_slot->opts = cpu_to_le32(opts);
pp->cmd_slot->status = 0;
pp->cmd_slot->tbl_addr = cpu_to_le32(pp->cmd_tbl & 0xffffffff);
pp->cmd_slot->tbl_addr_hi = 0;

Здесь заполняется поле CFL значением размера FIS (у меня константа 20) в двойных словах. Поле PRDTL заполняется количество используемых буферов по 4Мб.

079093a0cec7485596c93a1b1fdd24dd.PNG
Формат заголовка списка команд

Формирование таблицы команд:

        #define MAX_DATA_BYTE_COUNT  (4*1024*1024)
        sg_count = ((buf_len - 1) / MAX_DATA_BYTE_COUNT) + 1;

        for (i = 0; i < sg_count; i++) {
                ahci_sg->addr =
                    cpu_to_le32((uint32_t) buf + i * MAX_DATA_BYTE_COUNT);
                ahci_sg->addr_hi = 0;
                ahci_sg->flags_size = cpu_to_le32(0x3fffff &
                                          (buf_len < MAX_DATA_BYTE_COUNT
                                           ? (buf_len - 1)
                                           : (MAX_DATA_BYTE_COUNT - 1)));
                ahci_sg++;
                buf_len -= MAX_DATA_BYTE_COUNT;
        }

        return sg_count;


Функция заполняет структуру в котором указывает какие буферы будут участвовать в передачи данных. Так как размер буфера в записи не может превышать 4Мб, то функция занимается разбиением буфера на несколько, если его размер превышает 4Мб.

78bbe140b9064dcf9a018fdfd3a84f31.PNG
Формат таблицы команд

Описание FIS в документации на AHCI нет. Следует искать его в описании на SATA. В структуре FIS описывается код операции (Чтение идентификации, чтение, запись, и др.) и параметры (смещение, размер, и д.р.).

d8cca134d64f4620826cd063fca0dd42.PNG
Структура FIS

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

 memset(fis, 0, 20);
        fis[0] = 0x27;
        fis[1] = 1 << 7;
        fis[2] = ATA_CMD_IDENT;
        fis[12] = __ilog2(probe_ent->udma_mask + 1) + 0x40 - 0x01;*/

При запросе записи блока данных формируется FIS:

 memset(fis, 0, 20);
        /* Construct the FIS */
        fis[0] = 0x27;          /* Host to device FIS. */
        fis[1] = 1 << 7;  /* Command FIS. */
        fis[2] = ATA_CMD_WR_DMA;        /* Command byte. */

        /* LBA address, only support LBA28 in this driver */
        fis[4] = ((unsigned char) (start))&0xff;
        fis[5] = ((unsigned char) (start>>8))&0xff;
        fis[6] = ((unsigned char) (start>>16))&0xff;
        fis[7] = (((unsigned char) (start>>24)) & 0x0f) | 0xe0;

        /* Sector Count */
        fis[12] = (unsigned char) blocks & 0xff;
        fis[13] = ((unsigned char) (blocks>>8))&0xff;

При запросе чтения блока данных формируется FIS:

 memset(fis, 0, 20);
        /* Construct the FIS */
        fis[0] = 0x27;          /* Host to device FIS. */
        fis[1] = 1 << 7;  /* Command FIS. */
        fis[2] = ATA_CMD_RD_DMA;        /* Command byte. */

        /* LBA address, only support LBA28 in this driver */
        fis[4] = ((unsigned char) (start))&0xff;
        fis[5] = ((unsigned char) (start>>8))&0xff;
        fis[6] = ((unsigned char) (start>>16))&0xff;
        fis[7] = (((unsigned char) (start>>24)) & 0x0f) | 0xe0;

        /* Sector Count */
        fis[12] = (unsigned char) count & 0xff;
        fis[13] = ((unsigned char) (count >> 8))&0xff;

Запуск на выполнение


После того как контроллер через ABAR настроен, все списки в памяти подготовлены, достаточно записать 0×1 в PORT_CMD_ISSUE и дождаться, пока флаг сбросится. Ждать можно в цикле (я поступил именно так) или по прерыванию.

Сразу после записи в PORT_CMD_ISSUE — контроллер через DMA будет обращаться в ОЗУ процессора и выполнять те действия, которые от него ожидали.

Так в результате выполнения операции чтения мы получим в в буферах, указанных в списке команд данные из носителя.

Заключение


До использования AHCI в проекте скорость чтения/записи была: ~100 кб/с
После использования AHCI в проекте скорость чтения/записи стала: 30 / 10 Мб/с

© Habrahabr.ru