オープンソース開発と、あるフレームバッファコンソールの話

謎マシンいじりが最近の趣味のN君。
某オークションで掘り出し物をゲットしてホクホクです。
「Sunがリリースした最後のSPARCワークステーション
こころがぴょんぴょんするんじゃ~〜〜

さっそくNetBSDをインストールしましたが...

画面描画がおっそーい!(ぜかまし風)

行がスクロールするたびに、動きはもっさり、ゆらゆら画面がゆれています...

なぜこんなに画面描画が遅いのか?さっそく原因を調べてみましょう!

というわけで前置きが長くなりましたが、
"オープンソース開発と、あるフレームバッファコンソールの話"と題しまして、
NetBSD Advent Calendar 2014 - Qiita
23目に寄稿させてもらいます。

このような遊びに必要なものは
動作する実装が公開されているGPU
・例えばXの実装。
・例えばLinuxBSDの実装。
GPU仕様書
になります。

今回は
Xの実装として
xc/programs/Xserver/hw/xfree86/drivers/glint
Linuxの実装として
linux/pm2fb.c at master · torvalds/linux · GitHub
NetBSDの実装として
Cross Reference: /src/sys/dev/pci/pm2fb.c
OpenBSDの実装として
src/sys/arch/sparc64/dev/gfxp.c - view - 1.13
GPU仕様書として
TVP4020 Programmer's Reference User's Guide (Rev. A)
を参考にしました。

また、個人的にはつついさんの書かれた
FreeBSD Press No.12 NetBSDにおけるデバイスドライバの読み方・書き方
の記事を補完する内容だと思っています。
(つついさんの記事は後半でbus_dmaの解説に向かうので)
なので、つついさんの書かれた記事を先に読むと、より理解が深まると思います。

また、しばらく実装の話が続きますので、パッチが出来てからの話に興味が有るかたは、
読み飛ばしてうしろの方から読んでくださってもかまいません。

さて、N君が手に入れたマシンはSun MicrosystemsのUltra 25という機種で、
使われているCPUはUltraSPARC IIIi(SPARC64)です。
[今までに発表した製品]UNIXサーバ S series Sun Ultra 25 : 富士通

ささっていたグラフィックボードはPGX32(Raptor GFX)でした。

PGX32にはPermedia 2というGPUが使われています。
そしてPGX32はNetBSDではpm2fbという名前でドライバが提供されています。
https://wiki.netbsd.org/users/macallan/pm2fb/

カーネルの設定ファイルを見ると...
/sys/arch/sparc64/conf/GENERIC
http://nxr.netbsd.org/xref/src/sys/arch/sparc64/conf/GENERIC?r=1.177#904

# Sun PGX32 / TechSource Raptor GFX 8P
pm2fb*		at pci?

と、ちゃんとカーネルに組み込まれるようになっています。

画面描画が遅いということは、ドライバの性能が悪いのでしょうか?
そこでdmesgを見て確認してみます。

Copyright (c) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
    2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014
    The NetBSD Foundation, Inc.  All rights reserved.
Copyright (c) 1982, 1986, 1989, 1991, 1993
    The Regents of the University of California.  All rights reserved.

NetBSD 7.99.3 (GENERIC) #1: Mon Dec 15 20:27:06 UTC 2014
        naruaki@:/usr/obj.sparc64/sys/arch/sparc64/compile/GENERIC
total memory = 1024 MB
avail memory = 986 MB
timecounter: Timecounters tick every 10.000 msec
mainbus0 (root): SUNW,Ultra-25 (Sun Ultra 25 Workstation): hostid 84581962
cpu0 at mainbus0: SUNW,UltraSPARC-IIIi @ 1336 MHz, CPU id 0
cpu0: manuf 3e, impl 16, mask 34
cpu0: system tick frequency 20 MHz
cpu0: 32K instruction (32 b/l), 64K data (32 b/l), 1024K external (64 b/l)
...
pci10: i/o space, memory space enabled
genfb0 at pci10 dev 1 function 0: Texas Instruments TVP4020 Permedia 2 (rev. 0x11)
genfb0: framebuffer at 0x1000000, size 1280x1024, depth 32, stride 5120
wsdisplay0 at genfb0 kbdmux 1: console (default, vt100 emulation)
wsmux1: connecting to wsdisplay0
wsdisplay0: screen 1-3 added (default, vt100 emulation)
drm at genfb0 not configured

genfb0 at pci10 dev 1 function 0: Texas Instruments TVP4020 Permedia 2 (rev. 0x11)
genfb0: framebuffer at 0x1000000, size 1280x1024, depth 32, stride 5120

アイエエエエ! genfb!? genfbナンデ!?

どうやら、PGX32用のpm2fbではなく、汎用のフレームバッファコンソールドライバのgenfbがアタッチしてしまったようです...
http://wiki.netbsd.org/users/macallan/genfb/
"The genfb driver provides an unaccelerated framebuffer console"
とありますし、画面描画が遅いのもしかたがありませんね...

