ARM Cortex-M çekirdeklerinde Exception Modeli ve NVIC (Nested Vectored Interrupt Controller)

Serbay Özkan
10 min readJun 7, 2021

ARM firmasının farklı amaçlar için tasarlanmış Cortex-M, Cortex-R ve Cortex-A serisi çekirdekleri bulunmaktadır. ARM Cortex M çekirdek ailesi ARM’ın mikrodenetleyiciler (microcontroller) için geliştirdiği çekirdekleri içermektedir. Cortex-R serisi gerçek zamanlı çekirdekleri (real-time processors), Cortex-A serisi de uygulama çekirdeklerini (application processors) içerir. Cortex-M, Cortex-R ve Cortex-A kısaltmaları da sırasıyla Microcontroller Processors, Real-Time Processors ve Application Processors’dan gelmektedir.

Mikrodenetleyici üreten firmalar (NXP, Texas Instruments, ST…) bu ARM çekirdeklerinin etrafına kendi çevre birimlerini (peripheral) koyarak ürünlerini tasarlamaktadırlar.

Örneğin yukarıda Texas Instruments firmasının TM4C123x ailesi işlemcilerin blok diyagramı belirtilmiştir. Görüldüğü üzere Texas Instruments ARM Cortex-M4 çekirdeğini alarak çevresine Bellek, Analog, Seri Arayüz, Kontrol ve Sistem birimlerini ekleyerek TM4C123X mikrodenetleyici ürününü geliştirmiştir.

ARM Cortex-M Çekirdek Uyumlulukları

ARM Cortex M serisi çekirdekleri birbirleriyle ISA (Instruction Set Architecture) uyumludur. Cortex-M0, Cortex-M0+ ve Cortex-M1 çekirdekleri ARMv6-M, Cortex-M3, Cortex-M4 ve Cortex-M7 çekirdekleri ARMv7-M, Cortex-M23 ve Cortex M-33 çekirdekleri de ARMv8-M mimarisine sahiptir. ARMv7-M mimarisi ARMv6-M mimarisininin genişletilmiş halidir. ARMv8-M mimarisi ise kendi içerisinde Baseline ve Mainline olarak ayrılmaktadır ve ARMv6-M ve ARMv7-M mimarilerine TrustZone güvenlik uzantısı ve yeni ek özellikler eklemektedir.

Teorik olarak eğer memory map aynı ise Cortex-M0, M0+ veya M1 için üretilen bir binary imaj Cortex M3, M4, M7 çekirdeklerinde de çalışmaktadır. Tam tersi durum için aynısını söylemek mümkün değil çünkü ARMv7-M mimarisi ile birlikte Floating Point, DSP ve Advanced Data Processing Bit Manipulation setleri eklenmiştir.

ARM Cortex-M çekirdekleri ile ilgili genel bir bilgilendirmeden sonra artık asıl konumuza geçebiliriz.

NVIC (Nested Vectored Interrupt Controller) Nedir?

ARM Cortex-M çekirdeklerinde kesme yönetim mekanizması olarak kullanılan bir çekirdek çevre birimidir. Bütün ARM Cortex-M çekirdeğine sahip mikrodenetleyicilerde kesme yönetimi NVIC ile hızlı bir şekilde ve düşük gecikme ile yapılabilmektedir. NVIC bütün kesmeler ve exception’lar için öncelik ve maskeleme yönetimi sağlamaktadır. NVIC’in çekirdeğe kazandırdığı özellikleri aşağıdaki gibi sıralayabiliriz.

  • Öncelik seviyesi programlama
  • Kesme sinyalleri için seviye ve darbe (pulse) algılama
  • Öncelik değerlerini grup (group priority) ve alt öncelikler (subpriority) olarak sınıflandırma
  • Öncelik kuyruk-zinciri (Priority tail-chaining)
  • Opsiyonel ultra düşük güç uyku modu desteği

Exception Nedir?

Programın çalışma zamanında senkron veya asenkron olarak araya girerek işlemcinin modunu değiştiren olaya exception denir. ARM Cortex-M çekirdeklerinde program çalışma zamanı süresince işlemci Thread Mode veya Handler Mode denilen iki farklı modda bulunabilir.

