В этой части мы обсудим изменения в коде SsdSim для поддержки асинхронной передачи данных хоста и добавления возможности обработки случая, когда мы не можем получить ресурс буфера. На самом деле было бы разумнее рассказать об этом вместе с Часть 14, но код может немного измениться, и я бы хотел, чтобы он был пошаговым, с минимальным прогрессом изменений, насколько это возможно.

Для поддержки асинхронной передачи данных хоста мы можем начать с нашего собственного примера с NandHal. В настоящее время NandHal поддерживает асинхронные операции благодаря следующим характеристикам:

  1. Реализует асинхронный интерфейс для очереди запросов команд или операций.
  2. Выполняется в отдельном потоке (для имитации работы оборудования в асинхронном режиме)
  3. Реализует интерфейсы для запроса и получения завершений

Мы начнем с переименования CustomProtocolInterface в CustomProtocolHal, чтобы он был аналогичен NandHal, и сделаем его производным классом от класса FrameworkThread. Мы добавим механизмы для обработки отправки и завершения команд, как показано ниже:

struct TransferCommandDesc;
class TransferCommandListener
{
public:
  virtual void HandleCommandCompleted(const TransferCommandDesc& command) = 0;
};
struct TransferCommandDesc
{
  enum class Direction
  {
    In,
    Out
  };
  CustomProtocolCommand* Command;
  U32 SectorIndex;
  Direction Direction;
  Buffer Buffer;
  NandHal::NandAddress NandAddress;
  TransferCommandListener* Listener;
};
void QueueCommand(const TransferCommandDesc& command);

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

Ранее для завершения команд NandHal поддерживал единую внутреннюю очередь завершения и помещает завершенные команды в очередь. На «вызывающего» (любого вызывающего) остается запросить очередь и обработать завершение. Новый механизм позволит NandHal отправлять завершения команд в конкретную очередь завершения, специфичную для отправителя команды. Это достигается тем, что клиент предоставляет Listener при отправке дескриптора команды. Класс прослушивателя команд определяется с помощью одной чистой виртуальной функции, которая служит интерфейсом. Таким образом, клиент может быть получен из интерфейса и подписаться в качестве слушателя. Например,

class SimpleFtl : public CustomProtocolHal::TransferCommandListener, public NandHal::CommandListener

Страница записи SimpleFtl:

void SimpleFtl::WritePage(const NandHal::NandAddress &nandAddress, const Buffer &inBuffer)
{
  assert((nandAddress.Sector._ + nandAddress.SectorCount._) <= _SectorsPerPage);
  
  NandHal::CommandDesc commandDesc;
  commandDesc.Address = nandAddress;
  commandDesc.Operation = (nandAddress.SectorCount._ == _SectorsPerPage) ? NandHal::CommandDesc::Op::Write : NandHal::CommandDesc::Op::WritePartial;
  commandDesc.Buffer = inBuffer;
  commandDesc.Listener = this;
  _NandHal->QueueCommand(commandDesc);
}

HandleCommandCompleted просто помещает команду в очередь завершения, принадлежащую клиенту:

void SimpleFtl::HandleCommandCompleted(const NandHal::CommandDesc &command)
{
  Event event;
  event.EventType = Event::Type::NandCommandCompleted;
  event.EventParams.NandCommand = command;
  assert(_EventQueue->push(event) == true);
}

Во время «цикла» обработки событий SimpleFtl может запрашивать _EventQueue и соответствующим образом обрабатывать события.

CustomProtocolHal будет разработан и реализован почти так же, как NandHal. Мы будем использовать платформу моделирования, чтобы инициализировать CustomProtocolHal и запустить его как поток. SimpleFtl, как и любой другой модуль FirmwareCode, будет иметь доступ к экземпляру Hal.

Хотя это может быть простой и распространенной концепцией программного обеспечения, я хотел бы отметить несколько вещей, которые важны и, возможно, уникальны для разработки встроенного ПО.

  1. Помните, что реализация наших классов Hal — это имитация соответствующего аппаратного компонента. Таким образом, мы запускаем их в отдельных потоках, чтобы имитировать поведение оборудования на целевой платформе. Хотя при моделировании мы определяем реализацию HandleCommandCompleted как часть клиентского класса, это на самом деле вводит в заблуждение, потому что это должно происходить внутри аппаратного модуля. Аппаратная логика конкретного приложения не работает на ЦП общего назначения, этот код на самом деле не является частью прошивки. Возможно, что аппаратное обеспечение спроектировано так, чтобы его можно было программировать, и может быть предоставлен микрокод, но обычно это не является нормой. В общем случае набор аппаратно-определяемой «очереди» может быть доступен на данной ASIC, и микропрограмма должна управлять назначением, распределением и использованием этих конструкций.
  2. Для простоты наша симуляция в настоящее время не предназначена для работы с общей памятью между прошивкой и оборудованием. Примером такой разделяемой памяти являются дескрипторы команд. В конце концов, мы должны понимать, что это то, что и прошивка, и аппаратное обеспечение могут читать и записывать. Такие компоненты памяти обычно ограничены и выделены. Взгляните еще раз на метод SimpleFtl WritePage, мы просто объявили NandDesc в стеке, отправляем в NandHal и ожидаем, что произойдет волшебство. На самом деле с этими дескрипторами нужно обращаться как с ресурсом. Во-первых, они могут закончиться, и прошивка должна иметь логику, чтобы справиться с этим обстоятельством (мы будем использовать управление буфером, чтобы продемонстрировать это). Кроме того, должны быть созданы механизмы для защиты памяти дескрипторов от доступа после того, как они были обработаны от одного «объекта» к другому. На некоторых контроллерах команды могут быть отправлены путем прямой записи в набор аппаратных регистров, но принцип аналогичен. Прошивка должна знать о подходящем времени, в которое определенные регистры могут быть записаны, и избегать этого, когда аппаратное обеспечение «занято» или «недоступно».
  3. То, что аппаратный компонент, такой как NandHal, делает после завершения команды, можно настроить (хотя это не всегда программируется). Поведение, которое мы реализуем в настоящее время (помещение завершения в очередь), настроено для архитектуры опроса. Другое распространенное использование - генерировать прерывание, т. Е. Аппаратное обеспечение может вызвать вызов процедуры обслуживания прерывания (ISR), а встроенное ПО может решить, что делать с событием завершения в это время. Обратите внимание, что это приведет к тому, что любой ЦП, назначенный для обработки вызова ISR, будет «прерван» и потратит циклы на обработку прерывания, что приведет к переключению задач.

Возвращаясь к изменениям SimpleFtl, связанным с поддержкой асинхронной передачи данных хоста и управления ресурсами буфера, нам необходимо реализовать концепцию «повторить попытку позже». Мы больше не можем предполагать, что после возврата WriteToNand или ReadToNand мы завершили обработку конкретной команды хоста, т.е. она могла не завершиться и мы должны «повторить попытку позже». С помощью этой концепции разработчик может проявить творческий подход для разработки различных способов обработки таких ситуаций. Каждый вариант будет иметь разные последствия и, как правило, требует тонкой настройки для достижения определенных желаемых результатов в различных ситуациях (в частности, для оптимизации производительности). Мы реализовали простое начальное решение этого «учебника». Вы можете взглянуть на код:



Ознакомьтесь с другими концепциями прошивки:

Немного о прошивке

С чего начинается прошивка