tolst0v
45 просмотров0 комментариев0

Ускоряем работу с eMMC на Repka Pi 4 в пять раз

Начиная с Repka OS v1.0.21_d06.05.26, мы добавили для Repka Pi 4 ряд изменений в работу с eMMC. В результате скорость чтения с eMMC выросла примерно в 5.2 раза: с 45 MB/s до 232-236 MB/s. Запись тоже ускорилась: в зависимости от eMMC-модуля прирост составил от 2.4 до 4.4 раз. Для пользователя это значительный прирост производительности без замены платы или накопителя: достаточно обновиться до актуальной версии Repka OS. А если интересно, что именно пришлось изменить в ядре, почему раньше eMMC упирался в режим HS и как мы проверяли результат, дальше в статье разбираем это по шагам.

Введение #

Когда речь заходит о производительности одноплатных компьютеров, чаще всего внимание уделяют процессору, объему оперативной памяти или системе охлаждения. Однако в реальных сценариях использования скорость работы системы очень сильно зависит от накопителя. Именно накопитель влияет на время загрузки ОС, скорость установки пакетов, работу Docker-контейнеров, баз данных, браузера и общую отзывчивость системы.

В случае с современными SBC (Single Board Computer) роль основного накопителя становится особенно важной. У классических Raspberry Pi для этого обычно используется microSD-карта — решение простое и универсальное, но не самое удачное, когда хочется получить более предсказуемую производительность встроенного накопителя. В Repka Pi для этой задачи предусмотрен eMMC: он может быть как распаян непосредственно на плате в кастомных конфигурациях, так и использоваться в виде отдельного модуля. В обоих случаях eMMC работает через специализированный интерфейс и лучше подходит на роль системного накопителя.

При этом многие воспринимают eMMC как «просто флеш-память», хотя внутри это достаточно сложная система со своим контроллером, кэшем, алгоритмами распределения нагрузки и внутренним параллелизмом.

В этой статье мы сначала разберем матчасть: как устроен eMMC, что находится внутри микросхемы, зачем нужен контроллер, как работает NAND-память, почему важны FTL, кэш, wear leveling и внутренний параллелизм. Отдельно посмотрим на режимы интерфейса HS, HS200 и HS400, потому что без этого трудно понять, почему один и тот же модуль может работать совершенно по-разному.

Затем перейдем к практической проблеме на Repka Pi: один из eMMC-модулей не хотел нормально стартовать в Repka OS со стандартным драйвером. В процессе разбора мы доработали поддержку eMMC в ядре Repka OS и добавили режим HS200, то есть сняли старое ограничение режима HS.

В конце покажем результаты решения в виде бенчмарков. В тестах участвуют два eMMC-чипа: FORESEE FEMDNN256G-A3V01 объемом 256 GB и Samsung KLMCG2UCTB-B041 объемом 64 GB. На них и сравним поведение до и после перехода на HS200.

Содержание

Введение

Часть 1. Что такое eMMC и как он устроен внутри?

eMMC — это не просто флешка

Как устроена NAND-память

Контроллер eMMC

MCU Core

FTL

NAND Flash Controller

SRAM

Взаимодействие с SoC

Пример получения команды на чтение от SoC

Пример получения команды на запись от SoC

Почему одни eMMC дороже других

Часть 2. Решили проблему: eMMC заработал и стал быстрее

Почему иногда недостаточно mainline-ядра

Как мы включили HS200 в Repka OS

Что происходит при загрузке Linux

Часть 3. Бенчмарки: что изменилось после HS200

Методика тестирования

Тестовое оборудование

Тестируемый eMMC

Сравнение режимов HS и HS200

Проверка режима работы eMMC

Автоматизация тестирования

Набор тестов

Архивация и распаковка /usr

Sequential Write (Последовательная запись)

Sequential Read (Последовательное чтение)

Результаты тестирования FORESEE FEMDNN256G-A3V01 256 GB

Sequential read/write

Архивация и распаковка /usr

Результаты тестирования Samsung KLMCG2UCTB-B041 64 GB

Sequential read/write

Архивация и распаковка /usr

Сводная таблица результатов

Итоги

Часть 1. Что такое eMMC и как он устроен внутри? #

eMMC — это не просто флешка #

eMMC часто воспринимают как «флешку в виде микросхемы»: подключили к плате, Linux увидел блочное устройство, можно ставить систему. На практике внутри eMMC находится не просто массив ячеек памяти, а законченный накопитель со своим контроллером, служебной логикой и NAND Flash.

На самом верхнем уровне все выглядит так: SoC на плате общается с eMMC по MMC-интерфейсу, отправляет команды чтения и записи, а внутри eMMC эти команды принимает контроллер. Уже контроллер решает, как именно разместить данные в NAND-памяти, какие блоки использовать, где хранить служебные таблицы и когда запускать внутренние операции обслуживания.

Важно, что операционная система не работает с NAND в eMMC напрямую. Linux видит eMMC как обычное блочное устройство: есть логические адреса, есть операции чтения и записи. Все детали NAND-памяти, включая ограничения на стирание блоков, плохие блоки, коррекцию ошибок и распределение износа, скрыты внутри самого eMMC.

Именно поэтому два модуля eMMC одинакового объема могут вести себя по-разному. На скорость и стабильность влияет не только сама NAND, но и контроллер, его прошивка, объем внутренней памяти, алгоритмы FTL и поддерживаемые режимы интерфейса.

Как устроена NAND-память #

Физически данные внутри eMMC хранятся в NAND Flash. Эта память устроена иерархически:

die
 └─ plane
     └─ block
         └─ page
             └─ cells

page — минимальная единица чтения и записи. block — группа страниц, которую можно стирать только целиком. plane и die дают контроллеру возможность выполнять часть операций параллельно, если конкретная NAND и контроллер это поддерживают.

Главная особенность NAND в том, что данные нельзя просто перезаписать поверх старых. Если в обычной оперативной памяти можно изменить байт на месте, то NAND работает иначе:

  1. читать можно страницами;
  2. записывать можно страницами;
  3. стирать можно только блоками.

Из-за этого маленькая запись часто превращается во внутреннюю цепочку: прочитать старые данные, подготовить новые, записать их в другое место, а старое место пометить как неактуальное. Позже контроллер очистит такие блоки через Garbage Collector.

Кроме структуры page/block/plane/die, NAND отличается тем, сколько бит хранится в одной ячейке:

Тип NAND Бит на ячейку Что это значит на практике
SLC 1 быстрее, надежнее, дороже
MLC 2 компромисс между ценой, ресурсом и скоростью
TLC 3 дешевле на гигабайт, но сложнее в записи и коррекции ошибок
QLC 4 еще плотнее и дешевле, но обычно медленнее и менее выносливая

Чем больше бит хранится в одной ячейке, тем больше уровней заряда должен различать контроллер. Для SLC достаточно двух состояний. Для TLC их уже восемь, а для QLC — шестнадцать. Чем ближе эти уровни друг к другу, тем сложнее надежно считать данные, тем важнее ECC и тем ниже запас по ресурсу.

Поэтому в современных накопителях часто используется SLC cache. Часть TLC-памяти временно работает как SLC: ячейка хранит не три бита, а один. Запись в таком режиме быстрее, потому что контроллеру нужно различать меньше состояний заряда. Пока данные помещаются в этот быстрый кэш, накопитель может показывать высокую скорость записи. Когда кэш заканчивается, контроллеру приходится писать напрямую в TLC, и скорость может заметно просесть.

Для системного накопителя это важная деталь. Короткий тест на 1 GB может попасть в SLC cache и показать красивую скорость, а длинная запись на 10 GB уже лучше показывает, как накопитель ведет себя после исчерпания быстрого буфера.

Контроллер eMMC #

Контроллер — это мозг eMMC. Он принимает команды от SoC, управляет NAND-памятью, выполняет коррекцию ошибок, ведет таблицы размещения данных и следит за состоянием физических блоков.

Упрощенно его устройство можно представить так (его компоненты выделены голубым цветом):

