Skip site navigation (1)Skip section navigation (2)
Date:      Fri, 30 May 2003 15:24:57 +0300
From:      Marian Dobre <mari@onix.ro>
To:        Thomas Moestl <t.moestl@tu-bs.de>
Cc:        freebsd-sparc64@freebsd.org
Subject:   Re: PLEASE TEST: interrupt assignment patch
Message-ID:  <3ED74D99.7080308@onix.ro>
References:  <20030529113731.GB630@crow.dom2ip.de>

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

I can't compile the kernel after I applied the patch on my E450.
This is the error I'm getting.

rib/ipfilter -D_KERNEL -include opt_global.h -mcmodel=medlow 
-msoft-float -fno-common  -ffreestanding -Werror 
/usr/src/sys/sparc64/pci/ofw_pci.c
/usr/src/sys/sparc64/pci/ofw_pci.c:76: conflicting types for 
`ofw_pci_orb_callback'
/usr/src/sys/sparc64/pci/ofw_pci.h:74: previous declaration of 
`ofw_pci_orb_callback'
/usr/src/sys/sparc64/pci/ofw_pci.c: In function `ofw_pci_orb_callback':
/usr/src/sys/sparc64/pci/ofw_pci.c:92: `slot' undeclared (first use in 
this function)
/usr/src/sys/sparc64/pci/ofw_pci.c:92: (Each undeclared identifier is 
reported only once
/usr/src/sys/sparc64/pci/ofw_pci.c:92: for each function it appears in.)
/usr/src/sys/sparc64/pci/ofw_pci.c:94: `found' undeclared (first use in 
this function)
cc1: warnings being treated as errors
/usr/src/sys/sparc64/pci/ofw_pci.c: In function `ofw_pci_route_intr':
/usr/src/sys/sparc64/pci/ofw_pci.c:110: warning: passing arg 3 of 
`ofw_bus_route_intr' from incompatible pointer type
*** Error code 1

Stop in /usr/obj/usr/src/sys/CUSTOM.
*** Error code 1

Stop in /usr/src.
*** Error code 1

Stop in /usr/src.

I'll try to remove the kernel sources and start fresh but I dont think 
that will help.

Marian


Thomas Moestl wrote:
> Hi,
> 
> I would like to get the attached patch into 5.1; although I am quite
> confident that it will not break anything, we need a good test coverage
> (i.e. testing on many different models) to make sure this is the case,
> since we are that close to a release.
> 
> Therefore, I would very much appreciate if people could give this a
> spin on their machines and report back.
> 
> The aim of the patch is to fix the interrupt assignment on e450s; it
> should not have any effect on other boxen. It has been tested so far
> on two e450s, a Blade 100 and an u60.
> 
> Thanks,
> 	- Thomas
> 
> 
> 
> ------------------------------------------------------------------------
> 
> Index: ebus/ebus.c
> ===================================================================
> RCS file: /vol/ncvs/src/sys/sparc64/ebus/ebus.c,v
> retrieving revision 1.7
> diff -u -r1.7 ebus.c
> --- ebus/ebus.c	19 Feb 2003 05:47:44 -0000	1.7
> +++ ebus/ebus.c	23 May 2003 10:53:26 -0000
> @@ -112,7 +112,7 @@
>      u_long, u_long, u_long, u_int);
>  static struct resource_list *ebus_get_resource_list(device_t, device_t);
>  
> -static struct ebus_devinfo *ebus_setup_dinfo(struct ebus_softc *,
> +static struct ebus_devinfo *ebus_setup_dinfo(device_t, struct ebus_softc *,
>      phandle_t, char *);
>  static void ebus_destroy_dinfo(struct ebus_devinfo *);
>  static int ebus_print_res(struct ebus_devinfo *);
> @@ -200,7 +200,7 @@
>  		if ((OF_getprop_alloc(node, "name", 1, (void **)&cname)) == -1)
>  			continue;
>  
> -		if ((edi = ebus_setup_dinfo(sc, node, cname)) == NULL) {
> +		if ((edi = ebus_setup_dinfo(dev, sc, node, cname)) == NULL) {
>  			device_printf(dev, "<%s>: incomplete\n", cname);
>  			free(cname, M_OFWPROP);
>  			continue;
> @@ -363,7 +363,8 @@
>  }
>  
>  static struct ebus_devinfo *
> -ebus_setup_dinfo(struct ebus_softc *sc, phandle_t node, char *name)
> +ebus_setup_dinfo(device_t dev, struct ebus_softc *sc, phandle_t node,
> +    char *name)
>  {
>  	struct ebus_devinfo *edi;
>  	struct isa_regs *reg;
> @@ -398,7 +399,8 @@
>  	nintr = OF_getprop_alloc(node, "interrupts",  sizeof(*intrs),
>  	    (void **)&intrs);
>  	for (i = 0; i < nintr; i++) {
> -		intr = ofw_bus_route_intr(node, intrs[i], ofw_pci_orb_callback);
> +		intr = ofw_bus_route_intr(node, intrs[i], ofw_pci_orb_callback,
> +		    dev);
>  		if (intr == ORIR_NOTFOUND) {
>  			panic("ebus_setup_dinfo: could not map ebus "
>  			    "interrupt %d", intrs[i]);
> Index: include/ofw_bus.h
> ===================================================================
> RCS file: /vol/ncvs/src/sys/sparc64/include/ofw_bus.h,v
> retrieving revision 1.3
> diff -u -r1.3 ofw_bus.h
> --- include/ofw_bus.h	7 Nov 2002 16:07:46 -0000	1.3
> +++ include/ofw_bus.h	23 May 2003 10:53:26 -0000
> @@ -32,8 +32,8 @@
>  #define	ORIR_NOTFOUND	0xffffffff
>  
>  typedef int obr_callback_t(phandle_t, u_int8_t *, int, u_int8_t *, int,
> -    u_int8_t **, int *);
> +    u_int8_t **, int *, void *);
>  
> -u_int32_t ofw_bus_route_intr(phandle_t, int, obr_callback_t *);
> +u_int32_t ofw_bus_route_intr(phandle_t, int, obr_callback_t *, void *);
>  
>  #endif /* !_MACHINE_OFW_BUS_H_ */
> Index: isa/isa.c
> ===================================================================
> RCS file: /vol/ncvs/src/sys/sparc64/isa/isa.c,v
> retrieving revision 1.5
> diff -u -r1.5 isa.c
> --- isa/isa.c	7 Nov 2002 16:07:46 -0000	1.5
> +++ isa/isa.c	23 May 2003 10:53:26 -0000
> @@ -134,7 +134,7 @@
>  		if (ino > 7)
>  			panic("isa_init: XXX: ino too large");
>  		isa_ino[ino] = ofw_bus_route_intr(node, ino,
> -		    ofw_pci_orb_callback);
> +		    ofw_pci_orb_callback, dev);
>  	}
>  
>  	for (nbr -= 1; nbr >= 0; nbr--) {
> Index: pci/ofw_pci.c
> ===================================================================
> RCS file: /vol/ncvs/src/sys/sparc64/pci/ofw_pci.c,v
> retrieving revision 1.10
> diff -u -r1.10 ofw_pci.c
> --- pci/ofw_pci.c	27 Mar 2003 02:01:59 -0000	1.10
> +++ pci/ofw_pci.c	27 May 2003 11:55:48 -0000
> @@ -46,44 +46,90 @@
>  
>  #include <sparc64/pci/ofw_pci.h>
>  
> +#include <machine/bus.h>
>  #include <machine/cache.h>
>  #include <machine/iommureg.h>
>  #include <machine/ofw_bus.h>
>  #include <machine/ver.h>
>  
>  #include "pcib_if.h"
> +#include "sparcbus_if.h"
>  
>  u_int8_t pci_bus_cnt;
>  phandle_t *pci_bus_map;
>  int pci_bus_map_sz;
>  
> -#define	OPQ_NO_SWIZZLE	1
> +/* Do not swizzle on a PCI bus node with no interrupt-map propery. */
> +#define	OPQ_NO_SWIZZLE		1
> +/*
> + * INOs < 255 are really intpin numbers; use a driver method to figure out
> + * the real INO.
> + */
> +#define	OPQ_INO_CALLBACK	2
> +/*
> + * Do not map EBus interrupts at PCI buses, but assume that they are fully
> + * specified already.
> + */
> +#define	OPQ_EBUS_NOMAP		4
> +
>  static struct ofw_pci_quirk {
>  	char	*opq_model;
>  	int	opq_quirks;
>  } ofw_pci_quirks[] = {
> -	{ "SUNW,Ultra-4", OPQ_NO_SWIZZLE },
> -	{ "SUNW,Ultra-1-Engine", OPQ_NO_SWIZZLE },
> +	{ "SUNW,Ultra-4",		OPQ_INO_CALLBACK | OPQ_EBUS_NOMAP },
> +	{ "SUNW,Ultra-1-Engine",	OPQ_NO_SWIZZLE },
>  };
>  #define	OPQ_NENT	(sizeof(ofw_pci_quirks) / sizeof(ofw_pci_quirks[0]))
>  
>  static int pci_quirks;
>  
>  #define	OFW_PCI_PCIBUS	"pci"
> +#define	OFW_PCI_EBUS	"ebus"
>  #define	PCI_BUS_MAP_INC	10
>  
>  int
>  ofw_pci_orb_callback(phandle_t node, u_int8_t *pintptr, int pintsz,
> -    u_int8_t *pregptr, int pregsz, u_int8_t **rintr, int *terminate)
> +    u_int8_t *pregptr, int pregsz, u_int8_t **rintr, int *terminate,
> +    void *cookie)
>  {
> +	device_t dev = cookie;
>  	struct ofw_pci_register preg;
>  	u_int32_t pintr, intr;
> +	u_int slot;
>  	char type[32];
> +	int found = 0;
>  
> -	if (pintsz != sizeof(u_int32_t))
> +	if ((pci_quirks & OPQ_EBUS_NOMAP) != 0 &&
> +	    OF_getprop(node, "name", type, sizeof(type)) != -1 &&
> +	    strcmp(type, OFW_PCI_EBUS) == 0) {
> +		*terminate = 1;
> +		return (-1);
> +	}
> +	if (pintsz != sizeof(u_int32_t) || pregsz < sizeof(preg))
>  		return (-1);
>  	bcopy(pintptr, &pintr, sizeof(pintr));
> -	if ((pci_quirks & OPQ_NO_SWIZZLE) == 0 && pregsz >= sizeof(preg) &&
> +	bcopy(pregptr, &preg, sizeof(preg));
> +	slot = OFW_PCI_PHYS_HI_DEVICE(preg.phys_hi);
> +
> +	if ((pci_quirks & OPQ_INO_CALLBACK) != 0 && pintr <= 255) {
> +		/*
> +		 * The e450 has no interrupt maps at all, and it usually has
> +		 * full interrupt numbers, including IGN, in the interrupt
> +		 * properties. There is one exception, however: the property
> +		 * values for external PCI devices seem to always be below 255
> +		 * and describe the interrupt pin to be used on the slot, while
> +		 * we have to figure out the base INO by looking at the slot
> +		 * number (which we do using a sparcbus method).
> +		 *
> +		 * Of course, there is an exception to that nice rule:
> +		 * in the ebus case, the interrupt property has the correct
> +		 * INO (but without IGN). This is dealt with above.
> +		 */
> +		intr = SPARCBUS_GUESS_INO(dev, node, slot, pintr);
> +		found = intr != 255;
> +		*terminate = found;
> +	}
> +	if (!found && (pci_quirks & OPQ_NO_SWIZZLE) == 0 &&
>  	    OF_getprop(node, "device_type", type, sizeof(type)) != -1 &&
>  	    strcmp(type, OFW_PCI_PCIBUS) == 0 && pintr >= 1 && pintr <= 4) {
>  		/*
> @@ -91,29 +137,31 @@
>  		 * PCI bridges without interrupt maps, where we apparently must
>  		 * do the PCI swizzle and continue to map on at the parent.
>  		 */
> -		bcopy(pregptr, &preg, sizeof(preg));
> -		intr = (OFW_PCI_PHYS_HI_DEVICE(preg.phys_hi) + pintr + 3) %
> -		    4 + 1;
> +		intr = (slot + pintr + 3) % 4 + 1;
> +		*terminate = 0;
> +		found = 1;
> +	}
> +
> +	if (found) {
>  		*rintr = malloc(sizeof(intr), M_OFWPROP, M_WAITOK);
>  		bcopy(&intr, *rintr, sizeof(intr));
> -		*terminate = 0;
>  		return (sizeof(intr));
> -	}
> -	return (-1);
> +	} else
> +		return (-1);
>  }
>  
> -u_int32_t
> -ofw_pci_route_intr(phandle_t node, u_int32_t ign)
> +static u_int32_t
> +ofw_pci_route_intr(device_t dev, phandle_t node, u_int32_t ign)
>  {
>  	u_int32_t rv;
>  
> -	rv = ofw_bus_route_intr(node, ORIP_NOINT, ofw_pci_orb_callback);
> +	rv = ofw_bus_route_intr(node, ORIP_NOINT, ofw_pci_orb_callback, dev);
>  	if (rv == ORIR_NOTFOUND)
>  		return (255);
>  	/*
> -	 * Some machines (notably the SPARCengine Ultra AX) have no mappings
> -	 * at all, but use complete interrupt vector number including the IGN.
> -	 * Catch this case and remove the IGN.
> +	 * Some machines (notably the SPARCengine Ultra AX and the e450) have
> +	 * no mappings at all, but use complete interrupt vector number
> +	 * including the IGN. Catch this case and remove the IGN.
>  	 */
>  	if (rv > ign)
>  		rv -= ign;
> @@ -273,7 +321,7 @@
>  			}
>  
>  			/* Initialize the intline registers. */
> -			if ((intr = ofw_pci_route_intr(node, ign)) != 255) {
> +			if ((intr = ofw_pci_route_intr(dev, node, ign)) != 255) {
>  #ifdef OFW_PCI_DEBUG
>  				device_printf(dev, "%s: mapping intr for "
>  				    "%d/%d/%d to %d (preset was %d)\n",
> Index: pci/ofw_pci.h
> ===================================================================
> RCS file: /vol/ncvs/src/sys/sparc64/pci/ofw_pci.h,v
> retrieving revision 1.4
> diff -u -r1.4 ofw_pci.h
> --- pci/ofw_pci.h	7 Nov 2002 16:07:46 -0000	1.4
> +++ pci/ofw_pci.h	23 May 2003 10:53:26 -0000
> @@ -71,7 +71,6 @@
>  	struct ofw_pci_bdesc	*obd_super;
>  };
>  
> -u_int32_t ofw_pci_route_intr(phandle_t, u_int32_t);
>  obr_callback_t ofw_pci_orb_callback;
>  u_int8_t ofw_pci_alloc_busno(phandle_t);
>  ofw_pci_binit_t ofw_pci_binit;
> Index: pci/psycho.c
> ===================================================================
> RCS file: /vol/ncvs/src/sys/sparc64/pci/psycho.c,v
> retrieving revision 1.32
> diff -u -r1.32 psycho.c
> --- pci/psycho.c	2 May 2003 01:21:36 -0000	1.32
> +++ pci/psycho.c	23 May 2003 10:53:26 -0000
> @@ -144,6 +144,7 @@
>      int);
>  static int psycho_route_interrupt(device_t, device_t, int);
>  static int psycho_intr_pending(device_t, int);
> +static u_int32_t psycho_guess_ino(device_t, phandle_t, u_int, u_int);
>  static bus_space_handle_t psycho_get_bus_handle(device_t dev, enum sbbt_id id,
>      bus_space_handle_t childhdl, bus_space_tag_t *tag);
>  
> @@ -170,6 +171,7 @@
>  
>  	/* sparcbus interface */
>  	DEVMETHOD(sparcbus_intr_pending,	psycho_intr_pending),
> +	DEVMETHOD(sparcbus_guess_ino,	psycho_guess_ino),
>  	DEVMETHOD(sparcbus_get_bus_handle,	psycho_get_bus_handle),
>  
>  	{ 0, 0 }
> @@ -339,7 +341,6 @@
>  	struct upa_regs *reg;
>  	struct ofw_pci_bdesc obd;
>  	struct psycho_desc *desc;
> -	vm_paddr_t pcictl_offs;
>  	phandle_t node;
>  	u_int64_t csr;
>  	u_long mlen;
> @@ -377,13 +378,25 @@
>  			panic("psycho_attach: %d not enough registers", nreg);
>  		sc->sc_basepaddr = (vm_paddr_t)UPA_REG_PHYS(&reg[2]);
>  		mlen = UPA_REG_SIZE(&reg[2]);
> -		pcictl_offs = UPA_REG_PHYS(&reg[0]);
> +		sc->sc_pcictl = UPA_REG_PHYS(&reg[0]) - sc->sc_basepaddr;
> +		switch (sc->sc_pcictl) {
> +		case PSR_PCICTL0:
> +			sc->sc_half = 0;
> +			break;
> +		case PSR_PCICTL1:
> +			sc->sc_half = 1;
> +			break;
> +		default:
> +			panic("psycho_attach: bogus pci control register "
> +			    "location");
> +		}
>  	} else {
>  		if (nreg <= 0)
>  			panic("psycho_attach: %d not enough registers", nreg);
>  		sc->sc_basepaddr = (vm_paddr_t)UPA_REG_PHYS(&reg[0]);
>  		mlen = UPA_REG_SIZE(reg);
> -		pcictl_offs = sc->sc_basepaddr + PSR_PCICTL0;
> +		sc->sc_pcictl = PSR_PCICTL0;
> +		sc->sc_half = 0;
>  	}
>  
>  	/*
> @@ -418,17 +431,14 @@
>  		sc->sc_bustag = osc->sc_bustag;
>  		sc->sc_bushandle = osc->sc_bushandle;
>  	}
> -	if (pcictl_offs < sc->sc_basepaddr)
> -		panic("psycho_attach: bogus pci control register location");
> -	sc->sc_pcictl = pcictl_offs - sc->sc_basepaddr;
>  	csr = PSYCHO_READ8(sc, PSR_CS);
>  	sc->sc_ign = 0x7c0; /* APB IGN is always 0x7c */
>  	if (sc->sc_mode == PSYCHO_MODE_PSYCHO)
>  		sc->sc_ign = PSYCHO_GCSR_IGN(csr) << 6;
>  
> -	device_printf(dev, "%s, impl %d, version %d, ign %#x\n",
> +	device_printf(dev, "%s, impl %d, version %d, ign %#x, bus %c\n",
>  	    desc->pd_name, (int)PSYCHO_GCSR_IMPL(csr),
> -	    (int)PSYCHO_GCSR_VERS(csr), sc->sc_ign);
> +	    (int)PSYCHO_GCSR_VERS(csr), sc->sc_ign, 'A' + sc->sc_half);
>  
>  	/*
>  	 * Setup the PCI control register
> @@ -1275,6 +1285,31 @@
>  		return (0);
>  	}
>  	return (diag != 0);
> +}
> +
> +static u_int32_t
> +psycho_guess_ino(device_t dev, phandle_t node, u_int slot, u_int pin)
> +{
> +	struct psycho_softc *sc = (struct psycho_softc *)device_get_softc(dev);
> +	bus_addr_t intrmap;
> +
> +	/*
> +	 * If this is not for one of our direct children (i.e. we are mapping
> +	 * at our node), tell the interrupt mapper to go on - we need the
> +	 * slot number of the device or it's topmost parent bridge to guess
> +	 * the INO.
> +	 */
> +	if (node != sc->sc_node)
> +		return (255);
> +	/*
> +	 * Actually guess the INO. We always assume that this is a non-OBIO
> +	 * device, and use from the slot number to determine it.
> +	 * We only need to do this on e450s, it seems; here, the slot numbers
> +	 * for bus A are one-based, while those for bus B seemingly have an
> +	 * offset of 2 (hence the factor of 3 below).
> +	 */
> +	intrmap = PSR_PCIA0_INT_MAP + 8 * (slot - 1 + 3 * sc->sc_half);
> +	return (INTINO(PSYCHO_READ8(sc, intrmap)) + pin - 1);
>  }
>  
>  static bus_space_handle_t
> Index: pci/psychovar.h
> ===================================================================
> RCS file: /vol/ncvs/src/sys/sparc64/pci/psychovar.h,v
> retrieving revision 1.7
> diff -u -r1.7 psychovar.h
> --- pci/psychovar.h	8 Apr 2003 06:35:08 -0000	1.7
> +++ pci/psychovar.h	23 May 2003 10:53:26 -0000
> @@ -61,6 +61,9 @@
>  #define	PSYCHO_MODE_SABRE	1
>  #define	PSYCHO_MODE_PSYCHO	2
>  
> +	/* Bus A or B of a psycho pair? */
> +	int				sc_half;
> +
>  	struct iommu_state		*sc_is;
>  	u_int32_t			sc_dvmabase;
>  
> Index: sparc64/ofw_bus.c
> ===================================================================
> RCS file: /vol/ncvs/src/sys/sparc64/sparc64/ofw_bus.c,v
> retrieving revision 1.5
> diff -u -r1.5 ofw_bus.c
> --- sparc64/ofw_bus.c	19 Feb 2003 05:47:45 -0000	1.5
> +++ sparc64/ofw_bus.c	23 May 2003 10:53:26 -0000
> @@ -160,7 +160,7 @@
>   * This should work for all bus systems.
>   */
>  u_int32_t
> -ofw_bus_route_intr(phandle_t node, int intrp, obr_callback_t *cb)
> +ofw_bus_route_intr(phandle_t node, int intrp, obr_callback_t *cb, void *cookie)
>  {
>  	u_int8_t *reg, *intr, *tintr, *imap, *imapmsk;
>  	phandle_t parent;
> @@ -201,7 +201,7 @@
>  			 */
>  			if (cb != NULL) {
>  				tisz = cb(parent, intr, isz, reg, regsz, &tintr,
> -				    &found);
> +				    &found, cookie);
>  				if (tisz != -1) {
>  					isz = tisz;
>  					free(intr, M_OFWPROP);
> Index: sparc64/sparcbus_if.m
> ===================================================================
> RCS file: /vol/ncvs/src/sys/sparc64/sparc64/sparcbus_if.m,v
> retrieving revision 1.1
> diff -u -r1.1 sparcbus_if.m
> --- sparc64/sparcbus_if.m	9 Nov 2001 20:43:44 -0000	1.1
> +++ sparc64/sparcbus_if.m	23 May 2003 10:53:26 -0000
> @@ -26,6 +26,8 @@
>  #include <sys/bus.h>
>  #include <machine/bus.h>
>  
> +#include <dev/ofw/openfirm.h>
> +
>  INTERFACE sparcbus;
>  
>  HEADER {
> @@ -37,10 +39,6 @@
>  };
>  
>  CODE {
> -	static int sparcbus_default_intr_pending(device_t, int);
> -	static bus_space_handle_t sparcbus_default_get_bus_handle(device_t,
> -	    enum sbbt_id, bus_space_handle_t childhdl, bus_space_tag_t *tag);
> -
>  	static int
>  	sparcbus_default_intr_pending(device_t dev, int intr)
>  	{
> @@ -48,6 +46,15 @@
>  		return (SPARCBUS_INTR_PENDING(device_get_parent(dev), intr));
>  	}
>  
> +	static u_int32_t
> +	sparcbus_default_guess_ino(device_t dev, phandle_t node, u_int slot,
> +	    u_int pin)
> +	{
> +
> +		return (SPARCBUS_GUESS_INO(device_get_parent(dev), node, slot,
> +		    pin));
> +	}
> +
>  	static bus_space_handle_t
>  	sparcbus_default_get_bus_handle(device_t dev, enum sbbt_id id,
>  	    bus_space_handle_t childhdl, bus_space_tag_t *tag)
> @@ -63,6 +70,17 @@
>  	device_t dev;
>  	int intr;
>  } DEFAULT sparcbus_default_intr_pending;
> +
> +# Let the bus driver guess the INO of the device at the given slot and intpin
> +# on the bus described by the node if it could not be determined from the
> +# firmware properties. Returns 255 if no INO could be found (mapping will
> +# continue at the parent), or the desired INO.
> +METHOD u_int32_t guess_ino {
> +	device_t dev;
> +	phandle_t node;
> +	u_int slot;
> +	u_int pin;
> +} DEFAULT sparcbus_default_guess_ino;
>  
>  # Get the bustag for the root bus. This is needed for ISA old-stlye
>  # in[bwl]()/out[bwl]() support, where no tag retrieved from a resource is
> 
> 
> ------------------------------------------------------------------------
> 
> _______________________________________________
> freebsd-sparc64@freebsd.org mailing list
> http://lists.freebsd.org/mailman/listinfo/freebsd-sparc64
> To unsubscribe, send any mail to "freebsd-sparc64-unsubscribe@freebsd.org"




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