ハードウェアとの通信
1)、I/OポートとI/Oメモリ
各周辺デバイスは、そのレジスタを読み書きすることで制御されます。
そのアドレスは、メモリアドレス空間か、I/Oアドレス空間の中に置かれています。
①、I/Oレジスタとコンベンショナルメモリ
I/OレジスタとRAMの主な違いは、I/O操作には付帯的な影響があることです。
I/O操作にとってはコンパイラの最適化(キャッシング、読み書きの並べ替え)が致命的なものになります。
・ハードウェアキャッシングの対策 (対応不要)
既にLinuxの初期化コードが、ハードウェアのI/O領域(メモリまたはポート領域)にアクセスするときに、
ハードウェアキャッシングを無効化にするように設定しています。
・コンパイラの最適化とハードウェアによる並べ替えの対策 (要対応)
特定の順序であることが見えなければならない操作の間に「メモリバリア」を置くことで対策します。
一般メモリバリア (ハードウェアに影響しない): void barrier(void)
ハードウェアによるメモリバリア:
void rmb(void);
void read_barrier_depends(void);
void wmb(void);
void mb(void);
SMPシステム対応のハードウェアメモリバリア:
void smp_rmb(void);
void smp_read_barrier_depends(void);
void smp_wmb(void);
void smp_mb(void);
メモリバリア使用上の注意点:
・メモリバリアは処理速度に影響します。
・利用できるもののうち最も適したものを使います。
・スピンロックやatomic_t操作など、同期を扱う他のカーネルプリミティブのほとんどは、メモリバリアのように機能します。
・一部のペリフェラルバス(例えばPCIバス)にはそれ自体にキャッシュの問題がある点に注意してください。
2)、I/Oポートの使い方
①、I/Oポートの割り当て
I/Oポートを使う前に、ポートへの排他的なアクセスをまず獲得する必要があります。
関連関数:
struct resource *request_region(unsigned long first, unsigned long n, const char *name);
void release_region(unsigned long start, unsigned long n);
②、I/Oポートの操作
ほとんどのハードウェアは、8ビット、16ビット、32ビットのポートを区別します。これらを混在させることはできません。
・8ビットポートの読み書き:
unsigned inb(unsigned port);
void outb(unsigned char byte, unsigned port);
・16ビットポートの読み書き:
unsigned inw(unsigned port);
void outw(unsigned short word, unsigned port);
・32ビットポートの読み書き:
unsigned inl(unsigned port);
void outl(unsigned longword, unsigned port);
③、ユーザ空間からのI/Oポートアクセス
略。
④、ストリング操作
・8ビットポート
void insb(unsigned port, void *addr, unsigned long count);
void outsb(unsigned port, void *addr, unsigned long count);
・16ビットポート
void insw(unsigned port, void *addr, unsigned long count);
void outsw(unsigned port, void *addr, unsigned long count);
・32ビットポート
void insl(unsigned port, void *addr, unsigned long count);
void outsl(unsigned port, void *addr, unsigned long count);
⑤、I/O処理の休止
一部のプラットフォーム(特にi386)では、CPUとバスの間のデータ転送速度が速すぎて、問題が起きることがあります。
問題が発生するのは、CPUのクロックとペリフェラルバスに対して速すぎるためで、デバイスボードが遅すぎると追いつけないです。
解決するために各いI/O命令の間に短い遅延を挿入します。
・「outb」命令
・ビジー待ち
・データ欠損の恐れがある場合は、通常の関数の代わりに休止(pause)付きの関数群を使います。
⑥、プラットフォーム依存性
互換性のなさ
・データの型 (ARMのI/Oポートは「unsigned int」型、PowerPCは「unsigned char *」型です。)
・CPU構造の違い
3)、I/Oメモリの使い方
レジスタとデバイスメモリはソフトウェアにとっては等価的なので、どちらも「I/Oメモリ」と呼ばれます。
I/Oメモリは、プロセッサがバス経由でデバイスにアクセスするためのものです。
I/Oメモリの使い方はコンピュータアーキテクチャ、バス、使用するデバイスに依存します。(ページテーブルを使う使わない場合がある)
①、I/Oメモリ割り当てとマッピング
I/Oメモリ領域を使う前に、割り当てを行います。全てのI/Oメモリ割り当ては「/proc/iomem」にリストされます。
関連関数:
struct resource *request_mem_region(unsigned long start, unsigned long len, char *name);
void release_mem_region(unsigned long start, unsigned long len);
多くのシステムでは、I/Oメモリに直接アクセスできないです。即ち、まずマッピングを最初に設定する必要があります。
/* ioremapから返されたアドレスは、そのままでは参照することができず、
カーネルが提供する関数を使用する必要があります。 */
void *ioremap(unsigned long phys_addr, unsigned long size);
void *ioremap_nocache(unsigned long phys_addr, unsigned long size);
void iounmap(void * addr);
②、I/Oメモリのアクセス
I/Oメモリを正しく扱うには、その目的のために提供されている関数群(asm/io.h)を使います。
unsigned int ioread8(void *addr);
unsigned int ioread16(void *addr);
unsigned int ioread32(void *addr);
void iowrite8(u8 value, void *addr);
void iowrite16(u16 value, void *addr);
void iowrite32(u32 value, void *addr);
void ioread8_rep(void *addr, void *buf, unsigned long count);
void ioread16_rep(void *addr, void *buf, unsigned long count);
void ioread32_rep(void *addr, void *buf, unsigned long count);
void iowrite8_rep(void *addr, const void *buf, unsigned long count);
void iowrite16_rep(void *addr, const void *buf, unsigned long count);
void iowrite32_rep(void *addr, const void *buf, unsigned long count);
void memset_io(void *addr, u8 value, unsigned int count);
void memcpy_fromio(void *dest, void *source, unsigned int count);
void memcpy_toio(void *dest, void *source, unsigned int count);
③、ポートをI/Oメモリとして扱う
I/OポートをI/Oメモリとして扱うため、下記関数を使います。
void *ioport_map(unsigned long port, unsigned int count);
void ioport_unmap(void *addr);