Skip site navigation (1)Skip section navigation (2)
Date:      Wed, 23 Nov 2005 22:51:08 GMT
From:      Sam Leffler <sam@FreeBSD.org>
To:        Perforce Change Reviews <perforce@freebsd.org>
Subject:   PERFORCE change 87159 for review
Message-ID:  <200511232251.jANMp8Q0004410@repoman.freebsd.org>

next in thread | raw e-mail | index | archive | help
http://perforce.freebsd.org/chv.cgi?CH=87159

Change 87159 by sam@sam_ebb on 2005/11/23 22:50:06

	IFC

Affected files ...

.. //depot/projects/wifi/sys/dev/ath/if_ath.c#112 edit
.. //depot/projects/wifi/sys/dev/ral/if_ral.c#7 edit
.. //depot/projects/wifi/sys/dev/usb/if_ural.c#7 edit
.. //depot/projects/wifi/sys/dev/wi/if_wi.c#20 edit

Differences ...

==== //depot/projects/wifi/sys/dev/ath/if_ath.c#112 (text+ko) ====

@@ -2151,7 +2151,7 @@
 	 *
 	 * XXX should get from lladdr instead of arpcom but that's more work
 	 */
-	IEEE80211_ADDR_COPY(ic->ic_myaddr, IFP2ENADDR(ifp));
+	IEEE80211_ADDR_COPY(ic->ic_myaddr, IF_LLADDR(ifp));
 	ath_hal_setmac(ah, ic->ic_myaddr);
 
 	/* calculate and install multicast filter */

==== //depot/projects/wifi/sys/dev/ral/if_ral.c#7 (text+ko) ====

@@ -1,4 +1,4 @@
-/*	$FreeBSD: src/sys/dev/ral/if_ral.c,v 1.17 2005/09/19 03:10:19 imp Exp $	*/
+/*	$FreeBSD: src/sys/dev/ral/if_ral.c,v 1.19 2005/11/15 17:17:15 damien Exp $	*/
 
 /*-
  * Copyright (c) 2005
@@ -18,7 +18,7 @@
  */
 
 #include <sys/cdefs.h>
-__FBSDID("$FreeBSD: src/sys/dev/ral/if_ral.c,v 1.17 2005/09/19 03:10:19 imp Exp $");
+__FBSDID("$FreeBSD: src/sys/dev/ral/if_ral.c,v 1.19 2005/11/15 17:17:15 damien Exp $");
 
 /*-
  * Ralink Technology RT2500 chipset driver
@@ -1280,7 +1280,7 @@
 	struct ieee80211_frame *wh;
 	struct ieee80211_node *ni;
 	struct ral_node *rn;
-	struct mbuf *m;
+	struct mbuf *mnew, *m;
 	int hw, error;
 
 	/* retrieve last decriptor index processed by cipher engine */
@@ -1308,12 +1308,51 @@
 			goto skip;
 		}
 
+		/*
+		 * Try to allocate a new mbuf for this ring element and load it
+		 * before processing the current mbuf. If the ring element
+		 * cannot be loaded, drop the received packet and reuse the old
+		 * mbuf. In the unlikely case that the old mbuf can't be
+		 * reloaded either, explicitly panic.
+		 */
+		mnew = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR);
+		if (mnew == NULL) {
+			ifp->if_ierrors++;
+			goto skip;
+		}
+
 		bus_dmamap_sync(sc->rxq.data_dmat, data->map,
 		    BUS_DMASYNC_POSTREAD);
 		bus_dmamap_unload(sc->rxq.data_dmat, data->map);
 
+		error = bus_dmamap_load(sc->rxq.data_dmat, data->map,
+		    mtod(mnew, void *), MCLBYTES, ral_dma_map_addr, &physaddr,
+		    0);
+		if (error != 0) {
+			m_freem(mnew);
+
+			/* try to reload the old mbuf */
+			error = bus_dmamap_load(sc->rxq.data_dmat, data->map,
+			    mtod(data->m, void *), MCLBYTES, ral_dma_map_addr,
+			    &physaddr, 0);
+			if (error != 0) {
+				/* very unlikely that it will fail... */
+				panic("%s: could not load old rx mbuf",
+				    device_get_name(sc->sc_dev));
+			}
+			ifp->if_ierrors++;
+			goto skip;
+		}
+
+		/*
+	 	 * New mbuf successfully loaded, update Rx ring and continue
+		 * processing.
+		 */
+		m = data->m;
+		data->m = mnew;
+		desc->physaddr = htole32(physaddr);
+
 		/* finalize mbuf */