Снаружи контроллер выглядит как простой приемник команд: прочитать блок, записать блок, сбросить кэш, выполнить erase. Но внутри каждая такая команда проходит через несколько подсистем. Важны не только сами NAND-чипы, но и то, насколько быстро контроллер принимает команды, как он планирует операции, сколько служебных данных держит рядом с собой и насколько эффективно работает его прошивка.

MCU Core #

MCU Core — это внутренний микроконтроллер, который выполняет прошивку производителя eMMC. Он координирует работу остальных блоков: обрабатывает команды, обновляет служебные таблицы, запускает операции чтения и записи, следит за ошибками и фоновыми задачами.

Входом в eMMC можно считать Command Queue. Именно туда попадают запросы от хост-системы, а MCU Core забирает их из очереди, разбирает и решает, какие внутренние действия нужно выполнить.

Для обычного пользователя все это скрыто за простыми действиями: открыть файл, сохранить данные, удалить каталог, дождаться завершения установки пакета. Но на уровне MMC-интерфейса такие действия превращаются в конкретные CMD-команды. Одни передают пользовательские данные, другие меняют настройки устройства, третьи помогают ядру понять состояние накопителя или сообщить ему, какие области больше не нужны.

Команда Назначение Что делает на практике
CMD6 (SWITCH) изменение параметров переключает режимы и поля EXT_CSD, например ширину шины, включение кэша или timing mode
CMD6 (SWITCH, FLUSH_CACHE) сброс кэша через поле FLUSH_CACHE в EXT_CSD просит накопитель записать кэшированные данные в NAND
CMD8 (SEND_EXT_CSD) чтение EXT_CSD позволяет драйверу узнать возможности eMMC: поддерживаемые режимы, размер, cache, erase group и другие параметры
CMD13 (SEND_STATUS) чтение статуса показывает, готово ли устройство, не занято ли оно внутренней операцией и не возникла ли ошибка
CMD17 (READ_SINGLE_BLOCK) чтение одного блока читает один логический блок данных
CMD18 (READ_MULTIPLE_BLOCK) многоблочное чтение читает последовательность логических блоков, обычно эффективнее для больших запросов
CMD24 (WRITE_BLOCK) запись одного блока записывает один логический блок
CMD25 (WRITE_MULTIPLE_BLOCK) многоблочная запись записывает последовательность логических блоков
CMD35 (ERASE_GROUP_START) начало диапазона erase задает первый блок диапазона, который нужно стереть или пометить как ненужный
CMD36 (ERASE_GROUP_END) конец диапазона erase задает последний блок диапазона erase
CMD38 (ERASE) запуск erase/trim/discard запускает операцию над выбранным диапазоном

Эта таблица важна не сама по себе, а как мост между привычными действиями и внутренней работой накопителя. Чтение файла превращается в CMD17 или CMD18, запись — в CMD24 или CMD25, удаление данных может привести к связке CMD35/CMD36/CMD38, а переключение режимов и сброс кэша выполняются через CMD6.

Для Linux все это выглядит как обычные операции с блочным устройством. Внутри же MCU Core должен разобрать команду, обратиться к FTL, задействовать NAND-контроллер, обновить служебные таблицы и сохранить целостность данных. От производительности MCU Core и качества прошивки зависит, насколько хорошо eMMC справляется с большим количеством мелких операций. В последовательном чтении слабый контроллер может выглядеть нормально, но при установке пакетов, распаковке архива или работе базы данных разница становится заметной.

FTL #

FTL (Flash Translation Layer) — слой, который превращает физически неудобную NAND-память в понятное для Linux блочное устройство.

Linux работает с логическими адресами (LBA). Например, файловая система просит записать данные в логический блок 1000. Но физически этот блок не обязан лежать в одном и том же месте NAND всю жизнь. При следующей перезаписи контроллер может положить новые данные в другой физический блок, а старое место пометить как неактуальное.

Именно этим занимается FTL: он ведет карту соответствия между логическими адресами и физическими страницами NAND. Упрощенно это можно представить как таблицу:

LBA 1000 -> die 0, plane 1, block 42, page 10
LBA 1001 -> die 0, plane 1, block 42, page 11
LBA 1002 -> die 1, plane 0, block 87, page 3

Для Linux все три адреса выглядят как соседние логические блоки. Но внутри eMMC они могут оказаться в разных физических местах: на разных блоках, plane или даже die.

При чтении такая адресация работает относительно просто: FTL смотрит, где сейчас лежит нужный LBA, и передает физический адрес в NAND Flash Controller. При записи все сложнее. Если Linux снова записывает LBA 1000, контроллер обычно не стирает старую страницу и не пишет поверх нее. Он выбирает новую свободную страницу, записывает туда данные, обновляет запись в таблице соответствия, а старую физическую страницу помечает как устаревшую.

Из-за этого карта FTL постоянно меняется. Возникает важный вопрос: где она хранится? Внутри eMMC обычно нет большой DRAM, как у некоторых SSD, поэтому контроллер не может постоянно держать всю карту соответствий только в быстрой памяти. Типичная схема устроена в несколько уровней.

Самые горячие фрагменты таблиц находятся в SRAM контроллера. Это небольшая быстрая память рядом с MCU Core, поэтому из нее удобно быстро брать часто используемые соответствия LBA -> physical page: например, для текущего потока чтения, активной записи или недавно использованных областей файловой системы.

Основная же служебная информация хранится в самой NAND, но не как обычные пользовательские файлы, а во внутренних служебных областях, которыми управляет контроллер. Linux эти области не видит. Там могут лежать фрагменты mapping table, информация о свободных и занятых блоках, списки плохих блоков, счетчики износа, журналы последних изменений и резервные копии критичных метаданных.

Внутри этой логики работают несколько важных механизмов.

Bad Block Manager следит за плохими блоками. NAND-память может иметь дефектные блоки уже с завода, а новые плохие блоки могут появляться во время эксплуатации. Контроллер помечает такие области как непригодные и использует резервные блоки вместо них.

Wear Leveling распределяет износ. У NAND ограниченное количество циклов стирания и записи, поэтому нельзя постоянно писать в одни и те же физические блоки. Контроллер старается равномерно использовать разные области памяти. Динамический wear leveling распределяет новые записи, а статический может переносить даже редко меняющиеся данные, чтобы одни блоки не оставались почти новыми, пока другие уже сильно изношены.

Garbage Collector освобождает место. Когда данные перезаписываются, старые страницы не стираются сразу, а помечаются как устаревшие. Со временем в блоках накапливается смесь актуальных и неактуальных страниц. Garbage Collector переносит актуальные данные в новые места, после чего старый блок можно стереть целиком и снова использовать.

Эти процессы идут прозрачно для Linux, но напрямую влияют на производительность. Если накопитель занят сборкой мусора или переносом данных, задержки могут вырасти, а скорость записи — просесть.

NAND Flash Controller #

NAND Flash Controller — это блок, который непосредственно общается с NAND-памятью. Он выполняет операции чтения, программирования страниц и стирания блоков, а также управляет передачей данных между NAND и внутренними буферами контроллера.

Важная часть этого блока — ECC Engine (Error Correction Code). NAND-память не идеальна: при чтении могут появляться битовые ошибки, а с ростом износа их становится больше. Это нормальная часть жизни flash-памяти, поэтому контроллер не просто читает данные, а проверяет и исправляет ошибки.

При записи ECC Engine рассчитывает дополнительные контрольные данные и сохраняет их вместе с пользовательскими данными. При чтении он снова проверяет содержимое страницы и, если ошибок немного и они укладываются в возможности алгоритма коррекции, исправляет их на лету.

Чем плотнее NAND, тем важнее ECC. Для TLC и тем более QLC контроллеру приходится различать много близких уровней заряда, поэтому требования к коррекции ошибок выше. Более качественный ECC Engine помогает накопителю дольше сохранять надежность и стабильность чтения по мере износа.

SRAM #

SRAM — небольшая быстрая память внутри контроллера eMMC. Она не предназначена для постоянного хранения пользовательских данных. Ее задача — быть рабочей областью для внутренних механизмов накопителя.

