Разработка драйвера флеш-памяти

В статье приведён обзор общего подхода к разработке драйверов данного типа


Note: В публичном репозитории можно изучить реализацию опубликованных примеров реальных драйверов.

Список подразделов:

Введение
Структура драйвера
Слои dispatch*(), resmgr*() и iofunc*()
Файловая система флеш-памяти
Службы сокетов
Службы флеш-памяти
Функция сканирования
Разработка собственного драйвера файловой системы флеш-памяти
Дерево исходного кода
Makefile
Сборка драйвера
Функция main()
Интерфейс служб сокетов
Анализ параметров
Интерфейс служб флеш-памяти
Выбор подходящих реализаций callback-функций библиотеки libmtd-flash.a
Шаг 1: исключение необычных конфигураций
Шаг 2: изучение доступных материалов
Шаг 3: выбор подходящих callback-функций
Пример драйвера (devf-ram)
main()
f3s_ram_open()
f3s_ram_page()

Введение

В состав ЗОСРВ «Нейтрино» входят несколько готовых драйверов файловых систем флеш-памяти, которые предназначены для конкретных встраиваемых систем. Эти драйверы находятся в каталоге ${KPDA_TARGET}/${CPU}/sbin. Драйверы файловых систем флеш-памяти имеют схему именования devf-*.

Если в ЗОСРВ «Нейтрино» отсутствует драйвер для конкретной целевой встраиваемой системы, следует сначала опробовать «общий» драйвер ( devf-generic), который достаточно часто (но не всегда) штатно работает со стандартной флеш-памятью. Для работы этому драйверу необходима поддержка подсистемы MTD (Memory Technology Driver, драйвер запоминающего устройства) и линейной адресации памяти.

Если ни один из наших драйверов не подходит для вашего устройства, вам необходимо разработать драйвер самостоятельно. В составе BSP обычно предоставляется исходный код, достаточный для разработки производного драйвера файловой системы флеш-памяти. Отправной точкой является каталог рабочий_каталог_bsp/src/hardware/flash/boards.

Помимо каталога boards, информация о платах/драйверах, которые поддерживаются на текущий момент, содержится в следующих источниках:

Обратите внимание, что в настоящее время мы поддерживаем только модификацию драйверов для систем со встроенной флеш-памятью (которая также называется резидентным массивом флеш-памяти — Resident Flash Array, RFA). Если вам требуется поддержка съемных накопителей (например, устройств PCMCIA, компактных или миниатюрных карт памяти), следует обратиться в адрес нашей технической поддержки.

Структура драйвера

Каждый драйвер файловой системы флеш-памяти состоит из следующих компонентов:

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

flashfs.png
Рисунок 1. Структура драйвера флеш-памяти

Слои dispatch*(), resmgr*() и iofunc*()

Как и все менеджеры устройств ЗОСРВ «Нейтрино», файловая система флеш-памяти использует стандартный интерфейс resmgr*() / iofunc*() и принимает стандартный набор сообщений для менеджеров ресурсов. Файловая система реагирует на эти сообщения выполнением операций чтения, записи и удаления данных из флеш-памяти.

Например, сообщение open() активизирует код, который считывает структуры данных файловой системы флеш-накопителя и обнаруживает требуемый файл. Последующее сообщение write() приводит к изменению содержимого файла на флеш-накопителе. Специальные функции, такие как стирание данных флеш-устройства, реализованы с помощью сообщений devctl().

Файловая система флеш-памяти

Файловая система флеш-памяти является основным компонентом драйвера. Она содержит весь код, который обрабатывает запросы к файловой системе флеш-устройства и управляет ей. Для доступа к флеш-устройству файловая система использует службы сокетов и флеш-памяти.

Код файловой системы флеш-памяти не зависит от платформы и находится в библиотеке libfs-flash3.a.

Службы сокетов

Службы сокетов выполняют системно-зависимую инициализацию флеш-устройств при запуске и обеспечивают их адресуемость (в основном это касается флеш-интерфейсов с окнами).

