4.2 Programmer’s model
PRIMASK, FAULTMASK và BASEPRI
Các thanh ghi PRIMASK, FAULTMASK và BASEPRI đều được sử dụng để mask exception hoặc interrupt. Mỗi exception (bao gồm cả interrupt) đều có một mức ưu tiên, trong đó:
- số càng nhỏ thì mức ưu tiên càng cao
- số càng lớn thì mức ưu tiên càng thấp
Ba thanh ghi đặc biệt này được dùng để mask exception dựa trên mức ưu tiên. Chúng chỉ có thể được truy cập ở privileged access level. Nếu đang ở unprivileged, thao tác ghi vào các thanh ghi này sẽ bị bỏ qua và thao tác đọc sẽ trả về 0.
Theo mặc định, cả ba thanh ghi này đều có giá trị 0, nghĩa là cơ chế mask, tức vô hiệu hóa exception hoặc interrupt, chưa được kích hoạt.
PRIMASK
Thanh ghi PRIMASK là một thanh ghi mask interrupt có độ rộng 1 bit. Khi bit này được set, nó sẽ chặn tất cả các exception (bao gồm cả interrupt), ngoại trừ:
- NMI (Non-Maskable Interrupt)
- HardFault
Về bản chất, việc set PRIMASK làm cho mức ưu tiên exception hiện tại được nâng lên mức 0, tức là mức cao nhất trong nhóm exception hoặc interrupt có thể lập trình được.
Cách sử dụng phổ biến nhất của PRIMASK là tạm thời vô hiệu hóa toàn bộ interrupt trong một đoạn xử lý cần tính thời gian chính xác hoặc cần tính nguyên tử. Sau khi đoạn xử lý đó hoàn tất, PRIMASK cần được xóa để cho phép interrupt hoạt động trở lại.
FAULTMASK
Thanh ghi FAULTMASK khá giống với PRIMASK, nhưng mạnh hơn ở chỗ nó còn chặn cả HardFault. Điều này tương đương với việc nâng mức ưu tiên exception hiện tại lên -1.
FAULTMASK thường được sử dụng trong các đoạn mã xử lý lỗi (fault handler) để ngăn các lỗi khác tiếp tục phát sinh trong khi đang xử lý lỗi hiện tại. Ví dụ, FAULTMASK có thể được dùng để:
- bỏ qua kiểm tra của MPU
- chặn bus fault
nhằm giúp đoạn mã xử lý lỗi có thể thực hiện các hành động khắc phục dễ dàng hơn.
Khác với PRIMASK, FAULTMASK sẽ tự động được xóa khi exception return.
BASEPRI
Để cung cấp khả năng mask interrupt linh hoạt hơn, kiến trúc ARMv7-M còn cung cấp thanh ghi BASEPRI. Thanh ghi này cho phép mask exception hoặc interrupt dựa trên ngưỡng ưu tiên.
Độ rộng của BASEPRI phụ thuộc vào số lượng mức ưu tiên mà vi điều khiển hiện thực, và điều này do nhà sản xuất vi điều khiển quyết định. Phần lớn các vi điều khiển Cortex-M3/Cortex-M4 hỗ trợ:
- 8 mức ưu tiên có thể lập trình → BASEPRI rộng 3 bit
- hoặc 16 mức ưu tiên → BASEPRI rộng 4 bit
Cách hoạt động của BASEPRI:
- Khi BASEPRI = 0 → cơ chế mask bị vô hiệu
- Khi BASEPRI ≠ 0 → các exception hoặc interrupt có mức ưu tiên bằng hoặc thấp hơn giá trị đó sẽ bị chặn
- Những exception có mức ưu tiên cao hơn vẫn có thể được CPU chấp nhận
Điều này rất hữu ích khi cần chặn một nhóm interrupt “ít quan trọng hơn”, nhưng vẫn cho phép các interrupt quan trọng hơn được xử lý.
Truy cập bằng CMSIS-Core
CMSIS-Core cung cấp một số hàm để truy cập các thanh ghi PRIMASK, FAULTMASK và BASEPRI trong môi trường C:
Cx = __get_BASEPRI(); // Đọc thanh ghi BASEPRI
x = __get_PRIMASK(); // Đọc thanh ghi PRIMASK
x = __get_FAULTMASK(); // Đọc thanh ghi FAULTMASK
__set_BASEPRI(x); // Ghi giá trị mới vào BASEPRI
__set_PRIMASK(x); // Ghi giá trị mới vào PRIMASK
__set_FAULTMASK(x); // Ghi giá trị mới vào FAULTMASK
__disable_irq(); // Set PRIMASK, vô hiệu hóa IRQ
__enable_irq(); // Clear PRIMASK, cho phép IRQ
Truy cập bằng Assembly
Ngoài ra, có thể truy cập các thanh ghi mask exception này bằng Assembly:
asmMRS r0, BASEPRI ; Đọc BASEPRI vào R0 MRS r0, PRIMASK ; Đọc PRIMASK vào R0 MRS r0, FAULTMASK ; Đọc FAULTMASK vào R0 MSR BASEPRI, r0 ; Ghi R0 vào BASEPRI MSR PRIMASK, r0 ; Ghi R0 vào PRIMASK MSR FAULTMASK, r0 ; Ghi R0 vào FAULTMASK
Ngoài ra, lệnh CPS (Change Processor State) cũng cho phép set hoặc clear PRIMASK và FAULTMASK bằng cách ngắn gọn hơn:
asmCPSIE i ; Enable interrupt (clear PRIMASK) CPSID i ; Disable interrupt (set PRIMASK) CPSIE f ; Enable fault (clear FAULTMASK) CPSID f ; Disable fault (set FAULTMASK)
Lưu ý: Thanh ghi FAULTMASK và BASEPRI không có trên kiến trúc ARMv6-M (ví dụ: Cortex-M0).
CONTROL register
Thanh ghi CONTROL được sử dụng để xác định:
- lựa chọn Stack Pointer đang dùng (MSP hoặc PSP)
- mức đặc quyền khi chạy ở Thread mode (Privileged hoặc Unprivileged)
Ngoài ra, với Cortex-M4 có FPU, một bit trong CONTROL còn cho biết ngữ cảnh hiện tại có đang sử dụng floating point unit hay không.
Lưu ý: Trên ARMv6-M (ví dụ Cortex-M0), hỗ trợ cho bit nPRIV và chế độ unprivileged là phụ thuộc hiện thực. Nó không có trên thế hệ Cortex-M0 đầu tiên và Cortex-M1, còn trên Cortex-M0+ thì là tùy chọn.
Thanh ghi CONTROL:
- chỉ có thể được ghi khi đang ở privileged access level
- nhưng có thể được đọc ở cả privileged và unprivileged
Các bit trong CONTROL
-
nPRIV (bit 0): xác định mức đặc quyền trong Thread mode
0(mặc định): Thread mode chạy ở privileged1: Thread mode chạy ở unprivileged
Trong Handler mode, CPU luôn ở privileged
-
SPSEL (bit 1): chọn Stack Pointer dùng trong Thread mode
0(mặc định): Thread mode dùng MSP1: Thread mode dùng PSP
Trong Handler mode, bit này luôn được xem là0và việc ghi vào bit này sẽ bị bỏ qua
-
FPCA (bit 2): chỉ có trên Cortex-M4 có FPU
Bit này cho biết ngữ cảnh hiện tại có sử dụng các thanh ghi floating point hay không.0: ngữ cảnh hiện tại chưa dùng FPU, không cần lưu các thanh ghi FPU khi có exception1: ngữ cảnh hiện tại đã dùng FPU, cần lưu thêm các thanh ghi FPU khi có exception
Bit FPCA sẽ được set tự động khi có lệnh floating point được thực thi, và sẽ được phần cứng clear khi vào exception.
Giá trị mặc định sau reset
Sau reset, thanh ghi CONTROL = 0. Điều này có nghĩa là:
- Thread mode dùng MSP
- Thread mode chạy ở privileged
Một chương trình đang chạy ở privileged Thread mode có thể đổi:
- từ MSP sang PSP
- hoặc từ privileged sang unprivileged
bằng cách ghi vào CONTROL.
Tuy nhiên, một khi bit nPRIV đã được set, đoạn mã đang chạy trong Thread mode sẽ không còn quyền truy cập CONTROL để tự nâng quyền lại.
Đây là điểm rất quan trọng để xây dựng một mô hình bảo mật cơ bản. Ví dụ, một hệ thống nhúng có thể chạy các ứng dụng không đáng tin cậy ở unprivileged, từ đó hạn chế khả năng chúng làm hỏng toàn bộ hệ thống hoặc gây ra lỗ hổng bảo mật.
Quay trở lại privileged bằng exception
Một chương trình chạy ở unprivileged Thread mode không thể tự chuyển lại thành privileged Thread mode. Muốn làm điều đó, bắt buộc phải dùng exception mechanism.
Trong quá trình xử lý exception, exception handler có thể clear bit nPRIV. Sau khi exception return, bộ xử lý sẽ quay lại Thread mode ở mức privileged.
Khi có embedded OS, thanh ghi CONTROL có thể được thay đổi tại mỗi lần context switch, cho phép:
- một số task chạy ở privileged
- một số task khác chạy ở unprivileged
Các tổ hợp của nPRIV và SPSEL
Về mặt lý thuyết có 4 tổ hợp giữa nPRIV và SPSEL, nhưng trong thực tế chỉ có 3 tổ hợp là phổ biến:
-
nPRIV = 0, SPSEL = 0
Ứng dụng đơn giản, toàn bộ chương trình chạy ở privileged, chỉ dùng MSP -
nPRIV = 0, SPSEL = 1
Hệ thống có embedded OS, task hiện tại chạy ở privileged Thread mode, dùng PSP; còn MSP dùng cho kernel và exception handler -
nPRIV = 1, SPSEL = 1
Hệ thống có embedded OS, task hiện tại chạy ở unprivileged Thread mode, dùng PSP; còn MSP dùng cho kernel và exception handler -
nPRIV = 1, SPSEL = 0
Thread mode chạy ở unprivileged nhưng vẫn dùng MSP. Trường hợp này ít gặp trong thực tế vì đa số embedded OS tách riêng stack của user task và kernel
Trong các ứng dụng đơn giản không dùng embedded OS, thường không cần thay đổi giá trị của CONTROL. Toàn bộ chương trình có thể chạy ở privileged và chỉ dùng MSP.
Truy cập CONTROL bằng CMSIS
Trong các thư viện tuân theo CMSIS, có thể truy cập CONTROL bằng các hàm sau:
Cx = __get_CONTROL(); // Đọc giá trị hiện tại của CONTROL
__set_CONTROL(x); // Ghi giá trị mới vào CONTROL
Hai lưu ý quan trọng khi sửa CONTROL
-
Với Cortex-M4 có FPU, bit FPCA có thể được set tự động nếu chương trình có dùng lệnh floating point. Nếu chương trình có sử dụng floating point mà vô tình clear FPCA, sau đó xảy ra interrupt thì các thanh ghi FPU có thể không được lưu lại trong quá trình exception entry và có nguy cơ bị interrupt handler ghi đè. Khi đó chương trình sẽ không thể tiếp tục xử lý chính xác sau khi quay lại task bị ngắt.
-
Về mặt kiến trúc, sau khi sửa CONTROL, nên dùng lệnh ISB (Instruction Synchronization Barrier) hoặc hàm
__ISB()trong CMSIS để đảm bảo thay đổi có hiệu lực với các lệnh tiếp theo. Tuy nhiên, do pipeline của Cortex-M3/M4/M0/M0+/M1 khá đơn giản nên trên thực tế việc bỏ qua ISB thường không gây vấn đề.
Truy cập CONTROL bằng Assembly
asmMRS r0, CONTROL ; Đọc CONTROL vào R0 MSR CONTROL, r0 ; Ghi R0 vào CONTROL
Kiểm tra CPU đang ở privileged hay không
Có thể kiểm tra mức thực thi hiện tại bằng cách kết hợp IPSR và CONTROL:
Cint in_privileged(void)
{
if (__get_IPSR() != 0) return 1; // Đang ở Handler mode -> privileged
else if ((__get_CONTROL() & 0x1) == 0) return 1; // Thread mode và nPRIV = 0
else return 0; // Unprivileged Thread mode
}
Ý tưởng của đoạn mã trên là:
- nếu IPSR != 0 thì CPU đang ở Handler mode, mà Handler mode luôn là privileged
- nếu đang ở Thread mode thì kiểm tra bit nPRIV của CONTROL
- nếu
nPRIV = 0thì đang ở privileged - ngược lại là unprivileged