EFSL: добавление поддержки новой аппаратуры носителя данных Печать
Добавил(а) microsin   

Перевод раздела "6.3 Adding support for a new endpoint (0.2)", стр. 36, документации EFSL Embedded Filesystems Library - 0.3, 2005 г.

[6.3 Добавление поддержки новой аппаратуры (new endpoint)]

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

Сначала рассмотрим основной принцип взаимодействия объектов EFSL.

EFSL-porting-common-diagram

Как Вы можете видеть, мы сделали линейную модель взаимодействия объектов, которая довольно проста. Файл (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-porting-disk-and-sectors

В этом примере мы создадим для 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.