ARM Cortex-M çekirdeklerinin hepsinde işlemci resetlendikten sonra varsayılan olarak Thread modunda çalışmaya başlar. İşlemci eğer çalışma zamanında yukarıda belirtilen herhangi bir exception ile karşılaşması durumunda çalışma modunu Handler moduna çeker ve ilgili exception’un servis edilmesini sağlar.

Exception’ları aşağıdaki gibi üç farklı gruba ayırabilmemiz mümkündür.

  • System Exceptions (Reset, NMI, SVCall, PendSV, SysTick)
  • Fault Exceptions (Hard Fault, MemManage Fault, UsageFault, BusFault)
  • Interrupts

Bir exception hangi durumlarda olabilir?

  • Active: Exception’un işlemci tarafından servis edildiğini ancak henüz servis işleminin tamamlanmadığını belirtmektedir.
  • Pending: Exception’un işlemci tarafından servis edilmek için beklediğini belirtmektedir.
  • Inactive: Exception’un ne aktif olduğunu ne de servis edilmek için beklediğini belirtmektedir.
  • Active and Pending: Exception’un işlemci tarafından servis edildiğini ancak yine aynı kaynaktan servis edilmek için bekleyen başka bir exception olduğunu belirtmektedir.

System Exceptions

Reset

Reset enerjilendirme veya warm reset ile tetiklenir. Reset exception’u gerçekleştiği zaman işlemci o an hangi komutu işletiyorsa çalışmasını durdurur. Daha sonra vektör tablosunda Reset Handler kaydına bakarak o adresten priviliged seviyede ve Thread modunda çalışmaya başlar.

NMI (Non Maskable Interrupt)

NMI bir çevrebiriminden veya yazılımdan tetiklenebilmektedir. Reset’den sonra en yüksek öncelikli exception’dur. NMI adından da anlaşıldığı üzere maskelenemez ve Reset dışında başka bir exception tarafından kesilemez.

SVCall (Supervisor Call)

SVC komutu ile tetiklenmektedir. İşletim sistemlerinde kullanıcı uygulamalarından çekirdek (kernel) fonksiyonlarına ve cihaz sürücülerine erişmek amacıyla çekirdek seviyesine çağrıda bulunmak için kullanılmaktadır.

PendSV (Pendable Service)

İşletim sistemlerinde eğer herhangi bir exception aktif durumda değil ise Context Switching işlemi için kullanılmaktadır.

SysTick

Sistem zamanlayıcısı tarafından 0'a ulaştığında üretilen exception’dur. Yazılım tarafından da bu exception’u üretmemiz mümkündür. İşletim sistemlerinde işlemci bu exception’u sistem tick’i olarak kullanabilir.

Fault Exceptions

HardFault

Exception ele alma/işleme sürecinde oluşan hatalar sonucunda oluşmaktadır. Eğer MemManage, BusFault ve UsageFault exception’ları aktif edilmemiş ise çalışma zamanında bu hatalardan biri oluşursa bu exception HardFault tarafından ele alınır.

Vektör tablosunda işleyici adresinin gidip alınıp getirilmesi sürecinde (vector fetching) ve debug olayları ile ilgili hatalar da HardFault meydana getirmektedir.

MemManage

Bellek korumasıyla (Memory Protection) ilgili oluşan hatalardır. Cortex-M0, M0+, M1 ve M23 çekirdeklerinde MPU (Memory Protection Unit) olmadığı için bu hata uygulanmamıştır.

BusFault

Bellek sisteminde bus üzerinde veya stacking/unstacking işlemlerinde oluşabilecek hatalardır. Cortex-M0, M0+, M1 ve M23 çekirdeklerinde bu hata uygulanmamıştır.

UsageFault

Komut gerçekleştirmesi sırasında kullanım hatalarından dolayı oluşan hatalardır. Aşağıdaki kullanımlardan dolayı oluşabilecek hatalar UsageFault hatası meydana getirmektedir. Cortex-M0, M0+, M1 ve M23 çekirdeklerinde bu hata uygulanmamıştır.

  • Tanımlı olmayan komut (Undefined instruction)
  • İllegal sıralanmamış erişim (Illegal unaligned access)
  • Komut gerçekleşimi sırasında geçerli olmayan duruma girme (invalid state on instruction execution)
  • Exception dönüşünde hata durumu