-		m = data->m;
 		m->m_pkthdr.rcvif = ifp;
 		m->m_pkthdr.len = m->m_len =
 		    (le32toh(desc->flags) >> 16) & 0xfff;
@@ -1343,7 +1382,7 @@
 		    (struct ieee80211_frame_min *)wh);
 
 		/* send the frame to the 802.11 layer */
-		ieee80211_input(ic, m, ni, desc->rssi, 0);
+		ieee80211_input(ic, m, ni, desc->rssi, -95/*XXX*/, 0);
 
 		/* give rssi to the rate adatation algorithm */
 		rn = (struct ral_node *)ni;
@@ -1352,25 +1391,6 @@
 		/* node is no longer needed */
 		ieee80211_free_node(ni);
 
-		data->m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR);
-		if (data->m == NULL) {
-			device_printf(sc->sc_dev,
-			    "could not allocate rx mbuf\n");
-			break;
-		}
-
-		error = bus_dmamap_load(sc->rxq.data_dmat, data->map,
-		    mtod(data->m, void *), MCLBYTES, ral_dma_map_addr,
-		    &physaddr, 0);
-		if (error != 0) {
-			device_printf(sc->sc_dev,
-			    "could not load rx buf DMA map\n");
-			m_freem(data->m);
-			data->m = NULL;
-			break;
-		}
-
-		desc->physaddr = htole32(physaddr);
 skip:		desc->flags = htole32(RAL_RX_BUSY);
 
 		DPRINTFN(15, ("decryption done idx=%u\n", sc->rxq.cur_decrypt));
