ロック(アトミック)レジスタの読み取り/書き込み


8

私はGPIOの直接制御を使用して何かをコーディングしていますが、http://elinux.org/RPi_Low-level_peripherals#GPIO_hardware_hackingのような、このための優れたリソースがいくつかあります。このプロセスにはopen( "/ dev / mem")が含まれ、次にmmap操作が目的の物理アドレスを仮想アドレス空間に効果的にマッピングします。次に、このhttp://www.raspberrypi.org/wp-content/uploads/2012/02/BCM2835-ARM-Peripherals.pdfのセクション6を読み、I / Oがどのように制御されるかを確認します。

ピンの機能(入力、出力、またはさまざまな特殊機能)に変更するには、GPFSELx I / Oレジスタのこれらの3ビットフィールドを変更します(000 =入力、001 =出力敵インスタンス)。これらの変更操作は、通常の読み込みと保存を伴う操作にコンパイルされます(たとえば、GPIO0を入力に変更するには:*(regptr)&=〜7;これは、次のようにコンパイルされます

    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)

問題はこれです。ロードとストアの間に割り込みが発生し、別のプロセスまたはISRが同じI / Oレジスタを変更すると、ストア操作(r2への古い読み取りに基づく)が他の操作の影響を元に戻します。したがって、これらのI / Oレジスタを変更するには、アトミックな(ロックされた)読み取り/変更/書き込み操作を行う必要があります。私が見た例では、ロックされた操作を使用していません。

これらのI / Oレジスタは通常、何かを設定するときにのみ変更されるため、問題が発生する可能性はほとんどありませんが、「決してない」より「常にない」の方が常に優れています。また、オープンコレクター出力をエミュレートするためにビットバッシングしているアプリケーションがある場合、(私が知る限り)これには、出力を0にプログラミングしてから、出力(低)または入力(オフ/高)。したがって、その場合、これらのI / Oレジスタに頻繁に変更が加えられ、安全でない変更が問題を引き起こす可能性がはるかに高くなります。

それで、おそらくこれを行うためにここで使用できるARMの「比較して設定」または同様の操作があるでしょう、誰でも私にそれを指摘できますか、そしてそれをCコードから実行する方法は?

[注、I / Oを出力としてプログラムし、それを0から1に、またはその逆に変更するだけの場合、特別なことは必要ありません。書き込みを行うI / Oレジスタがあるため、選択したビットを1に設定し、別のビットを0にクリアします。この操作に読み取り/書き込みは必要ないため、割り込みによる危険はありません。


多分私はこれを正しく理解していなかったかもしれませんが、あなたが開い/dev/memているので、あなたのコードはユーザースペースコードのようです。最近のOSでは、ユーザー空間コードのレジスタ値を変更する割り込みに注意する必要があるとは思いません。Linuxは割り込みハンドラーがその仕事を終えたときにすべてのレジスターを復元するので、これはカーネル空間コードでも問題ではないと私は信じています。
Krzysztof Adamski 2013

1
私の理解では、ロード/ストアは、mmap(CPUレジスタではなくI / Oレジスタ)によって設定されたVMマッピングを介して物理レジスタに行きます。この場合、別のプロセスまたはデバイスドライバーが同じことを同時に実行して同じレジスターを変更できないという理由はありません。(私はそれが正規表現の異なるビットのセットを変更していると思います、または明らかにより大きな問題がある)プロセッサレジスタの場合とは異なり、IOレジスタの保存/復元はありません。
greggo 2013

私は、R2などとは対照的に、「I / Oレジスタ」明確にするためにビットを編集した
greggo

私は今あなたの要点を見ることができます。ただし、これは割り込み処理の問題というよりは、プリエンプションの問題です。アトミック操作を使用すると、少なくとも2つのプロセスが同時に異なるビットを設定しようとする場合に役立ちます。
Krzysztof Adamski 2013

ldrex / strexは、キャッシュされていないメモリでは機能しません。排他モニターはキャッシュに依存しています。実際、たとえばCortex-A9 SMPシステムでCPUをハードロックしようとすると、以前はハードロックすることが可能でした。
thinkfat 2018年

回答:


3

私はこれを調べました、ARMには「ldrexと「strex」命令があります。コンテキストスイッチ(または同じを変更する別のプロセッサ)を含むldrexが原因で独占権が失われた(または失われた可能性がある)場合、strexは失敗の結果を返しますマルチプロセッサ環境で登録します)。したがって、それを使用して行うことができます。strexが失敗した場合はループアップし、操作をやり直します(新しいldrexを使用)。

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

以下のルーチンはRaspberry Piで機能しているようです(期待どおりのアセンブラーを生成します。また、使用したときのビットへの影響は予想どおりです。コンテキストスイッチの問題から保護されていることを確認していません)。 。これらは関数ではなくインラインであるため、ヘッダーファイルに配置する必要があります。

[ 編集:これは議論された目的のために機能しませ、それはどういうわけか許可されていないようです。* addrが通常の変数であるこれらのルーチンを使用すると、うまく機能します。* addrがマップされたGPIOレジスタを指す場所で使用すると、プロセスでバスエラーが発生します。(ldrex / strexをldr / strに変更してdoループを無効にすると、機能します)。そのため、ARM専用モニターは、メモリマップされたI / Oレジスタで機能できないか、機能しないようであり、質問は未解決のままです。]

//
// 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);
    }
}

これはプロセッサ固有の.hファイルにあるべきもののようですが、/ usr / includeまたは/ usr / lib / gcc / arm-linux-gnueabihf /の下の.hファイルには文字列 'ldrexが含まれていません'。多分組み込み、またはカーネルヘッダーの1つですか?
greggo 2013

1
ldrex / strexは、リソースのマルチコア共有(共有RAM)を対象としています。swpは従来、シングルコアリソースのシングルコアロックに使用されます。ldrex / strexは、たまたまシングルコアソリューション(チップベンダーに依存)として機能するため、誤用されます。ただし、ラズベリーpiプロセッサでは動作するようです。
old_timer 2013
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.