Bu hataların yanında eğer çekirdek konfigüre edilmişse aşağıdaki kullanımlar sonrasında da UsageFault oluşur.

  • 0 ile bölme işlemi (Division by zero)
  • Word/Halfword bellek erişimlerinde sıralanmamış adres durumu

Interrupts (IRQ)

Kesmeler çevre birimleri veya yazılım tarafından üretilmektedir. Çevre birimleri işlemci ile konuşmak için kesmeleri kullanmaktadır. ARM Cortex-M ailesinde çekirdek türüne göre maksimum desteklenen kesme sayısı aşağıda belirtilmiştir.

Vektör Tablosu (Vector Table)

Vektör tablosu Stack Pointer’in reset değerini ve bütün exceptionların servis edilmesi (exception service) için işleyicilerin (handler) başlangıç adresini tutan bir tablodur. Vektör tablosundaki her vektörün ilk biti 1 olarak ayarlanır. Bu ilk bit exception işleyicisinin Thumb komutlarını desteklediğini belirtmektedir. ARM Cortex-M çekirdekleri sadece Thumb komut setini desteklenmektedir. Thumb2 diye birşey de duyarsanız bu da 16 bit ve 32 bit komutları ikisini de desteklemek için genişletilmiş Thumb komut setini belirtmektedir.

Exception işleyicileri C fonksiyonu olarak yazılabilmektedir ve doğal olarak biz de bu tabloya exception servis edildğinde çağrılması için bu fonksiyonların adreslerini girmekteyiz.

Sistem reseti sonrasında vektör tablosu varsayılan olarak 0x00000000 adresinde yer almaktadır. Vektör tablosu daha sonrasında istenildiği takdirde yazılım tarafından VTOR (Vector Table Offset Register) register’ı kullanılarak başka bir adrese yerleştirilebilmektedir.

Vektör tablosundaki ilk vektör Stack Pointer (SP)’in başlangıç değerini tutar. Yani işlemci resetlendikten sonra ilk olarak PC (Program Counter) register’ina 0x00000000 adresi yüklenir ve daha sonra bu adresteki değer de stack işaretçisine yüklenir. Genellikle bu ayarlamalar startup kodu ile yapılmaktadır. Startup kodu derleyiciye göre değişse de genel çerçevesi ve teorisi aynıdır.

ARM Cortex-M çekirdeklerinde işlemci resetlendiğinde stack pointer görevini varsayılan olarak MSP (Main Stack Pointer) üstlenir. Gerçek zamanlı işletim sistemlerinde en iyi kullanım olarak güvenlik ve çalışma kararlılığını sağlamak amacıyla işletim sistemi resetinden sonra varsayılan stack işaretçisi MSP olsa da daha sonra bu görevi PSP (Process Stack Pointer) üstlenir.

Yine ARM Cortex-M çekirdeklerinde işlemci resetlendiğinde işlemci varsayılan olarak Privileged seviyesinde olmaktadır. Priviliged seviyede bir yazılım çalışma zamanında stack işaretçisini işlemcinin CONTROL register’ini kullanarak MSP’den PSP’ye çekebilmektedir. Çalışma sırasında exception oluşması halinde de (yani işlemci modu Handler moduna geçtiğinde) otomatik olarak stack işaretçisi işlemci tarafından MSP olarak ayarlanır. Yani kullanıcı seviyesinde stack işaretçisi görevini PSP çekirdek seviyesinde de MSP üstlenmektedir.

MSP ve PSP’nin gerçek zamanlı işletim sistemlerinde ayrı stack işaretçileri olarak kullanılmasının nedeni işletim sistemi ve exception işleyicilerinin stack’ini kullanıcı uygulamalarının kullandığı stack’den ayırmaktır. Böylelikle işletim sistemi stack’i kullanıcı uygulamalarından korunmaktadır. Ayrıca kullanıcı uygulamaları PSP stack alanını tüketse bile her zaman işletim sisteminin ayakta olması için yeterli alan olacak ve sistem, hata ve kesme işleyicileri her zaman servis edilebilecektir.