@@ -1567,33 +1587,18 @@
 ral_txtime(int len, int rate, uint32_t flags)
 {
 	uint16_t txtime;
-	int ceil, dbps;
 
 	if (RAL_RATE_IS_OFDM(rate)) {
-		/*
-		 * OFDM TXTIME calculation.
-		 * From IEEE Std 802.11a-1999, pp. 37.
-		 */
-		dbps = rate * 2; /* data bits per OFDM symbol */
-
-		ceil = (16 + 8 * len + 6) / dbps;
-		if ((16 + 8 * len + 6) % dbps != 0)
-			ceil++;
-
-		txtime = 16 + 4 + 4 * ceil + 6;
+		/* IEEE Std 802.11a-1999, pp. 37 */
+		txtime = (8 + 4 * len + 3 + rate - 1) / rate;
+		txtime = 16 + 4 + 4 * txtime + 6;
 	} else {
-		/*
-		 * High Rate TXTIME calculation.
-		 * From IEEE Std 802.11b-1999, pp. 28.
-		 */
-		ceil = (8 * len * 2) / rate;
-		if ((8 * len * 2) % rate != 0)
-			ceil++;
-
+		/* IEEE Std 802.11b-1999, pp. 28 */
+		txtime = (16 * len + rate - 1) / rate;
 		if (rate != 2 && (flags & IEEE80211_F_SHPREAMBLE))
-			txtime =  72 + 24 + ceil;
+			txtime +=  72 + 24;
 		else
-			txtime = 144 + 48 + ceil;
+			txtime += 144 + 48;
 	}
 
 	return txtime;

==== //depot/projects/wifi/sys/dev/usb/if_ural.c#7 (text+ko) ====

@@ -1,4 +1,4 @@
-/*	$FreeBSD: src/sys/dev/usb/if_ural.c,v 1.16 2005/09/19 18:19:22 damien Exp $	*/
+/*	$FreeBSD: src/sys/dev/usb/if_ural.c,v 1.21 2005/11/19 15:08:05 damien Exp $	*/
 
 /*-
  * Copyright (c) 2005
@@ -18,7 +18,7 @@
  */
 
 #include <sys/cdefs.h>
-__FBSDID("$FreeBSD: src/sys/dev/usb/if_ural.c,v 1.16 2005/09/19 18:19:22 damien Exp $");
+__FBSDID("$FreeBSD: src/sys/dev/usb/if_ural.c,v 1.21 2005/11/19 15:08:05 damien Exp $");
 
 /*-
  * Ralink Technology RT2500USB chipset driver
@@ -70,7 +70,7 @@
 #ifdef USB_DEBUG
 #define DPRINTF(x)	do { if (uraldebug > 0) logprintf x; } while (0)
 #define DPRINTFN(n, x)	do { if (uraldebug >= (n)) logprintf x; } while (0)
-int uraldebug = 2;
+int uraldebug = 0;
 SYSCTL_NODE(_hw_usb, OID_AUTO, ural, CTLFLAG_RW, 0, "USB ural");
 SYSCTL_INT(_hw_usb_ural, OID_AUTO, debug, CTLFLAG_RW, &uraldebug, 0,
     "ural debug level");
@@ -158,6 +158,13 @@
 Static void		ural_set_rxantenna(struct ural_softc *, int);
 Static void		ural_init(void *);
 Static void		ural_stop(void *);
+Static void		ural_amrr_start(struct ural_softc *,
+			    struct ieee80211_node *);
+Static void		ural_amrr_timeout(void *);
+Static void		ural_amrr_update(usbd_xfer_handle, usbd_private_handle,
+			    usbd_status status);
+Static void		ural_ratectl(struct ural_amrr *,
+			    struct ieee80211_node *);
 
 /*
  * Supported rates for 802.11a/b/g modes (in 500Kbps unit).
@@ -410,6 +417,7 @@
 	    MTX_DEF | MTX_RECURSE);
 
 	usb_init_task(&sc->sc_task, ural_task, sc);
+	callout_init(&sc->amrr_ch, 0);
 
 	/* retrieve RT2570 rev. no */
 	sc->asic_rev = ural_read(sc, RAL_MAC_CSR0);
@@ -520,6 +528,7 @@
 	struct ifnet *ifp = ic->ic_ifp;
 
 	usb_rem_task(sc->sc_udev, &sc->sc_task);
+	callout_stop(&sc->amrr_ch);
 
 	if (sc->sc_rx_pipeh != NULL) {
 		usbd_abort_pipe(sc->sc_rx_pipeh);
@@ -734,6 +743,12 @@
 
 		if (ic->ic_opmode != IEEE80211_M_MONITOR)
 			ural_enable_tsf_sync(sc);
+
+		/* enable automatic rate adaptation in STA mode */
+		if (ic->ic_opmode == IEEE80211_M_STA &&
+		    ic->ic_fixed_rate == IEEE80211_FIXED_RATE_NONE)
+			ural_amrr_start(sc, ic->ic_bss);
+
 		break;
 
 	default:
@@ -749,6 +764,7 @@
 	struct ural_softc *sc = ic->ic_ifp->if_softc;
 
 	usb_rem_task(sc->sc_udev, &sc->sc_task);
+	callout_stop(&sc->amrr_ch);
 
 	/* do it in a process context */
 	sc->sc_state = nstate;
@@ -810,7 +826,7 @@
 	struct ural_rx_desc *desc;
 	struct ieee80211_frame *wh;
 	struct ieee80211_node *ni;
-	struct mbuf *m;
+	struct mbuf *mnew, *m;
 	int len;
 
 	if (status != USBD_NORMAL_COMPLETION) {
@@ -844,8 +860,17 @@
 		goto skip;
 	}
 
+	mnew = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR);
+	if (mnew == NULL) {
+		ifp->if_ierrors++;
+		goto skip;
+	}
+
+	m = data->m;
+	data->m = mnew;
+	data->buf = mtod(data->m, uint8_t *);
+
 	/* finalize mbuf */
-	m = data->m;
 	m->m_pkthdr.rcvif = ifp;
 	m->m_pkthdr.len = m->m_len = (le32toh(desc->flags) >> 16) & 0xfff;
 	m->m_flags |= M_HASFCS; /* hardware appends FCS */
@@ -854,20 +879,11 @@
 	ni = ieee80211_find_rxnode(ic, (struct ieee80211_frame_min *)wh);
 
 	/* send the frame to the 802.11 layer */
-	ieee80211_input(ic, m, ni, desc->rssi, 0);
+	ieee80211_input(ic, m, ni, desc->rssi, -95/*XXX*/, 0);
 
 	/* node is no longer needed */
 	ieee80211_free_node(ni);
 
-	data->m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR);
-	if (data->m == NULL) {
-		printf("%s: could not allocate rx mbuf\n",
-		    USBDEVNAME(sc->sc_dev));
-		return;
-	}
-
-	data->buf = mtod(data->m, uint8_t *);
-
 	DPRINTFN(15, ("rx done\n"));
 
 skip:	/* setup a new transfer */
