* * *
Преамбула.
Некоторое время назад у меня случился переезд с Windows на Linux (смена основной рабочей платформы). И если с софтом для обычной работы/досуга никаких (кроме привыкания) серьёзных проблем нет, то с САПРами всё далеко не так шоколадно. Основная трудность в том, что у некоторых из них нет Linux версий. Отчасти обходным путём может быть использование виртуальной машины, но это не лучший вариант — от Windows тут избавиться не удаётся, а использование становится ещё менее удобным. Поэтому естественно желание применять нативный софт.
Для меня основной проблемой стало то, что на Windows использовался ADI VisualDSP++/Blackfin. На виртуалке это всё поднялось (в том числе и коннект с железкой через ICE-100B), т.ч. как запасной вариант годилось, но хотелось, повторяю, нативного пакета. Особого выбора тут нет — это ADI GNU Toolchain (GCC Blackfin в разных комплектациях). Особенность данного тулчейна в том, что он не предназначен для обычной работы в bare-metal окружении как тот же VDSP++, он предназначен для запуска uClinux на платформе Blackfin.
Исходя из этого, содержание ADI GNU Toolchain'а составляет вариант для сборки приложений под uClinux — называется bfin-uclinux, набор библиотек и опций для всего этого и вариант для сборки С-приложения uboot — называется он bfin-elf. Поскольку uClinux меня не интересует, а нужен инструмент для работы в bare-metal окружении на С++, то выбор пал на bfin-elf.
При освоении данного тулчейна пришлось столкнуться с рядом трудностей, которые были более-менее успешно преодолены, а рабочие проекты портированы с VDSP++ на bfin-elf. Одной из таких трудностей стало то, что не всё необходимое для работы в bare-metal режиме на С++ содержалось в самом тулчейне (bfin-elf), а кое-что оказалось неприемлемого качества. Поэтому в результате этой работы было скомплектовано несколько библиотек (подробнее тут).
* * *
Амбула.
Вся тема возникла из-за того, что штатно (т. е. «из коробки») упомянутый тулчейн не позволяет полноценно работать. Он не включает в себя средства для регистрации обработчиков исключений (прерываний), не поддерживает вызов конструкторов глобальных объектов и имеет некоторые другие недостатки. Собственно, это обусловлено тем, что пакет сырой, «недоделанный» - по сути он предназначался для для того, чтобы собрать uboot, никто на нём разрабатывать bare-metal с использованием C++ приложения не собирался.
Вторым отягчающим обстоятельством является то, что пакет основан на версии gcc 4.3.5, которая является уже довольно старой, и не видно инициатив к развитию (попросту говоря, на данный инструмент «забили»).
Ниже перечислены основные проблемы, варианты их решения и обходные пути.
* * *
Отсутствие функции-регистратора обработчиков исключений было восполнено путём подтягивания соответствующей функции из VDSP++ (характерный момент: там в заголовочных файлах всё есть, а вот в библиотеке — нет, поэтому компилируется без вопросов, а на сборке возникает ошибка).
* * *
Вызов конструкторов глобальных объектов в нынешних GCC пакетах осуществляется с помощью функции __libc_init_array из библиотеки тулчейна. Такая функция там есть. Вот только её вызова в basiccrt.S и других стартапах из состава bfin-elf нет.
И второй момент: __libc_init_array вызывает пользовательскую функцию _init(), которая по замыслу предназначена для выполнения низкоуровневой инициализации. Но вот далеко не всегда такая функция нужна, а пользователь вынужден её определять. По-хорошему, такая функция должна была бы лежать в стандартной библиотеке рядом с __libc_init_array, объявленная с атрибутом weak, но этого там не сделано. Поэтому такая weak функция помещена в упомянутую библиотеку.
Кроме того, добавлен файл crt.S, который есть минимально необходимый код стартапа:
- настройка регистров процессора;
- перевод в режим супервизора на нижнем приоритете;
- обработчики исключений по умолчанию;
- обнуление секции .bss;
- и, конечно, вызов конструкторов глобальных объектов (__libc_init_array).
Обнуление секции .bss потребовалось потому, что тулчейн генерирует эту секцию незагружаемой (флаг ALLOCATE есть, флага LOAD нету), поэтому при загрузке программы в процессор с помощью gdb данная секция оказывается необнулённой со всеми вытекающими.
* * *
В тулчейн bfin-elf из VDSP++ портировано некоторое количество интринсиков (builtins.h), но, к сожалению, далеко не все, и реализация некоторых не вполне корректна. В частности, мне не хватило доступа к регистру CYCLES (такой интринсик был добавлен), а интринсики cli/sei содержат баг, который приводит к малопонятному сообщению об ошибке (типа, недопустимый символ с индексом nnnn в потоке).
Баг уже известный, суть его сводится к тому, что в реализации, которая сделана с помощью inline assembler, используется паттерн 'r', который соответствует R0-R7 и P0-P5 регистрам процессора, в то время, как инструкции cli/sei допускают только регистры данных, т. е. R0-R7, поэтому правильный паттерн для этих функций не 'r', а 'd'. Оба интринсика так же добавлены в описываемую библиотеку в виде встраиваемых функций __cli/__sei.
* * *
Тулчейн bfin-elf v4.3.5 содержит очень гадкий баг: при некоторых обстоятельствах в обработчике прерываний портится один из регистров (я налетел на порчу регистра I0): регистр используется в теле обработчика, но не сохраняется/восстанавливается в прологе/эпилоге. Это приводит к очень неприятным, непредсказуемым и трудноуловимым глюкам при работе программы. На эту тему составлен багрепорт, но полагаю, что исправляться это не будет.
В качестве обходного пути: обработчики прерываний делать простыми и, главное, избегать в них циклов. Тогда проблем не возникает.
* * *
Из состава VDSP++ подтянут файл с низкоуровневыми функциями настройки pll/clock (pll_set_system_vco/pll_set_system_clocks).
* * *
Утилита для генерации загрузочного образа bfin-elf-ldr имеет следующие недостатки:
- она тупо грузит все секции, какие есть — например, если в SDRAM находится здоровенная секция, которую не нужно инициализировать, секция всё равно будет загружена, что порождает большой размер файла загрузочного образа и увеличивает время загрузки;
- загрузочный образ оказывается непригодным для использования в некоторых случаях — конкретно, для загрузки в режиме SPI Slave (описано тут: https://ez.analog.com/thread/71256?start=0&tstart=0), т. к. загрузчик процессора имеет фичу, которая ориентирована на мультизагрузочную конфигурацию образа (загрузчик из состава VDSP++ всегда делает мультизагрузочную конфигурацию, поэтому проблем не создаёт). Фича нигде не описана.
Для обхода этой проблемы написан загрузчик, свободный от этих недостатков: помещает в LDR файл только секции с флагом LOAD и генериует LDR файл в виде мультизагрузочного образа. Загрузчик написан на языке python. Взять его можно тут. Использовать можно как из командной строки, так и из сборочного скрипта. Требует утилит bfin-elf-objdump и bfin-elf-objcopy, обе есть в составе bfin-elf.
* * *
Ну, и последнее. Немало возни было с организацией сборки. Поэтому для облегчения старта создан простой пример, который собирается и работает. Сборка осуществляется с помощью утилиты Scons. Если используется другой инструмент для сборки, то всё равно может оказаться полезным посмотреть опции сборки в сборочном скрипте SConstruct.
P.S. При прохождении этого нелёгкого пути очень большую помощь мне оказал AHTOXA. Как технического плана при погружении в мир GCC, так и морально-психологического, выслушивая нытьё на тему «что ж оно всё такое кривое-горбатое?!..» Выражаю ему и Сергею Борщу признательность и благодарность.