Перед выполнением операций чтения/записи над флеш-устройством другие компоненты проверяют доступность требуемого диапазона адресов с помощью служб сокетов. Если флеш-устройство отображено в линейное адресное пространство процессора, можно обеспечить его адресуемость; в системах, где флеш-память реализована в виде устройства с переключением банков или скрыта за другим интерфейсом (например, PCMCIA), эта задача несколько усложняется.

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

Службы флеш-памяти

Службы флеш-памяти содержат код, который записывает и стирает данные на конкретном флеш-устройстве. Этот компонент также называется драйвером запоминающего устройства (Memory Technology Driver, MTD).

Каталог ${KPDA_TARGET}/${CPU}/lib содержит библиотеку MTD с именем libmtd-flash.a, которая работает с поддерживаемыми флеш-устройствами.


Note: Исходный код библиотеки libmtd-flash.a обычно размещен в каталоге рабочий_каталог_bsp/src/hardware/flash/mtd-flash.

Функция сканирования

Функция сканирования определяет размер массива флеш-памяти с помощью специального алгоритма. Поскольку ее исходный код открыт, разработчик может беспрепятственно обнаруживать ошибки, связанные с некорректным определением объема флеш-накопителей.

Разработка собственного драйвера файловой системы флеш-памяти

Перед разработкой собственного драйвера файловой системы флеш-памяти следует изучить исходный код примеров драйверов. Скорее всего, один из существующих драйверов можно легко адаптировать для конкретной встраиваемой системы. В противном случае можно взять за основу исходный код менеджера devf-ram.

Дерево исходного кода

Исходный код файловых систем флеш-памяти имеет следующую структуру:

рабочий_каталог_bsp/src/hardware/ |--> ipl/ |--> startup/ `--> flash/ |--> boards/ | |--> generic/ | |--> ram/ | `--> ... | `--> mtd-flash/ |--> amd/ |--> fujitsu/ |--> hynix/ |--> intel/ |--> numonyx/ |--> rom/ |--> sharp/ |--> sram/ `--> ...

Следующие каталоги также относятся к файловым системам флеш-памяти:

${KPDA_TARGET}/usr/include/sys
Заголовочный файл f3s_mtd.h.
${KPDA_TARGET}/usr/include/fs
Заголовочные файлы f3s_api.h, f3s_socket.h и f3s_flash.h.
${KPDA_TARGET}/${CPU}/lib
Библиотеки файловой системы и служб флеш-памяти.
рабочий_каталог_bsp/src/hardware/flash/boards
Исходный код служб сокетов.
рабочий_каталог_bsp/src/hardware/flash/mtd-flash
Исходный код служб флеш-памяти, функций сканирования и вспомогательных функций.

Перед внесением изменений в исходный код следует:

  1. создать для драйвера новый подкаталог в каталоге рабочий_каталог_bsp/src/hardware/flash/boards
  2. скопировать в него файлы из каталога выбранного примера

Например, чтобы создать драйвер myboard на основе примера для платы 800FADS, выполните следующие команды:

cd рабочий_каталог_bsp/hardware/flash/boards mkdir myboard cp -cRv 800fads myboard cd myboard make clean

Команда cp с параметром -R (рекурсивное копирование) копирует все файлы, которые находятся в каталоге исходного кода, в том числе подкаталог процессора, для которого требуется создать драйвер. В вышеуказанном примере каталог 800fads содержит подкаталог ppc, где выполняется сборка драйвера myboard для процессора PowerPC.

Makefile

При сборке нового драйвера файловой системы флеш-памяти не требуется вносить изменения в Makefile. Его рекурсивная структура обеспечивает корректную компоновку библиотек.

Сборка драйвера

Сборка драйвера выполняется командой:

make F3S_VER=3 MTD_VER=2

Функция main()

Функция main() драйвера находится в файле main.c в подкаталогах примеров. С нее начинается адаптация драйвера для конкретной встраиваемой системы. Рассмотрим файл main.c для платы 800FADS:

/*
* Файл: main.c для платы 800FADS
*/
#include <sys/f3s_mtd.h>
#include "f3s_800fads.h"
int main( int argc, char **argv )
{
int error;
static f3s_service_t service[] = { { sizeof( f3s_service_t ),
f3s_800fads_open,
f3s_800fads_page,
f3s_800fads_status,
f3s_800fads_close },
{ /* обязательная последняя запись */
0, 0, 0, 0, 0 } };
static f3s_flash_v2_t flash[] = { { sizeof( f3s_flash_v2_t ),
f3s_a29f040_ident, /* общее обнаружение */
f3s_a29f040_reset, /* общий сброс */
/* v1 чтение/запись/стирание/приостановка/возобновление/синхронизация
* (не используются) */
NULL, NULL, NULL, NULL, NULL, NULL,
NULL, /* v2 чтение(по умолчанию) */
f3s_a29f040_v2write, /* v2 запись */
f3s_a29f040_v2erase, /* v2 стирание */
f3s_a29f040_v2suspend, /* v2 приостановка */
f3s_a29f040_v2resume, /* v2 возобновление */
f3s_a29f040_v2sync, /* v2 синхронизация */
/* v2 проверка блокировки/блокировка/разблокировка/полная разблокировка
* (не поддерживаются) */
NULL, NULL, NULL, NULL },
{ /* обязательная последняя запись */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } };
/* инициализация f3s */
f3s_init( argc, argv, flash );
/* запуск f3s драйвера */
error = f3s_start( service, flash );
return error;
}

Массив service содержит одну или несколько структур f3s_service_t в зависимости от количества различных сокетов, которые должен поддерживать драйвер. Структура f3s_service_t определена в заголовочном файле <fs/f3s_socket.h> и содержит указатели на функции служб сокетов.

Массив flash содержит одну или несколько структур f3s_flash_t в зависимости от количества типов флеш-устройств, которые должен поддерживать драйвер. Структура f3s_flash_t определена в заголовочном файле <fs/f3s_flash.h> и содержит указатели на функции служб флеш-памяти.

Функции f3s_init() и f3s_start() определены в заголовочном файле <fs/f3s_api.h>.


Caution: Не используйте заголовочные файлы <fs/f3s_socket.h>, <fs/f3s_flash.h> и <fs/f3s_api.h> непосредственно. Следует подключать в коде драйвера заголовочный файл <sys/f3s_mtd.h> для обеспечения прямой и обратной совместимости.

В файле main.c необходимо задавать:

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

Интерфейс служб сокетов

Интерфейс служб сокетов определен в заголовочном файле <fs/f3s_socket.h> и включает в себя следующие функции:

и callback-и структуры f3s_service_t:

Параметр socket в обработчиках используется для передачи аргументов службам сокетов, возврата результатов их работы и хранения информации о каждом сокете. Для взаимодействия со сложными интерфейсами, такими как PCMCIA, определена структура, в которой можно указывать несколько сокетов с несколькими окнами. Обычный массив линейной флеш-памяти имеет один сокет без окон.

Анализ параметров

В службах сокетов следует анализировать все применимые параметры перед инициализацией флеш-устройств с помощью функции f3s_*_open(). Для этого используются две функции:

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

Интерфейс служб флеш-памяти

Интерфейс служб флеш-памяти определен в заголовочном файле <fs/f3s_flash.h> и включает в себя следующие функции:

Изучая перечень реализаций данных callback-функций, представленный в заголовочном файле sys/f3s_mtd.h, в подавляющем числе случаев не требуется разрабатывать собственные, сосредоточившись на выборе наиболее подходящих реализаций из состава библиотеки libmtd-flash.a (см. следующий параграф).


Note: Значения параметра flags определены в заголовочном файле <fs/s3s_flash.h>. Самым важным является флаг F3S_VERIFY_WRITE — если он установлен, функция должна проверять успешность записи посредством контрольного чтения. Тем не менее, даже этот механизм иногда не обнаруживает ошибки при записи.

Выбор подходящих реализаций callback-функций библиотеки libmtd-flash.a

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