С ней взаимодействуют почти все основные блоки контроллера:

  • MCU Core использует SRAM для служебных структур, очередей и временных данных;
  • Command Queue может хранить состояние текущих команд;
  • FTL держит рядом часто используемые фрагменты таблиц соответствия логических и физических адресов;
  • NAND Flash Controller использует буферы для передачи данных к NAND и обратно;
  • ECC Engine работает с данными и контрольной информацией во время проверки и исправления ошибок.

SRAM важна потому, что NAND сама по себе медленная для служебных операций. Если контроллеру каждый раз приходится лезть в NAND за метаданными, random I/O становится тяжелее. Чем лучше контроллер умеет использовать быструю внутреннюю память, тем стабильнее он ведет себя при мелких операциях, смешанной нагрузке и фоновой сборке мусора.

При этом SRAM обычно немного. Это не большой кэш как в настольных SSD с DRAM, а компактная рабочая память контроллера. Поэтому многое зависит от прошивки: какие данные держать в SRAM, что выгружать, как планировать операции и как не мешать пользовательским чтениям и записям фоновыми задачами.

Иногда можно встретить вопрос: бывают ли варианты eMMC с DRAM? Здесь важно не перепутать два разных класса устройств. В классическом eMMC внутри обычно есть контроллер, NAND и небольшая SRAM/буферы, но нет отдельного большого DRAM-чипа для пользовательского кэша или полной карты FTL.

А вот микросхемы, где рядом с NAND и eMMC-контроллером в одном корпусе находится еще и оперативная память, действительно существуют. Они называются eMCP (embedded Multi-Chip Package). Это не «eMMC с большим DRAM-кэшем», а комбинированный корпус: внутри может быть eMMC-накопитель и LPDDR/DDR-память, но для системы они остаются разными устройствами. SoC общается с flash-частью по MMC-интерфейсу, а с оперативной памятью — по отдельному DRAM-интерфейсу.

Такие решения часто используют в смартфонах, планшетах и компактных embedded-устройствах, где важно сэкономить место на плате и упростить разводку. Для нашей темы это полезное уточнение: если в спецификации встречается eMCP, это не новый быстрый режим eMMC и не гарантия более высокой скорости накопителя. Это способ упаковать постоянную память и оперативную память в один физический компонент.

Взаимодействие с SoC #

С точки зрения Repka Pi и любой другой платы, eMMC подключается не как SATA или NVMe-накопитель, а через MMC-интерфейс. Между SoC и eMMC есть линия команд (CMD), линия тактирования (CLK) и линии данных (DAT).

На производительность влияют три вещи:

  1. сколько линий данных используется;
  2. на какой частоте работает интерфейс;
  3. какой timing mode выбран.

Шина данных может быть 1-bit, 4-bit или 8-bit. Для системного eMMC обычно используется 8-bit, потому что это дает максимальную пропускную способность при той же частоте. Если представить интерфейс как дорогу, то увеличение числа линий похоже на увеличение количества полос: за один такт можно передать больше данных.

Эти режимы появились не случайно. eMMC вырос из семейства MMC/SD-подобных интерфейсов, где важна была обратная совместимость: новое устройство должно уметь стартовать в простом низкоскоростном режиме, чтобы старый или еще не настроенный контроллер мог его обнаружить. Поэтому быстрые режимы не заменили базовые, а добавились поверх них. Сначала устройство поднимается в безопасном режиме, драйвер читает его возможности, а уже потом переключает шину на более высокую скорость.

Развитие шло по трем понятным направлениям. Первое — использовать больше линий данных: не 1-bit, а 4-bit или 8-bit. Второе — поднять частоту тактирования: от десятков мегагерц к 200 MHz. Третье — передавать данные не только по одному краю такта, а по двум, то есть перейти от SDR к DDR. Так появились режимы HS, HS200 и HS400: каждый следующий режим старается выжать больше пропускной способности из компактной параллельной шины без перехода на принципиально другой интерфейс вроде PCIe/NVMe.

Режимы работы отличаются частотой и способом передачи данных:

Режим Тип передачи Частота 8-bit теоретически
Legacy SDR до 26 MHz до 26 MB/s
HS SDR до 52 MHz до 52 MB/s
HS200 SDR до 200 MHz до 200 MB/s
HS400 DDR до 200 MHz до 400 MB/s

SDR (Single Data Rate) означает, что данные передаются один раз за такт. У тактового сигнала есть два характерных момента — фронт и спад. В SDR-режиме для передачи данных используется только один из них, чаще всего фронт. Поэтому если интерфейс работает на 52 MHz, то на каждой линии данных происходит до 52 миллионов передач в секунду.

DDR (Double Data Rate) использует оба края тактового сигнала: и фронт, и спад. За один период такта данные успевают передаться два раза. Поэтому при той же частоте 200 MHz DDR-режим дает вдвое большую теоретическую пропускную способность, чем SDR: не 200 MB/s, а до 400 MB/s на 8-битной шине.

Это не значит, что DDR всегда автоматически лучше. Чем чаще контроллер должен ловить данные, тем меньше запас по времени и тем выше требования к качеству сигнала, задержкам, разводке платы, питанию и настройке драйвера. Поэтому HS400 потенциально быстрее HS200, но и заметно капризнее.

Legacy — базовый совместимый режим. Он нужен, чтобы устройство можно было надежно обнаружить и инициализировать на низкой скорости.

HS (High Speed) — более быстрый, но все еще относительно простой режим. В нем eMMC часто работает из коробки: частота до 52 MHz, передача SDR, требования к сигналам умеренные. Для старых или простых систем этого достаточно, но для современного eMMC режим HS быстро становится потолком.

HS200 сохраняет SDR-передачу, но поднимает частоту до 200 MHz. Теоретический потолок на 8-битной шине — около 200 MB/s. На практике скорость зависит от конкретной платы, SoC, драйвера, частоты, tuning и самого накопителя. В нашем случае переход с HS на HS200 важен именно потому, что интерфейс перестает ограничивать быстрый eMMC уровнем около 50 MB/s.

HS400 идет дальше и использует DDR-передачу: данные передаются дважды за такт, по фронту и спаду сигнала. Поэтому при частоте до 200 MHz теоретический потолок на 8-битной шине достигает 400 MB/s. Но этот режим гораздо требовательнее к разводке платы, задержкам, настройке сигналов и поддержке со стороны драйвера. В этой статье мы сравниваем именно HS и HS200.

Почему один eMMC работает только в HS, а другой поддерживает HS200 или HS400? Здесь сходятся сразу несколько физических и архитектурных факторов.

Во-первых, быстрый режим должен поддерживать сам чип eMMC. Его контроллер, входные/выходные буферы, внутренняя PLL (Phase-Locked Loop, блок синхронизации и умножения частоты для тактового сигнала) и прошивка должны уметь надежно принимать и отдавать данные на нужной частоте. Эта информация хранится в служебных регистрах вроде EXT_CSD, которые Linux читает при инициализации. Если устройство не заявляет HS200 или HS400, драйвер не должен включать такой режим.

Во-вторых, быстрый режим должен поддерживать хост-контроллер в SoC и драйвер ядра. Недостаточно, чтобы eMMC умел HS200: контроллер SoC должен поддерживать нужную частоту, ширину шины, напряжение I/O, tuning задержек, DMA и корректную обработку ошибок. Поэтому один и тот же модуль может работать быстрее на одной плате и ограничиваться HS на другой.

В-третьих, важна физика платы. На 52 MHz запас по времени большой, и небольшие различия в длине дорожек или форме сигнала обычно терпимы. На 200 MHz, особенно в DDR, окно, в которое контроллер должен точно поймать данные, становится намного уже. Влияют длина и согласованность линий DAT, качество питания, шумы, подтяжки, емкостная нагрузка, корпус микросхемы и разводка платы. Если сигнал приходит слишком рано, поздно или с искажениями, режим может быть нестабильным даже при формальной поддержке в спецификации.

Именно поэтому быстрые режимы используют tuning. Драйвер перебирает задержки выборки сигнала и ищет устойчивое окно, где тестовая команда проходит без ошибок. Если такого окна нет или оно слишком узкое, безопаснее остаться в более медленном режиме. В embedded-системах это нормальная инженерная реальность: максимальный режим определяется не только надписью в datasheet, но и всей цепочкой SoC -> плата -> eMMC -> драйвер.

