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