محمل الإقلاع المشفر لـ STM32

في هذه المقالة ، أود أن أكتب عن تجربتي في إنشاء أداة تحميل التشغيل لـ STM32 بتشفير البرامج الثابتة. أنا مطور فردي ، لذلك قد لا تلتزم الشفرة أدناه بأي معايير خاصة بالشركة

في هذه العملية ، تم تعيين المهام التالية:

  • قم بتوفير تحديث البرنامج الثابت لمستخدم الجهاز من بطاقة SD.
  • تأكد من التحكم في سلامة البرنامج الثابت واستبعد تسجيل البرامج الثابتة غير الصحيحة في ذاكرة وحدة التحكم.
  • توفير تشفير البرامج الثابتة لمنع استنساخ الجهاز.

تمت كتابة التعليمات البرمجية في Keil uVision باستخدام مكتبات stdperiph و fatFS و tinyAES. المتحكم التجريبي كان STM32F103VET6 ، ولكن يمكن تكييف الكود بسهولة مع وحدة تحكم STM أخرى. يتم توفير التحكم في النزاهة بواسطة خوارزمية CRC32 ، ويقع المجموع الاختباري في آخر 4 بايت من ملف البرنامج الثابت.

لا تصف المقالة إنشاء مشروع ، وربط المكتبات ، وتهيئة الأجهزة الطرفية والخطوات الأخرى البسيطة.

تحتاج أولاً إلى تحديد ماهية أداة تحميل التشغيل. تتضمن بنية STM32 عنونة مسطحة للذاكرة عندما تكون ذاكرة فلاش وذاكرة الوصول العشوائي والسجلات الطرفية وكل شيء آخر في مساحة عنوان واحدة. أداة تحميل التشغيل عبارة عن برنامج يتم تشغيله عند بدء تشغيل المتحكم الدقيق ، ويتحقق مما إذا كان من الضروري تحديث البرنامج الثابت ، إذا لزم الأمر ، وتنفيذه ، وتشغيل البرنامج الرئيسي للجهاز. سوف تصف هذه المقالة آلية التحديث من بطاقة SD ، ولكن يمكنك استخدام أي مصدر آخر.

يتم إجراء تشفير البرنامج الثابت بواسطة خوارزمية AES128 ويتم تنفيذه باستخدام مكتبة tinyAES. يتكون من ملفين فقط ، أحدهما بالملحق .c والآخر بالملحق .h ، لذلك يجب ألا تكون هناك مشاكل في الاتصال به.

بعد إنشاء المشروع ، يجب أن تقرر حجم اللودر والبرنامج الرئيسي. للراحة ، ينبغي اختيار الأحجام بمضاعفات من حجم صفحة الذاكرة من متحكم. في هذا المثال ، سيشغل محمل الإقلاع 64 كيلو بايت ، وسيشغل البرنامج الرئيسي 448 كيلو بايت المتبقية. سيكون موقع أداة تحميل التشغيل في بداية ذاكرة الفلاش ، والبرنامج الرئيسي مباشرة بعد أداة تحميل التشغيل. يجب تحديد هذا في إعدادات المشروع في Keil. يبدأ محمل الإقلاع بالعنوان 0x80000000 (ومنه يبدأ STM32 في تنفيذ الكود بعد الإطلاق) وله حجم 0x10000 ، ونشير إلى ذلك في الإعدادات.



سيبدأ البرنامج الرئيسي بـ 0x08010000 وينتهي بـ 0x08080000 للراحة ، وسوف نحدد بكل العناوين:

#define MAIN_PROGRAM_START_ADDRESS 0x08010000 #define MAIN_PROGRAM_END_ADDRESS 0x08080000 

نضيف أيضًا مفاتيح التشفير ومتجه تهيئة AES إلى البرنامج. يتم إنشاء هذه المفاتيح بشكل أفضل بشكل عشوائي.

 static const uint8_t AES_FW_KEY[] = {0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF}; static const uint8_t AES_IV[] = {0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA}; 

في هذا المثال ، يتم إنشاء إجراء تحديث البرنامج الثابت بالكامل كجهاز حالة. يسمح هذا لعملية التحديث بعرض شيء ما على الشاشة ، وإعادة ضبط الوكالة الدولية للطاقة وتنفيذ أي إجراءات أخرى. للراحة ، سوف نحدد مع الحالات الأساسية للأوتوماتون ، حتى لا نتشوش في الأرقام:

 #define FW_START 5 #define FW_READ 1000 #define FW_WRITE 2000 #define FW_FINISH 10000 #define FW_ERROR 100000 

