NetBSD Problem Report #41698
From sverre@abbor.fesk.com Sat Jul 11 02:25:00 2009
Return-Path: <sverre@abbor.fesk.com>
Received: from mail.netbsd.org (mail.netbsd.org [204.152.190.11])
by www.NetBSD.org (Postfix) with ESMTP id 5DC0263B913
for <gnats-bugs@gnats.NetBSD.org>; Sat, 11 Jul 2009 02:25:00 +0000 (UTC)
Message-Id: <20090711022458.6BCB827417@abbor.fesk.com>
Date: Fri, 10 Jul 2009 20:24:58 -0600 (MDT)
From: sverre@viewmark.com
Reply-To: sverre@viewmark.com
To: gnats-bugs@gnats.NetBSD.org
Subject: Import of changes to sys/dev/isa/aps.c from OpenBSD
X-Send-Pr-Version: 3.95
>Number: 41698
>Category: port-amd64
>Synopsis: aps driver does not work on newer ThinkPads
>Confidential: no
>Severity: non-critical
>Priority: low
>Responsible: jmcneill
>State: closed
>Class: sw-bug
>Submitter-Id: net
>Arrival-Date: Sat Jul 11 02:30:00 +0000 2009
>Closed-Date: Mon Jan 17 03:11:40 +0000 2011
>Last-Modified: Wed Feb 16 10:10:03 +0000 2011
>Originator: Sverre Froyen
>Release: NetBSD 5.99.15
>Organization:
Viewmark
>Environment:
System: NetBSD abbor.fesk.com 5.99.15 NetBSD 5.99.15 (ABBOR) #29: Fri Jul 10 17:48:52 MDT 2009 root@abbor.fesk.com:/usr/src/objdir/sys/arch/amd64/compile.amd64/ABBOR amd64
Architecture: x86_64
Machine: amd64
>Description:
aps driver does not work on recent ThinkPads.
>How-To-Repeat:
Boot NetBSD on a ThinkPad T500. Notice that aps0 is not
initialized and that the sensor values are missing from
envstat.
>Fix:
Port OpenBSD changes to the driver. My changes, below, should
be considered a hack and not be imported without review. In
particular, I have not tested the suspend/resume part of the
code. That said, it makes the driver work for me and the sensor
values appear correct.
Index: sys/dev/isa/aps.c
===================================================================
RCS file: /cvsroot/src/sys/dev/isa/aps.c,v
retrieving revision 1.8
diff -u -r1.8 aps.c
--- sys/dev/isa/aps.c 4 Apr 2008 09:41:40 -0000 1.8
+++ sys/dev/isa/aps.c 11 Jul 2009 02:11:10 -0000
@@ -1,8 +1,9 @@
/* $NetBSD: aps.c,v 1.8 2008/04/04 09:41:40 xtraeme Exp $ */
/* $OpenBSD: aps.c,v 1.15 2007/05/19 19:14:11 tedu Exp $ */
-
+/* $OpenBSD: aps.c,v 1.17 2008/06/27 06:08:43 canacar Exp $ */
/*
* Copyright (c) 2005 Jonathan Gray <jsg@openbsd.org>
+ * Copyright (c) 2008 Can Erkin Acar <canacar@openbsd.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -44,23 +45,62 @@
#define DPRINTF(x)
#endif
-#define APS_ACCEL_STATE 0x04
-#define APS_INIT 0x10
-#define APS_STATE 0x11
-#define APS_XACCEL 0x12
-#define APS_YACCEL 0x14
-#define APS_TEMP 0x16
-#define APS_XVAR 0x17
-#define APS_YVAR 0x19
-#define APS_TEMP2 0x1b
-#define APS_UNKNOWN 0x1c
-#define APS_INPUT 0x1d
-#define APS_CMD 0x1f
-#define APS_STATE_NEWDATA 0x50
+/*
+ * EC interface on Thinkpad Laptops, from Linux HDAPS driver notes.
+ * From Renesans H8S/2140B Group Hardware Manual
+ * http://documentation.renesas.com/eng/products/mpumcu/rej09b0300_2140bhm.pdf
+ *
+ * EC uses LPC Channel 3 registers TWR0..15
+ */
+
+/* STR3 status register */
+#define APS_STR3 0x04
+
+#define APS_STR3_IBF3B 0x80 /* Input buffer full (host->slave) */
+#define APS_STR3_OBF3B 0x40 /* Output buffer full (slave->host)*/
+#define APS_STR3_MWMF 0x20 /* Master write mode */
+#define APS_STR3_SWMF 0x10 /* Slave write mode */
+
+
+/* Base address of TWR registers */
+#define APS_TWR_BASE 0x10
+#define APS_TWR_RET 0x1f
+
+/* TWR registers */
+#define APS_CMD 0x00
+#define APS_ARG1 0x01
+#define APS_ARG2 0x02
+#define APS_ARG3 0x03
+#define APS_RET 0x0f
+
+/* Sensor values */
+#define APS_STATE 0x01
+#define APS_XACCEL 0x02
+#define APS_YACCEL 0x04
+#define APS_TEMP 0x06
+#define APS_XVAR 0x07
+#define APS_YVAR 0x09
+#define APS_TEMP2 0x0b
+#define APS_UNKNOWN 0x0c
+#define APS_INPUT 0x0d
+
+/* write masks for I/O, send command + 0-3 arguments*/
+#define APS_WRITE_0 0x0001
+#define APS_WRITE_1 0x0003
+#define APS_WRITE_2 0x0007
+#define APS_WRITE_3 0x000f
+
+/* read masks for I/O, read 0-3 values (skip command byte) */
+#define APS_READ_0 0x0000
+#define APS_READ_1 0x0002
+#define APS_READ_2 0x0006
+#define APS_READ_3 0x000e
-#define APS_CMD_START 0x01
+#define APS_READ_RET 0x8000
+#define APS_READ_ALL 0xffff
+/* Bit definitions for APS_INPUT value */
#define APS_INPUT_KB (1 << 5)
#define APS_INPUT_MS (1 << 6)
#define APS_INPUT_LIDOPEN (1 << 7)
@@ -108,25 +148,96 @@
static int aps_detach(device_t, int);
static int aps_init(struct aps_softc *);
-static uint8_t aps_mem_read_1(bus_space_tag_t, bus_space_handle_t,
- int, uint8_t);
-static void aps_refresh_sensor_data(struct aps_softc *sc);
+static int aps_read_data(struct aps_softc *);
+static void aps_refresh_sensor_data(struct aps_softc *);
static void aps_refresh(void *);
static bool aps_suspend(device_t PMF_FN_PROTO);
static bool aps_resume(device_t PMF_FN_PROTO);
+static int aps_do_io(bus_space_tag_t, bus_space_handle_t,
+ unsigned char *, int, int);
CFATTACH_DECL_NEW(aps, sizeof(struct aps_softc),
aps_match, aps_attach, aps_detach, NULL);
+/* properly communicate with the controller, writing a set of memory
+ * locations and reading back another set */
+static int
+aps_do_io(bus_space_tag_t iot, bus_space_handle_t ioh,
+ unsigned char *buf, int wmask, int rmask)
+{
+ int bp, stat, n;
+
+ DPRINTF(("aps_do_io: CMD: 0x%02x, wmask: 0x%04x, rmask: 0x%04x\n",
+ buf[0], wmask, rmask));
+
+ /* write init byte using arbitration */
+ for (n = 0; n < 100; n++) {
+ stat = bus_space_read_1(iot, ioh, APS_STR3);
+ if (stat & (APS_STR3_OBF3B | APS_STR3_SWMF)) {
+ bus_space_read_1(iot, ioh, APS_TWR_RET);
+ continue;
+ }
+ bus_space_write_1(iot, ioh, APS_TWR_BASE, buf[0]);
+ stat = bus_space_read_1(iot, ioh, APS_STR3);
+ if (stat & (APS_STR3_MWMF))
+ break;
+ delay(1);
+ }
+
+ if (n == 100) {
+ DPRINTF(("aps_do_io: Failed to get bus\n"));
+ return (1);
+ }
+
+ /* write data bytes, init already sent */
+ /* make sure last bye is always written as this will trigger slave */
+ wmask |= APS_READ_RET;
+ buf[APS_RET] = 0x01;
+
+ for (n = 1, bp = 2; n < 16; bp <<= 1, n++) {
+ if (wmask & bp) {
+ bus_space_write_1(iot, ioh, APS_TWR_BASE + n, buf[n]);
+ DPRINTF(("aps_do_io: write %2d 0x%02x\n", n, buf[n]));
+ }
+ }
+
+ for (n = 0; n < 100; n++) {
+ stat = bus_space_read_1(iot, ioh, APS_STR3);
+ if (stat & (APS_STR3_OBF3B))
+ break;
+ delay(5 * 100);
+ }
+
+ if (n == 100) {
+ DPRINTF(("aps_do_io: timeout waiting response\n"));
+ return (1);
+ }
+ /* wait for data available */
+ /* make sure to read the final byte to clear status */
+ rmask |= APS_READ_RET;
+
+ /* read cmd and data bytes */
+ for (n = 0, bp = 1; n < 16; bp <<= 1, n++) {
+ if (rmask & bp) {
+ buf[n] = bus_space_read_1(iot, ioh, APS_TWR_BASE + n);
+ DPRINTF(("aps_do_io: read %2d 0x%02x\n", n, buf[n]));
+ }
+ }
+
+ return (0);
+}
+
static int
aps_match(device_t parent, cfdata_t match, void *aux)
{
struct isa_attach_args *ia = aux;
bus_space_tag_t iot = ia->ia_iot;
bus_space_handle_t ioh;
- int iobase, i;
+ int iobase;
uint8_t cr;
+ unsigned char iobuf[16];
+
/* Must supply an address */
if (ia->ia_nio < 1)
return 0;
@@ -144,16 +255,12 @@
return 0;
}
- /* See if this machine has APS */
- bus_space_write_1(iot, ioh, APS_INIT, 0x13);
- bus_space_write_1(iot, ioh, APS_CMD, 0x01);
- /* ask again as the X40 is slightly deaf in one ear */
- bus_space_read_1(iot, ioh, APS_CMD);
- bus_space_write_1(iot, ioh, APS_INIT, 0x13);
- bus_space_write_1(iot, ioh, APS_CMD, 0x01);
+ /* See if this machine has APS */
- if (!aps_mem_read_1(iot, ioh, APS_CMD, 0x00)) {
+ /* get APS mode */
+ iobuf[APS_CMD] = 0x13;
+ if (aps_do_io(iot, ioh, iobuf, APS_WRITE_0, APS_READ_1)) {
bus_space_unmap(iot, ioh, APS_ADDR_SIZE);
return 0;
}
@@ -163,17 +270,15 @@
* 0x01: T42
* 0x02: chip already initialised
* 0x03: T41
+ * 0x05: T61
*/
- for (i = 0; i < 10; i++) {
- cr = bus_space_read_1(iot, ioh, APS_STATE);
- if (cr > 0 && cr < 6)
- break;
- delay(5 * 1000);
- }
-
+
+ cr = iobuf[APS_ARG1];
+
bus_space_unmap(iot, ioh, APS_ADDR_SIZE);
DPRINTF(("aps: state register 0x%x\n", cr));
- if (cr < 1 || cr > 5) {
+
+ if (iobuf[APS_RET] != 0 || cr < 1 || cr > 5) {
DPRINTF(("aps0: unsupported state %d\n", cr));
return 0;
}
@@ -205,10 +310,8 @@
aprint_naive("\n");
aprint_normal("\n");
- if (!aps_init(sc)) {
- aprint_error_dev(self, "failed to initialise\n");
+ if (aps_init(sc))
goto out;
- }
/* Initialize sensors */
#define INITDATA(idx, unit, string) \
@@ -261,43 +364,60 @@
out:
bus_space_unmap(sc->sc_iot, sc->sc_ioh, APS_ADDR_SIZE);
+ aprint_error_dev(self, "failed to initialize\n");
}
static int
aps_init(struct aps_softc *sc)
{
- bus_space_write_1(sc->sc_iot, sc->sc_ioh, APS_INIT, 0x17);
- bus_space_write_1(sc->sc_iot, sc->sc_ioh, APS_STATE, 0x81);
- bus_space_write_1(sc->sc_iot, sc->sc_ioh, APS_CMD, 0x01);
- if (!aps_mem_read_1(sc->sc_iot, sc->sc_ioh, APS_CMD, 0x00))
- return 0;
- if (!aps_mem_read_1(sc->sc_iot, sc->sc_ioh, APS_STATE, 0x00))
- return 0;
- if (!aps_mem_read_1(sc->sc_iot, sc->sc_ioh, APS_XACCEL, 0x60))
- return 0;
- if (!aps_mem_read_1(sc->sc_iot, sc->sc_ioh, APS_XACCEL + 1, 0x00))
- return 0;
- bus_space_write_1(sc->sc_iot, sc->sc_ioh, APS_INIT, 0x14);
- bus_space_write_1(sc->sc_iot, sc->sc_ioh, APS_STATE, 0x01);
- bus_space_write_1(sc->sc_iot, sc->sc_ioh, APS_CMD, 0x01);
- if (!aps_mem_read_1(sc->sc_iot, sc->sc_ioh, APS_CMD, 0x00))
- return 0;
- bus_space_write_1(sc->sc_iot, sc->sc_ioh, APS_INIT, 0x10);
- bus_space_write_1(sc->sc_iot, sc->sc_ioh, APS_STATE, 0xc8);
- bus_space_write_1(sc->sc_iot, sc->sc_ioh, APS_XACCEL, 0x00);
- bus_space_write_1(sc->sc_iot, sc->sc_ioh, APS_XACCEL + 1, 0x02);
- bus_space_write_1(sc->sc_iot, sc->sc_ioh, APS_CMD, 0x01);
- if (!aps_mem_read_1(sc->sc_iot, sc->sc_ioh, APS_CMD, 0x00))
- return 0;
- /* refresh data */
- bus_space_write_1(sc->sc_iot, sc->sc_ioh, APS_INIT, 0x11);
- bus_space_write_1(sc->sc_iot, sc->sc_ioh, APS_CMD, 0x01);
- if (!aps_mem_read_1(sc->sc_iot, sc->sc_ioh, APS_ACCEL_STATE, 0x50))
- return 0;
- if (!aps_mem_read_1(sc->sc_iot, sc->sc_ioh, APS_STATE, 0x00))
- return 0;
+ unsigned char iobuf[16];
- return 1;
+
+ /* command 0x17/0x81: check EC */
+ iobuf[APS_CMD] = 0x17;
+ iobuf[APS_ARG1] = 0x81;
+
+ if (aps_do_io(sc->sc_iot, sc->sc_ioh, iobuf, APS_WRITE_1, APS_READ_3))
+ return (1);
+
+ if (iobuf[APS_RET] != 0 ||iobuf[APS_ARG3] != 0)
+ return (1);
+
+ /* Test values from the Linux driver */
+ if ((iobuf[APS_ARG1] != 0 || iobuf[APS_ARG2] != 0x60) &&
+ (iobuf[APS_ARG1] != 1 || iobuf[APS_ARG2] != 0))
+ return (1);
+
+ /* command 0x14: set power */
+ iobuf[APS_CMD] = 0x14;
+ iobuf[APS_ARG1] = 0x01;
+
+ if (aps_do_io(sc->sc_iot, sc->sc_ioh, iobuf, APS_WRITE_1, APS_READ_0))
+ return (1);
+
+ if (iobuf[APS_RET] != 0)
+ return (1);
+
+ /* command 0x10: set config (sample rate and order) */
+ iobuf[APS_CMD] = 0x10;
+ iobuf[APS_ARG1] = 0xc8;
+ iobuf[APS_ARG2] = 0x00;
+ iobuf[APS_ARG3] = 0x02;
+
+ if (aps_do_io(sc->sc_iot, sc->sc_ioh, iobuf, APS_WRITE_3, APS_READ_0))
+ return (1);
+
+ if (iobuf[APS_RET] != 0)
+ return (1);
+
+ /* command 0x11: refresh data */
+ iobuf[APS_CMD] = 0x11;
+ if (aps_do_io(sc->sc_iot, sc->sc_ioh, iobuf, APS_WRITE_0, APS_READ_1))
+ return (1);
+ if (iobuf[APS_ARG1] != 0)
+ return (1);
+
+ return (0);
}
static int
@@ -313,58 +433,44 @@
return 0;
}
-static uint8_t
-aps_mem_read_1(bus_space_tag_t iot, bus_space_handle_t ioh, int reg,
- uint8_t val)
+static int
+aps_read_data(struct aps_softc *sc)
{
- int i;
- uint8_t cr;
- /* should take no longer than 50 microseconds */
- for (i = 0; i < 10; i++) {
- cr = bus_space_read_1(iot, ioh, reg);
- if (cr == val)
- return 1;
- delay(5 * 1000);
- }
+ unsigned char iobuf[16];
+
+ /* command 0x11: refresh data */
+ iobuf[APS_CMD] = 0x11;
+ if (aps_do_io(sc->sc_iot, sc->sc_ioh, iobuf, APS_WRITE_0, APS_READ_ALL))
+ return (1);
+
+ sc->aps_data.state = iobuf[APS_STATE];
+ sc->aps_data.x_accel = iobuf[APS_XACCEL] + 256 * iobuf[APS_XACCEL + 1];
+ sc->aps_data.y_accel = iobuf[APS_YACCEL] + 256 * iobuf[APS_YACCEL + 1];
+ sc->aps_data.temp1 = iobuf[APS_TEMP];
+ sc->aps_data.x_var = iobuf[APS_XVAR] + 256 * iobuf[APS_XVAR + 1];
+ sc->aps_data.y_var = iobuf[APS_YVAR] + 256 * iobuf[APS_YVAR + 1];
+ sc->aps_data.temp2 = iobuf[APS_TEMP2];
+ sc->aps_data.input = iobuf[APS_INPUT];
- DPRINTF(("aps: reg 0x%x not val 0x%x!\n", reg, val));
- return 0;
+ return (0);
}
static void
aps_refresh_sensor_data(struct aps_softc *sc)
{
int64_t temp;
+#if 0
+ int i;
+#endif
- /* ask for new data */
- bus_space_write_1(sc->sc_iot, sc->sc_ioh, APS_INIT, 0x11);
- bus_space_write_1(sc->sc_iot, sc->sc_ioh, APS_CMD, 0x01);
- if (!aps_mem_read_1(sc->sc_iot, sc->sc_ioh, APS_ACCEL_STATE, 0x50))
+ if (aps_read_data(sc))
return;
- sc->aps_data.state =
- bus_space_read_1(sc->sc_iot, sc->sc_ioh, APS_STATE);
- sc->aps_data.x_accel =
- bus_space_read_2(sc->sc_iot, sc->sc_ioh, APS_XACCEL);
- sc->aps_data.y_accel =
- bus_space_read_2(sc->sc_iot, sc->sc_ioh, APS_YACCEL);
- sc->aps_data.temp1 =
- bus_space_read_1(sc->sc_iot, sc->sc_ioh, APS_TEMP);
- sc->aps_data.x_var =
- bus_space_read_2(sc->sc_iot, sc->sc_ioh, APS_XVAR);
- sc->aps_data.y_var =
- bus_space_read_2(sc->sc_iot, sc->sc_ioh, APS_YVAR);
- sc->aps_data.temp2 =
- bus_space_read_1(sc->sc_iot, sc->sc_ioh, APS_TEMP2);
- sc->aps_data.input =
- bus_space_read_1(sc->sc_iot, sc->sc_ioh, APS_INPUT);
-
- bus_space_write_1(sc->sc_iot, sc->sc_ioh, APS_INIT, 0x11);
- bus_space_write_1(sc->sc_iot, sc->sc_ioh, APS_CMD, 0x01);
-
- /* tell accelerometer we're done reading from it */
- bus_space_read_1(sc->sc_iot, sc->sc_ioh, APS_CMD);
- bus_space_read_1(sc->sc_iot, sc->sc_ioh, APS_ACCEL_STATE);
+#if 0
+ for (i = 0; i < APS_NUM_SENSORS; i++) {
+ sc->sensors[i].flags &= ~SENSOR_FINVALID;
+ }
+#endif
sc->sc_sensor[APS_SENSOR_XACCEL].value_cur = sc->aps_data.x_accel;
sc->sc_sensor[APS_SENSOR_YACCEL].value_cur = sc->aps_data.y_accel;
@@ -414,22 +520,20 @@
aps_resume(device_t dv PMF_FN_ARGS)
{
struct aps_softc *sc = device_private(dv);
+ unsigned char iobuf[16];
/*
* Redo the init sequence on resume, because APS is
* as forgetful as it is deaf.
*/
- bus_space_write_1(sc->sc_iot, sc->sc_ioh, APS_INIT, 0x13);
- bus_space_write_1(sc->sc_iot, sc->sc_ioh, APS_CMD, 0x01);
- bus_space_read_1(sc->sc_iot, sc->sc_ioh, APS_CMD);
- bus_space_write_1(sc->sc_iot, sc->sc_ioh, APS_INIT, 0x13);
- bus_space_write_1(sc->sc_iot, sc->sc_ioh, APS_CMD, 0x01);
- if (aps_mem_read_1(sc->sc_iot, sc->sc_ioh, APS_CMD, 0x00) &&
- aps_init(sc))
- callout_schedule(&sc->sc_callout, (hz) / 2);
- else
+ /* get APS mode */
+ iobuf[APS_CMD] = 0x13;
+ if (aps_do_io(sc->sc_iot, sc->sc_ioh, iobuf, APS_WRITE_0, APS_READ_1)
+ || aps_init(sc))
aprint_error_dev(dv, "failed to wake up\n");
+ else
+ callout_schedule(&sc->sc_callout, (hz) / 2);
return true;
}
>Release-Note:
>Audit-Trail:
Responsible-Changed-From-To: port-amd64-maintainer->jruoho
Responsible-Changed-By: jruoho@NetBSD.org
Responsible-Changed-When: Wed, 28 Jul 2010 11:17:25 +0000
Responsible-Changed-Why:
I'll try to deal with this some day.
These changes are worth importing already because the amount of magic
constants is reduced to an almost tolerable level...
State-Changed-From-To: open->feedback
State-Changed-By: dholland@NetBSD.org
State-Changed-When: Sun, 16 Jan 2011 05:51:28 +0000
State-Changed-Why:
jmcneill@ just did a merge - please test at your convenience.
From: Sverre Froyen <sverre@viewmark.com>
To: gnats-bugs@netbsd.org
Cc: jruoho@netbsd.org,
netbsd-bugs@netbsd.org,
gnats-admin@netbsd.org,
dholland@netbsd.org
Subject: Re: port-amd64/41698 (aps driver does not work on newer ThinkPads)
Date: Sun, 16 Jan 2011 13:41:49 -0700
> Synopsis: aps driver does not work on newer ThinkPads
>
> State-Changed-From-To: open->feedback
> State-Changed-By: dholland@NetBSD.org
> State-Changed-When: Sun, 16 Jan 2011 05:51:28 +0000
> State-Changed-Why:
> jmcneill@ just did a merge - please test at your convenience.
New version works for me (current on a T500).
Perhaps change the names of the sensors? I notice that the aps temperature
sensors are still named TEMP_1/2 whereas all my other ones are named
temperature something.
Thanks,
Sverre
Responsible-Changed-From-To: jruoho->jmcneill
Responsible-Changed-By: jmcneill@NetBSD.org
Responsible-Changed-When: Mon, 17 Jan 2011 03:11:40 +0000
Responsible-Changed-Why:
confirmed fixed, thanks for the report!
State-Changed-From-To: feedback->closed
State-Changed-By: jmcneill@NetBSD.org
State-Changed-When: Mon, 17 Jan 2011 03:11:40 +0000
State-Changed-Why:
From: "Jukka Ruohonen" <jruoho@netbsd.org>
To: gnats-bugs@gnats.NetBSD.org
Cc:
Subject: PR/41698 CVS commit: src/sys/dev/isa
Date: Wed, 16 Feb 2011 10:08:06 +0000
Module Name: src
Committed By: jruoho
Date: Wed Feb 16 10:08:05 UTC 2011
Modified Files:
src/sys/dev/isa: aps.c
Log Message:
As suggested by Sverre Froyen in a follow-up to PR # 41698, change the
sensor names to real words (e.g. "temperature 1" instead of "TEMP_1").
No functional change; only consistent envstat(8) output with other sensors.
To generate a diff of this commit:
cvs rdiff -u -r1.13 -r1.14 src/sys/dev/isa/aps.c
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
>Unformatted:
(Contact us)
$NetBSD: query-full-pr,v 1.39 2013/11/01 18:47:49 spz Exp $
$NetBSD: gnats_config.sh,v 1.8 2006/05/07 09:23:38 tsutsui Exp $
Copyright © 1994-2007
The NetBSD Foundation, Inc. ALL RIGHTS RESERVED.