Bagaimana menerapkan bagian-bagian penting pada ARM Cortex A9

15

Saya porting beberapa kode warisan dari inti ARM926 ke CortexA9. Kode ini baremetal dan tidak termasuk OS atau pustaka standar, semuanya tersuai. Saya mengalami kegagalan yang tampaknya terkait dengan kondisi balapan yang harus dicegah dengan pemisahan kode secara kritis.

Saya ingin umpan balik tentang pendekatan saya untuk melihat apakah bagian kritis saya mungkin tidak diterapkan dengan benar untuk CPU ini. Saya menggunakan GCC. Saya menduga ada beberapa kesalahan halus.

Juga, apakah ada pustaka opensource yang memiliki tipe primitif untuk ARM (atau pustaka spinlock / semefor ringan yang bagus)?

#define ARM_INT_KEY_TYPE            unsigned int
#define ARM_INT_LOCK(key_)   \
asm volatile(\
    "mrs %[key], cpsr\n\t"\
    "orr r1, %[key], #0xC0\n\t"\
    "msr cpsr_c, r1\n\t" : [key]"=r"(key_) :: "r1", "cc" );

#define ARM_INT_UNLOCK(key_) asm volatile ("MSR cpsr_c,%0" : : "r" (key_))

Kode tersebut digunakan sebagai berikut:

/* lock interrupts */
ARM_INT_KEY_TYPE key;
ARM_INT_LOCK(key);

<access registers, shared globals, etc...>

ARM_INT_UNLOCK(key);

Gagasan "kunci" adalah untuk memungkinkan bagian kritis bersarang, dan ini digunakan pada awal dan akhir fungsi untuk membuat fungsi reentrant.

Terima kasih!

CodePoet
sumber
1
silakan merujuk ke infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dht0008a/... jangan lakukan itu dalam embedded asm btw. buatlah fungsinya seperti artikelnya.
Jason Hu
Saya tidak tahu apa-apa tentang ARM, tetapi saya berharap bahwa untuk mutex (atau fungsi cross-thread atau sinkronisasi proses lainnya), Anda harus menggunakan clobber "memory" untuk memastikan bahwa a) semua nilai memori yang di-cache dalam register menjadi memerah kembali ke memori sebelum menjalankan asm dan b) nilai apa pun dalam memori yang diakses setelah asm dimuat ulang. Perhatikan bahwa melakukan panggilan (seperti yang disarankan HuStmpHrrr) harus secara implisit melakukan clobber ini untuk Anda.
Juga, sementara saya masih tidak berbicara ARM, kendala Anda untuk 'key_' tidak terlihat benar. Karena Anda mengatakan ini dimaksudkan untuk digunakan kembali, menyatakannya sebagai "= r" di kunci sepertinya mencurigakan. '=' berarti Anda bermaksud menimpanya, dan nilai yang ada tidak penting. Tampaknya Anda cenderung menggunakan '+' untuk menunjukkan niat Anda untuk memperbarui nilai yang ada. Dan lagi untuk membuka, mendaftar sebagai input memberitahu gcc Anda tidak bermaksud mengubahnya, tetapi jika saya tidak salah, Anda lakukan (ubah). Saya menduga ini juga harus terdaftar sebagai output '+'.
1
+1 untuk pengkodean dalam perakitan untuk inti dengan spesifikasi tinggi. Lagi pula, mungkinkah ini terkait dengan mode privilege?
Dzarda
Saya cukup yakin Anda harus menggunakan ldrexdan strexmelakukannya dengan benar. Berikut adalah halaman web yang menunjukkan kepada Anda cara menggunakan ldrexdan strexmenerapkan spinlock.

Jawaban:

14

Bagian tersulit dalam menangani bagian penting tanpa OS sebenarnya bukan menciptakan mutex, melainkan mencari tahu apa yang harus terjadi jika kode ingin menggunakan sumber daya yang saat ini tidak tersedia. Instruksi load-exclusive dan conditional-store-exclusive membuatnya cukup mudah untuk membuat fungsi "swap" yang, jika diberi sebuah pointer ke integer, secara atomik akan menyimpan nilai baru tetapi mengembalikan apa yang telah dimasukkan oleh integer-to integer:

int32_t atomic_swap(int32_t *dest, int32_t new_value)
{
  int32_t old_value;
  do
  {
    old_value = __LDREXW(&dest);
  } while(__STREXW(new_value,&dest);
  return old_value;
}

Diberikan fungsi seperti di atas, seseorang dapat dengan mudah memasukkan mutex melalui sesuatu seperti

if (atomic_swap(&mutex, 1)==0)
{
   ... do stuff in mutex ... ;
   mutex = 0; // Leave mutex
}
else
{ 
  ... couldn't get mutex...
}

Dengan tidak adanya OS, kesulitan utama sering terletak pada kode "tidak bisa mendapatkan mutex". Jika interupsi terjadi ketika sumber daya yang dijaga mutex sibuk, mungkin perlu untuk memiliki kode penanganan interupsi menetapkan bendera dan menyimpan beberapa informasi untuk menunjukkan apa yang ingin dilakukan, dan kemudian memiliki kode seperti utama yang memperoleh mutex memeriksa kapan pun ia akan melepaskan mutex untuk melihat apakah interupsi ingin melakukan sesuatu sementara mutex diadakan dan, jika demikian, lakukan tindakan atas nama interupsi.

Meskipun dimungkinkan untuk menghindari masalah dengan interupsi yang ingin menggunakan sumber daya yang dijaga mutex dengan hanya menonaktifkan interupsi (dan memang, menonaktifkan interupsi dapat menghilangkan kebutuhan untuk jenis mutex lainnya), secara umum diinginkan untuk menghindari menonaktifkan interupsi lebih lama dari yang diperlukan.

Kompromi yang bermanfaat adalah menggunakan flag seperti dijelaskan di atas, tetapi memiliki kode jalur utama yang akan melepaskan interupsi penangguhan mutex dan memeriksa flag tersebut sebelum melakukan hal itu (mengaktifkan kembali interupsi setelah melepaskan mutex). Pendekatan semacam itu tidak mengharuskan interupsi dinonaktifkan sangat lama, tetapi akan menjaga kemungkinan bahwa jika kode jalur utama menguji bendera interupsi setelah melepaskan mutex, ada bahaya bahwa antara waktu ia melihat bendera dan waktu menindaklanjuti hal itu, itu mungkin akan didahului dengan kode lain yang memperoleh dan melepaskan mutex dan dan bertindak atas bendera interrupt; jika kode jalur utama tidak menguji flag interrupt setelah melepaskan mutex,

Dalam kasus apa pun, yang paling penting adalah memiliki sarana dengan kode yang mencoba menggunakan sumber daya yang dijaga mutex ketika tidak tersedia akan memiliki sarana untuk mengulangi usahanya setelah sumber daya dirilis.

supercat
sumber
7

Ini adalah cara yang berat untuk melakukan bagian-bagian penting; nonaktifkan interupsi. Ini mungkin tidak berfungsi jika sistem Anda memiliki / menangani kesalahan data. Ini juga akan meningkatkan latensi interupsi. The Linux irqflags.h memiliki beberapa macro yang menangani hal ini. The cpsiedan cpsidpetunjuk mungkin berguna; Namun, mereka tidak menyelamatkan negara dan tidak akan memungkinkan untuk bersarang. cpstidak menggunakan register.

Untuk seri Cortex-A , ldrex/strexyang lebih efisien dan dapat bekerja untuk membentuk mutex untuk bagian kritis atau mereka dapat digunakan dengan algoritma bebas kunci untuk menyingkirkan bagian kritis.

Dalam beberapa hal, ldrex/strexsepertinya ARMv5 swp. Namun, mereka jauh lebih kompleks untuk diterapkan dalam praktik. Anda memerlukan cache yang berfungsi dan memori target ldrex/strexharus ada di cache. Dokumentasi ARM pada ldrex/strexagak samar karena mereka ingin mekanisme untuk bekerja pada CPU non Cortex-A. Namun, untuk Cortex-A mekanisme untuk menjaga cache CPU lokal tetap sinkron dengan CPU lain adalah sama dengan yang digunakan untuk mengimplementasikan ldrex/strexinstruksi. Untuk seri Cortex-A cadangan granual (ukuran ldrex/strexmemori yang dipesan) adalah sama dengan garis cache; Anda juga perlu menyelaraskan memori ke baris cache jika Anda ingin memodifikasi beberapa nilai, seperti dengan daftar yang ditautkan dua kali lipat.

Saya menduga ada beberapa kesalahan halus.

mrs %[key], cpsr
orr r1, %[key], #0xC0  ; context switch here?
msr cpsr_c, r1

Anda perlu memastikan bahwa urutannya tidak pernah dapat dikuasai sebelumnya . Jika tidak, Anda mungkin mendapatkan dua variabel kunci dengan interupsi diaktifkan dan pelepasan kunci akan salah. Anda dapat menggunakan swpinstruksi dengan memori utama untuk memastikan konsistensi pada ARMv5, tetapi instruksi ini sudah tidak digunakan lagi pada Cortex-A ldrex/strexkarena lebih baik untuk sistem multi-CPU.

Semua ini tergantung pada jenis penjadwalan yang dimiliki sistem Anda. Sepertinya Anda hanya memiliki saluran utama dan interupsi. Anda sering memerlukan primitif bagian kritis untuk memiliki beberapa kait ke penjadwal tergantung pada tingkat apa (ruang sistem / pengguna / dll) yang Anda inginkan bagian kritis untuk bekerja dengannya.

Juga, apakah ada pustaka opensource yang memiliki tipe primitif untuk ARM (atau pustaka spinlock / semefor ringan yang bagus)?

Ini sulit untuk ditulis dengan cara yang portabel. Yaitu, perpustakaan tersebut mungkin ada untuk versi ARM CPU tertentu dan untuk OS tertentu.

kebisingan tanpa seni
sumber
2

Saya melihat beberapa potensi masalah dengan bagian-bagian kritis itu. Ada peringatan dan solusi untuk semua ini, tetapi sebagai ringkasan:

  • Tidak ada yang mencegah kompiler untuk memindahkan kode di makro ini, untuk optimasi atau alasan acak lainnya.
  • Mereka menyimpan dan mengembalikan beberapa bagian dari status prosesor yang diharapkan oleh kompiler inline untuk dibiarkan sendiri (kecuali jika dikatakan sebaliknya).
  • Tidak ada yang mencegah gangguan terjadi di tengah-tengah urutan dan mengubah keadaan antara ketika itu dibaca dan ketika itu ditulis.

Pertama, Anda pasti membutuhkan beberapa penghalang memori kompiler . GCC mengimplementasikan ini sebagai penjahat . Pada dasarnya, ini adalah cara untuk memberitahu kompiler "Tidak, Anda tidak dapat memindahkan akses memori di seluruh unit inline ini karena hal itu dapat mempengaruhi hasil dari akses memori." Khususnya, Anda membutuhkan keduanya "memory"dan "cc"clobbers, pada makro awal dan akhir. Ini akan mencegah hal-hal lain (seperti pemanggilan fungsi) diatur ulang relatif terhadap rakitan inline juga, karena kompiler tahu mereka mungkin memiliki akses memori. Saya telah melihat GCC untuk ARM terus status dalam register kode kondisi seluruh perakitan inline dengan "memory"clobbers, jadi Anda pasti perlu "cc"clobber.

Kedua, bagian-bagian penting ini menyimpan dan memulihkan lebih dari sekadar apakah interupsi diaktifkan. Secara khusus, mereka menyimpan dan memulihkan sebagian besar CPSR (Current Program Status Register) (tautannya adalah untuk Cortex-R4 karena saya tidak dapat menemukan diagram yang bagus untuk A9, tetapi harus identik). Ada batasan - batasan halus di mana bagian-bagian negara sebenarnya dapat dimodifikasi, tetapi ini lebih dari perlu di sini.

Antara lain, ini termasuk kode kondisi (di mana hasil instruksi seperti cmpdisimpan sehingga instruksi bersyarat berikutnya dapat bertindak atas hasilnya). Compiler pasti akan bingung dengan ini. Ini mudah dipecahkan menggunakan "cc"clobber seperti yang disebutkan di atas. Namun, ini akan membuat kode gagal setiap saat, sehingga tidak terdengar seperti apa yang Anda lihat bermasalah. Agak dari bom waktu yang berdetak, dalam memodifikasi acak kode lain dapat menyebabkan kompiler melakukan sesuatu yang sedikit berbeda yang akan rusak oleh ini.

Ini juga akan mencoba untuk menyimpan / mengembalikan bit IT, yang digunakan untuk mengimplementasikan eksekusi kondisional Thumb . Perhatikan bahwa jika Anda tidak pernah menjalankan kode Thumb, ini tidak masalah. Saya tidak pernah mengetahui bagaimana perakitan inline GCC dengan bit-bit IT, selain menyimpulkannya, berarti kompiler tidak boleh meletakkan perakitan inline di blok TI dan selalu mengharapkan perakitan berakhir di luar blok TI. Saya belum pernah melihat GCC menghasilkan kode yang melanggar asumsi ini, dan saya telah melakukan beberapa perakitan inline yang cukup rumit dengan optimasi berat, jadi saya cukup yakin mereka memegangnya. Ini berarti mungkin tidak akan benar-benar mencoba untuk mengubah bit IT, dalam hal ini semuanya baik-baik saja. Mencoba untuk memodifikasi bit-bit ini diklasifikasikan sebagai "tidak dapat diprediksi secara arsitektur", jadi itu bisa melakukan segala macam hal buruk, tapi mungkin tidak akan melakukan apa pun.

Kategori bit terakhir yang akan disimpan / dikembalikan (selain yang benar-benar menonaktifkan interupsi) adalah bit mode. Ini mungkin tidak akan berubah, jadi mungkin tidak masalah, tetapi jika Anda memiliki kode yang dengan sengaja mengubah mode, bagian interupsi ini dapat menyebabkan masalah. Mengubah antara mode istimewa dan pengguna adalah satu-satunya kasus melakukan ini yang saya harapkan.

Ketiga, tidak ada yang mencegah interupsi dari mengubah bagian lain CPSR antara MRSdan MSRdi ARM_INT_LOCK. Setiap perubahan seperti itu dapat ditimpa. Dalam kebanyakan sistem yang masuk akal, interupsi asinkron tidak mengubah status kode yang diinterupsi (termasuk CPSR). Jika mereka melakukannya, menjadi sangat sulit untuk berpikir tentang kode apa yang akan dilakukan. Namun, itu mungkin (mengubah bit menonaktifkan FIQ tampaknya paling mungkin bagi saya), jadi Anda harus mempertimbangkan jika sistem Anda melakukan ini.

Inilah cara saya menerapkannya dengan cara yang mengatasi semua masalah potensial yang saya tunjukkan:

#define ARM_INT_KEY_TYPE            unsigned int
#define ARM_INT_LOCK(key_)   \
asm volatile(\
    "mrs %[key], cpsr\n\t"\
    "ands %[key], %[key], #0xC0\n\t"\
    "cpsid if\n\t" : [key]"=r"(key_) :: "memory", "cc" );
#define ARM_INT_UNLOCK(key_) asm volatile (\
    "tst %[key], #0x40\n\t"\
    "beq 0f\n\t"\
    "cpsie f\n\t"\
    "0: tst %[key], #0x80\n\t"\
    "beq 1f\n\t"\
    "cpsie i\n\t"
    "1:\n\t" :: [key]"r" (key_) : "memory", "cc")