بعد تهيئة الأجهزة الطرفية ، تحتاج إلى التحقق من الحاجة إلى تحديثات البرامج الثابتة. في الحالة الأولى ، تم إجراء محاولة لقراءة بطاقة SD والتحقق من وجود ملف عليها.

 uint32_t t; /*   */ uint32_t fw_step; /*     */ uint32_t fw_buf[512]; /*      */ uint32_t aes_buf[512]; /*       */ /*     Flash-*/ uint32_t idx; /*     */ char tbuf[64]; /*    sprintf */ FATFS FS; /*   fatFS -   */ FIL F; /*   fatFS -  */ case FW_READ: /*   */ { if(f_mount(&FS, "" , 0) == FR_OK) /*   SD-*/ { /* ,     . */ if(f_open(&F, "FIRMWARE.BIN", FA_READ | FA_OPEN_EXISTING) == FR_OK) { f_lseek(&F, 0); /*     */ CRC_ResetDR(); /*    CRC */ lcd_putstr(" ", 1, 0); /*     */ /*        */ idx = MAIN_PROGRAM_START_ADDRESS; fw_step = FW_READ + 10; /*     */ } else {fw_step = FW_FINISH;} /*    -   */ } else {fw_step = FW_FINISH;} /*   SD- -   */ break; } 

الآن نحن بحاجة إلى التحقق من البرامج الثابتة للتأكد من صحتها. هنا ، يأتي أولاً رمز التحقق الاختباري ، ويتم تنفيذه عند الانتهاء من قراءة الملف ، ثم القراءة نفسها. ربما لا ينبغي أن تكتب هكذا ، اكتب في التعليقات ما تفكر فيه. تتم القراءة في 2 كيلوبايت لتوفير الراحة للعمل مع ذاكرة فلاش ، لأنه يحتوي STM32F103VET6 على حجم صفحة ذاكرة 2 كيلو بايت.

 case FW_READ + 10: /*      */ { /*     ,    */ sprintf(tbuf, ": %d", idx - MAIN_PROGRAM_START_ADDRESS); lcd_putstr(tbuf, 2, 1); if (idx > MAIN_PROGRAM_END_ADDRESS) /*      */ { f_read(&F, &t, sizeof(t), &idx); /*  4    */ /*   4       CRC */ CRC_CalcCRC(t); if(CRC_GetCRC() == 0) /*   0,     */ { /*         */ idx = MAIN_PROGRAM_START_ADDRESS; f_lseek(&F, 0); /*     */ fw_step = FW_READ + 20; /*     */ break; } else { lcd_putstr(" ", 3, 2); /*     */ fw_step = FW_ERROR; /*       */ break; } } f_read(&F, &fw_buf, sizeof(fw_buf), &t); /*  2      */ if(t != sizeof(fw_buf)) /*     */ { lcd_putstr(" ", 3, 2); fw_step = FW_ERROR; /*       */ break; } /*     */ AES_CBC_decrypt_buffer((uint8_t*)&aes_buf, (uint8_t *)&fw_buf, sizeof(fw_buf), AES_FW_KEY, AES_IV); for(t=0;t<NELEMS(aes_buf);t++) /*     CRC */ { CRC_CalcCRC(aes_buf[t]); /*    4  */ } idx+=sizeof(fw_buf); /*     2  */ break; } 

الآن ، إذا لم تكن البرامج الثابتة تالفة ، فأنت بحاجة إلى قراءتها مرة أخرى ، ولكن هذه المرة اكتبها على ذاكرة الفلاش.

 case FW_READ + 20: // Flash Firmware { /*     ,    */ sprintf(tbuf, ": %d", idx - MAIN_PROGRAM_START_ADDRESS); lcd_putstr(tbuf, 4, 2); if (idx > MAIN_PROGRAM_END_ADDRESS) /*     */ { lcd_putstr("", 7, 3); /*     */ f_unlink("FIRMWARE.BIN"); /*     SD- */ fw_step = FW_FINISH; /*   */ break; } f_read(&F, &fw_buf, sizeof(fw_buf), &t); /*   2  */ if(t != sizeof(fw_buf)) /*     */ { lcd_putstr(" ", 3, 3); /*     */ fw_step = FW_ERROR; /*       */ break; } /*     */ AES_CBC_decrypt_buffer((uint8_t*)&aes_buf, (uint8_t *)&fw_buf, sizeof(fw_buf), AES_FW_KEY, AES_IV); FLASH_Unlock(); /*  FLash-   */ FLASH_ErasePage(idx); /*    */ for(t=0;t<sizeof(aes_buf);t+=4) /*    4  */ { FLASH_ProgramWord(idx+t, aes_buf[t/4]); } FLASH_Lock(); /*     */ idx+=sizeof(fw_buf); /*     */ break; } 

الآن من أجل الجمال ، سنقوم بإنشاء حالات لمعالجة الأخطاء والتحديثات الناجحة:

 case FW_ERROR: { /*  -     */ break; } case FW_FINISH: { ExecMainFW(); /*    */ /*      */ break; } 

