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

в 13:02, , рубрики: AHCI, ATA, DMA, mass storage, VxWorks, низкоуровневое программирование, Си, системное программирование, метки:

Введение

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

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

Опыт запуска AHCI в VxWorks653 - 1
Архитектура устройства с AHCI контроллером.

Описание AHCI

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

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

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

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

Опыт запуска AHCI в VxWorks653 - 2
Распределение адресного пространства ABAR.

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

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

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

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

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

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

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

Опыт запуска AHCI в VxWorks653 - 3
Распределение памяти в ABAR — HBA

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

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

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

Опыт запуска AHCI в VxWorks653 - 4
Структуры порта 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Мб.

Опыт запуска AHCI в VxWorks653 - 5
Формат заголовка списка команд

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

        #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Мб.

Опыт запуска AHCI в VxWorks653 - 6
Формат таблицы команд

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

Опыт запуска AHCI в VxWorks653 - 7
Структура 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 настроен, все списки в памяти подготовлены, достаточно записать 0x1 в PORT_CMD_ISSUE и дождаться, пока флаг сбросится. Ждать можно в цикле(я поступил именно так) или по прерыванию.

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

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

Заключение

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

Автор: krotos139

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js