Важно не путать скорость интерфейса со скоростью NAND. HS200 не делает ячейки памяти быстрее. Он только расширяет канал между SoC и контроллером eMMC. Если внутри накопитель не способен читать или писать быстрее, переход на более быстрый режим не даст большого прироста. Но если раньше узким местом был интерфейс, эффект будет очень заметным.

Пример получения команды на чтение от SoC #

Разберем упрощенную последовательность чтения. Допустим, Linux хочет прочитать несколько блоков с eMMC: например, файловая система запросила часть файла или загрузчик читает очередной кусок ядра.

  1. Приложение или ядро Linux запрашивает чтение данных.
  2. Файловая система и блочный слой превращают этот запрос в чтение логических блоков, например LBA 1000-1023.
  3. MMC-драйвер SoC формирует команду чтения и отправляет ее в eMMC по линиям CMD и DAT.
  4. Внутри eMMC команда попадает в Command Queue.
  5. MCU Core забирает команду из очереди и понимает, какие логические адреса нужно прочитать.
  6. FTL смотрит в таблицу соответствия и определяет, в каких физических страницах NAND сейчас лежат данные для этих LBA.
  7. Если нужные фрагменты таблиц уже есть в SRAM, контроллер использует их сразу. Если нет — может сначала считать служебные данные из NAND.
  8. MCU Core передает задачу в NAND Flash Controller.
  9. NAND Flash Controller читает нужные страницы NAND во внутренний буфер.
  10. ECC Engine проверяет данные и исправляет битовые ошибки, если они есть и укладываются в возможности коррекции.
  11. Исправленные данные попадают в буфер/SRAM и затем передаются обратно SoC по MMC-интерфейсу.
  12. MMC-драйвер завершает запрос, после чего данные возвращаются в блочный слой, файловую систему и приложение.

Для чтения ключевая работа контроллера — быстро найти физическое место данных через FTL, считать страницы NAND и проверить их через ECC. Если данные лежат последовательно и NAND позволяет параллелизм по planes или dies, контроллер может читать быстрее. Если запросы мелкие и разбросаны по накопителю, больше времени уходит на поиск соответствий, служебные операции и задержки NAND.

Пример получения команды на запись от SoC #

Запись устроена сложнее, потому что NAND нельзя перезаписать поверх старых данных. Допустим, Linux хочет записать данные в логические блоки LBA 1000-1023.

  1. Приложение записывает файл, а файловая система и блочный слой формируют запрос на запись логических блоков.
  2. MMC-драйвер SoC отправляет команду записи и сами данные в eMMC.
  3. Команда попадает в Command Queue, а данные принимаются во внутренний буфер контроллера.
  4. MCU Core разбирает команду и передает задачу в FTL.
  5. FTL не обязан писать новые данные в старое физическое место. Он выбирает подходящие свободные страницы NAND с учетом износа, плохих блоков и текущего состояния накопителя.
  6. Wear Leveling помогает выбрать область так, чтобы запись распределялась равномернее, а не убивала одни и те же блоки.
  7. Bad Block Manager исключает из выбора блоки, которые уже признаны плохими или ненадежными.
  8. Если свободного удобного места мало, контроллер может запустить или продолжить Garbage Collector: перенести актуальные страницы из старых блоков, стереть блок целиком и вернуть его в пул свободных.
  9. Перед записью ECC Engine рассчитывает контрольные данные для будущего исправления ошибок.
  10. NAND Flash Controller программирует выбранные страницы NAND: записывает пользовательские данные и служебную ECC-информацию.
  11. После успешной записи FTL обновляет карту соответствия: теперь LBA 1000-1023 указывают на новые физические страницы.
  12. Старые физические страницы, где раньше лежали эти же логические блоки, помечаются как неактуальные. Стереть их сразу нельзя: позже это сделает Garbage Collector на уровне целого блока.
  13. Когда критичные метаданные обновлены, eMMC сообщает SoC, что команда записи завершена.

Именно из-за этой цепочки запись обычно сложнее и менее предсказуема, чем чтение. Контроллеру нужно не просто принять данные, а выбрать место, подготовить ECC, обновить FTL, учитывать износ и иногда параллельно заниматься сборкой мусора.

Если запись небольшая и попадает в быстрый SLC cache, накопитель может показать высокую скорость. Если запись длинная, кэш заканчивается, свободных блоков мало или активно работает Garbage Collector, скорость может заметно снизиться. Поэтому в тестах важно смотреть не только короткую запись, но и более длинный сценарий.

Почему одни eMMC дороже других #

Цена eMMC складывается не только из объема памяти. Два чипа на 64 GB или 256 GB могут выглядеть одинаково в описании, но отличаться внутри почти всем важным: NAND, контроллером, прошивкой, запасом резервных блоков и поддержкой быстрых режимов.

Первый фактор — тип и качество NAND. SLC быстрее и надежнее, но дорогая. TLC дешевле и плотнее, поэтому чаще встречается в массовых накопителях, но сильнее зависит от ECC, SLC cache и работы контроллера. Даже внутри одного типа NAND могут отличаться ресурс, стабильность характеристик и количество ошибок чтения.

Второй фактор — внутренний параллелизм. Если в eMMC несколько NAND-кристаллов, несколько planes и контроллер умеет грамотно распределять операции, накопитель может одновременно читать, писать и выполнять фоновые задачи эффективнее. Поэтому два устройства с одинаковым интерфейсом HS200 могут показывать разную скорость.

Третий фактор — контроллер. Более сильный MCU Core, быстрый NAND Flash Controller, качественный ECC Engine и хорошая работа с SRAM помогают держать стабильную скорость не только в коротком тесте, но и при длительной записи, распаковке большого количества файлов или работе базы данных.

Если в описании компонента встречается eMCP, это отдельный случай: производитель совместил eMMC и DRAM в одном корпусе. Но эта DRAM обычно является системной оперативной памятью для SoC, а не внутренним кэшем eMMC-контроллера. Поэтому при сравнении eMMC-модулей корректнее смотреть не на наличие DRAM в названии корпуса, а на качество контроллера, объем и использование SRAM, прошивку, параллелизм NAND и стабильность скорости в длинных тестах.

Четвертый фактор — прошивка и FTL. Именно прошивка решает, как распределять износ, когда запускать Garbage Collector, как выбирать физические блоки, что держать в SRAM и как не допускать больших пауз при пользовательской нагрузке. Хороший FTL может быть важнее красивой пиковой скорости в спецификации.

Пятый фактор — резерв NAND и отбраковка. Часть памяти пользователь не видит: она нужна для замены плохих блоков, выравнивания износа и фоновых переносов данных. Чем больше такой запас и чем строже производственный контроль, тем дороже накопитель, но тем предсказуемее он ведет себя на длинной дистанции.

Наконец, быстрые режимы интерфейса тоже стоят денег. Поддержка HS200 и особенно HS400 требует от eMMC стабильной работы на высокой частоте, корректного tuning и более строгой валидации. При этом быстрый режим полезен только тогда, когда его поддерживают все участники цепочки: сам eMMC, SoC, разводка платы, питание и драйвер Linux.

Поэтому выбор eMMC нельзя сводить к объему и максимальным MB/s в коротком тесте. Для системного накопителя важны устойчивость скорости, поведение после заполнения SLC cache, random I/O, ресурс, работа FTL и то, раскрывает ли плата быстрый режим интерфейса.

Часть 2. Решили проблему: eMMC заработал и стал быстрее #

Почему иногда недостаточно mainline-ядра #

Когда речь идет про Linux на обычном ПК, часто можно поставить свежую версию ядра и ожидать, что большая часть оборудования будет работать. В мире одноплатных компьютеров и embedded-плат все сложнее.

Есть mainline-ядро Linux — основная ветка разработки, куда попадают универсальные драйверы, поддержка SoC, исправления и улучшения, прошедшие через процесс ревью сообщества. Это хороший путь: такой код проще поддерживать, обновлять и переносить между версиями ядра.

