AN3927 Freescale USB Mass Storage Device Bootloader Application Note Rev. 0, 11/2009 (перевод)





4. Использование загрузчика

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


4.1 Предупреждение перекрёстных вызовов

Очень важно понимать, что хороший загрузчик может взаимодействовать с целевым приложением только через абсолютные адреса. В этом случае вектор пользователя, указывающий на точку входа приложения, расположен по заранее известному абсолютному адресу. Загрузчик "знает" его и, переключаясь в режим целевого приложения, просто переходит по означенному адресу (вектору пользователя), который, в свою очередь, передаёт управление на начало собственно приложения. Это принципиальный момент, так как каждый раз при пересборке приложения компоновщик может изменить адрес точки входа целевого приложения. Загрузчику не надо знать новый адрес, потому что вектор пользователя всегда располагается по одному и тому же адресу и всегда содержит адрес точки входа целевого приложения. Забота о корректности объявления вектора и адреса целевого приложения ложится на конечного пользователя. Этот процесс описывается в статье далее.

Перекрёстные вызовы - ситуация при которой загрузчик или целевое приложение вызывает функцию из чужого адресного пространства без использования абсолютных адресов. Такое развитие событий совершенно недопустимо. В исходных текстах, сопровождающих данную статью, присутствует пример в котором загрузчик и целевое приложение объединяются в единый проект. Использование единого проекта имеет то преимущество, что в процессе производства загрузчик и приложение записываются в устройство в едином цикле записи программной памяти. Отладка единого проекта проще. Тем не менее, использование единого проекта допускает появление перекрёстных вызовов, чреватых серьёзными неприятностями.

Рассмотрим такой вариант развития событий:

Загрузчик использует библиотеку и собирается в рамках единого проекта с приложением, использующим ту же библиотеку. При сборке версии "RevA" программного обеспечения используемая загрузчиком библиотечная функция " My_func() " располагается компоновщиком в области загрузчика и получает адрес 0x100 . Целевое приложение также использует функцию " My_func() " и вызывает её из области кода загрузчика посредством перекрёстного вызова. Версия "RevA" устройства переходит в фазу массового производства и всё работает отлично. Через какое-то время программа модернизируется до версии "RevB". В момент сборки проекта компоновщик помещает функцию " My_func() " по адресу 0x105 . Загрузчик и целевое приложение собираются вместе, поэтому и эта версия работает как надо. А теперь попытаемся модернизировать целевую программу до версии "RevB" с помощью загрузчика версии "RevA". Приложение обновляется до версии "RevB", а загрузчик по-прежнему имеет версию "RevA". Когда целевое приложение делает перекрёстный вызов функции " My_func() ", управление передаётся коду по адресу 0x105 , но в загрузчике версии "RevA" " My_func() " расположени по адресу 0x100 и программа улетает в космос.

Данный пример показывает причины по которым следует всячески избегать перекрёстных вызовов, допуская вызовы только по абсолютным адресам.

Единственным местом взаимодействия загрузчика и целевой программы является вызов последней посредством выбора адреса точки входа из вектора пользователя. Версия загрузчика для MC9S08 объединяется с целевым приложением иначе, чем в версиях для ColdFire, которые включают загрузчик в виде исходных текстов или библиотеки. В случае MC9S08 и загрузчик, и целевое приложение используют библиотеку "ansiis.lib". Для предупреждения перекрёстных вызовов загрузчик для MC9S08 собирается как отдельный проект. Получившийся на выходе файл S19 включается в проект целевой программы командой " HEXFILE " в PRM-файле компоновщика. В таком варианте подключения компоновщик остаётся в неведении относительно наличия библиотечных функций в коде загрузчика и включает их в состав целевой программы, полностью исключая возможность перекрёстных вызовов. Таким образом проект приложения получает дополнительный плюс от единого цикла записи целевого приложения и загрузчика в устройство при массовом производстве.

В процессе сборки загрузчика и целевой программы, как единого проекта, следует проводить тестирование на наличие перекрёстный вызовов. Простым методом тестирования на отсутствие вызовов загрузчиком кода из прикладной области является стирание приложения и проверка загрузчика на работоспособность. Загрузчик самодостаточен и должен работать в отсутствие стороннего кода. Для использования данного метода запишите в устройство единый проект, после чего сотрите область программной памяти целевого приложения. Это можно сделать вручную или с использованием отладчика. В варианте с отладчиком поставьте точку останова сразу после того, как загрузчик стирает приложение в процессе его обновления. Запустите процедуру обновления целевой программы и сбросьте контроллер, по достижении точки останова. Загрузчик должен работать нормально. Невозможность нормальной работы означает, что загрузчик вызывает код из прикладной области. Используйте отладчик для обнаружени проблемных мест и поправьте код.