Существует две версии API MTD: f3s_flash_t и f3s_flash_v2_t. Причем, более новая версия интерфейса обратно совместима с исходной. Детали реализации можно узнать на страницах описания указанных типов. Для каждой callback-функции, присутствующей в дескрипторе службы флеш-памяти, существуют штатные библиотечные реализации, подходящие в подавляющем большинстве случаев. Они предоставляются потребителю в составе библиотеки libmtd-flash.a.

В идеале должна существовать таблица соответствия библиотечных реализаций callout-функций и конкретных экземпляров оборудования/флеш-памяти. Так, например, чтобы воспользоваться функцией f3s_*_v2erase() на 16-разрядном устройстве Intel, следует вызвать функцию f3s_iCFI_v2erase(). Поскольку в общем случае эта задача не реалистичная, в данном параграфе приводятся общие рекомендации по выбору конкретных реализаций. Для этого требуется:

Шаг 1: исключение необычных конфигураций

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

Примеры нестандартных конфигураций:

Особые ограничения выравнивания
Некоторые устройства требуют, чтобы доступ к памяти осуществлялся строго определенными порциями или с заданным выравниванием. В этом случае библиотека будет генерировать непредсказуемые явления (повреждение данных, SIGBUS или SIGSEGV) при чтении.
Неправильный порядок байтов
Некоторые устройства имеют неправильно подключенную флеш-память, из-за которой байты имеют неправильный порядок байтов. Это требует значительных пользовательских модификаций кода (переворачивание данных) многих вызовов MTD для записи команд и чтения данных CFI (см. далее) с прямым порядком байтов.
PCMCIA
В редких случаях поддерживаются некоторые устройства хранения данных PC Card на базе флеш-памяти NOR. Это требует особого взаимодействия с драйвером PCMCIA.
Доступ через контроллер SDRAM
Большинство флеш-чипов Micron используют интерфейс, основанный на командах шины SDRAM. Для их обслуживания необходимо написать специальный код для доступа к контроллеру памяти платы.
NAND флеш-память
CompactFlash, SD Card, SmartMedia, Sony MemoryStick и т.п. Такие устройства используют флеш-память NAND и не поддерживаются флеш-драйверами devf-*. Флеш-память NAND полностью отличается от NOR-флеш. Для таких устройств должен использоваться etfs-драйвер.
SRAM
Драйвер devf-ram может работать с некоторыми SRAM с батарейным питанием. Их необходимо оценивать в каждом конкретном случае. В таких экспериментах мы не гарантируем устойчивость драйвера к сбоям питания. Это связано с тем, что у SRAM совершенно другой способ обслуживания отказов, чем у NOR флеш-памяти.

Шаг 2: изучение доступных материалов

Полный исходный код библиотеки драйверов MTD может поставляться в составе BSP. В этом случае он доступен в каталоге рабочий_каталог_bsp/libs/src/hardware/flash/mtd-flash. Callback-функции сгруппированы по производителям (например, intel/iCFI_write.c). Каждый из этих файлов содержит комментарии, описывающие тип флеш-памяти, с которой они работают.

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

Шаг 3: выбор подходящих callback-функций

Определение callback-обработчика f3s_*_read()

В большинстве случаев достаточно установить для указателя в f3s_flash_t значение NULL. Это заставляет библиотеку использовать memcpy() для чтения непосредственно из флеш-памяти.

Пример:

#include <sys/f3s_mtd.h>
int32_t f3s_mtd_read( f3s_dbase_t *dbase, f3s_access_t *access, uint32_t flags,
uint32_t offset, int32_t size, uint8_t *buffer )
{
uint8_t *memory = NULL;
/* Установка корректной страницы сокета */
memory = (uint8_t *)access->service->page( &access->socket, F3S_POWER_ALL, offset, NULL );
if ( memory == NULL ) {
fprintf( stderr, "%s: %d page() returns NULL\n", __func__, __LINE__ );
return (-1);
}
/* (!!!) Код, который может потребоваться переопределить в нетипичных сценариях */
memcpy( buffer, memory, size );
return (size);
}