Но у многих ARM/RISC-V SoC, включая платформы Allwinner, Rockchip и другие, есть еще и BSP (Board Support Package) от производителя чипа или платы. Само появление BSP-ядер связано с тем, как обычно рождается новая embedded-платформа.

Производитель SoC проектирует чип, в котором есть десятки аппаратных блоков: CPU, контроллеры памяти, MMC, USB, HDMI, GPU, ISP, аудио, питание, тактирование, reset-логика, pinctrl и многое другое. Чтобы производители плат и устройств могли быстро начать разработку, вендор выпускает не только datasheet, но и стартовый программный комплект: загрузчики, Device Tree, патчи ядра, драйверы, утилиты, иногда Android- или Linux-образ. Это и есть BSP.

Главная задача BSP — не быть идеальным upstream-кодом, а быстро довести конкретное железо до рабочего состояния. Для производителя телефона, ТВ-приставки, промышленного контроллера или SBC важно, чтобы HDMI выводил картинку, eMMC загружался, Wi-Fi работал, питание переключалось в нужные режимы, а продукт можно было отдать разработчикам приложения или вывести на рынок. Поэтому BSP часто появляется раньше, чем полноценная поддержка этого SoC успевает пройти путь в mainline Linux.

Есть и практическая причина: часть знаний о железе сначала существует только внутри команды вендора. Например, как настроить PLL (Phase-Locked Loop) — блок, который формирует нужные тактовые частоты для контроллеров. Какие задержки нужны для MMC tuning, какой workaround требуется для конкретной ревизии контроллера, какие последовательности включения питания безопасны для платы. В идеале это постепенно оформляется в документацию и чистые драйверы, но на старте продукта быстрее положить эти знания в BSP-патчи.

В результате в BSP часто лежит ядро с большим набором vendor-патчей: драйверы, настройки тактирования, поддержка периферии, дополнительные режимы контроллеров, специфичные задержки и обходные пути для конкретного железа.

У этого подхода есть обратная сторона. BSP-ядро может быть основано на старой версии Linux, например на ветке 4.x или 5.x, и содержать код, который не всегда готов к прямому включению в mainline. Но при этом именно в BSP часто уже есть рабочие куски, без которых конкретная плата не раскрывает все возможности железа.

Для SBC это обычная ситуация:

  1. в mainline есть базовая поддержка SoC;
  2. плата загружается и работает в простых режимах;
  3. часть периферии работает не полностью или с ограничениями;
  4. быстрые режимы интерфейсов требуют дополнительных настроек;
  5. производитель или сообщество добавляет патчи поверх ядра.

Это касается не только eMMC. Похожая история может быть с HDMI, Wi-Fi, PCIe, GPU, камерами, питанием, suspend/resume и другими блоками SoC. Внутри чипа много контроллеров, и каждый из них может требовать не только общего драйвера, но и правильных clock/reset-настроек, pinctrl, регуляторов питания, задержек, tuning и особенностей конкретной платы.

Поэтому фраза «mainline-ядро поддерживает этот SoC» не всегда означает, что конкретная плата будет работать на максимуме. Иногда это означает только базовый уровень поддержки. А чтобы получить стабильный HS200, нормальный Wi-Fi или корректную работу питания, приходится брать идеи из BSP, переносить драйверы, адаптировать патчи или дописывать недостающие части.

В идеальном мире такие изменения постепенно приводятся в аккуратный вид и отправляются в upstream. На практике у продукта есть сроки, пользователи и конкретное железо, которое должно работать уже сейчас. Поэтому многие embedded-дистрибутивы живут между двумя мирами: стараются оставаться ближе к mainline, но держат набор платформенных или проектных патчей для тех мест, где mainline-ядра пока недостаточно.

Как мы включили HS200 в Repka OS #

К СВЕДЕНИЮ

Сам драйвер вы можете найти в репозитории с ядром, который доступен по ссылке.

До Repka OS v1.0.21_d06.05.26 eMMC на Repka Pi работал в режиме HS. Это означало, что даже быстрый модуль eMMC был ограничен интерфейсом: при 8-битной шине и частоте до 52 MHz верхняя теоретическая граница составляла около 52 MB/s.

История с HS200 началась не с желания просто “сделать быстрее”, а с конкретного eMMC-модуля, который не хотел стартовать со стандартным драйвером sunxi-mmc.c в текущем ядре Repka OS. При этом на Android 9 для Repka Pi, где используется BSP-ядро, этот же модуль стартовал. Более того, он запускался сразу в режиме HS400.

Это стало хорошей подсказкой: аппаратно плата и сам eMMC способны работать быстрее, но в основном ядре не хватает части логики, которая есть в BSP-драйвере от Allwinner.

Поэтому в версии Repka OS v1.0.21_d06.05.26 мы добавили в ядро отдельный драйвер MMC-контроллера sunxi-mmc_bsp.c, расположенный рядом со стандартным sunxi-mmc.c. Важно уточнить: мы не писали этот драйвер с нуля, а по сути портировали и адаптировали драйвер из BSP-ядра Allwinner на базе Linux 4.9 под текущее ядро Repka OS.

При этом портированием дело не ограничилось. Один важный механизм пришлось дописать самостоятельно — tuning при переходе в HS200.

Первой целью было не сразу включить максимум, а получить стабильную работу в режиме HS200. HS400 мы пока намеренно не включали: для этой платы он сложнее в настройке, а ожидаемый выигрыш не такой очевидный.

Причина в ограничениях контроллера SMHC2 внутри Allwinner H6. В datasheet для него указаны такие режимы:

SDR mode 150 MHz @ 1.8V IO pad
DDR mode 100 MHz @ 1.8V IO pad
DDR mode 50 MHz @ 3.3V IO pad

Проще говоря, для eMMC этот контроллер уверенно описан как SDR 150 MHz или DDR 100 MHz. Поэтому HS400 здесь не означает автоматический переход к «полным» 400 MB/s: хост-контроллер все равно ограничен частотой 100 MHz в DDR-режиме. А на практике HS400 еще и требовательнее к сигналам, задержкам и проверке на разных eMMC-модулях. Если включить его без аккуратной настройки и валидации, режим может оказаться очень нестабильным: с ошибками CRC, срывами инициализации или зависимостью от конкретного модуля, питания и температуры.

Поэтому для первой итерации мы выбрали более понятную цель: включить HS200, выполнить tuning и получить стабильный прирост относительно HS. Возможность перехода к HS400 в будущем остается, но ее нужно проверять отдельно.

Ключевые изменения:

  1. драйвер объявляет поддержку HS200 через capability MMC_CAP2_HS200_1_8V_SDR;
  2. в DTS для mmc2 используется новый compatible = "allwinner,repka-pi_sunxi-mmc_bsp", включается mmc-hs200-1_8v, задается 8-битная шина и ограничение частоты max-frequency = <150000000>;
  3. при переходе в HS200 выполняется tuning: драйвер перебирает значения sample delay, находит рабочее окно и выбирает середину стабильного диапазона;
  4. HS400 в этой версии намеренно не включается, поэтому сравнение в статье проводится именно между HS и HS200.

Зачем вообще нужен tuning? В режиме HS частота невысокая, поэтому контроллеру проще надежно считать данные с линий DAT: временное окно, в которое сигнал считается стабильным, достаточно широкое. В HS200 частота интерфейса вырастает до сотен мегагерц, и это окно становится намного уже. Если читать сигнал слишком рано или слишком поздно, можно получить ошибки CRC, нестабильную инициализацию или накопитель, который иногда работает, а иногда отваливается.

Поэтому при переходе в HS200 драйвер должен подобрать момент, в который хост-контроллер будет сэмплировать данные. В нашем случае это делается через настройку sample delay.

Логика tuning выглядит так:

  1. драйвер переводит eMMC в режим HS200;
  2. последовательно перебирает возможные значения sample delay;
  3. на каждом значении отправляет стандартную tuning-команду MMC;
  4. отмечает, при каких задержках обмен проходит без ошибок;
  5. находит самое длинное стабильное окно;
  6. выбирает значение примерно посередине этого окна.

