Основной код загрузчика🔗
Функция main🔗
Функция main
выполняет подготовительные действия, необходимые для запуска основной целевой программы, по окончании которых передаёт управление этой программе.
Код функции main
и всех её зависимостей размещается в DRAM. Это сделано из-за необходимости перемещения сегментов OCM (три сегмента OCM перемещаются с адреса 0x00000000
в адрес 0xfffc0000
, образуя с четвёртым сегментом, расположенным по адресу 0xffff0000
непрерывную область памяти размером 256 кбайт) – работающие код и данные не могут размещаться в перемещаемых сегментах, поэтому они помещены в DRAM.
Первое действие – настройка системы прерываний:
- инициализация массива указателей на обработчики прерываний;
- установка приоритетов прерываний;
- назначение целевого процессора для прерываний от периферийных устройств;
- разрешение прерываний.
Далее инициализируются контроллеры UART (для вывода сообщений на терминал) и QSPI флеши, в которой хранятся загрузочные образы битстрима ПЛИС и целевой программы.
Затем выполняется перемещение таблицы трансляции MMU в старшие адреса OCM – это необходимо сделать, т.к. в дальнейшем все сегменты OCM будут перемещены в старшие адреса памяти, и текущее положение таблицы трансляции окажется не валидным1.
Следующее действие: конфигурирование ПЛИС (вызов функции load_pl
) с предварительным включением преобразователей уровней PS-PL (без этого доступ к PCAP не работает), после чего PL часть становится полностью работоспособной.
После этого осуществляется перемещение сегментов OCM и настройка фильтрации адресов SCU, так чтобы младшие адреса адресного пространства отображались на DRAM.
Предпоследнее действие – загрузка образа целевой программы, которая осуществляется путём вызова функции load_img
.
Завершающее действие – передача управления целевой программе. Этот код запрещает прерывания, инвалидирует кэш инструкций и предсказатель ветвлений после чего передаёт управление на вектор старта целевой программы (обычно это адрес 0xfffc0000
– начало OCM в старших адресах).
Конфигурирование ПЛИС🔗
Битстрим ПЛИС хранится во внешней флешь памяти, откуда он извлекается загрузчиком и используются для конфигурирования ПЛИС посредством блока PCAP. Формат загрузочного образа описан на странице Flash Images.
Загрузка битстрима осуществляется путём вызова функции load_pl
с передачей ей адреса в QSPI флеши, по которому находится образ битстрима. Функция выполняет следующие действия:
- извлекает размер бистрима из заголовка образа;
- копирует в буфер, расположенный в DRAM, данные битстрима;
- проверяет контрольную сумму образа;
- инициализирует PCAP;
- инициализирует PL;
- загружает с помощью DMA блока PCAP битстрим в PL;
- проверяет правильность загрузки и возвращает код успешности операции.
ВАЖНО
После копирования данных в DRAM необходимо обазятельно обеспечить когерентность кэшей и памяти! Дело в том, что копирование данных бистрима осуществляется CPU, который использует свою аппаратуру доступа в память, включая кэши данных, а загрузку бистрима в PL выполняет DMA блока PCAP, который обращается в DRAM напрямую, минуя кэши.
Когерентность кэшей и памяти осуществляется путём вызова функции Xil_DCacheFlush()
.
Работа загрузчика PL сопровожадется продобным логом в терминале. Пример лога:
bld: load PL bitstream from addr: 40000
load_pl: >>>> prepare bitstream <<<<
load_pl: retrieve PL bitstream size from image header: 4045564 bytes (1011391 32-bit words)
load_pl: copy bitstream image (4045572 bytes) from QSPI to DRAM
load_pl: check PL bitsream image CRC...Ok!
load_pl: >>>> setup PCAP, initialize and configure PL <<<<
load_pl: 0. wait for PL power... Ok
load_pl: 1. set up PCAP path for PL configuration
load_pl: 2. clear interrupt flags
load_pl: 3.a. PROG_B = 1
load_pl: 3.b. PROG_B = 0
load_pl: 3.c. wait for PCFG_INIT == 0... done
load_pl: 3.d. set PROG_B
load_pl: 3.e. clear PCFG_DONE interrup flag
load_pl: 4. wait for the PL is ready for programming... done: 40000A30
load_pl: 5. DMA cmd queue ready
load_pl: 6. disable PCAP loopback
load_pl: 7. program PCAP_2x clock divider for non-secure mode
load_pl: 8.a. DMA src addr: 200040
load_pl: 8.b. DMA dst addr: FFFFFFFF
load_pl: 8.c. DMA src len: 1011391
load_pl: 8.d. setup and launch DMA transfer with size: 1011391
load_pl: 9. Wait for DMA transfer done...Ok
load_pl: 10. check for transfer errors
load_pl: 11. PL configuration done
load_pl: >>>> SUCCESS <<<<
Загрузка целевой программы🔗
Загрузочный образ целевой программы так же хранится во внешней флешь памяти. В отличие от образа битстрима PL этот образ состоит из блоков, каждый из которых имеет заголовок, в котором указаны адрес загрузки блока, длина блока и флаги. Блоки загружаются последовательно до тех пор, пока не обнаружится последний блок (на это указывает флаг в заголовке блока). В процессе загрузки осуществляется подсчёт контрольной суммы CRC32, которая по завершении загрузки проверяется.
После операций копирования в обязательном порядке выполняется очистка кэша данных (flush) и инвалидация кэша инструкций.
Логирование работы загрузчика🔗
Все важные действия отмечаются сообщениями в лог, выводимый на терминал. Типовой вывод работы программы загрузчика и передачи управления целевой программе:
------------------------------------------------
bld: start!
bld: relocate and remap MMU translation table... done
bld: bootloader DRAM stuff copy errors:
+--------+--------+----------+
| code | data | rodata |
+--------+--------+----------+
| 0 | 0 | 0 |
+--------+--------+----------+
bld: load PL bitstream from addr: 40000
load_pl: >>>> prepare bitstream <<<<
load_pl: retrieve PL bitstream size from image header: 4045564 bytes (1011391 32-bit words)
load_pl: copy bitstream image (4045572 bytes) from QSPI to DRAM
load_pl: check PL bitsream image CRC... Ok!
load_pl: >>>> setup PCAP, initialize and configure PL <<<<
load_pl: 0. wait for PL power... Ok
load_pl: 1. set up PCAP path for PL configuration
load_pl: 2. clear interrupt flags
load_pl: 3.a. PROG_B = 1
load_pl: 3.b. PROG_B = 0
load_pl: 3.c. wait for PCFG_INIT == 0... done
load_pl: 3.d. set PROG_B
load_pl: 3.e. clear PCFG_DONE interrup flag
load_pl: 4. wait for the PL is ready for programming... done: 40000A30
load_pl: 5. DMA cmd queue ready
load_pl: 6. disable PCAP loopback
load_pl: 7. program PCAP_2x clock divider for non-secure mode
load_pl: 8.a. DMA src addr: 200C60
load_pl: 8.b. DMA dst addr: FFFFFFFF
load_pl: 8.c. DMA src len: 1011391
load_pl: 8.d. setup and launch DMA transfer with size: 1011391
load_pl: 9. Wait for DMA transfer done...Ok
load_pl: 10. check for transfer errors
load_pl: 11. PL configuration done
load_pl: >>>> SUCCESS <<<<
bld: relocate OCM segments to upper memory... done!
load_img: >>>> load image from QSPI address: 440000 <<<<
load_img: load section block > addr: FFFC0000, size: 17980, attr: 0
load_img: load section block > addr: FFFE0000, size: 1772, attr: 0
load_img: load section block > addr: FFFE06EC, size: 8, attr: 0
load_img: load section block > addr: FFFE06F4, size: 144, attr: 0
load_img: load section block > addr: FFFE0784, size: 24, attr: 1
load_img: CRC Ok!
bld: loading cam program from QSPI done!
bld: >>>> handoff bld -> cam <<<<
------------------------------------------------
cam: start!
-
Следует отметить, что таблица трансляции MMU не загружается из образа целевой программы, а помещается по указанному адресу путём копирования таблицы трансляции MMU самого загрузчика – это возможно благодаря тому, что содержимое таблицы трансляции MMU загрузчика и целевой программы идентичны. Основная причина отказа от загрузки таблицы трансляции MMU средствами BootROM состоит в том, что в этом случае размер образа получается чрезмерно большим – утилита
bootgen
, с помощью которой генерируется образ загрузчика, по какой-то причине стремится сформировать образ, охватывающий все адреса программы, и получается, что если таблицу трансляции разместить в старших адресах (четвёртый сегмент OCM, куда таблица в конечном итоге и помещается), то размер образа начинает превышать адресное пространство процессора, что неприемлемо. ↩