Skip site navigation (1)Skip section navigation (2)
Date:      Sat, 11 Dec 2004 01:14:23 +0000
From:      Josef Karthauser <joe@FreeBSD.org>
To:        Nate Lawson <nate@root.org>
Cc:        freebsd-acpi@FreeBSD.org
Subject:   Re: S3 on a Sony VGN-A290
Message-ID:  <20041211011423.GL1615@genius.tao.org.uk>
In-Reply-To: <41B9EF34.9020500@root.org>
References:  <20041210133615.GA1482@genius.tao.org.uk> <41B9ED5A.80503@root.org> <20041210184350.GJ1615@genius.tao.org.uk> <41B9EF34.9020500@root.org>

next in thread | previous in thread | raw e-mail | index | archive | help

--00hq2S6J2Jlg6EbK
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline
Content-Transfer-Encoding: quoted-printable

Wicked, thanks :)

J.

On Fri, Dec 10, 2004 at 10:47:16AM -0800, Nate Lawson wrote:
> Josef Karthauser wrote:
> >On Fri, Dec 10, 2004 at 10:39:22AM -0800, Nate Lawson wrote:
> >
> >>Josef Karthauser wrote:
> >>
> >>>Grump.  I leave my new Sony in S3 and come back to it in the morning to
> >>>find that it's run out of battery :/.  I thought that S3 was a low
> >>>energy state.  Anyone else got a similar machine?  Is it a problem with
> >>>the machine or our ACPI?  (I'm running RELENG_5 on it).
> >>
> >>Try a -current kernel.  It has more code to power down devices while in=
=20
> >>suspend but this part is too experimental to MFC for a while.
> >>
> >
> >
> >Can I run a current kernel on a RELENG_5 userland?
>=20
> For testing, should work ok.  The recent mount changes may have hosed=20
> that path though.  Instead, just use the attached patch against 5.x
>=20
> --=20
> Nate

> Index: sys/dev/acpica/acpi.c
> =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
> RCS file: /home/ncvs/src/sys/dev/acpica/acpi.c,v
> retrieving revision 1.186.2.6
> diff -u -r1.186.2.6 acpi.c
> --- sys/dev/acpica/acpi.c	7 Nov 2004 20:24:05 -0000	1.186.2.6
> +++ sys/dev/acpica/acpi.c	30 Nov 2004 20:32:31 -0000
> @@ -59,6 +59,10 @@
>  #include <dev/acpica/acpiio.h>
>  #include <contrib/dev/acpica/acnamesp.h>
> =20
> +#include "pci_if.h"
> +#include <dev/pci/pcivar.h>
> +#include <dev/pci/pci_private.h>
> +
>  MALLOC_DEFINE(M_ACPIDEV, "acpidev", "ACPI devices");
> =20
>  /* Hooks for the ACPI CA debugging infrastructure */
> @@ -87,10 +91,14 @@
>  static void	acpi_identify(driver_t *driver, device_t parent);
>  static int	acpi_probe(device_t dev);
>  static int	acpi_attach(device_t dev);
> +static int	acpi_suspend(device_t dev);
> +static int	acpi_resume(device_t dev);
>  static int	acpi_shutdown(device_t dev);
>  static device_t	acpi_add_child(device_t bus, int order, const char *name,
>  			int unit);
>  static int	acpi_print_child(device_t bus, device_t child);
> +static void	acpi_probe_nomatch(device_t bus, device_t child);
> +static void	acpi_driver_added(device_t dev, driver_t *driver);
>  static int	acpi_read_ivar(device_t dev, device_t child, int index,
>  			uintptr_t *result);
>  static int	acpi_write_ivar(device_t dev, device_t child, int index,
> @@ -110,10 +118,14 @@
>  static ACPI_STATUS acpi_device_eval_obj(device_t bus, device_t dev,
>  		    ACPI_STRING pathname, ACPI_OBJECT_LIST *parameters,
>  		    ACPI_BUFFER *ret);
> +static int	acpi_device_pwr_for_sleep(device_t bus, device_t dev,
> +		    int *dstate);
>  static ACPI_STATUS acpi_device_scan_cb(ACPI_HANDLE h, UINT32 level,
>  		    void *context, void **retval);
>  static ACPI_STATUS acpi_device_scan_children(device_t bus, device_t dev,
>  		    int max_depth, acpi_scan_cb_t user_fn, void *arg);
> +static int	acpi_set_powerstate_method(device_t bus, device_t child,
> +		    int state);
>  static int	acpi_isa_pnp_probe(device_t bus, device_t child,
>  		    struct isa_pnp_id *ids);
>  static void	acpi_probe_children(device_t bus);
> @@ -145,12 +157,14 @@
>      DEVMETHOD(device_attach,		acpi_attach),
>      DEVMETHOD(device_shutdown,		acpi_shutdown),
>      DEVMETHOD(device_detach,		bus_generic_detach),
> -    DEVMETHOD(device_suspend,		bus_generic_suspend),
> -    DEVMETHOD(device_resume,		bus_generic_resume),
> +    DEVMETHOD(device_suspend,		acpi_suspend),
> +    DEVMETHOD(device_resume,		acpi_resume),
> =20
>      /* Bus interface */
>      DEVMETHOD(bus_add_child,		acpi_add_child),
>      DEVMETHOD(bus_print_child,		acpi_print_child),
> +    DEVMETHOD(bus_probe_nomatch,	acpi_probe_nomatch),
> +    DEVMETHOD(bus_driver_added,		acpi_driver_added),
>      DEVMETHOD(bus_read_ivar,		acpi_read_ivar),
>      DEVMETHOD(bus_write_ivar,		acpi_write_ivar),
>      DEVMETHOD(bus_get_resource_list,	acpi_get_rlist),
> @@ -160,7 +174,6 @@
>      DEVMETHOD(bus_release_resource,	acpi_release_resource),
>      DEVMETHOD(bus_child_pnpinfo_str,	acpi_child_pnpinfo_str_method),
>      DEVMETHOD(bus_child_location_str,	acpi_child_location_str_method),
> -    DEVMETHOD(bus_driver_added,		bus_generic_driver_added),
>      DEVMETHOD(bus_activate_resource,	bus_generic_activate_resource),
>      DEVMETHOD(bus_deactivate_resource,	bus_generic_deactivate_resource),
>      DEVMETHOD(bus_setup_intr,		bus_generic_setup_intr),
> @@ -169,8 +182,12 @@
>      /* ACPI bus */
>      DEVMETHOD(acpi_id_probe,		acpi_device_id_probe),
>      DEVMETHOD(acpi_evaluate_object,	acpi_device_eval_obj),
> +    DEVMETHOD(acpi_pwr_for_sleep,	acpi_device_pwr_for_sleep),
>      DEVMETHOD(acpi_scan_children,	acpi_device_scan_children),
> =20
> +    /* PCI emulation */
> +    DEVMETHOD(pci_set_powerstate,	acpi_set_powerstate_method),
> +
>      /* ISA emulation */
>      DEVMETHOD(isa_pnp_probe,		acpi_isa_pnp_probe),
> =20
> @@ -212,6 +229,12 @@
>  static int acpi_serialize_methods;
>  TUNABLE_INT("hw.acpi.serialize_methods", &acpi_serialize_methods);
> =20
> +/* Power devices off and on in suspend and resume.  XXX Remove once test=
ed. */
> +static int acpi_do_powerstate =3D 1;
> +TUNABLE_INT("debug.acpi.do_powerstate", &acpi_do_powerstate);
> +SYSCTL_INT(_debug_acpi, OID_AUTO, do_powerstate, CTLFLAG_RW,
> +    &acpi_do_powerstate, 1, "Turn off devices when suspending.");
> +
>  /*
>   * ACPI can only be loaded as a module by the loader; activating it after
>   * system bootstrap time is not useful, and can be fatal to the system.
> @@ -570,6 +593,72 @@
>  }
> =20
>  static int
> +acpi_suspend(device_t dev)
> +{
> +    struct acpi_softc *sc;
> +    device_t child, *devlist;
> +    int error, i, numdevs, pstate;
> +
> +    /* First give child devices a chance to suspend. */
> +    error =3D bus_generic_suspend(dev);
> +    if (error)
> +	return (error);
> +
> +    /*
> +     * Now, set them into the appropriate power state, usually D3.  If t=
he
> +     * device has an _SxD method for the next sleep state, use that power
> +     * state instead.
> +     */
> +    sc =3D device_get_softc(dev);
> +    device_get_children(dev, &devlist, &numdevs);
> +    for (i =3D 0; i < numdevs; i++) {
> +	/* If the device is not attached, we've powered it down elsewhere. */
> +	child =3D devlist[i];
> +	if (!device_is_attached(child))
> +	    continue;
> +
> +	/*
> +	 * Default to D3 for all sleep states.  The _SxD method is optional
> +	 * so set the powerstate even if it's absent.
> +	 */
> +	pstate =3D PCI_POWERSTATE_D3;
> +	error =3D acpi_device_pwr_for_sleep(device_get_parent(child),
> +	    child, &pstate);
> +	if ((error =3D=3D 0 || error =3D=3D ESRCH) && acpi_do_powerstate)
> +	    pci_set_powerstate(child, pstate);
> +    }
> +    free(devlist, M_TEMP);
> +    error =3D 0;
> +
> +    return (error);
> +}
> +
> +static int
> +acpi_resume(device_t dev)
> +{
> +    ACPI_HANDLE handle;
> +    int i, numdevs;
> +    device_t child, *devlist;
> +
> +    /*
> +     * Put all devices in D0 before resuming them.  Call _S0D on each one
> +     * since some systems expect this.
> +     */
> +    device_get_children(dev, &devlist, &numdevs);
> +    for (i =3D 0; i < numdevs; i++) {
> +	child =3D devlist[i];
> +	handle =3D acpi_get_handle(child);
> +	if (handle)
> +	    AcpiEvaluateObject(handle, "_S0D", NULL, NULL);
> +	if (device_is_attached(child) && acpi_do_powerstate)
> +	    pci_set_powerstate(child, PCI_POWERSTATE_D0);
> +    }
> +    free(devlist, M_TEMP);
> +
> +    return (bus_generic_resume(dev));
> +}
> +
> +static int
>  acpi_shutdown(device_t dev)
>  {
> =20
> @@ -624,6 +713,45 @@
>      return (retval);
>  }
> =20
> +/*
> + * If this device is an ACPI child but no one claimed it, attempt
> + * to power it off.  We'll power it back up when a driver is added.
> + *
> + * XXX Disabled for now since many necessary devices (like fdc and
> + * ATA) don't claim the devices we created for them but still expect
> + * them to be powered up.
> + */
> +static void
> +acpi_probe_nomatch(device_t bus, device_t child)
> +{
> +
> +    /* pci_set_powerstate(child, PCI_POWERSTATE_D3); */
> +}
> +
> +/*
> + * If a new driver has a chance to probe a child, first power it up.
> + *
> + * XXX Disabled for now (see acpi_probe_nomatch for details).
> + */
> +static void
> +acpi_driver_added(device_t dev, driver_t *driver)
> +{
> +    device_t child, *devlist;
> +    int i, numdevs;
> +
> +    DEVICE_IDENTIFY(driver, dev);
> +    device_get_children(dev, &devlist, &numdevs);
> +    for (i =3D 0; i < numdevs; i++) {
> +	child =3D devlist[i];
> +	if (device_get_state(child) =3D=3D DS_NOTPRESENT) {
> +	    /* pci_set_powerstate(child, PCI_POWERSTATE_D0); */
> +	    if (device_probe_and_attach(child) !=3D 0)
> +		; /* pci_set_powerstate(child, PCI_POWERSTATE_D3); */
> +	}
> +    }
> +    free(devlist, M_TEMP);
> +}
> +
>  /* Location hint for devctl(8) */
>  static int
>  acpi_child_location_str_method(device_t cbdev, device_t child, char *buf,
> @@ -1064,6 +1192,57 @@
>      return (AcpiEvaluateObject(h, pathname, parameters, ret));
>  }
> =20
> +static int
> +acpi_device_pwr_for_sleep(device_t bus, device_t dev, int *dstate)
> +{
> +    struct acpi_softc *sc;
> +    ACPI_HANDLE handle;
> +    ACPI_STATUS status;
> +    char sxd[8];
> +    int error;
> +
> +    sc =3D device_get_softc(bus);
> +    handle =3D acpi_get_handle(dev);
> +
> +    /*
> +     * XXX If we find these devices, don't try to power them down.
> +     * The serial and IRDA ports on my T23 hang the system when
> +     * set to D3 and it appears that such legacy devices may
> +     * need special handling in their drivers.
> +     */
> +    if (handle =3D=3D NULL ||
> +	acpi_MatchHid(handle, "PNP0500") ||
> +	acpi_MatchHid(handle, "PNP0501") ||
> +	acpi_MatchHid(handle, "PNP0502") ||
> +	acpi_MatchHid(handle, "PNP0510") ||
> +	acpi_MatchHid(handle, "PNP0511"))
> +	return (ENXIO);
> +
> +    /*
> +     * Override next state with the value from _SxD, if present.  If no
> +     * dstate argument was provided, don't fetch the return value.
> +     */
> +    snprintf(sxd, sizeof(sxd), "_S%dD", sc->acpi_sstate);
> +    if (dstate)
> +	status =3D acpi_GetInteger(handle, sxd, dstate);
> +    else
> +	status =3D AcpiEvaluateObject(handle, sxd, NULL, NULL);
> +
> +    switch (status) {
> +    case AE_OK:
> +	error =3D 0;
> +	break;
> +    case AE_NOT_FOUND:
> +	error =3D ESRCH;
> +	break;
> +    default:
> +	error =3D ENXIO;
> +	break;
> +    }
> +
> +    return (error);
> +}
> +
>  /* Callback arg for our implementation of walking the namespace. */
>  struct acpi_device_scan_ctx {
>      acpi_scan_cb_t	user_fn;
> @@ -1138,6 +1317,34 @@
>  	acpi_device_scan_cb, &ctx, NULL));
>  }
> =20
> +/*
> + * Even though ACPI devices are not PCI, we use the PCI approach for set=
ting
> + * device power states since it's close enough to ACPI.
> + */
> +static int
> +acpi_set_powerstate_method(device_t bus, device_t child, int state)
> +{
> +    ACPI_HANDLE h;
> +    ACPI_STATUS status;
> +    int error;
> +
> +    error =3D 0;
> +    h =3D acpi_get_handle(child);
> +    if (state < ACPI_STATE_D0 || state > ACPI_STATE_D3)
> +	return (EINVAL);
> +    if (h =3D=3D NULL)
> +	return (0);
> +
> +    /* Ignore errors if the power methods aren't present. */
> +    status =3D acpi_pwr_switch_consumer(h, state);
> +    if (ACPI_FAILURE(status) && status !=3D AE_NOT_FOUND
> +	&& status !=3D AE_BAD_PARAMETER)
> +	device_printf(bus, "failed to set ACPI power state D%d on %s: %s\n",
> +	    state, acpi_name(h), AcpiFormatException(status));
> +
> +    return (error);
> +}
> +
>  static int
>  acpi_isa_pnp_probe(device_t bus, device_t child, struct isa_pnp_id *ids)
>  {
> Index: sys/dev/acpica/acpi_if.m
> =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
> RCS file: /home/ncvs/src/sys/dev/acpica/acpi_if.m,v
> retrieving revision 1.2
> diff -u -r1.2 acpi_if.m
> --- sys/dev/acpica/acpi_if.m	15 Jul 2004 16:29:08 -0000	1.2
> +++ sys/dev/acpica/acpi_if.m	30 Nov 2004 20:32:31 -0000
> @@ -109,6 +109,26 @@
>  };
> =20
>  #
> +# Get the highest power state (D0-D3) that is usable for a device when
> +# suspending/resuming.  If a bus calls this when suspending a device, it
> +# must also call it when resuming.
> +#
> +# device_t bus:  parent bus for the device
> +#
> +# device_t dev:  check this device's appropriate power state
> +#
> +# int *dstate:  if successful, contains the highest valid sleep state
> +#
> +# Returns:  0 on success, ESRCH if device has no special state, or
> +#   some other error value.
> +#
> +METHOD int pwr_for_sleep {
> +	device_t	bus;
> +	device_t	dev;
> +	int		*dstate;
> +};
> +
> +#
>  # Rescan a subtree and optionally reattach devices to handles.  Users
>  # specify a callback that is called for each ACPI_HANDLE of type Device
>  # that is a child of "dev".
> Index: sys/dev/pci/pci.c
> =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
> RCS file: /home/ncvs/src/sys/dev/pci/pci.c,v
> retrieving revision 1.264
> diff -u -r1.264 pci.c
> --- sys/dev/pci/pci.c	2 Jul 2004 13:42:36 -0000	1.264
> +++ sys/dev/pci/pci.c	30 Nov 2004 20:33:15 -0000
> @@ -60,6 +60,10 @@
>  #include "pcib_if.h"
>  #include "pci_if.h"
> =20
> +#include <contrib/dev/acpica/acpi.h>
> +#include <dev/acpica/acpivar.h>
> +#include "acpi_if.h"
> +
>  static uint32_t		pci_mapbase(unsigned mapreg);
>  static int		pci_maptype(unsigned mapreg);
>  static int		pci_mapsize(unsigned testval);
> @@ -169,15 +173,15 @@
>  SYSCTL_NODE(_hw, OID_AUTO, pci, CTLFLAG_RD, 0, "PCI bus tuning parameter=
s");
> =20
>  static int pci_enable_io_modes =3D 1;
> -TUNABLE_INT("hw.pci.enable_io_modes", (int *)&pci_enable_io_modes);
> +TUNABLE_INT("hw.pci.enable_io_modes", &pci_enable_io_modes);
>  SYSCTL_INT(_hw_pci, OID_AUTO, enable_io_modes, CTLFLAG_RW,
>      &pci_enable_io_modes, 1,
>      "Enable I/O and memory bits in the config register.  Some BIOSes do =
not\n\
>  enable these bits correctly.  We'd like to do this all the time, but the=
re\n\
>  are some peripherals that this causes problems with.");
> =20
> -static int pci_do_powerstate =3D 0;
> -TUNABLE_INT("hw.pci.do_powerstate", (int *)&pci_do_powerstate);
> +static int pci_do_powerstate =3D 1;
> +TUNABLE_INT("hw.pci.do_powerstate", &pci_do_powerstate);
>  SYSCTL_INT(_hw_pci, OID_AUTO, do_powerstate, CTLFLAG_RW,
>      &pci_do_powerstate, 0,
>      "Power down devices into D3 state when no driver attaches to them.\n\
> @@ -1015,43 +1019,78 @@
>  int
>  pci_suspend(device_t dev)
>  {
> -	int numdevs;
> -	device_t *devlist;
> -	device_t child;
> +	int dstate, error, i, numdevs;
> +	device_t acpi_dev, child, *devlist;
>  	struct pci_devinfo *dinfo;
> -	int i;
> =20
>  	/*
> -	 * Save the pci configuration space for each child.  We don't need
> -	 * to do this, unless the BIOS suspend code powers down the bus and
> -	 * the devices on the bus.
> +	 * Save the PCI configuration space for each child and set the
> +	 * device in the appropriate power state for this sleep state.
>  	 */
> +	acpi_dev =3D NULL;
> +	if (pci_do_powerstate)
> +		acpi_dev =3D devclass_get_device(devclass_find("acpi"), 0);
>  	device_get_children(dev, &devlist, &numdevs);
>  	for (i =3D 0; i < numdevs; i++) {
>  		child =3D devlist[i];
>  		dinfo =3D (struct pci_devinfo *) device_get_ivars(child);
>  		pci_cfg_save(child, dinfo, 0);
>  	}
> +
> +	/* Suspend devices before potentially powering them down. */
> +	error =3D bus_generic_suspend(dev);
> +	if (error)
> +		return (error);
> +
> +	/*
> +	 * Always set the device to D3.  If ACPI suggests a different
> +	 * power state, use it instead.  If ACPI is not present, the
> +	 * firmware is responsible for managing device power.  Skip
> +	 * children who aren't attached since they are powered down
> +	 * separately.  Only manage type 0 devices for now.
> +	 */
> +	for (i =3D 0; acpi_dev && i < numdevs; i++) {
> +		child =3D devlist[i];
> +		dinfo =3D (struct pci_devinfo *) device_get_ivars(child);
> +		if (device_is_attached(child) && dinfo->cfg.hdrtype =3D=3D 0) {
> +			dstate =3D PCI_POWERSTATE_D3;
> +			ACPI_PWR_FOR_SLEEP(acpi_dev, child, &dstate);
> +			pci_set_powerstate(child, dstate);
> +		}
> +	}
>  	free(devlist, M_TEMP);
> -	return (bus_generic_suspend(dev));
> +	return (0);
>  }
> =20
>  int
>  pci_resume(device_t dev)
>  {
> -	int numdevs;
> -	device_t *devlist;
> -	device_t child;
> +	int i, numdevs;
> +	device_t acpi_dev, child, *devlist;
>  	struct pci_devinfo *dinfo;
> -	int i;
> =20
>  	/*
> -	 * Restore the pci configuration space for each child.
> +	 * Set each child to D0 and restore its PCI configuration space.
>  	 */
> +	acpi_dev =3D NULL;
> +	if (pci_do_powerstate)
> +		acpi_dev =3D devclass_get_device(devclass_find("acpi"), 0);
>  	device_get_children(dev, &devlist, &numdevs);
>  	for (i =3D 0; i < numdevs; i++) {
> +		/*
> +		 * Notify ACPI we're going to D0 but ignore the result.  If
> +		 * ACPI is not present, the firmware is responsible for
> +		 * managing device power.  Only manage type 0 devices for now.
> +		 */
>  		child =3D devlist[i];
>  		dinfo =3D (struct pci_devinfo *) device_get_ivars(child);
> +		if (acpi_dev && device_is_attached(child) &&
> +		    dinfo->cfg.hdrtype =3D=3D 0) {
> +			ACPI_PWR_FOR_SLEEP(acpi_dev, child, NULL);
> +			pci_set_powerstate(child, PCI_POWERSTATE_D0);
> +		}
> +
> +		/* Now the device is powered up, restore its config space. */
>  		pci_cfg_restore(child, dinfo);
>  	}
>  	free(devlist, M_TEMP);


--=20
Josef Karthauser (joe@tao.org.uk)	       http://www.josef-k.net/
FreeBSD (cvs meister, admin and hacker)     http://www.uk.FreeBSD.org/
Physics Particle Theory (student)   http://www.pact.cpes.sussex.ac.uk/
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D An eclectic mix of fact an=
d theory. =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D

--00hq2S6J2Jlg6EbK
Content-Type: application/pgp-signature
Content-Disposition: inline

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.6 (FreeBSD)

iEYEARECAAYFAkG6Se4ACgkQXVIcjOaxUBZ4jgCgvR9meiQwYBTBNPS9A5Z9jUo/
YO8AoJL+eVJ+l88wtAKmJEDdc/0uPF0A
=Inly
-----END PGP SIGNATURE-----

--00hq2S6J2Jlg6EbK--



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?20041211011423.GL1615>