В консоли это можно увидеть по сообщениям ядра. Драйвер выводит карту tuning и выбранную задержку:

sunxi-mmc-bsp 4022000.mmc: HS200 tuning map: 0000001111111111111111111111111111111111111111111111000000000000
sunxi-mmc-bsp 4022000.mmc: HS200 tuning selected sample delay 28, window [6-51|46]

В первой строке 1 означает удачное значение sample delay, а 0 — значение, при котором tuning-команда прошла с ошибкой. Во второй строке видно выбранное значение задержки и границы стабильного окна. В этом примере рабочим оказался диапазон с 6 по 51, поэтому драйвер выбрал значение 28, ближе к середине.

Выбор середины важен: если оставить значение на самом краю рабочего диапазона, небольшое изменение температуры, питания или особенностей конкретного eMMC-модуля может снова привести к ошибкам. Середина окна дает запас по стабильности.

Именно эта часть делает переход на HS200 не просто включением флага в драйвере, а полноценной настройкой высокоскоростного интерфейса. Без tuning контроллер может объявить поддержку HS200, но работать нестабильно или не раскрывать скорость на практике.

Интересно, что первые замеры показали не самый очевидный результат: HS200 на Repka OS при частоте 150 MHz по выводу ios оказался быстрее по чтению из eMMC, чем HS400 на Android 9 при 100 MHz. Проверка выполнялась с помощью hdparm.

Параметр Android 9 Repka OS Basic
Плата Repka Pi 4 v2.1 Repka Pi 4 v2.1
Частота 100 MHz 150 MHz
Количество линий 8 8
Timing 10 (HS400) 9 (HS200)
Чтение из eMMC 104.8 MB/s 201.8 MB/s

Отдельно стоит пояснить число 201.8 MB/s в HS200. На первый взгляд она выглядит неожиданно: в DTS для mmc2 мы ограничили частоту значением max-frequency = <150000000>, то есть 150 MHz. Но в драйвере частота проходит через clock framework Linux: драйвер запрашивает нужную частоту, а затем ядро подбирает ближайшее значение, которое реально может выдать тактовая схема SoC.

Эту логику мы взяли из BSP-драйвера Allwinner на базе Linux 4.9. Правда, само BSP-ядро почти не объясняет, почему частота считается именно так: в коде видно действие, но не описана причина. Поэтому дальше скорее наша догадка по коду и результатам измерений. Упрощенно схема выглядит так: для SDR-режимов драйвер запрашивает внутреннюю частоту примерно в два раза выше желаемой частоты шины, а потом делит ее обратно. Если вместо условных 300 MHz тактовая система округляет значение до 400 MHz, наружная частота MMC-шины может получиться около 200 MHz. При этом в ios все равно может быть записано наше заданное значение, например 150 MHz, поэтому по одному выводу ios не всегда понятно, что реально получилось на линии. А HS200 на 8-битной шине при 200 MHz как раз дает теоретический потолок около 200 MB/s. Поэтому результат 201.8 MB/s похож на ситуацию, где фактическая частота шины оказалась ближе к 200 MHz, чем к 150 MHz.

Точную причину, почему Android в этом замере оказался медленнее, мы отдельно не разбирали, поэтому корректнее говорить о гипотезах. Самая простая — Android работал в HS400 только на 100 MHz, то есть режим был быстрым по названию, но ограниченным по частоте контроллера. Кроме того, могли повлиять настройки BSP-драйвера, выбранные задержки, DMA, параметры кэша, состояние файловой системы и то, как именно Android-образ инициализирует eMMC. Поэтому этот результат не стоит читать как «HS200 всегда быстрее HS400». Скорее он показывает, что реальная скорость зависит от всей настройки платформы, а не только от названия timing mode.

Важно, что это изменение не делает саму NAND-память быстрее. Оно убирает ограничение со стороны интерфейса между SoC и eMMC. Поэтому в тестах прирост должен быть особенно заметен на последовательном чтении и записи, если конкретный модуль eMMC способен выдавать скорость выше потолка режима HS.

Что происходит при загрузке Linux #

Со стороны пользователя все выглядит просто: включаем Repka Pi, через некоторое время появляется Repka OS, а накопитель доступен как обычный диск. Но внутри ядра в этот момент происходит довольно длинная цепочка событий.

Здесь есть важная деталь: само ядро Linux уже загружается с этой же eMMC. До старта ядра никакой Linux-драйвер sunxi-mmc-bsp еще не работает. Сначала управление получает встроенный ROM-код SoC, затем загрузчик. Именно загрузчик инициализирует eMMC в достаточно простом и надежном режиме, читает с нее загрузочные файлы, загружает ядро Linux, initramfs при его наличии и Device Tree, а уже потом передает управление ядру.

То есть на раннем этапе eMMC нужна просто как источник загрузки: контроллер SoC и накопитель должны уметь обменяться данными в базовом режиме, достаточном для чтения образов. Быстрый режим HS200, tuning, DMA-настройки Linux-драйвера и все оптимизации производительности появляются позже, когда ядро уже находится в памяти и начинает инициализировать устройства заново, уже своими драйверами.

К моменту старта Linux загрузчик уже передает ядру описание платы — Device Tree. В нем указано, какие устройства есть на плате, по каким адресам они находятся, какие такты, reset-линии, регуляторы питания и прерывания им нужны.

Для eMMC на Repka Pi 4 Optimal Ver 2.1 важен узел mmc2. Именно в нем мы указываем, что этот контроллер должен обслуживаться нашим BSP-драйвером:

&mmc2 {
	compatible = "allwinner,repka-pi_sunxi-mmc_bsp";
	device_type = "sdc2";
	cap-mmc-hw-reset;
	non-removable;
	bus-width = <8>;
	max-frequency = <150000000>;
	mmc-hs200-1_8v;
	status = "okay";
};

Это не просто набор декоративных параметров. Для ядра это инструкция:

  1. использовать новый драйвер sunxi-mmc-bsp;
  2. считать eMMC несъемным устройством;
  3. работать по 8-битной шине;
  4. задать для MMC core ограничение частоты 150 MHz;
  5. разрешить режим HS200 при сигнальном напряжении 1.8 V;
  6. использовать указанные регуляторы питания для самого накопителя и линий ввода-вывода.

Дальше Linux находит подходящий драйвер по строке compatible. В нашем случае эта строка совпадает с таблицей of_match_table в sunxi-mmc_bsp.c, после чего ядро вызывает функцию инициализации драйвера для этого контроллера.

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

  1. создает объект MMC-хоста, через который общий MMC-слой Linux будет общаться с контроллером;
  2. получает из Device Tree адреса регистров, такты, reset, питание и pinctrl;
  3. включает питание eMMC и линии ввода-вывода;
  4. включает тактирование контроллера;
  5. выводит контроллер из reset;
  6. настраивает прерывание;
  7. выделяет DMA-дескрипторы;
  8. сообщает MMC-слою Linux, какие режимы поддерживает контроллер.

Последний пункт особенно важен. Драйвер явно объявляет поддержку обычного MMC High Speed и HS200 1.8V SDR, но намеренно отключает HS400. Поэтому MMC core видит, что контроллер и плата могут работать в HS200, но не пытается переходить в HS400.

После регистрации хоста общий MMC-слой Linux начинает опрос устройства. Здесь уже работает не только наш драйвер, а стандартная логика ядра:

  1. подать питание;
  2. запустить шину на безопасной низкой частоте;
  3. отправить eMMC начальные команды;
  4. прочитать служебные регистры накопителя;
  5. понять, какие режимы поддерживает сам eMMC;
  6. выбрать ширину шины;
  7. поднять частоту;
  8. перейти в более быстрый timing mode, если это поддерживают и хост, и накопитель.

Драйвер в этой схеме отвечает за аппаратную сторону каждого шага. Когда MMC core говорит: «поставь такую частоту, такую ширину шины и такой timing», драйвер получает это через set_ios и переводит реальные регистры контроллера в нужное состояние.