Note: В составе BSP файл <sys/f3s_mtd.h> находится в каталоге рабочий_каталог_bsp/src/hardware/flash/mtd-flash/public/sys/f3s_mtd.h.

Определение callback-обработчика f3s_*_ident()

В настоящее время существует две основных реализации вызова f3s_*_ident(): для CFI (Common Flash Interface) и остальных. Большинство современных флеш-чипов поддерживают спецификацию CFI, а также общий метод идентификации флеш-чипа и его возможностей. Если ваш чип поддерживает спецификацию, следует использовать обработчик специфичный для CFI. Альтернативой является использование жестко закодированной таблицы распознаваемых флеш-идентификаторов. Использовать их следует лишь в крайнем случае.

Определение callback-обработчика f3s_*_write()

Память может быть записана либо словами (по 8 или 16 бит), либо буферами (несколько слов за раз). Все чипы поддерживают запись по одному слову, поэтому этот выбор всегда возможен, но не всегда оптимален. Более сложные чипы поддерживают буферизованную запись, которая значительно быстрее. К последним можно отнести Intel StrataFlash и AMD MirrorBit.

Просматривая документацию на память AMD, не путайте режим записи "Unlock Bypass" с буферизованной записью. Этот режим устраняет только дополнительное подтверждение после записи каждого слова. Реальные режимы буферизации собирают несколько слов во внутренний буфер и обслуживают их параллельно.

Определение callback-обработчика f3s_*_erase()

Обычно существует единственный вариант.

Определение callback-обработчика f3s_*_sync()

Для MTDv1 ( f3s_flash_t) обычно существует два типа обработчиков: для флеш-памяти загрузочного модуля и для обычной флеш-памяти. Первый тип имеет два разных размера для блоков стирания. Такому вызову необходимо знать размер блока, который он хочет стереть. Обычный обработчик не нуждается в дополнительной логике. Если вы не уверены, выберите обработчик для флеш-памяти загрузочного модуля — он несколько медленнее, но универсален для любого типа флеш-памяти.

Если вам нужно использовать данный callback-обработчик, обычно вариант выбора только один.

Определение callback-обработчиков f3s_*_suspend() и f3s_*_resume()

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

Определение callback-обработчиков f3s_*_islock(), f3s_*_lock(), f3s_*_unlock() и f3s_*_unlockall()

Блокировки являются возможностью лишь нового API библиотеки MTD (MTDv2, см. f3s_flash_v2_t). Поддержка блокировок становится очевидной из документации на флеш-память. Для таких микросхем, поддерживающих защиту от записи на уровне блоков, существует две разные реализации: постоянная и энергозависимая.

Пример драйвера (devf-ram)

В этом драйвере файловая система флеш-памяти хранится в основной памяти, а не на флеш-накопителе. По этой причине файловая система не является постоянной — при перезагрузке системы или удалении каталога /dev/shmem/fs0 все данные теряются. Этот драйвер используется преимущественно для тестирования.

main()

В функции main() объявляется один массив services для функций служб сокетов и нулевая запись для функций служб флеш-памяти.