@@ -919,33 +935,18 @@
 ural_txtime(int len, int rate, uint32_t flags)
 {
 	uint16_t txtime;
-	int ceil, dbps;
 
 	if (RAL_RATE_IS_OFDM(rate)) {
-		/*
-		 * OFDM TXTIME calculation.
-		 * From IEEE Std 802.11a-1999, pp. 37.
-		 */
-		dbps = rate * 2; /* data bits per OFDM symbol */
-
-		ceil = (16 + 8 * len + 6) / dbps;
-		if ((16 + 8 * len + 6) % dbps != 0)
-			ceil++;
-
-		txtime = 16 + 4 + 4 * ceil + 6;
+		/* IEEE Std 802.11a-1999, pp. 37 */
+		txtime = (8 + 4 * len + 3 + rate - 1) / rate;
+		txtime = 16 + 4 + 4 * txtime + 6;
 	} else {
-		/*
-		 * High Rate TXTIME calculation.
-		 * From IEEE Std 802.11b-1999, pp. 28.
-		 */
-		ceil = (8 * len * 2) / rate;
-		if ((8 * len * 2) % rate != 0)
-			ceil++;
-
+		/* IEEE Std 802.11b-1999, pp. 28 */
+		txtime = (16 * len + rate - 1) / rate;
 		if (rate != 2 && (flags & IEEE80211_F_SHPREAMBLE))
-			txtime =  72 + 24 + ceil;
+			txtime +=  72 + 24;
 		else
-			txtime = 144 + 48 + ceil;
+			txtime += 144 + 48;
 	}
 
 	return txtime;
@@ -1170,7 +1171,6 @@
 
 	wh = mtod(m0, struct ieee80211_frame *);
 
-	/* XXX should do automatic rate adaptation */
 	if (ic->ic_fixed_rate != IEEE80211_FIXED_RATE_NONE)
 		rate = ic->ic_fixed_rate;
 	else
@@ -1906,7 +1906,7 @@
 	struct ifnet *ifp = ic->ic_ifp;
 	struct ieee80211_key *wk;
 	struct ural_rx_data *data;
-	uint16_t sta[11], tmp;
+	uint16_t tmp;
 	usbd_status error;
 	int i, ntries;
 
@@ -1942,7 +1942,7 @@
 	ural_set_chan(sc, ic->ic_curchan);
 
 	/* clear statistic registers (STA_CSR0 to STA_CSR10) */
-	ural_read_multi(sc, RAL_STA_CSR0, sta, sizeof sta);
+	ural_read_multi(sc, RAL_STA_CSR0, sc->sta, sizeof sc->sta);
 
 	ural_set_txantenna(sc, sc->tx_ant);
 	ural_set_rxantenna(sc, sc->rx_ant);
@@ -1960,6 +1960,16 @@
 	}
 
 	/*
+	 * Allocate xfer for AMRR statistics requests.
+	 */
+	sc->amrr_xfer = usbd_alloc_xfer(sc->sc_udev);
+	if (sc->amrr_xfer == NULL) {
+		printf("%s: could not allocate AMRR xfer\n",
+		    USBDEVNAME(sc->sc_dev));
+		goto fail;
+	}
+
+	/*
 	 * Open Tx and Rx USB bulk pipes.
 	 */
 	error = usbd_open_pipe(sc->sc_iface, sc->sc_tx_no, USBD_EXCLUSIVE_USE,
@@ -2052,6 +2062,11 @@
 	ural_write(sc, RAL_MAC_CSR1, RAL_RESET_ASIC | RAL_RESET_BBP);
 	ural_write(sc, RAL_MAC_CSR1, 0);
 
+	if (sc->amrr_xfer != NULL) {
+		usbd_free_xfer(sc->amrr_xfer);
+		sc->amrr_xfer = NULL;
+	}
+
 	if (sc->sc_rx_pipeh != NULL) {
 		usbd_abort_pipe(sc->sc_rx_pipeh);
 		usbd_close_pipe(sc->sc_rx_pipeh);
@@ -2068,4 +2083,149 @@
 	ural_free_tx_list(sc);
 }
 
+Static void
+ural_amrr_start(struct ural_softc *sc, struct ieee80211_node *ni)
+{
+	struct ural_amrr *amrr = &sc->amrr;
+	int i;
+
+	/* clear statistic registers (STA_CSR0 to STA_CSR10) */
+	ural_read_multi(sc, RAL_STA_CSR0, sc->sta, sizeof sc->sta);
+
+	amrr->success = 0;
+	amrr->recovery = 0;
+	amrr->success_threshold = 0;
+	amrr->txcnt = amrr->retrycnt = 0;
+
+	/* set rate to some reasonable initial value */
+	for (i = ni->ni_rates.rs_nrates - 1;
+	     i > 0 && (ni->ni_rates.rs_rates[i] & IEEE80211_RATE_VAL) > 72;
+	     i--);
+
+	ni->ni_txrate = i;
+
+	callout_reset(&sc->amrr_ch, hz, ural_amrr_timeout, sc);
+}
+
+Static void
+ural_amrr_timeout(void *arg)
+{
+	struct ural_softc *sc = (struct ural_softc *)arg;
+	usb_device_request_t req;
+	int s;
+
+	s = splusb();
+
+	/*
+	 * Asynchronously read statistic registers (cleared by read).
+	 */
+	req.bmRequestType = UT_READ_VENDOR_DEVICE;
+	req.bRequest = RAL_READ_MULTI_MAC;
+	USETW(req.wValue, 0);
+	USETW(req.wIndex, RAL_STA_CSR0);
+	USETW(req.wLength, sizeof sc->sta);
+
+	usbd_setup_default_xfer(sc->amrr_xfer, sc->sc_udev, sc,
+	    USBD_DEFAULT_TIMEOUT, &req, sc->sta, sizeof sc->sta, 0,
+	    ural_amrr_update);
+	(void)usbd_transfer(sc->amrr_xfer);
+
+	splx(s);
+}
+
+Static void
+ural_amrr_update(usbd_xfer_handle xfer, usbd_private_handle priv,
+    usbd_status status)
+{
+	struct ural_softc *sc = (struct ural_softc *)priv;
+	struct ural_amrr *amrr = &sc->amrr;
+
+	if (status != USBD_NORMAL_COMPLETION)
+		return;
+
+	amrr->retrycnt =
+	    sc->sta[7] +	/* TX one-retry ok count */
+	    sc->sta[8] +	/* TX more-retry ok count */
+	    sc->sta[8];		/* TX retry-fail count */
+
+	amrr->txcnt =
+	    amrr->retrycnt +
+	    sc->sta[6];		/* TX no-retry ok count */
+
+	ural_ratectl(amrr, sc->sc_ic.ic_bss);
+
+	callout_reset(&sc->amrr_ch, hz, ural_amrr_timeout, sc);
+}
+
+/*-
+ * Naive implementation of the Adaptive Multi Rate Retry algorithm:
+ *     "IEEE 802.11 Rate Adaptation: A Practical Approach"
+ *     Mathieu Lacage, Hossein Manshaei, Thierry Turletti
+ *     INRIA Sophia - Projet Planete
+ *     http://www-sop.inria.fr/rapports/sophia/RR-5208.html
+ *
+ * This algorithm is particularly well suited for ural since it does not
+ * require per-frame retry statistics.  Note however that since h/w does
+ * not provide per-frame stats, we can't do per-node rate adaptation and
+ * thus automatic rate adaptation is only enabled in STA operating mode.
+ */
+
+#define URAL_AMRR_MIN_SUCCESS_THRESHOLD	 1
+#define URAL_AMRR_MAX_SUCCESS_THRESHOLD	10
+
+#define is_success(amrr)	\
+	((amrr)->retrycnt < (amrr)->txcnt / 10)
+#define is_failure(amrr)	\
+	((amrr)->retrycnt > (amrr)->txcnt / 3)
+#define is_enough(amrr)		\
+	((amrr)->txcnt > 10)
+#define is_min_rate(ni)		\
+	((ni)->ni_txrate == 0)
+#define is_max_rate(ni)		\
+	((ni)->ni_txrate == (ni)->ni_rates.rs_nrates - 1)
+#define increase_rate(ni)	\
+	((ni)->ni_txrate++)
+#define decrease_rate(ni)	\
+	((ni)->ni_txrate--)
+#define reset_cnt(amrr)		\
+	do { (amrr)->txcnt = (amrr)->retrycnt = 0; } while (0)
+Static void
+ural_ratectl(struct ural_amrr *amrr, struct ieee80211_node *ni)
+{
+	int need_change = 0;
+
+	if (is_success(amrr) && is_enough(amrr)) {
+		amrr->success++;
+		if (amrr->success >= amrr->success_threshold &&
+		    !is_max_rate(ni)) {
+			amrr->recovery = 1;
+			amrr->success = 0;
+			increase_rate(ni);
+			need_change = 1;
+		} else {
+			amrr->recovery = 0;
+		}
+	} else if (is_failure(amrr)) {
+		amrr->success = 0;
+		if (!is_min_rate(ni)) {
+			if (amrr->recovery) {
+				amrr->success_threshold *= 2;
+				if (amrr->success_threshold >
+				    URAL_AMRR_MAX_SUCCESS_THRESHOLD)
+					amrr->success_threshold =
+					    URAL_AMRR_MAX_SUCCESS_THRESHOLD;
+			} else {
+				amrr->success_threshold =
+				    URAL_AMRR_MIN_SUCCESS_THRESHOLD;
+			}
+			decrease_rate(ni);
+			need_change = 1;
+		}
+		amrr->recovery = 0;	/* original paper was incorrect */
+	}
+
+	if (is_enough(amrr) || need_change)
+		reset_cnt(amrr);
+}
+
 DRIVER_MODULE(ural, uhub, ural_driver, ural_devclass, usbd_driver_load, 0);

==== //depot/projects/wifi/sys/dev/wi/if_wi.c#20 (text+ko) ====

@@ -62,7 +62,7 @@
  */
 
 #include <sys/cdefs.h>
-__FBSDID("$FreeBSD: src/sys/dev/wi/if_wi.c,v 1.193 2005/10/02 04:29:08 avatar Exp $");
+__FBSDID("$FreeBSD: src/sys/dev/wi/if_wi.c,v 1.194 2005/11/11 16:04:56 ru Exp $");
 
 #define WI_HERMES_AUTOINC_WAR	/* Work around data write autoinc bug. */
 #define WI_HERMES_STATS_WAR	/* Work around stats counter bug. */
@@ -659,8 +659,6 @@
 	struct wi_joinreq join;
 	int i;
 	int error = 0, wasenabled;
-	struct ifaddr *ifa;
-	struct sockaddr_dl *sdl;
 	WI_LOCK_DECL();
 
 	WI_LOCK(sc);
@@ -724,9 +722,7 @@
 	wi_write_ssid(sc, WI_RID_OWN_SSID, ic->ic_des_ssid[0].ssid,
 		ic->ic_des_ssid[0].len);
 
-	ifa = ifaddr_byindex(ifp->if_index);
-	sdl = (struct sockaddr_dl *) ifa->ifa_addr;
-	IEEE80211_ADDR_COPY(ic->ic_myaddr, LLADDR(sdl));
+	IEEE80211_ADDR_COPY(ic->ic_myaddr, IF_LLADDR(ifp));
 	wi_write_rid(sc, WI_RID_MAC_NODE, ic->ic_myaddr, IEEE80211_ADDR_LEN);
 
 	if (ic->ic_caps & IEEE80211_C_PMGT)
@@ -1578,7 +1574,7 @@
 	/*
 	 * Send frame up for processing.
 	 */
-	ieee80211_input(ic, m, ni, rssi, rstamp);
+	ieee80211_input(ic, m, ni, rssi, -95/*XXXXwi_rx_silence?*/, rstamp);
 	/*
 	 * The frame may have caused the node to be marked for
 	 * reclamation (e.g. in response to a DEAUTH message)



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