Terkunci (Atomik) Daftar baca / tulis

8

Saya mengode sesuatu menggunakan kontrol langsung GPIO, ada beberapa sumber daya yang bagus untuk ini, seperti http://elinux.org/RPi_Low-level_peripherals#GPIO_hardware_hacking ; prosesnya melibatkan open ("/ dev / mem") dan kemudian operasi mmap secara efektif memetakan alamat fisik yang diinginkan ke dalam ruang alamat virtual Anda. Kemudian Anda membaca bagian 6 dari http://www.raspberrypi.org/wp-content/uploads/2012/02/BCM2835-ARM-Peripherals.pdf ini untuk mengetahui bagaimana I / O dikontrol.

Untuk mengubah ke fungsi pin (input, atau output, atau berbagai fungsi khusus) Anda memodifikasi bidang 3-bit ini di register I / O GPFSELx (000 = input, 001 = output musuh contoh). Operasi modifikasi ini dikompilasi ke operasi dengan pemuatan dan penyimpanan biasa (mis. Untuk mengubah GPIO0 menjadi input: * (regptr) & = ~ 7; yang mengkompilasi ke sesuatu seperti

    ldr     r2, [r3, #0]     ; r = *ptr (load r2 from I/O register)
    bic     r2, r2, #7       ; r2 &= ~7
    str     r2, [r3, #0]     ; *ptr = r2 (store r2 to I/O register)

Masalahnya adalah ini: jika terjadi interupsi antara beban dan penyimpanan, dan proses lain atau ISR memodifikasi register I / O yang sama, operasi penyimpanan (berdasarkan basi yang dibaca dalam r2) akan mengembalikan efek dari operasi lainnya. Jadi mengubah register I / O ini benar-benar perlu dilakukan dengan operasi baca / modifikasi / penulisan atom (terkunci). Contoh yang saya lihat tidak menggunakan operasi yang terkunci.

Karena register I / O ini umumnya diubah hanya ketika mengatur sesuatu, kecil kemungkinan masalah akan terjadi, tetapi 'tidak pernah' selalu lebih baik daripada 'tidak mungkin'. Juga, jika Anda memiliki aplikasi di mana Anda bit-bashing untuk meniru keluaran kolektor terbuka, maka (sejauh yang saya tahu) ini melibatkan pemrograman output ke 0 dan kemudian beralih antara output (untuk rendah) atau input ( untuk off / high). Jadi dalam hal ini akan sering ada mod untuk register I / O ini, dan modifikasi yang tidak aman akan jauh lebih mungkin menyebabkan masalah.

Jadi, mungkin ada 'bandingkan dan set' ARM atau operasi serupa yang dapat digunakan di sini untuk melakukan ini, adakah yang bisa mengarahkan saya ke sana, dan bagaimana membuat itu terjadi dari kode C?

[Catatan, tidak ada hal khusus yang diperlukan ketika Anda memprogram I / O sebagai output dan hanya mengubahnya dari 0 menjadi 1 atau sebaliknya; karena ada register I / O tempat Anda menulis, untuk mengatur bit yang dipilih menjadi 1 dan yang lain untuk menghapus bit yang dipilih menjadi 0. Tidak diperlukan baca / tulis untuk operasi ini, sehingga tidak ada bahaya dari interupsi].

greggo
sumber
Mungkin saya tidak mengerti ini dengan benar, tetapi karena Anda membukanya /dev/mem, sepertinya kode Anda adalah kode userspace. Saya tidak berpikir bahwa dalam OS modern kita harus berhati-hati tentang interupsi perubahan nilai register dalam kode userspace. Saya percaya bahwa ini tidak akan menjadi masalah bahkan dalam kode ruang kernel karena Linux mengembalikan semua register ketika interrupt handler menyelesaikan tugasnya.
Krzysztof Adamski
1
Pemahaman saya adalah bahwa load / store pergi ke register fisik melalui pemetaan VM yang diatur oleh mmap (register I / O, bukan register CPU). Dalam hal ini tidak ada alasan untuk proses lain, atau driver perangkat tidak dapat melakukan hal yang sama secara bersamaan dan memodifikasi register yang sama. (Saya menganggap itu memodifikasi set bit yang berbeda di reg, atau jelas kami memiliki masalah yang lebih besar). Tidak ada save / restore register IO karena ada untuk register prosesor.
greggo
Saya telah mengedit sedikit untuk memperjelas 'I / O register' sebagai kebalikan dari r2 dll.
greggo
Saya bisa mengerti maksud Anda sekarang. Ini lebih merupakan preemption daripada interrupt handling problem. Menggunakan operasi atom akan membantu setidaknya ketika dua proses mencoba untuk mengatur bit yang berbeda secara bersamaan.
Krzysztof Adamski
ldrex / strex tidak bekerja pada memori yang tidak disimpan. Monitor eksklusif bergantung pada cache. Bahkan, dulu mungkin untuk mengunci CPU keras jika Anda mencoba itu pada sistem Cortex-A9 SMP, misalnya.
thinkfat

Jawaban:

3

Saya melihat ke dalam ini, ARM memiliki instruksi 'ldrex dan' strex ', strex akan mengembalikan hasil yang gagal jika eksklusivitas hilang (atau mungkin telah hilang) sejak ldrex, yang mencakup sakelar konteks (atau prosesor lain yang memodifikasi yang sama) daftar di lingkungan multi-prosesor). Jadi bisa dilakukan menggunakan itu; jika strex gagal Anda loop up, dan lakukan kembali operasi (dengan ldrex segar).

ref: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dht0008a/ch01s02s01.html

Rutinitas di bawah ini tampaknya bekerja pada Raspberry Pi (dalam hal mereka menghasilkan assembler yang saya harapkan; dan bahwa efek pada bit ketika saya menggunakannya seperti yang diharapkan. Saya belum memverifikasi bahwa mereka melindungi terhadap masalah beralih konteks) . Perhatikan bahwa ini adalah inline daripada fungsi, sehingga harus dimasukkan ke dalam file header.

[ EDIT : Ini tidak berfungsi untuk tujuan yang didiskusikan, sepertinya itu tidak diizinkan. Jika saya menggunakan rutinitas ini di mana * addr adalah variabel biasa, itu berfungsi dengan baik. Ketika saya menggunakannya di mana * addr diarahkan ke register GPIO yang dipetakan, proses mendapatkan kesalahan bus. (Ketika saya mengubah ldrex / strex ke ldr / str dan menonaktifkan do loop, itu kemudian berfungsi). Jadi sepertinya monitor eksklusif ARM tidak dapat, atau tidak diatur, berfungsi pada I / O yang dipetakan di memori, dan pertanyaannya tetap terbuka.]

//
// Routines to atomically modify 32-bit registers using ldrex and strex.
// 
//
//
//  locked_bic_to_reg( volatile unsigned * addr, unsigned val )
//                 *addr &= ~val
//  locked_or_to_reg( volatile unsigned * addr, unsigned val )
//                 *addr |= val
//   locked_insert_to_reg( volatile unsigned * addr, unsigned val, int width, int pos )
//           insert 'width' lsbs of 'val into *addr, with the lsb at bit 'pos'.
//           Caller must ensure 1 <= width <= 32 and 0 <= pos < 32-width
//
//
static inline void
locked_bic_to_reg( volatile unsigned * addr, unsigned val )
{
    int fail;
    do{
        asm volatile ("ldrex r0,[%1]\n"
           "   bic r0,r0,%2\n"
           "   strex %0,r0,[%1]": "=r"(fail) : "r"(addr), "r"(val): "r0" );
    }while(fail!=0);
}
static inline void
locked_or_to_reg( volatile unsigned * addr, unsigned val)
{
    int fail;
    do{
        asm volatile ("ldrex r0,[%1]\n"
           "   orr r0,r0,%2\n"
           "   strex %0,r0,[%1]": "=r"(fail) : "r"(addr), "r"(val): "r0" );
    }while(fail!=0);
}

static inline void
locked_insert_to_reg( volatile unsigned * addr, unsigned val, int width, int pos )
{
    int fail;
    if(width >=32 ) {
        *addr = val;    // assume wid = 32, pos = 0;
    }else{
        unsigned m=(1<<width)-1;
        val = (val&m) << pos;   // mask and position
        m <<= pos;

        do{
            asm volatile ("ldrex r0,[%1]\n"
               "   bic r0,r0,%2\n"   /// bic with mask
               "   orr r0,r0,%3\n"    // or result
               "   strex %0,r0,[%1]": "=r"(fail) : "r"(addr), "r"(m), "r"(val): "r0" );
        }while(fail!=0);
    }
}
greggo
sumber
Menurut saya ini adalah hal yang harus ada dalam file .h khusus prosesor, tetapi tidak ada file .h di bawah / usr / include atau / usr / lib / gcc / arm-linux-gnueabihf / berisi string 'ldrex ' Mungkin builtin , atau salah satu header kernel?
greggo
1
ldrex / strex dimaksudkan untuk berbagi sumber daya multi-core (ram bersama). swp secara tradisional digunakan untuk penguncian inti tunggal dari sumber daya inti tunggal. ldrex / strex, kebetulan berfungsi sebagai solusi inti tunggal (TERGANTUNG PADA CHIP VENDOR) sehingga disalahgunakan. itu tampaknya bekerja pada prosesor pi raspberry.
old_timer