/*
* Файл: f3s_ram_main.c
* Описание: файл содержит функцию main для файловой системы флеш-памяти f3s
*/
#include "f3s_ram.h"
int main( int argc, char **argv )
{
int error;
static f3s_service_t service[] = { { sizeof( f3s_service_t ),
f3s_ram_open,
f3s_ram_page,
f3s_ram_status,
f3s_ram_close },
{ /* обязательная последняя запись */
0, 0, 0, 0, 0 } };
static f3s_flash_v2_t flash[] = { { sizeof( f3s_flash_v2_t ),
f3s_sram_ident, /* общее обнаружение */
f3s_sram_reset, /* общий сброс */
NULL, /* v1 чтение (устаревшая функция) */
NULL, /* v1 запись (устаревшая функция) */
NULL, /* v1 стирание (устаревшая функция) */
NULL, /* v1 приостановка (устаревшая функция) */
NULL, /* v1 возобновление (устаревшая функция) */
NULL, /* v1 синхронизация (устаревшая функция) */
NULL, /* v2 чтение (по умолчанию) */
f3s_sram_v2write, /* v2 запись */
f3s_sram_v2erase, /* v2 стирание */
NULL, /* v2 приостановка (не используется) */
NULL, /* v2 возобновление (не используется) */
f3s_sram_v2sync, /* v2 синхронизация */
f3s_sram_v2islock, /* v2 проверка наличия блокировки */
f3s_sram_v2lock, /* v2 блокировка */
f3s_sram_v2unlock, /* v2 снятие блокировки */
f3s_sram_v2unlockall /* v2 всеобщее снятие блокировки */ },
{ /* обязательная последняя запись */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } };
/* инициализация f3s */
f3s_init( argc, argv, (f3s_flash_t *)flash );
/* запуск f3s драйвера */
error = f3s_start( service, (f3s_flash_t *)flash );
return (error);
}

f3s_ram_open()

В функции open() служб сокетов присваивается имя драйверу, а затем обрабатываются переданные ему параметры. При отсутствии параметров задается размер по умолчанию и выделяется память для (виртуального) флеш-накопителя.

/*
* Файл: f3s_ram_open.c
* Описание: файл содержит функцию открытия для библиотеки ram
*/
#include "f3s_ram.h"
int32_t f3s_ram_open( f3s_socket_t *socket, uint32_t flags )
{
static void *memory;
char name[8];
int fd;
int flag;
/* выполнена ли инициализация */
if ( !memory )
{
/* получение привилегий ввода/вывода */
ThreadCtl( _NTO_TCTL_IO, NULL );
/* присвоение имени сокету */
socket->name = "RAM (имитация флеш-устройства)";
/* заданы ли параметры сокета */
if ( f3s_socket_option( socket ) )
socket->window_size = 1024 * 1024;
/* выбран ли размер массива */
if ( !socket->array_size )
socket->array_size = socket->window_size;
/* задан ли размер массива */
if ( !socket->array_size )
return (ENXIO);
/* присвоение имени общей памяти */
sprintf( name, "/fs%X", socket->socket_index );
/* открытие общей памяти */
fd = shm_open( name, O_CREAT | O_RDWR, 0777 );
if ( fd < 0 )
return (errno);
/* присвоение размера общей памяти */
flag = ftruncate( fd, socket->array_size );
if ( flag )
{
close( fd );
return (errno);
}
/* отображение физического адреса в память */
memory = mmap( NULL, socket->array_size, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, socket->address );
if ( !memory )
{
close( fd );
return (errno);
}
/* копирование дескриптора сокета */
socket->socket_handle = (void *)fd;
}
/* присвоение ранее инициализированного значения указателю на память сокета */
socket->memory = memory;
return (EOK);
}

f3s_ram_page()

В функции page() служб сокетов мы сначала проверяем, что заданное значение offset находится в пределах выделенной памяти, а затем при необходимости присваиваем значение size размеру окна. Эта функция возвращает адрес смещения по модулю размера окна.

/*
* Файл: f3s_ram_page.c
* Описание: файл содержит функцию доступа к странице для библиотеки ram
*/
#include "f3s_ram.h"
uint8_t * f3s_ram_page( f3s_socket_t *socket, uint32_t flags, uint32_t offset, int32_t *size )
{
/* находится ли смещение в пределах массива */
if ( offset >= socket->window_size )
{
errno = ERANGE;
return (NULL);
}
/* выбор подходящей страницы */
socket->window_offset = offset & ~(socket->window_size - 1);
/* задание корректного размера */
*size = min( (offset & ~(socket->window_size - 1)) +
socket->window_size - offset, *size );
/* возврат указателя */
return (socket->memory + offset);
}

Функции status() и close() этого драйвера не выполняют каких-либо заслуживающих внимания действий.




Предыдущий раздел: Библиотеки разработки драйверов флеш-памяти