Перевод раздела "6.3 Adding support for a new endpoint (0.2)", стр. 36, документации EFSL Embedded Filesystems Library - 0.3, 2005 г.
[6.3 Добавление поддержки новой аппаратуры (new endpoint)]
Эта секция дает пошаговые инструкции, как сделать портирование EFSL на другую аппаратуру носителя данных. Это может потребоваться в том случае, если разработчик не сможет найти подходящей реализации обращения к своей аппаратуре хранения данных. Сначала рассмотрим основной принцип взаимодействия объектов EFSL.
Как Вы можете видеть, мы сделали линейную модель взаимодействия объектов, которая довольно проста. Файл (File) в файловой системе (Filesystem) работает в соответствии с её специфичной организацией. Ниже мы найдем объект раздела (Partition), который отвечает за трансляцию адресации относительно раздела (partition relative addressing) в адресацию относительно диска (disc-based LBA addressing, LBA переводится как Logical Block Addressing, см. [1]).
Объект диска (Disc) хранит в себе таблицу разделов (partition table), и имеет прямое взаимодействие с менеджером кэша IOMan (см. [2]). Внутри IOMan все запросы к секторам диска объединяются друг с другом. IOMan проверяет, какие сектора нужно прочитать непосредственно с диска, а какие - из памяти кэша (если эти сектора ранее уже были прочитаны с диска), и какие сектора должны быть синхронизированы с диском (записаны обратно на диск). Если требуется, то при чтении/записи диска запросы переводятся на самый нижний уровень - объект аппаратного интерфейса (hwInterface).
Аппаратный интерфейс hwInterface отвечает за 3 функции: - инициализация аппаратуры (например, для карт SDC/MMC это может быть инициализация интерфейса SPI и подготовка режима обмена с картой). - чтение секторов с диска. - запись секторов на диск.
Все запросы базируются на основе адреса сектора, где сектор - часть диска, блок из 512 байт, который выровнен на 512-байтную границу байтового адреса.
В этом примере мы создадим для EFSL новый порт и добавим поддержку переноса данных на примере аппаратного интерфейса "pigeon carrier" ("голубиная почта"). Инициализация аппаратуры состоит в том, что нужно накормить голубя и сказать ему, где находятся данные. Чтение/запись повлечет за собой предоставление птице сектора и возможности полететь. Выполним следующие шаги:
1. Выбор имени для нашего порта. Нам понадобится имя, чтобы создать нужные определения в исходном коде. В нашем примере было выбрано имя PIGEON_CARRIER (ГОЛУБИНАЯ_ПОЧТА). Соответствующее конечное имя для аппаратуры получилось HW_ENDPOINT_PIGEON_CARRIER.
2. Проверка размерности целых чисел. Откройте файл inc/types.h и создайте новую запись для нашей голубиной почты. Возможно, что нас уже устроит существующий набор типов, и можно сделать простое копирование.
3. Добавление нашего порта в файл interface.h. Найдите файл interface.h в папке inc/. Добавьте секцию для голубиной почты (выше секции #else ... NO_INTERFACE_DEFINED).
#if defined (HW_ENDPOINT_0) #include ”interfaces/0.h” #elif defined (HW_ENDPOINT_1) #include ”interfaces/1.h” #elif defined (HWENDPOINT_PIGEON_CARRIER) #include ”interfaces/pigeon.h” #else #error ”NO INTERFACE DEFINED - see interface.h” #endif
4. Выберите Ваш порт в файле conf/config.h (определением HWENDPOINT_PIGEON_CARRIER).
5. Создайте файлы исходного кода. Создайте файл заголовка (*.h) в папке inc/ и файл модуля (*.c) в папке src/interfaces. Для нашего примера это будут файлы pigeon.h и pigeon.c соответственно.
6. Добавьте компиляцию и линковку нового модуля в Makefile, либо настройте это в Вашей среде разработки.
Базовая работа проделана, теперь осталось написать код, который будет выполнять реальную работу.
[6.3.1 hwInterface]
Эта структура представляет образ нижележащего аппаратного обеспечения. В ней имеется отдельное поле, которое обязательно должно присутствовать (так как EFSL использует его), однако в него Вы можете добавить и другие поля, которые могут потребоваться для доступа Вашего драйвера к аппаратуре. Основное правило для встраиваемых систем - рекомендуется иметь эту структуру как можно меньше по размеру. Пример:
struct hwInterface { /* Поле, специально вставленное для ИМЕННО ЭТОЙ аппаратуры */ Pigeon pigeon ;
/* Обязательные поля для EFSL (для текущей версии EFSL нужно только одно поле) */ euint32 sectorCount; }; typedef struct hwInterface hwInterface;
[6.3.2 if_initInterface]
Эта функция будет вызвана только один раз, когда объект аппаратуры будет инициализирован вызовом efs_init(). Код в процедуре if_initInterface переводит аппаратуру в состояние готовности к использованию. Прототип функции if_initInterface:
esint16 if_initInterface(hwInterface *hw, euint8* opts);
Не обязательно, однако желательно, чтобы Вы заполнили поле hw->sectorCount количеством секторов на носителе данных. Это поле используется для проверки корректности запросов к секторам. В случае успешной инициализации функция if_initInterface должна вернуть 0. Пример функции if_initInterface:
esint16 if_initInterface ( hwInterface *hw, euint8 *opts ) { /* Обработка опций. Вашей программе опции могут и не понадобиться. */ parse_options(opts);
/* Проверка состояния аппаратуры */ if (!alive(hw->pigeon)) { // printf (”Голубь умер! :-(\n” ); return (DEAD PIGEON) ; /* #define DEAD_PIGEON -1 */ }
/* Инициализация аппаратуры */ feed (hw->pigeon); //покормите голубя pet (hw->pigeon); //приласкайте голубя
/* Получите количество секторов */ hw->sectorCount = ask_pigeon_num_sectors (hw->pigeon);
return (0); }
[6.3.3 if_readBuf]
Эта функция отвечает за чтение сектора с диска и сохранение его данных в буфере пользователя. На входе функции - объект аппаратуры, адрес и указатель на память, где находится буфер сектора размером в 512 байт. Пожалуйста, будьте особо внимательны к границам буферов, так как обычно функцию if_readBuf будет вызывать IOMan, и если у Вас случится переполнение буфера, то может повредиться содержимое кэша, что наверняка повлечет за собой трудно отлавливаемые ошибки и непредсказуемое поведение системы. Прототип функции if_readBuf следующий:
esint16 if_readBuf(hwInterface *hw, euint32 address, euint8* buf);
Параметр address задается в адресации LBA, относительно начала диска. Это означает, что если представить весь диск как огромный байтовый массив с побайтовой адресацией byte_address, то второй параметр address функции должен быть вычислен по формуле:
address = byte_address / 512;
Если Вы обращаетесь к старому диску, или к аппаратуре, которая имеет другую систему адресации данных, то нужно будет пересчитать адрес, чтобы привести его к нужной схеме адресации. Имейте в виду, что нет поддержки для секторов, которые имеют размер не равный 512 байт. В случае успеха функция if_readBuf должна возвратить 0. Пример простейшей реализации функции чтения if_readBuf:
esint8 if_readBuf (hwInterface *hw, euint32 address, euint8 *buf) { Message new_message ;
new_message.address = address; new_message.command = READ;
pigeon_send (hw->pigeon, new_message); /* Взлет голубя */ while(!pigeon_returned (hw->pigeon)); /* Ждем, когда птица вернется */ memcpy(new_message.data, buf, 512); /* Копирование буфера */ return (0); }
[6.3.4 if_writeBuf]
Функция if_writeBuf отвечает за запись данных, и работает точно по такому же принципу, как и if_readBuf. Параметры функции if_writeBuf те же самые, просто третий параметр указывает на буфер размером 512 байт, откуда должны браться данные для записи на диск.
[Ссылки]
1. Описание LBA на Википедии. 2. EFSL: I/O Manager (менеджер ввода/вывода). 3. Библиотека FatFS: модуль файловой системы FAT. |