とはいえ、せっかくの高速なUltraSPARC IIIiが台無しです...
バン (∩`・ω・) バンバン

そこで、このPGX32に使われているGPUの素性を調べてみることにします。
再度dmesgを見てみましょう。

genfb0 at pci10 dev 1 function 0: Texas Instruments TVP4020 Permedia 2 (rev. 0x11)
genfb0: framebuffer at 0x1000000, size 1280x1024, depth 32, stride 5120

おっ?どうやらPermedia 2のTVP4020というチップのようです。

そこで、NetBSDのpm2fbドライバのソースコードを頑張って読んでみます。
http://nxr.netbsd.org/xref/src/sys/dev/pci/pm2fb.c?r=1.25#266

	/*
	 * only card tested on so far is a TechSource Raptor GFX 8P /
	 * Sun PGX32, which happens to be a Permedia 2v
	 */

と書いてありました。
Permedia 2v?Permedia 2と違うのでしょうか?

そこでWikipediaをPermedia 2で検索してみます。
3Dlabs - Wikipedia
"後期にはマイナーチェンジ版のPermedia2Vが発売されている。"

どうやら、NetBSDのpm2fbドライバはPermedia 2v用に書かれているようです...
これでアタッチしなかった理由もわかりました!
でも、もうひとつ気になる点があります...
Permedia 2とPermedia 2vはWikipediaをみると3Dlabsの製品のようです。でもdmesgには
Texas Instruments TVP4020 Permedia 2 (rev. 0x11)
とありました。
Texas Instruments?どういうことなのでしょうか?
そこでXのソースコードを頑張って読んでみます。
http://nxr.netbsd.org/source/xref/xsrc/xfree/xc/programs/Xserver/hw/xfree86/drivers/glint/glint_driver.c?r=1.4#174

    { PCI_VENDOR_TI_CHIP_PERMEDIA2,	 PCI_VENDOR_TI_CHIP_PERMEDIA2,	    RES_SHARED_VGA },
    { PCI_VENDOR_TI_CHIP_PERMEDIA,	 PCI_VENDOR_TI_CHIP_PERMEDIA,	    NULL },
    { PCI_VENDOR_3DLABS_CHIP_R4, PCI_VENDOR_3DLABS_CHIP_R4, RES_SHARED_VGA },
    { PCI_VENDOR_3DLABS_CHIP_PERMEDIA4, PCI_VENDOR_3DLABS_CHIP_PERMEDIA4, RES_SHARED_VGA },
    { PCI_VENDOR_3DLABS_CHIP_PERMEDIA3, PCI_VENDOR_3DLABS_CHIP_PERMEDIA3, RES_SHARED_VGA },
    { PCI_VENDOR_3DLABS_CHIP_PERMEDIA2V, PCI_VENDOR_3DLABS_CHIP_PERMEDIA2V, RES_SHARED_VGA },
    { PCI_VENDOR_3DLABS_CHIP_PERMEDIA2,	 PCI_VENDOR_3DLABS_CHIP_PERMEDIA2,  RES_SHARED_VGA },

PCI_VENDOR_TI_CHIP_PERMEDIA2
PCI_VENDOR_3DLABS_CHIP_PERMEDIA2V
PCI_VENDOR_3DLABS_CHIP_PERMEDIA2

とPermedia 2には
Texas Instruments製のPermedia 2(TVP4020)

3Dlabs製のPermedia 2、Permedia 2V
があることがわかりました。
ああ、すっきりした!

では、GPUの素性もわかってきたので、NetBSDのpm2fbドライバに手をいれて、Permedia 2(TVP4020)で使えないかどうか試してみます!

NetBSDのpm2fbドライバのソースコードを読んでみると、
http://nxr.netbsd.org/xref/src/sys/dev/pci/pm2fb.c?r=1.25#256
のpm2fb_match関数でGPUの判定をしているようですね。
なら、ここを改造してTVP4020もプローブするようにしてみます。

const struct {
	int vendor;
	int product;
	int flags;
} pm2fb_pci_devices[] = {
	{
		PCI_VENDOR_3DLABS,
		PCI_PRODUCT_3DLABS_PERMEDIA2V,
		0
	},
	{
		PCI_VENDOR_TI,
		PCI_PRODUCT_TI_TVP4020,
		1
	},
	{
		0,
		0,
		0
	}
};

という構造体を定義して、

static int
pm2fb_match(device_t parent, cfdata_t match, void *aux)
{
	struct pci_attach_args *pa = (struct pci_attach_args *)aux;

	if (PCI_CLASS(pa->pa_class) != PCI_CLASS_DISPLAY)
		return 0;
	if (PCI_VENDOR(pa->pa_id) != PCI_VENDOR_3DLABS)
		return 0;

	/*
	 * only card tested on so far is a TechSource Raptor GFX 8P /
	 * Sun PGX32, which happens to be a Permedia 2v
	 */
	if (/*(PCI_PRODUCT(pa->pa_id) == PCI_PRODUCT_3DLABS_PERMEDIA2) ||*/
	    (PCI_PRODUCT(pa->pa_id) == PCI_PRODUCT_3DLABS_PERMEDIA2V))
		return 100;
	return (0);
}

static int
pm2fb_match(device_t parent, cfdata_t match, void *aux)
{
	struct pci_attach_args *pa = (struct pci_attach_args *)aux;
	int i;

	if (PCI_CLASS(pa->pa_class) != PCI_CLASS_DISPLAY)
		return 0;

	for (i = 0; pm2fb_pci_devices[i].vendor; i++) {
		if ((PCI_VENDOR(pa->pa_id) == pm2fb_pci_devices[i].vendor &&
		     PCI_PRODUCT(pa->pa_id) == pm2fb_pci_devices[i].product))
			return 100;
	}

	return (0);
}

と改造してみました。これならGPUの種類が増えても(増えんけど...)、構造体を定義し直せば、すぐ対応できますね。
PCI_VENDORやPCI_PRODUCTは何ぞや?というと、ハードにはハード固有の情報が定義されていて、それを参照しています。
PCI Vendor and Device Lists
http://nxr.netbsd.org/xref/src/sys/dev/pci/pcidevs.h?r=1.1202#750

int flags;

pm2fb_softc構造体に
http://nxr.netbsd.org/xref/src/sys/dev/pci/pm2fb.c?r=1.25#76

struct pm2fb_softc {
	device_t sc_dev;
...
	pci_chipset_tag_t sc_pc;
	pcitag_t sc_pcitag;
...
	int sc_is_pm2;
};

のようにsc_is_pm2と言う名前でPermedia 2(TVP4020)とPermedia 2vの処理を
分岐させる用のフラグが有ったので、活用してみました。
このフラグの存在を考えると、NetBSDのpm2fbドライバはPermedia 2(TVP4020)とPermedia 2vの両方に
対応しようとはしていたみたいです。
なので、
http://nxr.netbsd.org/xref/src/sys/dev/pci/pm2fb.c?r=1.25#295

	sc->sc_is_pm2 = (PCI_PRODUCT(pa->pa_id) ==
	    PCI_PRODUCT_3DLABS_PERMEDIA2);

	for (i = 0; pm2fb_pci_devices[i].vendor; i++) {
		if (PCI_PRODUCT(pa->pa_id) == pm2fb_pci_devices[i].product) {
			sc->sc_is_pm2 = pm2fb_pci_devices[i].flags ;
			break;
		}
	}

と書き換えてみます。

さぁ、カーネルコンパイルして、起動!

お、GPUを認識してアタッチすることに成功した結果?画面が真っ黒になってブラックアウトするようになりました...
良かった良かった?...

ですが、アタッチされるだけでは、残念ながらうまく動いてくれないようです。
そんな自分に都合の良い展開はエロゲの中だけで現実ではなかなかありませんね...

そもそもフレームバッファコンソールドライバの中でハードに依存している処理は、どのあたりなのでしょうか?

Probe、レジスタの初期化、パレットの設定、RAMDACの設定、ビットマップの描画等が
ハードウェアに依存した内容になっているようです。

そこで、このあたりのPermedia 2(TVP4020)専用の処理をpm2fbドライバに適切に追記すれば、
pm2fbドライバでPermedia 2(TVP4020)とPermedia 2v両方が使えるようになる気がします。

では、Permedia 2(TVP4020)とPermedia 2vの違いは何なのでしょうか?
Linuxのpm2fbドライバのソースコードを読んでみます。
https://github.com/torvalds/linux/blob/master/drivers/video/fbdev/pm2fb.c
pm2fb_probe関数が一番最初の処理っぽいですね。

/**
 * Device initialisation
 *
 * Initialise and allocate resource for PCI device.
 *
 * @param	pdev	PCI device.
 * @param	id	PCI device ID.
 */
static int pm2fb_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
	struct pm2fb_par *default_par;
	struct fb_info *info;
...
	switch (pdev->device) {
	case  PCI_DEVICE_ID_TI_TVP4020:
		strcpy(pm2fb_fix.id, "TVP4020");
		default_par->type = PM2_TYPE_PERMEDIA2;
		break;
	case  PCI_DEVICE_ID_3DLABS_PERMEDIA2:
		strcpy(pm2fb_fix.id, "Permedia2");
		default_par->type = PM2_TYPE_PERMEDIA2;
		break;
	case  PCI_DEVICE_ID_3DLABS_PERMEDIA2V:
		strcpy(pm2fb_fix.id, "Permedia2v");
		default_par->type = PM2_TYPE_PERMEDIA2V;
		break;
	}

...

なるほど。
PM2_TYPE_PERMEDIA2VとPM2_TYPE_PERMEDIA2を手がかりに調べてみます。
すると...

static void reset_config(struct pm2fb_par *p)
{
	WAIT_FIFO(p, 53);
	pm2_WR(p, PM2R_CHIP_CONFIG, pm2_RD(p, PM2R_CHIP_CONFIG) &
			~(PM2F_VGA_ENABLE | PM2F_VGA_FIXED));
	pm2_WR(p, PM2R_BYPASS_WRITE_MASK, ~(0L));
...
	switch (p->type) {
	case PM2_TYPE_PERMEDIA2:
		pm2_RDAC_WR(p, PM2I_RD_MODE_CONTROL, 0); /* no overlay */
		pm2_RDAC_WR(p, PM2I_RD_CURSOR_CONTROL, 0);
		pm2_RDAC_WR(p, PM2I_RD_MISC_CONTROL, PM2F_RD_PALETTE_WIDTH_8);
		pm2_RDAC_WR(p, PM2I_RD_COLOR_KEY_CONTROL, 0);
		pm2_RDAC_WR(p, PM2I_RD_OVERLAY_KEY, 0);
		pm2_RDAC_WR(p, PM2I_RD_RED_KEY, 0);
		pm2_RDAC_WR(p, PM2I_RD_GREEN_KEY, 0);
		pm2_RDAC_WR(p, PM2I_RD_BLUE_KEY, 0);
		break;
	case PM2_TYPE_PERMEDIA2V:
		pm2v_RDAC_WR(p, PM2VI_RD_MISC_CONTROL, 1); /* 8bit */
		break;
	}
}

static void set_memclock(struct pm2fb_par *par, u32 clk)
{
	int i;
	unsigned char m, n, p;

	switch (par->type) {
	case PM2_TYPE_PERMEDIA2V:
		pm2v_mnp(clk/2, &m, &n, &p);
		WAIT_FIFO(par, 12);
		pm2_WR(par, PM2VR_RD_INDEX_HIGH, PM2VI_RD_MCLK_CONTROL >> 8);
		pm2v_RDAC_WR(par, PM2VI_RD_MCLK_CONTROL, 0);
		pm2v_RDAC_WR(par, PM2VI_RD_MCLK_PRESCALE, m);
		pm2v_RDAC_WR(par, PM2VI_RD_MCLK_FEEDBACK, n);
		pm2v_RDAC_WR(par, PM2VI_RD_MCLK_POSTSCALE, p);
		pm2v_RDAC_WR(par, PM2VI_RD_MCLK_CONTROL, 1);
		rmb();
		for (i = 256; i; i--)
			if (pm2v_RDAC_RD(par, PM2VI_RD_MCLK_CONTROL) & 2)
				break;
		pm2_WR(par, PM2VR_RD_INDEX_HIGH, 0);
		break;
	case PM2_TYPE_PERMEDIA2:
		pm2_mnp(clk, &m, &n, &p);
		WAIT_FIFO(par, 10);
		pm2_RDAC_WR(par, PM2I_RD_MEMORY_CLOCK_3, 6);
		pm2_RDAC_WR(par, PM2I_RD_MEMORY_CLOCK_1, m);
		pm2_RDAC_WR(par, PM2I_RD_MEMORY_CLOCK_2, n);
		pm2_RDAC_WR(par, PM2I_RD_MEMORY_CLOCK_3, 8|p);
		pm2_RDAC_RD(par, PM2I_RD_MEMORY_CLOCK_STATUS);
		rmb();
		for (i = 256; i; i--)
			if (pm2_RD(par, PM2R_RD_INDEXED_DATA) & PM2F_PLL_LOCKED)
				break;
		break;
	}
}

static void set_video(struct pm2fb_par *p, u32 video)
{
	u32 tmp;
	u32 vsync = video;

	DPRINTK("video = 0x%x\n", video);

	/*
	 * The hardware cursor needs +vsync to recognise vert retrace.
	 * We may not be using the hardware cursor, but the X Glint
	 * driver may well. So always set +hsync/+vsync and then set
	 * the RAMDAC to invert the sync if necessary.
	 */
	vsync &= ~(PM2F_HSYNC_MASK | PM2F_VSYNC_MASK);
	vsync |= PM2F_HSYNC_ACT_HIGH | PM2F_VSYNC_ACT_HIGH;

	WAIT_FIFO(p, 3);
	pm2_WR(p, PM2R_VIDEO_CONTROL, vsync);

	switch (p->type) {
	case PM2_TYPE_PERMEDIA2:
		tmp = PM2F_RD_PALETTE_WIDTH_8;
		if ((video & PM2F_HSYNC_MASK) == PM2F_HSYNC_ACT_LOW)
			tmp |= 4; /* invert hsync */
		if ((video & PM2F_VSYNC_MASK) == PM2F_VSYNC_ACT_LOW)
			tmp |= 8; /* invert vsync */
		pm2_RDAC_WR(p, PM2I_RD_MISC_CONTROL, tmp);
		break;
	case PM2_TYPE_PERMEDIA2V:
		tmp = 0;
		if ((video & PM2F_HSYNC_MASK) == PM2F_HSYNC_ACT_LOW)
			tmp |= 1; /* invert hsync */
		if ((video & PM2F_VSYNC_MASK) == PM2F_VSYNC_ACT_LOW)
			tmp |= 4; /* invert vsync */
		pm2v_RDAC_WR(p, PM2VI_RD_SYNC_CONTROL, tmp);
		break;
	}
}

が見つかりました。

どうやら、
pm2_RDAC_RD関数pm2v_RDAC_WR関数の違いがキモのようです。

static inline void pm2_RDAC_WR(struct pm2fb_par *p, s32 idx, u32 v)
{
	pm2_WR(p, PM2R_RD_PALETTE_WRITE_ADDRESS, idx);
	wmb();
	pm2_WR(p, PM2R_RD_INDEXED_DATA, v);
	wmb();
}

static inline void pm2v_RDAC_WR(struct pm2fb_par *p, s32 idx, u32 v)
{
	pm2_WR(p, PM2VR_RD_INDEX_LOW, idx & 0xff);
	wmb();
	pm2_WR(p, PM2VR_RD_INDEXED_DATA, v);
	wmb();
}

RDAC?
RDACはRAMDACの略のようです。

ではRAMDACとは何なのでしょうか?
Wikipediaで調べてみます。
デジタル-アナログ変換回路 - Wikipedia
RAMDAC - Wikipedia, the free encyclopedia
なるほど。
"ビデオカードにおいて用いられる、映像信号処理用D/A変換回路"の事だったんですね。

そして、Linuxのpm2fbのソースコードを見る限り、Permedia 2(TVP4020)とPermedia 2vの主な違いはRAMDACの設定だけのようです。

つぎはXの実装を調べてみましょう。
http://cvsweb.xfree86.org/cvsweb/xc/programs/Xserver/hw/xfree86/drivers/glint/#dirlist
を見てみると...

おおっ。

pm2_dac.c
pm2ramdac.c

pm2v_dac.c
pm2vramdac.c

ときれいに処理が分かれているソースがあるじゃないですか!

ためしに、pm2_dac.cをのぞいてみます...
http://nxr.netbsd.org/source/xref/xsrc/external/mit/xf86-video-glint/dist/src/pm2_dac.c?r=1.3
PM2DAC_CalculateMNPCForClock関数やPermedia2Init関数、Permedia2Save関数、Permedia2Restore関数などがありますが、
関数名からしてPermedia2Init関数が重要な感じがします。

そしてPermedia2Initで調べてみると...
http://nxr.netbsd.org/source/xref/xsrc/external/mit/xf86-video-glint/dist/src/glint_driver.c?r=1.6#2481

/*
 * Initialise a new mode.  This is currently still using the old
 * "initialise struct, restore/write struct to HW" model.  That could
 * be changed.
 */

static Bool
GLINTModeInit(ScrnInfoPtr pScrn, DisplayModePtr mode)
{
    int ret = -1;
    GLINTPtr pGlint = GLINTPTR(pScrn);
    RamDacHWRecPtr pRAMDAC = RAMDACHWPTR(pScrn);
    RamDacRegRecPtr RAMDACreg;
    GLINTRegPtr glintReg = &pGlint->ModeReg[0];
    GLINTRegPtr glintReg2 = &pGlint->ModeReg[1];

    pScrn->vtSema = TRUE;

    switch (pGlint->Chipset) {
    case PCI_VENDOR_TI_CHIP_PERMEDIA2:
    case PCI_VENDOR_3DLABS_CHIP_PERMEDIA2:
	ret = Permedia2Init(pScrn, mode);
	break;
    case PCI_VENDOR_3DLABS_CHIP_PERMEDIA2V:
	ret = Permedia2VInit(pScrn, mode);
	break;

ビンゴ。

CI_VENDOR_TI_CHIP_PERMEDIA2
CI_VENDOR_3DLABS_CHIP_PERMEDIA2

Permedia2Init関数
PCI_VENDOR_3DLABS_CHIP_PERMEDIA2V

Permedia2VInit関数
をよんでいました。

XやLinuxのpm2fbドライバを調べた結果、
NetBSDのpm2fbドライバは、Permedia 2(TVP4020)用のRAMDACの設定が記述されていないようです。

さっそくNetBSDのpm2fbドライバにPermedia 2(TVP4020)用のRAMDACの処理を追記します。

NetBSDのpm2fbドライバのソースを読んでみると、ヒントとして、
http://nxr.netbsd.org/xref/src/sys/dev/pci/pm2fb.c?r=1.25#1498

/*
 * most of the following was adapted from the xf86-video-glint driver's
 * pm2v_dac.c
 */
static void
pm2fb_set_mode(struct pm2fb_softc *sc, const struct videomode *mode)
{
	int t1, t2, t3, t4, stride;
	uint32_t vclk;
	uint8_t sync = 0;

と書いてありました!
なのでPermedia 2(TVP4020)のRAMDACの設定はXのpm2_dac.cの処理を参考にして書く事に決定します。

まずは、いまのNetBSDのpm2fbドライバのRAMDACに関する処理は、Permedia 2v用の処理なので
関数名を書き換えてっと...

-pm2fb_set_pll(struct pm2fb_softc *sc, int freq)
+pm2vfb_set_pll(struct pm2fb_softc *sc, int freq)
-pm2fb_set_mode(struct pm2fb_softc *sc, const struct videomode *mode)
+pm2vfb_set_dac(struct pm2fb_softc *sc, const struct videomode *mode)

そして追記します。

static int
pm2fb_set_pll(struct pm2fb_softc *sc, int freq)
{
	uint8_t  reg, bm = 0, bn = 0, bp = 0;
	unsigned int  m, n, p, fi, diff, out_freq, bdiff = 1000000;

	for (n = 2; n < 15; n++) {
		for (m = 2 ; m < 256; m++) {
			fi = PM2_EXT_CLOCK_FREQ * m / n;
			if (fi >= PM2_PLL_FREQ_MIN && fi <= PM2_PLL_FREQ_MAX) {
				for (p = 0; p < 5; p++) {
					out_freq = fi >> p;
					diff = abs(out_freq - freq);
					if (diff < bdiff) {
						bm = m;
						bn = n;
						bp = p;
						bdiff = diff;
					}
				}
			}
		}
	}

	pm2fb_write_dac(sc, PM2_DAC_PIXELCLKA_M, bm);
	pm2fb_write_dac(sc, PM2_DAC_PIXELCLKA_N, bn);
	pm2fb_write_dac(sc, PM2_DAC_PIXELCLKA_P, (bp | 0x08));

	do {
		reg = bus_space_read_1(sc->sc_memt, sc->sc_regh,
			PM2_DAC_INDEX_DATA);
	} while (reg == PCLK_LOCKED);

	return 0;
}

/*
 * most of the following was adapted from the xf86-video-glint driver's
 * pm2_dac.c (8bpp only)
 */
static void
pm2fb_set_dac(struct pm2fb_softc *sc, const struct videomode *mode)
{
	int t1, t2, t3, t4, stride;
	uint32_t vclk, tmp;
	uint8_t sync = 0;

	t1 = mode->hsync_start - mode->hdisplay;
	t2 = mode->vsync_start - mode->vdisplay;
	t3 = mode->hsync_end - mode->hsync_start;
	t4 = mode->vsync_end - mode->vsync_start;

	/* first round up to the next multiple of 32 */
	stride = (mode->hdisplay + 31) & ~31;
	/* then find the next bigger one that we have partial products for */
	while ((partprodPermedia[stride >> 5] == -1) && (stride < 2048)) {
		stride += 32;
	}

	bus_space_write_4(sc->sc_memt, sc->sc_regh, PM2_HTOTAL,
	    ((mode->htotal) >> 2) - 1);
	bus_space_write_4(sc->sc_memt, sc->sc_regh, PM2_HSYNC_END,
	    (t1 + t3) >> 2);
	bus_space_write_4(sc->sc_memt, sc->sc_regh, PM2_HSYNC_START,
	    (t1 >> 2));
	bus_space_write_4(sc->sc_memt, sc->sc_regh, PM2_HBLANK_END,
	    (mode->htotal - mode->hdisplay) >> 2);
	bus_space_write_4(sc->sc_memt, sc->sc_regh, PM2_HGATE_END,
	    (mode->htotal - mode->hdisplay) >> 2);
	bus_space_write_4(sc->sc_memt, sc->sc_regh, PM2_SCREEN_STRIDE,
	    stride >> 2);

	bus_space_write_4(sc->sc_memt, sc->sc_regh, PM2_VTOTAL,
	    mode->vtotal - 2);
	bus_space_write_4(sc->sc_memt, sc->sc_regh, PM2_VSYNC_END,
	    t2 + t4 - 1);
	bus_space_write_4(sc->sc_memt, sc->sc_regh, PM2_VSYNC_START,
	    t2 - 1);
	bus_space_write_4(sc->sc_memt, sc->sc_regh, PM2_VBLANK_END,
	    mode->vtotal - mode->vdisplay);
	bus_space_write_4(sc->sc_memt, sc->sc_regh, PM2_VIDEO_CONTROL,
	    PM2_VC_VIDEO_ENABLE |
	    PM2_VC_HSYNC_ACT_HIGH | PM2_VC_VSYNC_ACT_HIGH);

	vclk = bus_space_read_4(sc->sc_memt, sc->sc_regh, PM2_VCLKCTL);
	bus_space_write_4(sc->sc_memt, sc->sc_regh, PM2_VCLKCTL,
	    vclk & 0xfffffffc);

	tmp = bus_space_read_4(sc->sc_memt, sc->sc_regh, PM2_CHIP_CONFIG);
	bus_space_write_4(sc->sc_memt, sc->sc_regh, PM2_CHIP_CONFIG,
	    tmp & 0xffffffdd);

	pm2fb_write_dac(sc, PM2_DAC_MODE_CONTROL, MOC_BUFFERFRONT);
	pm2fb_set_pll(sc, mode->dot_clock);

	sync = MC_PALETTE_8BIT;

	if (!(mode->flags & VID_PHSYNC))
	    sync |= MC_HSYNC_INV;
	if (!(mode->flags & VID_PVSYNC))
	    sync |= MC_VSYNC_INV;

	pm2fb_write_dac(sc, PM2_DAC_MISC_CONTROL, sync);
	pm2fb_write_dac(sc, PM2_DAC_COLOR_MODE,
	    CM_PALETTE | CM_GUI_ENABLE | CM_RGB);

	sc->sc_width = mode->hdisplay;
	sc->sc_height = mode->vdisplay;
	sc->sc_depth = 8;
	sc->sc_stride = stride;
	aprint_normal_dev(sc->sc_dev, "pm2 using %d x %d in 8 bit, stride %d\n",
	    sc->sc_width, sc->sc_height, stride);
}

static void
pm2fb_set_mode(struct pm2fb_softc *sc, const struct videomode *mode)
{
	if (sc->sc_is_pm2) {
		pm2fb_set_dac(sc, mode);
	} else {
		pm2vfb_set_dac(sc, mode);
	}
}

Permedia 2(TVP4020)とPermedia 2v用の処理をわけるフラグsc_is_pm2を利用して
RAMDACの処理を分岐させています。

さぁ、カーネルコンパイルして、起動!

文字も出てきて、ログインプロンプトも出てきましたが、画面が半分に圧縮?されています。
でも、ブラックアウトしているのに比べれば、大幅な進歩です。
やったネ!

もう一度、追記したソースコードを再度見直してみます...

	bus_space_write_4(sc->sc_memt, sc->sc_regh, PM2_SCREEN_STRIDE,
-	    stride >> 2);
+	    stride >> 3);

でした!
この謎の右シフト演算は、
Linuxのpm2fbドライバの
http://www.cs.fsu.edu/~baker/devices/lxr/http/source/linux/drivers/video/pm2fb.c#L238

/**
 *	pm2fb_set_par - Alters the hardware state.
 *	@info: frame buffer structure that represents a single frame buffer
 *
 *	Using the fb_var_screeninfo in fb_info we set the resolution of the
 *	this particular framebuffer.
 */
static int pm2fb_set_par(struct fb_info *info)
{
	struct pm2fb_par *par = info->par;
...
	depth = (depth > 32) ? 32 : depth;
	data64 = depth > 8 || par->type == PM2_TYPE_PERMEDIA2V;
...
	hsstart = to3264(info->var.right_margin, depth, data64);
	hsend = hsstart + to3264(info->var.hsync_len, depth, data64);
	hbend = hsend + to3264(info->var.left_margin, depth, data64);
	htotal = to3264(xres, depth, data64) + hbend - 1;
	vsstart = (info->var.lower_margin)
		? info->var.lower_margin - 1
		: 0;	/* FIXME! */
	vsend = info->var.lower_margin + info->var.vsync_len - 1;
	vbend = info->var.lower_margin + info->var.vsync_len +
		info->var.upper_margin;
	vtotal = info->var.yres + vbend - 1;
	stride = to3264(width, depth, 1);
...
	set_pixclock(par, pixclock);
	DPRINTK("Setting graphics mode at %dx%d depth %d\n",
		info->var.xres, info->var.yres, info->var.bits_per_pixel);
	return 0;
}

static u32 to3264(u32 timing, int bpp, int is64)
{
         switch (bpp) {
         case 8:
                 timing >>= 2 + is64;
                 break;
         case 16:
                 timing >>= 1 + is64;
                 break;
         case 24:
                 timing = (timing * 3) >> (2 + is64);
                 break;
         case 32:
                 if (is64)
                         timing >>= 1;
                 break;
         }
         return timing;
}

を参考にしました。

さぁ、カーネルコンパイルして、起動!


おおお〜、きれいに文字が表示されました!

pm2fb0 at pci10 dev 1 function 0: Texas Instruments TVP4020 Permedia 2 (rev. 0x11)
pm2fb0: 8 MB aperture at 0x01800000
no data for est. mode 640x480x67
pm2fb0: pm2fb using 1280 x 1024 in 8 bit, stride 1280
wsdisplay0 at pm2fb0 kbdmux 1: console (default, vt100 emulation)

pm2fb0 at pci10 dev 1 function 0: Texas Instruments TVP4020 Permedia 2 (rev. 0x11)
pm2fb0: 8 MB aperture at 0x01800000

ちゃんとアタッチもされています!

なにより、genfbと比べてHardware accelerationが効いて、体感できるレベルで画面描画がキビキビしています。
ああ、rasopsの歓声が聞こえるようだ...

これで一応NetBSD/sparc64でPermedia 2(TVP4020)が使えるようになりました。
しかし、

真にMachine Independentなドライバを目指すならば、SPARCのようなBig endianアーキテクチャで動いただけで満足してはいけません!
tested on sparc64 only so farで満足するなど、愚の骨頂!

という訳でN君は死蔵していた"ELSA Gloria Synergy"の存在を思い出しました!

ELSA Gloria SynergyはAlpha用のグラフィックカードで、
このカードにも実はPermedia 2(TVP4020)が使われているのです!


そして、PC用からAlpha用に転用できるお手軽グラフィックカードとして、Permedia 2は随分研究されています。
http://moon.hanya-n.org/comp/alpha/hct/graphics.html
そこそこ需要?もありそうですね!

さて、NetBSDのpm2fbドライバはNetBSD/alphaではそもそもカーネルに組み込まれていません。
そこで、カーネル設定ファイル
/sys/arch/alpha/conf/GENERICを編集して

+pm2fb*  at      pci? dev ? function ?           # 3Dlabs Permedia 2 Graphics
+wsdisplay*     at      pm2fb?

と付け足してあげます。

RAMDACの設定は追記してあげたので、問題なく動くはずです。

さぁ、カーネルコンパイルして、起動!


うーん、文字が正しく表示されないようです。

というわけで、フォント(ビットマップ)を描画するpm2fb_putchar関数を調べてみます。
http://nxr.netbsd.org/xref/src/sys/dev/pci/pm2fb.c?r=1.25#970

static void
pm2fb_putchar(void *cookie, int row, int col, u_int c, long attr)
{
	struct rasops_info *ri = cookie;
	struct wsdisplay_font *font = PICK_FONT(ri, c);
	struct vcons_screen *scr = ri->ri_hw;
	struct pm2fb_softc *sc = scr->scr_cookie;
	uint32_t mode;

	if (sc->sc_mode == WSDISPLAYIO_MODE_EMUL) {
		void *data;
		uint32_t fg, bg;
		int uc, i;
		int x, y, wi, he;

		wi = font->fontwidth;
		he = font->fontheight;
...
		if (c == 0x20) {
			pm2fb_rectfill(sc, x, y, wi, he, bg);
		} else {
			uc = c - font->firstchar;
			data = (uint8_t *)font->data + uc * ri->ri_fontscale;

			mode = PM2RM_MASK_MIRROR;
			switch (ri->ri_font->stride) {
				case 1:
					mode |= 3 << 7;
					break;
				case 2:
					mode |= 2 << 7;
					break;
			}

			pm2fb_wait(sc, 8);

			bus_space_write_4(sc->sc_memt, sc->sc_regh,
			    PM2_RE_MODE, mode);
			bus_space_write_4(sc->sc_memt, sc->sc_regh,
			    PM2_RE_CONFIG, PM2RECFG_WRITE_EN);
			bus_space_write_4(sc->sc_memt, sc->sc_regh,
			    PM2_RE_BLOCK_COLOUR, bg);
			bus_space_write_4(sc->sc_memt, sc->sc_regh,
			    PM2_RE_RECT_START, (y << 16) | x);
...
	}
}

むー、結構難しそうな関数ですね。

でも、よく見ると
mode = PM2RM_MASK_MIRROR;
という処理がとってもあやしぃ...

ここで、初めてGPU仕様書を読んでみます。
すると、
RasterizerModeの設定にどのように描画をするか指定できるオプションが有りました!



そこでビットマップの描画方法をLittle endianとBig endianで切りかえてあげます。

static void
pm2fb_putchar(void *cookie, int row, int col, u_int c, long attr)
{
	struct rasops_info *ri = cookie;
	struct wsdisplay_font *font = PICK_FONT(ri, c);
	struct vcons_screen *scr = ri->ri_hw;
	struct pm2fb_softc *sc = scr->scr_cookie;
	uint32_t mode;

	if (sc->sc_mode == WSDISPLAYIO_MODE_EMUL) {
		void *data;
		uint32_t fg, bg;
		int uc, i;
		int x, y, wi, he;

		wi = font->fontwidth;
		he = font->fontheight;

		if (!CHAR_IN_FONT(c, font))
			return;
		bg = ri->ri_devcmap[(attr >> 16) & 0xf];
		fg = ri->ri_devcmap[(attr >> 24) & 0xf];
		x = ri->ri_xorigin + col * wi;
		y = ri->ri_yorigin + row * he;
		if (c == 0x20) {
			pm2fb_rectfill(sc, x, y, wi, he, bg);
		} else {
			uc = c - font->firstchar;
			data = (uint8_t *)font->data + uc * ri->ri_fontscale;

			mode = PM2RM_MASK_MIRROR;
#if BYTE_ORDER == LITTLE_ENDIAN
			switch (ri->ri_font->stride) {
				case 1:
					mode |= 4 << 7;
					break;
				case 2:
					mode |= 3 << 7;
					break;
			}
#else
			switch (ri->ri_font->stride) {
				case 1:
					mode |= 3 << 7;
					break;
				case 2:
					mode |= 2 << 7;
					break;
			}
#endif
			pm2fb_wait(sc, 8);

			bus_space_write_4(sc->sc_memt, sc->sc_regh,
			    PM2_RE_MODE, mode);
			bus_space_write_4(sc->sc_memt, sc->sc_regh,
			    PM2_RE_CONFIG, PM2RECFG_WRITE_EN);
			bus_space_write_4(sc->sc_memt, sc->sc_regh,
			    PM2_RE_BLOCK_COLOUR, bg);
			bus_space_write_4(sc->sc_memt, sc->sc_regh,
			    PM2_RE_RECT_START, (y << 16) | x);
			bus_space_write_4(sc->sc_memt, sc->sc_regh,
			    PM2_RE_RECT_SIZE, (he << 16) | wi);
			bus_space_write_4(sc->sc_memt, sc->sc_regh,
			    PM2_RE_RENDER,
			    PM2RE_RECTANGLE |
			    PM2RE_INC_X | PM2RE_INC_Y | PM2RE_FASTFILL);
			bus_space_write_4(sc->sc_memt, sc->sc_regh,
			    PM2_RE_BLOCK_COLOUR, fg);
			bus_space_write_4(sc->sc_memt, sc->sc_regh,
			    PM2_RE_RENDER,
			    PM2RE_RECTANGLE | PM2RE_SYNC_ON_MASK |
			    PM2RE_INC_X | PM2RE_INC_Y | PM2RE_FASTFILL);

			pm2fb_wait(sc, he);
			switch (ri->ri_font->stride) {
			case 1: {
				uint8_t *data8 = data;
				uint32_t reg;
				for (i = 0; i < he; i++) {
					reg = *data8;
					bus_space_write_4(sc->sc_memt,
					    sc->sc_regh,
					    PM2_RE_BITMASK, reg);
					data8++;
				}
				break;
				}
			case 2: {
				uint16_t *data16 = data;
				uint32_t reg;
				for (i = 0; i < he; i++) {
					reg = *data16;
					bus_space_write_4(sc->sc_memt,
					    sc->sc_regh,
					    PM2_RE_BITMASK, reg);
					data16++;
				}
				break;
			}
			}
		}
		if (attr & 1)
			pm2fb_rectfill(sc, x, y + he - 2, wi, 1, fg);
	}
}

さぁ、カーネルコンパイルして、起動!

おおおおっ、文字がきれいに表示されました!
本当にきれいです!わ〜

むむ、しかし問題発生!
およよ?何故か文字が斜めっていますね。
 ヽ(`Д´)ノ  
  .ヽ`Д´)  
  (ヽ`Д).  
  (  ヽ`)  
  (   ヽ  
な感じ?になってしまっています...
一難去って、また一難...
でも、ゴールはあと少し。頑張ります!

では、なぜ文字が斜めに表示されるのでしょうか?
文字を移動させたりする関数が怪しい気がしますね...
では、さっそく調べてみましょう。

文字を移動させたりする関数は、
pm2fb_bitblt関数になります。
http://nxr.netbsd.org/xref/src/sys/dev/pci/pm2fb.c?r=1.25#860

ん?BitBltというのは何じゃらほい?
というわけでWikipediaで調べてみると...
Bit Block Transfer - Wikipedia
Bit blit - Wikipedia, the free encyclopedia
"描画する画像データ (ビットマップ) を一旦メインメモリに蓄え、最終的な画像データだけをVRAMにまとめて転送する。"だそうです。
なんだか、ゲームの開発みたいな話になってきましたね...

まずは、NetBSDのpm2fbの実装を調べてみます。

static void
pm2fb_bitblt(void *cookie, int xs, int ys, int xd, int yd,
    int wi, int he, int rop)
{
	struct pm2fb_softc *sc = cookie;
	uint32_t dir = 0;
	int rxd, rwi, rxdelta;

	if (yd <= ys) {
		dir |= PM2RE_INC_Y;
	}
	if (xd <= xs) {
		dir |= PM2RE_INC_X;
	}
...
	if (sc->sc_depth == 8) {
		int adjust;
		/*
		 * use packed mode for some extra speed
		 * this copies 32bit quantities even in 8 bit mode, so we need
		 * to adjust for cases where the lower two bits in source and
		 * destination X don't align, and/or where the width isn't a
		 * multiple of 4
		 */
		if (rop == 3) {
			bus_space_write_4(sc->sc_memt, sc->sc_regh,
			    PM2_RE_CONFIG,
			    PM2RECFG_READ_SRC | PM2RECFG_WRITE_EN |
			    PM2RECFG_ROP_EN | PM2RECFG_PACKED | (rop << 6));
		} else {
			bus_space_write_4(sc->sc_memt, sc->sc_regh,
			    PM2_RE_CONFIG,
			    PM2RECFG_READ_SRC | PM2RECFG_READ_DST |
			    PM2RECFG_WRITE_EN | PM2RECFG_PACKED |
			    PM2RECFG_ROP_EN | (rop << 6));
		}
		rxd = xd >> 2;
		rwi = (wi + 7) >> 2;
		rxdelta = (xs & 0xffc) - (xd & 0xffc);
		/* adjust for non-aligned x */
		adjust = ((xd & 3) - (xs & 3));
		bus_space_write_4(sc->sc_memt, sc->sc_regh,
		    PM2_RE_PACKEDDATA_LIMIT,
		    (xd << 16) | (xd + wi) | (adjust << 29));

	} else {
...
	}
	bus_space_write_4(sc->sc_memt, sc->sc_regh, PM2_RE_RECT_START,
	    (yd << 16) | rxd);
	bus_space_write_4(sc->sc_memt, sc->sc_regh, PM2_RE_RECT_SIZE,
	    (he << 16) | rwi);
	bus_space_write_4(sc->sc_memt, sc->sc_regh, PM2_RE_SOURCE_DELTA,
	    (((ys - yd) & 0xfff) << 16) | (rxdelta & 0xfff));
	bus_space_write_4(sc->sc_memt, sc->sc_regh, PM2_RE_RENDER,
	    PM2RE_RECTANGLE | dir);
}

次はXの実装を調べます。
http://nxr.netbsd.org/source/xref/xsrc/external/mit/xf86-video-glint/dist/src/pm2_accel.c?r=1.1#505
Permedia2SubsequentScreenToScreenCopy関数

static void
Permedia2SubsequentScreenToScreenCopy(ScrnInfoPtr pScrn, int x1, int y1,
					int x2, int y2, int w, int h)
{
    GLINTPtr pGlint = GLINTPTR(pScrn);
    char align;

    TRACE_ENTER("Permedia2SubsequentScreenToScreenCopy");
    /* We can only use GXcopy for Packed modes */
    if (pGlint->ROP != GXcopy) {
	GLINT_WAIT(5);
	GLINT_WRITE_REG(pGlint->FrameBufferReadMode, FBReadMode);
        Permedia2LoadCoord(pScrn, x2, y2, w, h);
        GLINT_WRITE_REG(((y1-y2)&0x0FFF)<<16 | ((x1-x2)&0x0FFF), FBSourceDelta);
    } else {
  	align = (x2 & pGlint->bppalign) - (x1 & pGlint->bppalign);
	GLINT_WAIT(6);
	GLINT_WRITE_REG(pGlint->FrameBufferReadMode|FBRM_Packed, FBReadMode);
        Permedia2LoadCoord(pScrn, x2>>pGlint->BppShift, y2,
						(w+7)>>pGlint->BppShift, h);
  	GLINT_WRITE_REG(align<<29|x2<<16|(x2+w), PackedDataLimits);
        GLINT_WRITE_REG(((y1-y2)&0x0FFF)<<16 | (((x1 & ~pGlint->bppalign)-(x2 & ~pGlint->bppalign))&0x0FFF), FBSourceDelta);
    }

    GLINT_WRITE_REG(PrimitiveRectangle | pGlint->BltScanDirection, Render);
    TRACE_EXIT("Permedia2SubsequentScreenToScreenCopy");
}

むー、NetBSDのpm2fb_bitblt関数と
XのPermedia2SubsequentScreenToScreenCopy関数やっている事は、ほぼ同じです。

これは詰んだ...

ソウルジェムが濁りそうです...

ですが、あきらめが悪いN君はpm2fb_bitbltをいじくっているうちにある重大な発見をしてしまいました。

static void
pm2fb_bitblt(void *cookie, int xs, int ys, int xd, int yd,
    int wi, int he, int rop)
{
...
	if (sc->sc_depth == 8) {
		int adjust;
		/*
		 * use packed mode for some extra speed
		 * this copies 32bit quantities even in 8 bit mode, so we need
		 * to adjust for cases where the lower two bits in source and
		 * destination X don't align, and/or where the width isn't a
		 * multiple of 4
		 */
		if (rop == 3) {
			bus_space_write_4(sc->sc_memt, sc->sc_regh,
			    PM2_RE_CONFIG,
			    PM2RECFG_READ_SRC | PM2RECFG_WRITE_EN |
			    PM2RECFG_ROP_EN | PM2RECFG_PACKED | (rop << 6));
		} else {
			bus_space_write_4(sc->sc_memt, sc->sc_regh,
			    PM2_RE_CONFIG,
			    PM2RECFG_READ_SRC | PM2RECFG_READ_DST |
			    PM2RECFG_WRITE_EN | PM2RECFG_PACKED |
			    PM2RECFG_ROP_EN | (rop << 6));
		}
		rxd = xd >> 2;
		rwi = (wi + 7) >> 2;
		rxdelta = (xs & 0xffc) - (xd & 0xffc);
		/* adjust for non-aligned x */
#if BYTE_ORDER == LITTLE_ENDIAN
                adjust = 1;
#else
                adjust = ((xd & 3) - (xs & 3));
#endif
		bus_space_write_4(sc->sc_memt, sc->sc_regh,
		    PM2_RE_PACKEDDATA_LIMIT,
		    (xd << 16) | (xd + wi) | (adjust << 29));

	} else {
...
}

なんと、adjustに1を指定してやると、ちゃんと表示されるのです。


そして、Big endianなSPARC64でadjustに1を指定してやると、Little endianなAlphaと同じ現象が発生しました。

この現象をまとめると
・Little endianなAlphaでadjustに1→きれいに表示される
・Big endianなSPARC64でadjustに1→ななめになる。
です。

これは非常に不思議な事です。
GPU仕様書にも、adjustのこの使い方は書いてないからです。


まぁ、結果オーライということで先に進みます。

幾多の難関を乗り越え遂にLittle endianでもBig endianでもNetBSDのpm2fbドライバを動かす事に成功したN君。
「勝ったッ!pm2fb完!」
なのでしょうか?

試しにwsfb-driverでXを動かしてみましょう!

wsfb-driverとは
"wsfb is an Xorg driver for OpenBSD and NetBSD wsdisplay framebuffer devices. This is a non-accelerated driver. "
WSFB(4) manual page
とのことで、フレームバッファコンソールの機能を利用した?Xのドライバです。
(たぶんLinuxのfbdevに近いものだとおもいますが、Linux良く知らないので違っていたらすいません...)

さぁ、startx!

ああっ、SPARC64では、このように画面が崩れてしまいました...

ですが、Alphaだと正しく表示されるようです!

むー。
これは困りました。
何か良い問題解決の手がかりは無いでしょうか?

そこで、OpenBSDの実装を見てみます。
OpenBSDでは、gfxpと言う名前でドライバが提供されているようです。
また、gfxpソースコードが、/sys/arch/sparc64/dev/直下にあることから、
異なるアーキテクチャ間でのgfxpドライバの共有は行われていないようです。
(NetBSDは/sys/dev/pci/直下)
GFXP(4)
http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/sys/arch/sparc64/dev/gfxp.c?rev=1.13&content-type=text/x-cvsweb-markup

/*
 * The Permedia 2 provides two views into its 64k register file.  The
 * first view is little-endian, the second is big-endian and
 * immediately follows the little-endian view.  Since bus_space(9)
 * already does the byte order conversion for us, we use the
 * little-endian view.
 *
 * There are also little-endian and big-endian views into the
 * framebuffer.  These are made available through separate BARs.  We
 * use the big-endian view in this driver to avoid unnecessary byte
 * swapping in rasops(9).
 */
#define PM2_PCI_MMIO		0x10 	/* Registers */
#define PM2_PCI_MEM_LE		0x14 	/* Framebuffer (little-endian) */
#define PM2_PCI_MEM_BE		0x18	/* Framebuffer (big-endian) */

大変興味深いことが書いてあります。
どうやら、Permedia 2はLittle endian用とBig endian用のフレームバッファ(VRAM)を切り替えられるようです。

NetBSDのpm2fbドライバでは
http://nxr.netbsd.org/xref/src/sys/dev/pci/pm2fb.c?r=1.25#328

	pci_mapreg_info(pa->pa_pc, pa->pa_tag, 0x14, PCI_MAPREG_TYPE_MEM,
	    &sc->sc_fb, &sc->sc_fbsize, &flags);

と0x14=Framebuffer (little-endian)を指定していました。
Little endianなAlphaでXが正しく表示されて、
Big endianなSPARC64でXの画面が崩れてしまうのは、コレが原因かもしれません。
なので、

#define PM2_PCI_MEM_LE		0x14 	/* Framebuffer (little-endian) */
#define PM2_PCI_MEM_BE		0x18	/* Framebuffer (big-endian) */

#if BYTE_ORDER == LITTLE_ENDIAN
	pci_mapreg_info(pa->pa_pc, pa->pa_tag, PM2_PCI_MEM_LE, PCI_MAPREG_TYPE_MEM,
 	    &sc->sc_fb, &sc->sc_fbsize, &flags);
#else
        pci_mapreg_info(pa->pa_pc, pa->pa_tag, PM2_PCI_MEM_BE, PCI_MAPREG_TYPE_MEM,
            &sc->sc_fb, &sc->sc_fbsize, &flags);
#endif

と書き換えてみます。
さぁ、カーネルコンパイルして、起動!& startx

おっ、うまくいきましたね。

補足
もし、この取得したフレームバッファ物理アドレスを使って、
どのようにwsfb-driverがXを表示しているか興味が有る方は、
あるmmapの話
をご覧ください。

しかし、これも不思議な現象です。
NetBSDのpm2fbドライバは"tested on sparc64 only so far"で"Permedia 2v"専用でした。
つまり、Little endian用のフレームバッファを指定しても、Permedia 2vはPermedia 2と違って、
wsfb-driverでXが正しく表示できているのでしょうか?

まぁ、結果オーライということで先に進みます。

さて、いよいよ幾多の難関を乗り越え遂にBig endianでもLittle endianでもX環境でも
NetBSDのpm2fbドライバを動かす事に成功したN君。
「勝ったッ!pm2fb完!」
なのでしょうか?

いいえ、まだ先が有ります。

せっかく書いた、NetBSDのpm2fbドライバのパッチをsend-prしましょう。
send-prとはエラーや改善希望の報告システム事です。
WEB上から簡単に出来ます。
http://www.netbsd.org/support/send-pr.html
http://www.netbsd.org/cgi-bin/sendpr.cgi?gndb=netbsd

残念ながら英語で書かなければなりませんが、心配しなくても大丈夫。
N君なんて英検四級です!

どうやらN君は今回のsend-prを書くにあたって、フレームバッファコンソールという
デバイスの性質も考慮して、写真を多用したようです。
また、adjustの件など疑問に思ったこともすべて書ききったようです。
kern/49226: pm2fb are not attached on Spsarc64 and Alpha.

WEB上から投稿すると、"Your electronic mail address: "で指定したメールアドレスに
以下のような内容で受付メールが来ます。

It has the internal identification `kern/49226'.
The individual assigned to look at your
report is: kern-bug-people.

>Category:       kern
>Responsible:    kern-bug-people
>Synopsis:       pm2fb are not attached on Spsarc64 and Alpha.

また、実際に受け付けされた内容もWEB上で確認できます。
http://mail-index.netbsd.org/netbsd-bugs/

しかし、この日は"たまたま"処理が重かったのか、10時間たっても反映されませんでした。

早漏なN君は「あら?うまく届かなかったのかな?」と思って
同じ内容でもう1回send-prしました。
kern/49229: pm2fb are not attached on Sparc64 and Alpha.

そんなことをすると、当然2回分受理されてしまいます...

1回目はSparc64の綴りをSpsarc64とミスしたので、
謝って取り消してもらいました。
http://mail-index.netbsd.org/netbsd-bugs/2014/09/20/msg038368.html
↑これは"revert"より"close"のほうが、適切な表現です。

けっかとして、前の1回目のsend-prは取り消され、2回目に一本化されました。
http://mail-index.netbsd.org/netbsd-bugs/2014/09/24/msg038401.html

さて、send-prも完了しました。
後は返事を待つだけです。
「いつ返事がくるかな?」N君は首を長くして待ちます。

.
..
...
.....
......

来ません!怒!

怒ったN君はtech-kernという別のメーリングリストで、
http://mail-index.netbsd.org/tech-kern/2014/12/08/msg018222.html
"Please point it out if there are any mistakes in my code."
「俺のコードに文句がある奴は出てこいやーっ!」(高田延彦風)

凸(゚Д゚#)ヤンノカゴルァ!!
しました。

すると返事が来ました!
http://mail-index.netbsd.org/tech-kern/2014/12/08/msg018223.html

中指を飛ばすことから始まるコミュニケーション、ス・テ・キ。

そして、議論を重ね
http://mail-index.netbsd.org/tech-kern/2014/12/10/msg018244.html
http://mail-index.netbsd.org/tech-kern/2014/12/10/msg018245.html
http://mail-index.netbsd.org/tech-kern/2014/12/11/msg018247.html
http://mail-index.netbsd.org/tech-kern/2014/12/11/msg018248.html
http://mail-index.netbsd.org/tech-kern/2014/12/12/msg018249.html
http://mail-index.netbsd.org/tech-kern/2014/12/12/msg018250.html
http://mail-index.netbsd.org/tech-kern/2014/12/13/msg018251.html
http://mail-index.netbsd.org/tech-kern/2014/12/13/msg018252.html
http://mail-index.netbsd.org/tech-kern/2014/12/17/msg018264.html
http://mail-index.netbsd.org/tech-kern/2014/12/17/msg018265.html

無事コミットされました。
http://cvsweb.netbsd.org/bsdweb.cgi/src/sys/dev/pci/pm2fb.c.diff?r1=1.26&r2=1.27&only_with_tag=MAIN&f=h

これで、ようやく
「勝ったッ!pm2fb完!」
です。

というわけで、ドライバの読み書きを題材に、オープンソース開発における
・問題の発見
・問題点の詳細な検討
・問題点を解決する手がかりの探し方とソースコードの書き方
・パッチを提供する
・議論をする
・パッチが取り込まれる
の流れを記事として書いてみました。

インターネットを検索すると、オープンソース開発での
「パッチを書いた」とか「コミットされた」とか
断片的な情報は出てくるのですが、はじめから終わりまでを書いた情報は、
なかなか見たことが無かったので「よい記録かな?」と思って筆をとった次第です。
勉強会だと時間の関係上、どうしても切らなければならない事が出てきますから...

フレームバッファコンソールのドライバ開発は、
視覚的に面白いので勉強にちょうど良いと思います。

残念なのはAMDRadeonNvidiaGeForceに収斂してしまって、
仕様の公開された勉強向きのおもしろいGPUの出てくる可能性がほぼ無い事でしょうか...

あしたは、いよいよNetBSD developerのnonakapさんの登場です!
xakaneという声がどこからか聞こえてくる気がしますが、
どんな記事を書いてくださるのか、大変楽しみですね!(・∀・)ニヤニヤ
(中指を飛ばすコミュニケーション)

"サンタさんに髪の毛頼んだら枕元にたくさん落ちてた"
というスレタイを見るたびに"ドキッ"とするお年頃のN君のお話でした。

おしまい。

謝辞:
いつもtwitterでふぁぼってくださる皆様
なまあたたかい目で見守ってくださるつついさんをはじめとしたNetBSD関係者の方々