Bài 5: Jump từ bootloader sang application trên microcontroller

Trong bootloader trên microcontroller, bước jump sang application nhìn thì ngắn nhưng lại là một trong những đoạn nhạy nhất. Nhiều lỗi boot không nằm ở phần update firmware hay phần kiểm tra image, mà nằm ở chính bước handoff giữa bootloader và application.

Bài này đi thẳng vào bước jump từ bootloader sang application, bao gồm stack pointer, reset handler, vector table, các trạng thái còn sót lại từ bootloader và những lỗi thường gặp trong thực tế.

Bản chất của bước jump sang application

Trên microcontroller, jump sang application không phải chỉ là gọi một hàm ở địa chỉ khác. Đây là bước chuyển quyền điều khiển từ execution context của bootloader sang execution context của application. Application cần bắt đầu trong một trạng thái đủ sạch để chạy đúng như thiết kế.

Nếu bootloader để lại interrupt đang bật, clock tree ở trạng thái lạ hoặc vector table chưa trỏ đúng, application có thể treo ngay cả khi image hoàn toàn hợp lệ.

Những chuẩn bị trước khi jump sang application

Trước khi chuyển quyền điều khiển, bootloader thường phải dọn lại một số trạng thái quan trọng. Mức độ dọn tới đâu phụ thuộc vào contract giữa bootloader và application.

  • Tắt interrupt nếu cần.
  • Dừng SysTick nếu đang dùng.
  • Xử lý pending interrupt.
  • Đưa peripheral về trạng thái phù hợp.
  • Xử lý clock tree nếu application không chấp nhận trạng thái hiện tại.
  • Dọn các trạng thái phần mềm riêng của bootloader nếu chúng ảnh hưởng tới application.

Không phải hệ nào cũng cần reset sạch mọi thứ trước khi jump. Có sản phẩm chấp nhận application khởi động trên nền một số cấu hình có sẵn để giảm thời gian boot, nhưng nếu chọn cách này thì contract giữa bootloader và application phải rất rõ.

Stack pointer và reset handler

Hai giá trị quan trọng nhất để jump vào application thường nằm ngay đầu vector table của application:

  • Giá trị stack pointer ban đầu.
  • Địa chỉ reset handler.

Stack pointer là con trỏ stack được CPU dùng cho call, return, local variable và interrupt context.

Reset handler là entry point đầu tiên của application sau reset.

Bootloader thường phải đọc stack pointer từ vector table của application, nạp giá trị này vào CPU, sau đó lấy địa chỉ reset handler và nhảy tới đó. Nếu một trong hai giá trị này sai, application có thể không chạy ngay từ lệnh đầu tiên.

Vector table và VTOR

Application cần vector table của riêng nó. Nếu CPU vẫn đang dùng vector table của bootloader, các exception hoặc interrupt sau khi jump có thể rơi vào handler sai.

Trên Cortex-M, việc này thường liên quan tới VTOR, viết tắt của Vector Table Offset Register. Đây là thanh ghi dùng để trỏ tới vector table hiện hành. Khi application không nằm ở địa chỉ mặc định, bootloader thường phải cập nhật VTOR sang base address của application trước khi jump.

Một số vi điều khiển không có VTOR hoặc có cơ chế remap khác. Khi đó cách xử lý sẽ phụ thuộc vào kiến trúc cụ thể của chip. Điểm cốt lõi vẫn không đổi: sau khi handoff, CPU phải nhìn đúng vector table của application.

Những lỗi hay gặp khi jump

Các lỗi dưới đây xuất hiện rất nhiều khi bước handoff giữa bootloader và application không được chốt rõ ngay từ đầu.

  • Quên tắt interrupt trước khi jump.
  • Quên đổi VTOR hoặc remap vector table.
  • Stack pointer không hợp lệ.
  • Reset handler nằm ngoài vùng application.
  • Peripheral hoặc clock bị để lại ở trạng thái application không mong đợi.
  • Cache, MPU hoặc memory remap chưa được xử lý đúng.
  • Bootloader và application dùng linker layout không khớp.

