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