Например, при изменении режима драйвер:

  • выбирает ширину шины 1, 4 или 8 bit;
  • включает или выключает тактирование карты;
  • выбирает источник частоты;
  • выставляет делители;
  • настраивает задержки выборки сигнала;
  • переключает сигнальное напряжение между 3.3 V и 1.8 V, если это требуется;
  • включает DMA для передачи данных без постоянного участия CPU.

То есть Linux не пишет данные в eMMC напрямую. Приложение работает с файлом, файловая система превращает это в блочные операции, блочный слой отправляет запросы в MMC core, а MMC core уже вызывает драйвер хост-контроллера. Драйвер превращает запрос в команды MMC, настраивает DMA, запускает передачу и ждет прерывание от контроллера.

Когда есть данные для чтения или записи, драйвер не перекладывает каждый байт вручную. Он подготавливает DMA-дескрипторы, указывает контроллеру адреса буферов в памяти, размер блока и количество блоков, затем отправляет команду eMMC. После этого основную передачу выполняет аппаратный DMA, а процессор получает прерывание, когда операция завершилась или произошла ошибка.

Это важная деталь для производительности. Если бы CPU постоянно копировал данные сам, быстрый режим интерфейса был бы менее полезен. DMA позволяет разгрузить процессор и лучше использовать пропускную способность шины.

В этой же цепочке появляется и HS200 tuning, который мы разбирали выше. С точки зрения ядра это не отдельная пользовательская операция, а часть инициализации MMC-устройства: MMC core переводит eMMC в быстрый режим, после чего вызывает у драйвера execute_tuning. Драйвер подбирает sample delay, сохраняет рабочее значение в регистре контроллера, и только после этого высокоскоростной режим считается пригодным для нормальной работы.

После успешной инициализации накопитель уже работает в HS200, а для остальной системы остается обычным блочным устройством. Пользовательские программы не знают, что внутри произошло переключение частоты, настройка задержек, подбор окна и включение DMA. Они просто видят, что чтение и запись стали быстрее.

Именно это и делает поддержку HS200 в ядре важной: мало написать в DTS mmc-hs200-1_8v. Нужно, чтобы драйвер умел корректно поднять питание и такты, зарегистрировать возможности контроллера, обслуживать команды, работать через DMA и выполнить tuning на высокой частоте.

Часть 3. Бенчмарки: что изменилось после HS200 #

Методика тестирования #

Для оценки производительности eMMC на Repka Pi 4 Optimal Ver 2.1 был подготовлен единый набор тестов, позволяющий сравнить влияние режимов работы интерфейса HS и HS200.

Тестовое оборудование #

В тестировании использовалась следующая аппаратная конфигурация:

Компонент Значение
Плата Repka Pi 4 Optimal Ver 2.1
ОЗУ 2 GB
Питание фирменный блок питания Repka Pi 3A

Тестируемый eMMC #

В тестировании участвовали два eMMC-чипа:

Модель Объем
FORESEE FEMDNN256G-A3V01 256 GB
Samsung KLMCG2UCTB-B041 64 GB

Сравнение режимов HS и HS200 #

Для каждого модуля тестирование проводилось дважды:

  1. На Repka OS v1.0.20_d23.04.26 eMMC работает в режиме HS

  2. На Repka OS v1.0.21_d06.05.26 eMMC работает в режиме HS200

Такое сравнение одновременно показывает два эффекта: разницу между версиями Repka OS и практический результат перехода MMC-контроллера Repka Pi со стандартного режима HS на HS200.

Таким образом удалось оценить:

  1. влияние нового режима интерфейса;
  2. прирост пропускной способности;
  3. изменение поведения накопителя под различными типами нагрузки.

Перед каждым запуском:

  1. выполнялась чистая установка ОС;
  2. использовалась одинаковая аппаратная конфигурация;
  3. запускался один и тот же набор тестов.

Проверка режима работы eMMC #

Перед запуском тестов фиксировались параметры MMC-интерфейса:

cat /sys/kernel/debug/mmc1/ios

Это позволяло сохранить: частоту работы интерфейса, ширину шины, активный timing mode, режим HS или HS200.

Автоматизация тестирования #

К СВЕДЕНИЮ

Сам скрипт доступен у нас в репозитории по данной ссылке.

Для исключения человеческого фактора был подготовлен автоматизированный bash-скрипт, выполняющий: подготовку окружения, запуск тестов, сохранение результатов, очистку временных файлов. Все результаты каждого запуска сохранялись в отдельную директорию с timestamp.

Набор тестов #

Тестирование включало как синтетические, так и приближенные к реальным сценарии нагрузки.

Архивация и распаковка /usr

Для имитации реальной пользовательской нагрузки выполнялись:

  1. упаковка каталога /usr в tar.gz;
  2. распаковка архива во временную директорию.

Этот сценарий одновременно нагружает: последовательное чтение, последовательную запись, файловую систему, работу page cache.

Sequential Write (Последовательная запись) #

Для оценки максимальной скорости последовательной записи использовалась утилита fio.

Проводилось два сценария:

  1. запись файла размером 1G с размером блока в 1M;
  2. длительная запись файла размером 10G с размером блока в 1M.

Тест 1G позволяет увидеть кратковременную производительность накопителя. Тест 10G показывает: поведение после исчерпания SLC cache, устойчивость скорости записи, особенности работы контроллера.

Sequential Read (Последовательное чтение) #

Аналогично проводились тесты последовательного чтения:

  1. чтение файла размером 1G с размером блока в 1M;
  2. длительное чтение файла размером 10G с размером блока в 1M.

Этот тест позволяет оценить: влияние режима HS200 на bandwidth, эффективность контроллера и практический потолок чтения в выбранной тестовой конфигурации.

К СВЕДЕНИЮ

Вместо простого dd использовался fio, поскольку он:

  • позволяет работать с direct I/O;
  • умеет контролировать глубину очереди;
  • ближе к реальным сценариям нагрузки;
  • дает более воспроизводимые результаты.

Результаты тестирования FORESEE FEMDNN256G-A3V01 256 GB #

В тестах использовался модуль FORESEE FEMDNN256G-A3V01 объемом 256 GB. Для него сравнение получилось особенно показательным: в режиме HS накопитель почти полностью упирался в ограничение интерфейса, а после перехода на HS200 скорость выросла в несколько раз.

Перед тестами параметры MMC-интерфейса были зафиксированы через /sys/kernel/debug/mmc1/ios. Важно: это то, что сообщает ядро после настройки контроллера, а не независимое измерение физической частоты прямо на линиях MMC.

Параметр HS HS200
Частота по ios 50 MHz 150 MHz
Ширина шины 8 bit 8 bit
Timing mmc high-speed mmc HS200
Signal voltage 1.8 V 1.8 V

Sequential read/write #

Тест HS HS200 Прирост
Sequential read 1G 45.5 MB/s 235 MB/s 5.16x
Sequential read 10G 45.5 MB/s 236 MB/s 5.19x
Sequential write 1G 42.1 MB/s 183 MB/s 4.35x
Sequential write 10G 42.7 MB/s 188 MB/s 4.40x

На последовательном чтении видно резкое снятие старого ограничения режима HS: скорость была около 45 MB/s, а в тесте HS200 поднялась до 235-236 MB/s. Для системного накопителя это уже совсем другой класс производительности.

Важно: такие значения выше теоретического потолка для обычного HS200 @ 200 MHz на 8-битной SDR-шине. Мы оставляем их как фактические метки теста, но читаем осторожно: вероятнее всего, в этой конфигурации повлияла фактическая частота шины после настройки clock framework, либо особенности методики чтения и кэширования. То есть это результат конкретной связки платы, драйвера, eMMC и теста, а не универсальная гарантия для любого HS200.

Запись тоже ускорилась радикально: с 42 MB/s до 183-188 MB/s. При этом результаты 1G и 10G почти не отличаются. Это важный признак: в пределах выбранного теста модуль не показывает резкого провала скорости после короткой быстрой записи.

Архивация и распаковка /usr

Синтетические тесты хорошо показывают практическую скорость в выбранной конфигурации, но пользовательские сценарии обычно сложнее: там одновременно работают файловая система, page cache, CPU и сама утилита архивации. Поэтому отдельно проверялась упаковка и распаковка каталога /usr.

