[ TOP | Recently ]

1999-07-07 bus.h


memory mapped I/O の話。

NEWS5000 の SCC (ESCC) の register の map のされ方は

struct zschan {
	unsigned char pad1[3];
	volatile unsigned char zc_csr;
	unsigned char pad2[3];
	volatile unsigned char zc_data;
};

みたいに、8bit port が 32bit 幅に map されている。
まぁ、ぜんぶの I/O がこんなふうに map されてるなら、
bus_space_write_* を 32bit 幅に合わせた macro
(base + offset*4 + 3 を access するような)にすればいいんだけど、
たとえば IDROM は、

struct idrom_reg {
	unsigned int	dummy1:28,
			data1:4;
	unsigned int	dummy2:28,
			data2:4;
};

となってて、data1<<4|data2 としないと目的の 8bit が得られなかったり
(なんでこんななってんだ?)、さらに ether の register は、

struct sonic_regs {
	unsigned long pad1;
	unsigned long cr;
	unsigned long pad2;
	unsigned long dcr;
	unsigned long pad3;
	unsigned long rcr;
		:
		:
};

と、32bit 幅にさらに padding 付きで map されてる。
まぁ、これも macro で逃げればいいんだけど...せっかくある driver
(arhc/mac68k/dev/if_sn.c) が bus_space_* 使ってくれてるんだから、
やっぱり bus_space を用意すべきだよね、っつーことで、
つらつらと他 architecture の実装を見る。

少くとも、device により上のように register の幅を変える必要があるので、
bus_space_read_* を bus_space_tag によって切り変えれる仕組みが必要。

というわけでそんなふうにやってる実装を探す。

*	*	*

alpha,arm32,atari は、全ての method を関数 table 化してあって、
bus_space_tag によってなんでも切り変えられる。
(これらの場合、bus_space_tag は、実際は関数のテーブルへのポインタになっている)

i386 は tag により inb/outb か、memory を read/write するかが切り変わる。
bus_space_tag は定数マクロ。

その他は map/unmap/alloc/free あたりだけを
bus_space_tag で関数 table 化しただけだったり、
完全固定 macro だったり。

いちばん news5000 に合ってるのは、amiga のやつかなぁ。というのは、

struct bus_space_tag {
	bus_addr_t	base;
	u_int8_t	stride;
	u_int8_t	dum[3];
	const struct amiga_bus_space_methods *absm;
};


と、stride という変数があって、bus_space_read_1 が

#define bus_space_read_1(t, h, o) \
    ((void) t, (*(volatile u_int8_t *)((h) + ((o)<<(t)->stride))))

という macro とか、

u_int16_t
amiga_interleaved_read_2(t, h, o)
	bus_space_tag_t t;
	bus_space_handle_t h;
	bus_size_t o;
{
	volatile u_int8_t *q;
	int step;

	step = 1 << t->stride;
	q = (volatile u_int8_t *)(h + (o << t->stride));

	return ((*q) << 8) | *(q + step);
}

といった関数になっている。
まさに news5000 の変則すきま空き I/O にぴったりというか。

ただ、amiga のは map/unmap/alloc/free とかが切り変えられない(固定マクロ)ので、
alpha/arm32/atari をベースにして、なおかつ amiga の stride を導入することにする。
ちょっと冗長な気もするが...

これでほぼどんな変態 I/O map があっても平気なはずだ。
最悪 bus_space_read_* をその device 特有のものに差し換えればいい。

というわけで、alpha/include/bus.h を copy して、s/alpha/newsmips/g して :)
それに amiga の stride のところを cut & paste して適当に書き変えてゆく。

先は長い...

*	*	*

整理する。

ex)ESCC
	struct peripheral_8_32 {
		unsigned char dummy[3];
		volatile unsigned char reg;
	};

ex)SONIC ETHER
	struct peripheral_32_64 {
		unsigned int dummy;
		volatile unsigned int reg;
	};