Stack işaretçisi ayarlandıktan sonra Reset vektörü olarak belirtilen işleyici (Reset Handler) çağrılır. Reset işleyicisi içerisinde sistem ilklendirmesi yapılır ve daha sonrasında main fonksiyonu çağrılır. Bu aşamadan sonra program akışı main fonksiyonu içerisinde gerçekleşmektedir. Bare-metal bir uygulamaysa bir superloop içerisinde programın hayat döngüsü devam ederken bir işletim sistemi ise de kullanıcı tarafından yaratılan görevler (task) dahilinde işleyiş görev planlayıcısına (Scheduler) bırakılır.

Exception Öncelikleri

Reset, HardFault ve NMI exceptionları dışındaki bütün exceptionlar için öncelik seviyeleri ayarlanabilmektedir. Düşük değerli öncelik yüksek seviyede öncelik, yüksek değerli öncelik düşük seviyede öncelik anlamına gelmektedir. Eğer yazılım tarafından ayarlanmadığı sürece öncelikleri ayarlanabilen bütün exceptionların varsayılan öncelik değeri 0'dır.

Önceliği ayarlanabilen exceptionlar değer olarak 0 ve daha büyük değer alırlar. Önceliği ayarlanamayan Reset, NMI ve HardFault exceptionları ise sırasıyla -3, -2, -1 negatif öncelik değerlerini almaktadır yani bu negatif değer alan üç exception her zaman diğer bütün exceptionlardan daha önceliklidir.

Pending durumunda bekleyen birden fazla exception bulunması halinde hangi exception’un servis edileceği bu önceliklere bakılarak belirlenir. Ne zaman pending durumuna geçtiğine bakılmaksızın yüksek öncelikli olanlardan düşük öncelikli olana doğru exceptionlar servis edilir. Eğer pending durumunda bekleyen birden fazla aynı öncelikte exception var ise düşük exception numarasına sahip olan exception ilk olarak servis edilir.

Bir exception servis edilirken 3 farklı senaryo ile karşılaşabilmemiz mümkündür.

  • Eğer bir exception servis edilirken daha düşük öncelikli bir exception meydana gelirse herhangi bir işlem üstünlüğü (preempt) durumu olmaz yüksek öncelikli exception kesintisiz servis edilmeye devam edilir.
  • Eğer bir exception servis edilirken aynı öncelikli bir exception daha meydana gelirse exception numarasından bağımsız olarak herhangi bir işlem üstünlüğü (preempt) durumu olmaz o an işlemi yapılan exception kesintisiz servis edilmeye devam edilir.
  • Eğer bir exception servis edilirken daha yüksek öncelikli bir exception meydana gelirse o an servis edilen exception durdurulur, en son gelen yüksek öncelikli exception servis edilir daha sonrasında durdurulan exception’un servis işlemi tamamlanır.

Kesme Öncelik Gruplaması

NVIC ile kesme öncelikleri gruplanabilmektedir. Kesme öncelik registerlarının bir kısmı grup bitleri olarak kullanılırken bir kısmı alt grup öncelik bitleri olarak kullanılabilmektedir.

Bir interrupt servis edilirken öncelikle grup önceliğine bakılır. Aynı anda servis edilmesi gereken birden fazla kesme varsa grup önceliği yüksek olan ilk servis edilir. Eğer birden fazla aynı grup önceliğine sahip kesme varsa da bu sefer alt grup önceliklerine bakılır.

Eğer birden fazla servis edilmesi gereken kesmenin hem grup hem de alt grup öncelikleri aynı ise IRQ numarası daha düşük olan kesme ilk servis edilir.

Bir Exception’a Giriş ve Çıkış sürecinde çekirdek seviyesinde neler olmaktadır?