Проверьте, что целевая программа не использует код из области загрузчика. Один из способов - внимательный просмотр ".MAP" файла после компоновки. Проверьте все имена функций, расположенных в области загрузчика, и убедитесь, что данные функции не используются целевой программой. Убедитесь, что загрузчик не использует общих библиотек. Проверьте инициализационный код, так как используемые им процедуры могут иметь одинаковые имена в областях загрузчика и целевого приложения. Другим методом проверки на перекрёстные вызовы является стирание области загрузчика, но этот метод довольно сложен, так как в ней расположена и таблица векторов прерываний.


4.2 Использование загрузчика с отладочными платами

Прилагаемые проекты для среды разработки CodeWarrior содержат примеры тестов для запуска на отладочных платах M52259DEMO и DEMOJM. Выберите, для начала, вычислительное ядро, соответствующее вашей отладочной плате. Изучите сопроводительную документацию на плату на предмет начала работы и процедуры записи программной памяти из среды CodeWarrior. После начального ознакомления со средой и платой проделайте следующие шаги:

  1. Аппаратура должна быть настроена для работы с шиной USB. Данный режим соответствует заводским установкам всех переключателей. Подробности можно узнать в сопроводительной документации. Если вы используете плату DEMOJM убедитесь, что к плате подключён правильный процессорный модуль (MC9S08 или Coldfire V1). Подсоедините USB кабель к плате и компьютеру.

  2. Запустите CodeWarrior и откройте один из файлов проекта ".MCP", соответствующий какому-либо примеру. Для Coldfire V2 убедитесь, что в свойствах проекта выбрана опция "52259 Example Flash". Соберите проект и запишите его в программную память.

  3. Перезапустите плату. Один из светодиодов должен мигать, демонстрируя активность программы.

  4. Перейдите в режим загрузчика, нажав и удерживая указанную ниже кнопку с последующим кратковременным нажатием на кнопку "Reset". На плате DEMOJM для перехода используется кнопка PTG0, для M52259DEMO - SW1. Плата перезапустится в режиме загрузчика, пройдёт процедуру опознания и появится в управляющем компьютере в виде нового диска. Рисунок 8 демонстрирует "диск" загрузчика в Windows XP. Обратите внимание на метку тома ("BOOTLOADER") и файл состояния ("READY.TXT"). Устройство готово к приёму файла в формате S19.


    Рисунок 8. Опознанный операционной системой "диск" загрузчика
    Рисунок 8. Опознанный диск загрузчика
  5. Откройте каталог "\S19 files" и скопируйте файл S19, cоответствующий выбранному вычислительному ядру, на "диск" загрузчика. Вы можете выбрать файл проекта, управляющий другим светодиодом, или пример HID, собранный с использованием стека компании CMX.

  6. Некоторые операционные системы, такие как "Linux Fedora 8", не записывают файл на диск до момента размонтирования диска из системы. Если вы используете одну из таких систем, размонтируйте диск загрузчика.

  7. Проверьте файл состояния. После завершения передачи файла "диск" загрузчика пропадает и через несколько секунд - после проведения процедуры повторного опознания - появляется вновь. Проверьте, что "диск" загрузчика содержит файл "SUCCESS.TXT", который означает, что обновление памяти программ завершено успешно.


    Рисунок 9. Загрузчик сообщает об успешном завершении операции
    Рисунок 9. Успешное завершение операции
  8. Перезагрузите отладочную плату кнопкой "Reset". Должно запуститься новое приложение. Если вы выбрали пример CMX HID, то на экране управляющего компьютера указатель мыши начнёт покачиваться, другие примеры мигают светодиодами на плате.

Вы можете повторить данную процедуру, начиная с шага 4, для другого примера.


4.3 Создание нового проекта с загрузчиком

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

Если вы принимаетесь за новый проект, начните процесс с изменения приводимых примеров под новые задачи. Примеры уже содержат в своём составе загрузчик и необходимые стартовые процедуры. Вы можете добавлять новые возможности в теле функции " main() " файла "main.c". Единственное необходимое изменение - правка таблицы векторов прерываний. Процедура переадресации таблицы векторов различна для различных версий вычислительного ядра и детализируется ниже.


4.3.1 Изменение таблицы векторов для Coldfire V2