Тест HS HS200 Изменение
tar /usr 12m41s 10m34s быстрее на 16.7%
untar /usr 3m09s 1m42s быстрее на 45.9%

Архивация ускорилась умеренно, потому что здесь заметную часть времени занимает сжатие данных процессором. Распаковка, где больше реальной записи на накопитель, выиграла значительно сильнее — почти в 1.85x.

Главный вывод по FORESEE FEMDNN256G-A3V01: переход на HS200 дает не косметический прирост, а принципиально меняет поведение накопителя. В режиме HS модуль ограничен уровнем около 45 MB/s, а в нашей тестовой конфигурации выходит на 236 MB/s чтения и 188 MB/s записи.

Результаты тестирования Samsung KLMCG2UCTB-B041 64 GB #

Вторым модулем стал Samsung KLMCG2UCTB-B041 объемом 64 GB. Он интересен как более компактный eMMC-накопитель: интерфейс после перехода на HS200 раскрывается очень хорошо на чтении, но запись оказывается заметно скромнее, чем у FORESEE FEMDNN256G-A3V01.

Параметры MMC-интерфейса:

Параметр HS HS200
Частота по ios 52 MHz / actual 50 MHz 150 MHz
Ширина шины 8 bit 8 bit
Timing mmc high-speed mmc HS200
Signal voltage 1.8 V 1.8 V

Sequential read/write #

Тест HS HS200 Прирост
Sequential read 1G 44.8 MB/s 235 MB/s 5.25x
Sequential read 10G 44.8 MB/s 232 MB/s 5.18x
Sequential write 1G 39.9 MB/s 118 MB/s 2.96x
Sequential write 10G 42.3 MB/s 101 MB/s 2.39x

На чтении картина почти такая же, как у FORESEE: режим HS ограничивает модуль уровнем около 45 MB/s, а после перехода на HS200 измеренная скорость поднимается до 232-235 MB/s. То есть старым узким местом действительно был интерфейс между SoC и eMMC-контроллером.

Здесь действует та же оговорка: 232-235 MB/s выглядят выше ожидаемого потолка для стандартного HS200 @ 200 MHz. Поэтому эти значения стоит воспринимать как измеренный результат нашей конфигурации. Возможные причины — фактическая частота шины выше той, что видна в простом описании режима, или влияние кэша/методики чтения.

С записью интереснее. В HS200 короткая запись 1G достигает 118 MB/s, но длинная запись 10G снижается до 101 MB/s. Это похоже на более заметное влияние внутренней архитектуры накопителя: контроллера, NAND, SLC cache и фоновых операций. Иными словами, быстрый интерфейс снял старый потолок, но дальше модуль уже упирается в собственные возможности записи.

Архивация и распаковка /usr

Тест HS HS200 Изменение
tar /usr 12m20s 10m36s быстрее на 14.1%
untar /usr 3m12s 1m49s быстрее на 43.1%

В реальном сценарии результат похож на FORESEE: архивация ускоряется умеренно, потому что заметную долю времени занимает CPU и gzip-сжатие. Распаковка выигрывает сильнее, так как там больше операций записи на накопитель и работы файловой системы.

Главный вывод по Samsung KLMCG2UCTB-B041: переход на HS200 особенно сильно ускоряет чтение, но запись растет не так драматично, как у FORESEE. Это хороший пример того, почему одного режима интерфейса недостаточно для полной оценки eMMC: два модуля могут одинаково перейти в HS200, но вести себя по-разному из-за NAND, контроллера и прошивки.

Сводная таблица результатов #

Если собрать оба модуля в одну таблицу, хорошо видно главное: в наших тестах переход на HS200 почти одинаково сильно помогает последовательному чтению, но скорость записи уже заметно зависит от самого eMMC.

Модуль Объем Read 10G HS Read 10G HS200 Write 10G HS Write 10G HS200 tar /usr HS → HS200 untar /usr HS → HS200
FORESEE FEMDNN256G-A3V01 256 GB 45.5 MB/s 236 MB/s 42.7 MB/s 188 MB/s 12m41s → 10m34s 3m09s → 1m42s
Samsung KLMCG2UCTB-B041 64 GB 44.8 MB/s 232 MB/s 42.3 MB/s 101 MB/s 12m20s → 10m36s 3m12s → 1m49s
Модуль Read 10G прирост Write 10G прирост tar /usr быстрее untar /usr быстрее
FORESEE FEMDNN256G-A3V01 5.19x 4.40x 16.7% 45.9%
Samsung KLMCG2UCTB-B041 5.18x 2.39x 14.1% 43.1%

По чтению оба модуля ведут себя почти одинаково: HS ограничивает их уровнем около 45 MB/s, а после перехода на HS200 измеренная скорость оказывается выше 230 MB/s. С учетом теоретического потолка стандартного HS200 @ 200 MHz эти цифры нужно читать вместе с оговоркой выше: вероятно, сказалась фактическая частота шины или особенности методики чтения. По записи разница уже большая: FORESEE в длинном 10G-тесте держит 188 MB/s, а Samsung — 101 MB/s. Это как раз тот случай, когда интерфейс уже не единственное узкое место, и начинают проявляться контроллер, NAND, кэширование и прошивка конкретного eMMC.

Итоги #

В этой работе мы начали с довольно простой практической проблемы: eMMC-модуль должен был нормально работать на Repka Pi в Repka OS, но стандартного драйвера оказалось недостаточно. В итоге задача выросла в полноценную доработку поддержки MMC-контроллера: мы перенесли и адаптировали BSP-драйвер, добавили поддержку HS200, реализовали tuning и проверили результат на реальных eMMC-модулях.

Главный технический результат — Repka Pi больше не ограничивает eMMC режимом HS. После перехода на HS200 последовательное чтение у двух разных модулей в наших тестах выросло примерно с 45 MB/s до 232-236 MB/s, то есть более чем в пять раз. При этом сами значения выше 200 MB/s стоит воспринимать как результат конкретной связки драйвера, тактирования и методики теста, а не как универсальный потолок режима HS200. Для системного накопителя прирост заметен не только в синтетике: быстрее читаются большие файлы, быстрее распаковываются архивы, отзывчивее становятся сценарии, где ОС активно работает с диском.

При этом тесты хорошо показали, что быстрый интерфейс — это только часть истории. На чтении оба модуля ведут себя почти одинаково, потому что раньше они упирались в потолок HS, а дальше на результат уже влияет вся конфигурация теста. На записи разница между ними большая: FORESEE FEMDNN256G-A3V01 в длинном тесте держит 188 MB/s, а Samsung KLMCG2UCTB-B041101 MB/s. Значит, после снятия интерфейсного ограничения становятся видны внутренние отличия eMMC: NAND, контроллер, прошивка, кэширование, параллелизм и работа FTL.

Практический вывод простой: для Repka Pi режим HS200 стоит включать и использовать, если модуль и плата работают стабильно. Но выбирать eMMC только по объему или заявленному режиму интерфейса нельзя. Два накопителя с поддержкой HS200 могут показывать близкие результаты на чтении, но заметно отличаться по записи и поведению в реальных сценариях.

Именно поэтому для системного накопителя важны не только красивые MB/s в коротком тесте, но и длинная запись, распаковка большого количества файлов, стабильность после заполнения кэша и повторяемость результатов. HS200 снимает старый потолок, а дальше уже становится видно качество самого eMMC.

Отдельно хочется подчеркнуть: работа над драйвером на этом не заканчивается. Сейчас мы получили стабильный HS200, но впереди остаются задачи по дальнейшей проверке разных eMMC-модулей, улучшению диагностики, возможной работе с HS400 и постепенному приведению кода в более удобный для сопровождения вид. Репозиторий ядра открыт по ссылке gitflic.ru/project/npo_rbs/repka-os_kernel, поэтому все желающие могут подключаться к доработке драйвера: смотреть код, проверять его на своих модулях, присылать результаты тестов, issue и патчи.


0

Комментарии (0)

Для участия в обсуждении Вы должны быть авторизованным пользователем

Еще посты по теме

Новые посты



Наиболее интересные по мнению читателей



Темы

Навигация

ВойтиРегистрация