デバイスドライバを書かずにIOを読み書きする

LinuxではI/Oポートの読み書きにはデバイスドライバを書く必要があります。しかしデバック時や簡易的な治具を作るなど、デバイスドライバの作成を省略したいこともあると思います。

このページでは簡易的なI/Oルーチンとして、デバイスドライバを書かずにI/Oポートを読み書きする方法について解説します。ただしこの手法は簡易的なものであり、本格的な開発にはあまりお勧めできません。この手法でアプリケーションを開発、デバックし最終的にはデバイスドライバ化することをお勧めします(理由は本ページの最後に記述します)。

ただ、今までドライバプログラミングが難しそうだからと、せっかくのLinuxボードでもI/Oプログラミングできずに放置されていた方にとっては、はじめの第一歩として学習、研究に大変おもしろい課題と思いますからこれを機にI/O操作の面白さをしっていただければうれしく思います。ネットワークI/O装置などいかがでしょうか。

能書きはそれくらいにして、さっそく。

対象

本ページはCAT709, CAT760 などの SH Linuxボードを対象にしていますが他のボード、他のアーキテクチャでも基本的な考え方はそのまま利用できます。

cat709.jpgcat760.jpg
SH3 Linuxボード CAT709SH4 Linuxボード CAT760

基本

特殊ファイル /dev/mem はメモリ全体を示す特殊ファイル(デバイスドライバ)です。/dev/mem を open()し、mmap() することで全てのメモリの読み書きが出来ます。mmap() とは 「ファイルをmalloc() するように」 ファイルをメモリに貼り付けるシステムコールです。

OPEN Section: Linux Programmer's Manual (2)

名前
open, creat - ファイルまたはデバイスのオープン、作成を行う。   
書式
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
int creat(const char *pathname, mode_t mode);

http://www.linux.or.jp/JM/html/LDP_man-pages/man2/open.2.html

MMAP Section: Linux Programmer's Manual (2)

名前
mmap, munmap - ファイルやデバイスをメモリにマップ/アンマップする   
書式
#include <sys/mman.h>

void *mmap(void *start, size_t length, int prot, int flags,
           int fd, off_t offset);
int munmap(void *start, size_t length);

http://www.linux.or.jp/JM/html/LDP_man-pages/man2/mmap.2.html

事例

CAT709 (SH3 Linuxボード) の LED をユーザランドから直接点灯してみます。

cat709_led.gif

CAT709 ボードの LED と DIPSW は図のように 「CPUのポートJ」に接続されています。またポートJ は 0xA4000130 番地に割り付けられてます。(詳しくは ルネサステクノロジー SH7709 ハードウェアマニュアル 19章 I/Oポート を参照)

ビット番号76543210
0xA4000130LED2LED1SW4SW3SWxSW1x
読み書きRWRWRRRxRx

LED .. 1で消灯 ..0で点灯 DIPSW .. 1でOFF ..0でON x読み書き禁止

0xA4000130番地を byte単位でアクセスし、該当ビットを操作することでLEDへの出力ややDIPSWの入力が行えます。本来はbit単位で入力あるいは出力へとポートの向きを変更する必要があるのですが、ここでは省略します。(CAT709,CAT760のLEDとDIPSWのI/Oピンは起動時に方向が初期化済みです)

事前準備

CAT709, CAT760 では出荷時のファイルシステム中に /dev/mem が用意されていませんので以下の操作を一回だけ実行しておきます。

CAT709 もしくは CAT760 で実行
# rommode rw
# mknod /dev/mem c 1 1
# chown root:kmem /dev/mem
# chmod 640 /dev/mem
# rommode ro

プログラム例

ヘッダー部及びopen()まで

#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

#define DEVNAME "/dev/mem"

int main(){
	int fd;
	unsigned long from,num,port;
	volatile unsigned char *iomap;
	printf("this is IOmemory test program 2007-01-22 Y.Ebihara\n");

	/* 物理メモリーデバイスをopenする */
	fd=open(DEVNAME, O_RDWR);
	if(fd<=0){
		perror(DEVNAME);
		exit(1);
	}

open() に成功すると fd に正が入ります。失敗すると負が入ります。

	/* fd を使ってmmap する(ポインタが戻る)*/
	from = 0xa4000000;
	num    = 0x400;
	iomap = mmap(0, num, PROT_READ|PROT_WRITE, MAP_SHARED, fd, from);
	if(iomap < 0){
		perror(DEVNAME);
    	exit(1);
	}
	printf("IOmem mmaped at %p\n",iomap);

読み書きしたい領域を指定してmmap() します。この例では 0xa4000000番地から0x400番地分のmapを開いています。成功時には iomap ポインタに、 0xa4000000番地から0x400バイト分の物理メモリーが貼り付けられています。

	port=0xa4000130;
	*(iomap + port - from) &= ~0x80;

注意点としてはmmap()するときはPAGE_SIZEごとにアライメントを取る必要があります。ほとんどのアーキテクチャでPAGE_SIZEが 4kbyteであるため、アドレスの下位12bitを 0x000 に合わせる必要があります。

この例では 0xa4000130 番地をアクセスするために 0xa4000000番地をmmap()してオフセット 0x130番地にアクセスしています。

ポート 0xa4000130 番地を実際に読み書きして見ます。 0xa4000130番地の最上位ビット(Bit7) をゼロクリア(= LED2の点灯)する書式です。iomap は unsigned char * 型ですから8bit幅アクセスとなります。16bit幅でアクセスするときはunsigned short *型へキャストして読み書きします

	*(volatile unsigned short*)(iomap + port - from) &= ~0x0080;
	/* mmap を閉じる*/
	munmap(iomap,num);

	/* デバイスを閉じる*/
	close(fd);
	return 0;
}

使い終わったら mmap() を閉じ、ファイルをクローズして終了です。

上記のプログラムはこちらからダウンロードできます。

fileuserland_io.c

汎用ライブラリ

次のページでは、これを汎用I/Oライブラリ化してみます。

リンク

Last-modified: 2020-01-23 (木) 18:20:59 (1553d)