Bu konuya girmeden önce bazı teknik terimlerin açıklamasını önden yapalım.

  • Preemption: İşlemcinin bir exception’u servis ederken, servis etme işleminde daha yüksek öncelikli bir exception oluşması halinde o anki servis işlemini keserek yeni gelen yüksek öncelikli exception’a öncelik vermek için kontrol akışını değiştirmesidir.
  • Tail-chaining: Exception servis edilme sürecini hızlandıran bir mekanizmadır. Eğer bir exception servis edilme sürecinin tamamlanma aşamasında daha yüksek öncelikli bir exception meydana gelirse, stack pop işlemi es geçilir ve kontrol yeni exception’un servis edilmesine aktarılır.
  • Late-arriving: Preemption sürecini hızlandıran bir mekanizmadır. Eğer işlemci tarafından ele alınan exception’un durum kaydı (state saving) sürecinde yüksek öncelikli bir exception oluşursa işlemci yüksek öncelikli exception’a geçiş yapar ancak durum kaydı bu durumdan etkilenmez. Yüksek öncelikli de olsa düşük öncelikli de olsa durum kaydı aynı şekilde yapılmaktadır bu yüzden de durum kaydı kesintisiz bir şekilde devam etmektedir. Exception çıkışında da normal tail-chaining kuralları geçerli olur.

Exception’a Giriş

Bir exception’a giriş yapıldığında işlemci Thread moduna geçer. Ele alınan exception süresince başka daha yüksek öncelikli bir exception gelmesi durumunda o an ele alınan exception kesilir. Yani daha yüksek öncelikli kesme daha düşük öncelikli kesmeye daha öncelik sağlayarak araya girmiş olur. Bu durumda bu exceptionlara iç içe (nested) exceptionlar denir.

İşlemci bir exception’u kabul ettiğinde eğer exception tail-chained veya late-arriving bir exception değilse işlemci bazı core registerlarını stack’e atmaktadır. Bu operasyona stacking, stack’e push edilen 8 register’a da stack frame denilmektedir. Bu işlem işlemci tarafından otomatik olarak gerçekleştirmektedir.

Floating-point komutları kullanıldığında yukarıda belirtilen stack frame’e ek olarak floating point durum registerları da eklenmektedir. Stack Frame’in FPU kullanılmadan ve kullanılırken ki durumu aşağıda belirtilmiştir.

ARM Cortex-M çekirdeklerinde Full Descending Stack yönelimi kullanılmaktadır. Stack’e veri atarken (push) stack işaretiçisinin değeri önce azaltılır daha sonra veri yazılır. Tam tersi durumda ise stack’den veri çekerken (pop) önce veri stack’den alınır daha sonra stack işaretçisinin değeri yükseltilir.

Stack Yönelim Çeşitleri

Stack frame içerisindeki Program Counter (PC) ile geri dönüş adresi tutulmaktadır. Bu adres kesmeye uğrayan programın bir sonraki işlenecek komutunu belirtmektedir.

Stacking operasyonunun parelelinde işlemci vektör tablosundan çağıracağı exception işleyicisinin adresini gidip alır. Stacking işlemi tamamlandıktan sonra işlemci tarafından exception işleyicisi çağrılır. Aynı zamanda da Link Register (LR)’ye EXC_RETURN değeri yazılır. Bu register exception oluşmadan önce stack pointer türünü ve işlemcinin çalışma modu bilgilerini tutmaktadır.

Eğer exception girişinde daha yüksek öncelikli bir exception meydana gelmediyse işlemci exception’u servis eder ve exception durumu otomatik olarak active durumuna çekilir.

Exception’dan Çıkış

İşlemci Handler modundayken ve PC değeri EXC_RETURN değeri ile yüklendiğinde artık exception’dan çıkış süreci gerçekleşmektedir. Normalde EXC_RETURN değeri exception girişinde LR’ye atanmıştı. Exception çıkışında da PC LR’nin değerini aldığı zaman exception işleyicisinin tamamlandığı anlaşılmaktadır. Aşağıdaki tablodan da anlaşılacağı üzere EXC_RETURN değerine göre exception işleyicisinden normal program akışına döndüğünde işlemcinin hangi modda olduğu ve stack işaretçisi olarak en son PSP mi yoksa MSP mi kullandığı anlaşılmaktadır.

Bir sonraki yazımı bu konuyla bağlantılı olduğu için ARM Cortex-M çekirdeklerinde hata analizi hakkında düşünüyorum. O yazıda bir hata gerçekleştiğinde IDE’ler üzerinde var olan Fault Analyzer araçlarının yaptığı gibi hatanın türünün ve yerinin nasıl belirlendiği üzerine yazmaya çalışacağım.

Diğer yazılarımda görüşmek üzere…

Referanslar ve Faydalı Kaynaklar

--

--