Опыт запуска AHCI в VxWorks653
Введение
Я занимаюсь разработкой приложений и драйверов для различных устройств авиационного применения. В Авиации используются ОС с более жесткими требованиями к надежности (ARINC 653), такие как VxWorks653, PikeOS или LynkOS. Разрабатывая приложения для авионики возникла проблема медленного доступа к данным на твердотельных накопителях подключенным по интерфейсу ATA. Это происходило из-за использования медленного программного интерфейса ATA. Я решил эту проблему реализацией драйвера AHCI.
В этой статье Я хочу кратко описать работу AHCI.
Архитектура устройства с AHCI контроллером.
Описание AHCI
Advanced Host Controller Interface (AHCI) — механизм, используемый для подключения накопителей информации по протоколу Serial ATA.
Для работы с устройством в режиме AHCI следует выполнить следующие действия:
- Настроить контроллер AHCI
- Заполнить структуры управления
- Выставить флаг запуска в контроллере
- Дождаться пока контроллер через DMA прочитает из памяти структуры со списком команд и после этого вернет данные в буферы, указанный в структурах. После выполнения всех действий выставляется флаг завершения транзакции.
- Profit!
Все взаимодействие с регистрами AHCI происходит через окно PCIe BAR5. Он называется ABAR (AHCI Base Address Region).
Распределение адресного пространства 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.
Распределение памяти в ABAR — HBA
Заполнение структур управления
Для каждого порта в ABAR должны быть указаны 2 структуры — список команд и FIS.
Список команд состоит из 2 частей — одного заголовка списка команд.
В заголовке указывается размер списка команд, размер FIS и ссылка на физический адрес самого списка команд.
В списке команд содержится адреса буферов, которые понадобятся при выполнении команды, указанной в FIS, и из размеры.
Структуры порта 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Мб.
Формат заголовка списка команд
Формирование таблицы команд:
#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Мб.
Формат таблицы команд
Описание FIS в документации на AHCI нет. Следует искать его в описании на SATA. В структуре FIS описывается код операции (Чтение идентификации, чтение, запись, и др.) и параметры (смещение, размер, и д.р.).
Структура 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 Мб/с