Архитектура Coldfire V2 позволяет изменять расположение таблицы векторов. Базовый адрес таблицы содержится в регистре VBR и при необходимости может быть изменён. После сброса адрес в регистре VBR равен нулю и таблица векторов располагается в защищённой области программной памяти загрузчика. Таким образом, вектор сброса находится по адресу 0x00000004 в той же защищённой памяти и указывает на код загрузчика. После запуска целевое приложение может изменить содержимое VBR и переадресовать таблицу векторов в оперативную память, а затем заполнить её своими векторами.


Рисунок 10. Переназначение вектора прерывания для Coldfire_V2
								void main (void) {
    //Set the interrupt handlers in the vector table

    mcf5xxx_wr_vbr((uint32)__VECTOR_RAM);
    mcf5xxx_set_handler(64 + 55, (long)pit0_isr);

Рисунок 10 демонстрирует код, используемый в примерах под Coldfire V2 для переназначения таблицы в оперативную память и её заполнения. Первая строка использует функцию " mcf5xxx_wr_vbr() " для переадресации таблицы в оперативную память. Вторая строка использует функцию " mcf5xxx_set_handler() " для установки вектора в таблицу. В приведённом примере вектор относится к переодическому прерыванию от таймера PIT0 и обработчик прерывания называется " pit0_isr() ". В MCF52259 PIT0 занимает 55-й вектор первого контроллера прерываний (Interrupt Controller0). Таким образом, номер вектора равен 64 + 55.

Когда вы изменяете таблицу векторов, используйте функцию " mcf5xxx_set_handler() " для установки новых обработчиков прерываний.


4.3.2 Изменение таблицы векторов для Coldfire V1

Архитектура Coldfire V1 позволяет изменять расположение таблицы векторов. Базовый адрес таблицы содержится в регистре VBR и при необходимости может быть изменён. После сброса адрес в регистре VBR равен нулю и таблица векторов располагается в защищённой области программной памяти загрузчика. Таким образом, вектор сброса находится по адресу 0x00000004 в той же защищённой памяти и указывает на код загрузчика. После запуска целевое приложение может изменить содержимое VBR и переадресовать таблицу векторов в оперативную память, а затем заполнить её адресами своих обработчиков.

Приложение для Coldfire V1 сохраняет свою собственную таблицу в программной памяти. На рисунке 11 изображена часть сохранённой в программной памяти таблицы векторов, названная " RAM_vector ". Целиком таблица приводится в приложении "Appendix F - hid_main.c из примера CMX для Coldfire V1". Тот факт, что таблица расположена в области целевого приложения, позволяет изменять её содержимое при обновлении памяти программ.


Рисунок 11. Таблица векторов прерываний для ColdFire V1
								void  (* const RAM_vector[])()@REDIRECT_VECTORS= {
    (pFun)&dummy_ISR,       // vector_0  INITSP
    (pFun)&dummy_ISR,       // vector_1  INITPC
    (pFun)&dummy_ISR,       // vector_2  Vaccerr
    ...
    (pFun)&dummy_ISR,       // vector_75 Vtpm1ch4
    (pFun)&dummy_ISR,       // vector_76 Vtpm1ch5
    (pFun)&dummy_ISR,       // vector_77 Vtpm1ovf
    (pFun)&dummy_ISR,       // vector_78 Vtpm2ch0
    (pFun)&dummy_ISR,       // vector_79 Vtpm2ch1
    ...
};

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


Рисунок 12. Переадресация векторов прерываний для ColdFire V1
								void main(void) {
    /* !! This section needs to be here to
       redirect interrupt vectors !! */

    dword *pdst,*psrc;
    byte i;

    asm (move.l #0x00800000,d0);
    asm (movec  d0,vbr);

    pdst=(dword*)0x00800000;
    psrc=(dword*)&RAM_vector;

    for (i=0;i<111;i++,pdst++,psrc++) {
          *pdst=*psrc;
    }
    /* !! Start application code below here !! */

Для новой программы нужно изменить в массиве " RAM_vector " содержимое соответствующего вектора.


4.3.3 Изменение таблицы векторов для MC9S08

В отличие от ColdFire, MC9S08 не может переадресовывать таблицу векторов в оперативную память. Вместо этого таблица векторов в области загрузчика указывает на таблицу переадресации, расположенную по известному (на момент компиляции) адресу, в области целевого приложения. На рисунке 13 изображена теблица переадресации " UserJumpVectors " из файла "main.c", расположенная по адресу " VectorAddressTableAddress ". Обратите внимание на константу " 0xCC ", стоящую перед каждым адресом в таблице. Это шестнадцатиричное значение инструкции " JMP " для MC9S08.


Рисунок 13. Таблица переадресации прерываний для MC9S08
								// User Interrupt Jump Vector Table

volatile const JumpVect UserJumpVectors [InterruptVectorsNum] @
  VectorAddressTableAddress = {
    { 0xCC, Dummy_ISR},           // 29 - RTC
    { 0xCC, Dummy_ISR},           // 28 - IIC
    { 0xCC, Dummy_ISR},           // 27 - ACMP
    { 0xCC, Dummy_ISR},           // 26 - ADC Conversion
    { 0xCC, Dummy_ISR},           // 25 - KBI
    { 0xCC, Dummy_ISR},           // 24 - SCI2 Transmit
    { 0xCC, Dummy_ISR},           // 23 - SCI2 Receive
    { 0xCC, Dummy_ISR},           // 22 - SCI2 Error
    { 0xCC, Dummy_ISR},           // 21 - SCI1 Transmit
    { 0xCC, Dummy_ISR},           // 20 - SCI1 Receive
    { 0xCC, Dummy_ISR},           // 19 - SCI1 Error
    { 0xCC, Dummy_ISR},           // 18 - TPM2 Overflow
    { 0xCC, Dummy_ISR},           // 17 - TPM2 Channel1
    { 0xCC, Dummy_ISR},           // 16 - TPM2 Channel0
    { 0xCC, Timer_Overflow},      // 15 - TPM1 Overflow
    { 0xCC, Dummy_ISR},           // 14 - TPM1 Channel5
    { 0xCC, Dummy_ISR},           // 13 - TPM1 Channel4
    { 0xCC, Dummy_ISR},           // 12 - TPM1 Channel3
    { 0xCC, Dummy_ISR},           // 11 - TPM1 Channel2
    { 0xCC, Dummy_ISR},           // 10 - TPM1 Channel1
    { 0xCC, Dummy_ISR},           // 9 - TPM1 Channel0
    { 0xCC, Dummy_ISR},           // 8 - Reserved
    { 0xCC, Dummy_ISR},           // 7 - USB Status
    { 0xCC, Dummy_ISR},           // 6 - SPI2
    { 0xCC, Dummy_ISR},           // 5 - SPI1
    { 0xCC, Dummy_ISR},           // 4 - MCG Loss Lock
    { 0xCC, Dummy_ISR},           // 3 - Low VoltDetect
    { 0xCC, Dummy_ISR},           // 2 - IRQ
    { 0xCC, Dummy_ISR},           // 1 - SWI
}; 

Таблица векторов прерываний " BootIntVectors " (рис. 14) объявляется в файле "Redirect_Vectors_S08.c" и располагается в области загрузчика. Каждый вектор в таблице указывает на соответствующий ему вектор в таблице " UserJumpVectors " (рис. 13). Когда происходит прерывание контроллер считывает содержимое соответствующего вектора из таблицы " BootIntVectors " и загружает его в указатель команд, после чего выбирает следующую команду уже из таблицы " UserJumpVectors ", расположенной в области целевого приложения. Эта команда - " 0xCC " - инструкция " JMP ", за которой следует адрес обработчика прерывания.


Рисунок 14. Таблица векторов загрузчика для MC9S08
								// Bootloader Redirected Interrupt Vectors

const unsigned int
BootIntVectors[InterruptVectorsNum] @ BootVectorTableAddress = {
    VectorAddressTableAddress + 0,
    VectorAddressTableAddress + 3,
    VectorAddressTableAddress + 6,
    VectorAddressTableAddress + 9,
    VectorAddressTableAddress + 12,
    VectorAddressTableAddress + 15,
    VectorAddressTableAddress + 18,
    VectorAddressTableAddress + 21,
    VectorAddressTableAddress + 24,
    VectorAddressTableAddress + 27,
    VectorAddressTableAddress + 30,
    VectorAddressTableAddress + 33,
    VectorAddressTableAddress + 36,
    VectorAddressTableAddress + 39,
    VectorAddressTableAddress + 42,
    VectorAddressTableAddress + 45,
    VectorAddressTableAddress + 48,
    VectorAddressTableAddress + 51,
    VectorAddressTableAddress + 54,
    VectorAddressTableAddress + 57,
    VectorAddressTableAddress + 60,
    VectorAddressTableAddress + 63,
    VectorAddressTableAddress + 66,
    VectorAddressTableAddress + 69,
    VectorAddressTableAddress + 72,
    VectorAddressTableAddress + 75,
    VectorAddressTableAddress + 78,
    VectorAddressTableAddress + 81,
    VectorAddressTableAddress + 84,
};

Для нового проекта заполните массив векторов " UserJumpVectors " в модуле "main.c" реальными адресами обработчиков прерываний.

ПредпросмотрAttachmentSize
an3927_html.zip258.37 КБ