Pastikan untuk mengkompilasi -mcpu=cortex-a9karena setidaknya beberapa versi GCC (seperti milik saya) default ke CPU ARM yang lebih lama yang tidak mendukung cpsiedan cpsid.

Saya menggunakan andsbukan hanya anddi ARM_INT_LOCKjadi instruksi 16-bit jika ini digunakan dalam kode Thumb. The "cc"mengkritik diperlukan anyways, jadi ketat ukuran manfaat kinerja / kode.

0dan 1yang label lokal , untuk referensi.

Ini harus dapat digunakan dalam semua cara yang sama seperti versi Anda. Ini ARM_INT_LOCKsama cepat / kecilnya dengan yang asli. Sayangnya, saya tidak dapat menemukan cara untuk melakukannya ARM_INT_UNLOCKdengan aman di mana pun dekat sebagai beberapa instruksi.

Jika sistem Anda memiliki batasan kapan IRQ dan FIQ dinonaktifkan, ini bisa disederhanakan. Misalnya, jika mereka selalu dinonaktifkan bersama-sama, Anda dapat bergabung menjadi cbz+ cpsie ifseperti ini:

#define ARM_INT_UNLOCK(key_) asm volatile (\
    "cbz %[key], 0f\n\t"\
    "cpsie if\n\t"\
    "0:\n\t" :: [key]"r" (key_) : "memory", "cc")

Atau, jika Anda tidak peduli tentang FIQ sama sekali maka itu sama dengan hanya drop mengaktifkan / menonaktifkannya sepenuhnya.

Jika Anda tahu bahwa tidak ada lagi yang pernah mengubah bit negara lainnya di CPSR antara kunci dan membuka kunci, maka Anda juga dapat menggunakan melanjutkan dengan sesuatu yang sangat mirip dengan kode asli Anda, kecuali dengan keduanya "memory"dan "cc"clobbers di keduanya ARM_INT_LOCKdanARM_INT_UNLOCK

Brian Silverman
sumber