وظيفة إطلاق البرنامج الرئيسي ExecMainFW () تستحق النظر بمزيد من التفصيل. ومن هنا:

 void ExecMainFW() { /*       */ /*    ,     */ /* +4  ,          */ uint32_t jumpAddress = *(__IO uint32_t*) (MAIN_PROGRAM_START_ADDRESS + 4); pFunction Jump_To_Application = (pFunction) jumpAddress; /*    APB1 */ RCC->APB1RSTR = 0xFFFFFFFF; RCC->APB1RSTR = 0x0; /*    APB2 */ RCC->APB2RSTR = 0xFFFFFFFF; RCC->APB2RSTR = 0x0; RCC->APB1ENR = 0x0; /*     APB1 */ RCC->APB2ENR = 0x0; /*     APB2 */ RCC->AHBENR = 0x0; /*     AHB */ /*      ,   HSI*/ RCC_DeInit(); /*   */ __disable_irq(); /*     */ NVIC_SetVectorTable(NVIC_VectTab_FLASH, MAIN_PROGRAM_START_ADDRESS); /*    */ __set_MSP(*(__IO uint32_t*) MAIN_PROGRAM_START_ADDRESS); /*     */ Jump_To_Application(); } 

مباشرة بعد بدء تشغيل ملف بدء التشغيل ، قام بإعادة تهيئة كل شيء ، لذلك يجب على البرنامج الرئيسي تعيين المؤشر مرة أخرى على ناقل المقاطعة داخل مساحة العنوان الخاصة به:

 __disable_irq(); NVIC_SetVectorTable(NVIC_VectTab_FLASH, MAIN_PROGRAM_START_ADDRESS); __enable_irq(); 

في مشروع البرنامج الرئيسي ، تحتاج إلى تحديد العناوين الصحيحة:



هنا ، في الواقع ، إجراء التحديث بأكمله. يتم التحقق من البرامج الثابتة للتأكد من صحتها ويتم تشفيرها ، ويتم الانتهاء من جميع المهام. في حالة حدوث انقطاع في الطاقة أثناء عملية التحديث ، سيقوم الجهاز بطبيعة الحال بالحفر ، لكن محمل الإقلاع سيبقى بلا تغيير ويمكن تكرار إجراء التحديث. في المواقف الحرجة بشكل خاص ، يمكنك قفل الصفحات التي يوجد بها المُحمل عبر بايت بايت للكتابة.

ومع ذلك ، في حالة وجود بطاقة SD ، يمكنك اتخاذ الترتيبات اللازمة لنفسك في أداة تحميل bootloader. عند الانتهاء من اختبار إصدار البرنامج الثابت الجديد وتصحيحه ، يمكنك إجبار الجهاز نفسه على تشفير وتحميل البرنامج الثابت النهائي على بطاقة SD لبعض الحالات الخاصة (على سبيل المثال ، زر أو وصلة ربط داخلية). في هذه الحالة ، يبقى فقط إزالة بطاقة SD من الجهاز وإدخالها في الكمبيوتر ووضع البرامج الثابتة على الإنترنت لإرضاء المستخدمين. نقوم بذلك في شكل دولتين أخريين من آلة الحالة المحدودة:

 case FW_WRITE: { if(f_mount(&FS, "" , 0) == FR_OK) /*   SD-*/ { /*    */ if(f_open(&F, "FIRMWARE.BIN", FA_WRITE | FA_CREATE_ALWAYS) == FR_OK) { CRC_ResetDR(); /*   CRC */ /*        */ idx = MAIN_PROGRAM_START_ADDRESS; fw_step = FW_WRITE + 10; /*     */ } else {fw_step = FW_ERROR;} /*      */ } else {fw_step = FW_ERROR;} /*      */ break; } case FW_WRITE + 10: { if (idx > MAIN_PROGRAM_END_ADDRESS) /*     */ { t = CRC_GetCRC(); f_write(&F, &t, sizeof(t), &idx); /*       */ f_close(&F); /*  ,   */ fw_step = FW_FINISH; /*   */ } /*  2    Flash-   */ memcpy(&fw_buf, (uint32_t *)idx, sizeof(fw_buf)); for(t=0;t<NELEMS(fw_buf);t++) /*  CRC    */ { CRC_CalcCRC(fw_buf[t]); } /*   */ AES_CBC_encrypt_buffer((uint8_t*)&aes_buf, (uint8_t *)&fw_buf, sizeof(fw_buf), AES_FW_KEY, AES_IV); /*      */ f_write(&F, &aes_buf, sizeof(aes_buf), &t); idx+=sizeof(fw_buf); /*     */ break; } 

هذا ، في الواقع ، هو كل ما أردت أن أقوله. في نهاية المقالة ، أود أن أتمنى لك بعد إنشاء محمل الإقلاع هذا ألا تنسى تضمين الحماية من قراءة ذاكرة متحكم في بايت بايت.

المراجع


tinyAES
فتفس

Source: https://habr.com/ru/post/ar432966/


All Articles