Những lỗi này khó ở chỗ image có thể hoàn toàn đúng, CRC vẫn pass, nhưng application vẫn không boot được vì execution context lúc handoff không đúng.

Hai cách chuyển từ bootloader sang application

Trong thực tế thường gặp hai cách chuyển từ bootloader sang application. Mỗi cách có ưu điểm, nhược điểm và điều kiện áp dụng khác nhau.

Jump thẳng sang application

Với cách này, bootloader tự dọn trạng thái cần thiết rồi nhảy thẳng sang reset handler của application mà không reset lại toàn hệ thống.

Ưu điểm

  • Nhanh hơn vì không phải qua thêm một vòng reset.
  • Giảm thời gian boot tổng thể.
  • Phù hợp khi contract giữa bootloader và application đã được kiểm soát chặt.

Nhược điểm

  • Bootloader phải dọn trạng thái đủ sạch trước khi handoff.
  • Dễ phát sinh lỗi nếu interrupt, clock, peripheral hoặc remap còn sót trạng thái không mong muốn.
  • Khó debug hơn nếu contract giữa hai bên không rõ.

Reset lại rồi vào application

Với cách này, bootloader ghi lại boot decision hoặc boot flag cần thiết, sau đó reset lại hệ thống để application đi vào từ một trạng thái gần với power-on hơn.

Ưu điểm

  • Trạng thái hệ thống sạch hơn và dễ dự đoán hơn.
  • Giảm rủi ro bootloader để lại trạng thái xấu cho application.
  • Dễ chuẩn hóa flow boot khi hệ thống có nhiều mode khởi động.

Nhược điểm

  • Chậm hơn vì phải qua thêm một vòng reset.
  • Cần logic boot decision rõ ràng để sau reset hệ thống vào đúng image mong muốn.
  • Có thể làm flow boot phức tạp hơn nếu phải phối hợp với update, recovery hoặc rollback.

Handoff contract giữa bootloader và application

Một hệ thống ổn định thường không dựa vào giả định mơ hồ. Giữa bootloader và application nên có một handoff contract rõ ràng, ít nhất ở các điểm sau:

  • Clock tree còn giữ nguyên hay đã reset về mặc định.
  • Interrupt còn bật hay đã tắt hết.
  • Vector table đã được chuyển sang application chưa.
  • RAM có được giữ lại dữ liệu hay không.
  • Peripheral nào còn được giữ nguyên trạng thái.
  • Application có quyền giả định điều gì ngay khi bắt đầu chạy.

Càng ít thỏa thuận ngầm, quá trình boot càng dễ debug và dễ bảo trì hơn.

Checklist trước khi release

Trước khi release bootloader, nên kiểm tra tối thiểu các điểm sau.

  • Stack pointer của application hợp lệ.
  • Reset handler nằm đúng trong vùng application.
  • Vector table hoặc VTOR đã được xử lý đúng.
  • Interrupt và SysTick đã ở trạng thái phù hợp.
  • Clock, peripheral, cache hoặc remap đã được xử lý theo đúng contract.
  • Application boot được cả từ power-on reset và từ bootloader path.

Bước jump nhìn ngắn, nhưng nếu không kiểm kỹ thì đây thường là chỗ gây mất thời gian debug nhiều nhất trong bootloader MCU.

Kết luận

Jump từ bootloader sang application là bước handoff giữa hai execution context khác nhau. Muốn bước này ổn định, bootloader phải biết chính xác cần dọn trạng thái nào, cần nạp giá trị nào và application được phép giả định điều gì khi bắt đầu chạy. Nếu contract giữa hai bên không rõ, lỗi boot sẽ rất khó truy ra đúng nguyên nhân.

Phản hồi về bài viết

Cùng thảo luận chút nhỉ!

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.