ex)IDROM とか
	struct peripheral_4_32 {
		unsigned char		dummy[3];
		volatile unsigned char	dummy_reg:4,
					reg:4;
	};

とりあえずこんだけのタイプが access できるようにすればいいのかな。

ただ、32bit 中下 8bit しか使ってない port は、word
(X68k で育ったせいか、word と聞くと 16bit を思い浮かべてしまう。
この場合の word は 32bit のこと)でアクセスしてもちゃんと読めるんだよな。
31-8bit にゴミが付く場合があるけど。

実際 NEWS-OS の device driver はそうやってアクセスしてるし。
そっちの方が符号拡張しなくていい分速い気がする。
とするならば、

ex)ESCC
	struct peripheral_8_32 {
		volatile unsigned int reg;
	};

ex)SONIC ETHER
	struct peripheral_32_64 {
		unsigned int dummy;
		volatile unsigned int reg;
	};

ex)IDROM とか
	struct peripheral_4_32 {
		volatile unsigned int	dummy_bit:28,
					reg:4;
	};

こうしたほうが速い? あんまかわんないか。
asm で見てみる..

struct peripheral_8_32 {
	volatile unsigned char dummy[3];
	volatile unsigned char reg;
};

struct peripheral_32_32 {
	volatile unsigned int reg;
};

/*
 * 8bit access をして unsigned char で返す。
 * いたって普通。
 */
unsigned char
test8_uc(p)
struct peripheral_8_32 *p;
{
	p->reg = 1;
	return(p->reg);
}

/*
 * 8bit access をして unsigned int で返す。
 * 符号(zero)拡張が入るはず。
 */
unsigned int
test8_ui(p)
struct peripheral_8_32 *p;
{
	p->reg = 1;
	return(p->reg);
}

/*
 * 32bit access をして unsigned char で返す。
 * いちばん遅いと思う。
 */
unsigned char
test32_uc(p)
struct peripheral_32_32 *p;
{
	p->reg = 1;
	return(p->reg);
}

/*
 * 32bit access をして unsigned int で返す。
 * ゴミが残る場合あり。
 * いちばん速いと思う。
 */
unsigned int
test32_ui(p)
struct peripheral_32_32 *p;
{
	p->reg = 1;
	return(p->reg);
}

で、gcc にお伺いをたててみる。
mips-sony-netbsd-gcc -mrnames -S -O9 -fomit-frame-pointer rw_test.c
と...

test8_uc:
	li	$v0,1			# 0x1
	sb	$v0,3($a0)
	lbu	$v0,3($a0)
	j	$31
	nop

test8_ui:
	li	$v0,1			# 0x1
	sb	$v0,3($a0)
	lbu	$v0,3($a0)
	j	$31
	nop

test32_uc:
	li	$v0,1			# 0x1
	sw	$v0,0($a0)
	lw	$v0,0($a0)
	j	$31
	andi	$v0,$v0,0x00ff

test32_ui:
	li	$v0,1			# 0x1
	sw	$v0,0($a0)
	lw	$v0,0($a0)
	j	$31
	nop

lbu なんて命令があるのか。load byte すると同時にゼロ拡張。
となると結局スピードは変わんないか。

32bit で読んで自分で unsigned char を返さなきゃいけない
test32_uc() が一番遅い...気もするけど、
and 操作は遅延 slot に入るから変わらないような気もする。
うーむ。MIPS 強し... :-)

*	*	*

う〜ん、結局この三つでカバーできるのかなぁ?
そうすると stride を導入しなくてもいいような気もしてきた。
毎回 stride を評価しなきゃいけないのでオーバーヘッドでかいし。

せっかく bus_space_read_* を関数へのポインタにしたんだから、
その内部でそれぞれの bus 幅形式用のものにしてしまったほうが
スッキリするような気もする...ということは alpha/arm32/atari のまんまパクれば ok ?

もうちょっと考えよう。


EOF