From owner-freebsd-net@FreeBSD.ORG Wed Oct 3 21:45:52 2007 Return-Path: Delivered-To: freebsd-net@FreeBSD.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2001:4f8:fff6::34]) by hub.freebsd.org (Postfix) with ESMTP id 568BB16A469 for ; Wed, 3 Oct 2007 21:45:52 +0000 (UTC) (envelope-from delphij@delphij.net) Received: from tarsier.geekcn.org (tarsier.geekcn.org [210.51.165.229]) by mx1.freebsd.org (Postfix) with ESMTP id A889F13C467 for ; Wed, 3 Oct 2007 21:45:50 +0000 (UTC) (envelope-from delphij@delphij.net) Received: from localhost (tarsier.geekcn.org [210.51.165.229]) by tarsier.geekcn.org (Postfix) with ESMTP id A9E44EBB3A5; Thu, 4 Oct 2007 05:45:49 +0800 (CST) X-Virus-Scanned: amavisd-new at geekcn.org Received: from tarsier.geekcn.org ([210.51.165.229]) by localhost (mail.geekcn.org [210.51.165.229]) (amavisd-new, port 10024) with ESMTP id kj0GE3O9uwQE; Thu, 4 Oct 2007 05:45:44 +0800 (CST) Received: from LI-Xins-MacBook.local (71.5.7.139.ptr.us.xo.net [71.5.7.139]) (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) (No client certificate requested) by tarsier.geekcn.org (Postfix) with ESMTP id 97EA8EBB334; Thu, 4 Oct 2007 05:45:42 +0800 (CST) DomainKey-Signature: a=rsa-sha1; s=default; d=delphij.net; c=nofws; q=dns; h=message-id:date:from:reply-to:organization:user-agent: mime-version:to:cc:subject:references:in-reply-to: x-enigmail-version:openpgp:content-type; b=JP5eh8UXLLAXLEyPAO8Ito6/Kil8F1B3kjyWmEzIT1Q9kxAgL5andxDDR9qjfwqu0 FdYmtuzURM3hkKVVHbJVw== Message-ID: <47040D83.9010706@delphij.net> Date: Wed, 03 Oct 2007 14:45:39 -0700 From: LI Xin Organization: The FreeBSD Project User-Agent: Thunderbird 2.0.0.6 (Macintosh/20070728) MIME-Version: 1.0 To: Vladimir Ivanov References: <46B07931.3080300@yandex-team.ru> <2a41acea0708010923m7b21095ajc2ee84c37e0d5354@mail.gmail.com> <470280F6.9070009@yandex-team.ru> <20071003111737.U14276@delplex.bde.org> <47037246.2070400@yandex-team.ru> In-Reply-To: <47037246.2070400@yandex-team.ru> X-Enigmail-Version: 0.95.3 OpenPGP: url=http://www.delphij.net/delphij.asc Content-Type: multipart/signed; micalg=pgp-sha512; protocol="application/pgp-signature"; boundary="------------enig19778564B8230D4BDB26AB74" Cc: "freebsd-net@freebsd.org" , Jack Vogel Subject: Re: SMPable version of EM driver X-BeenThere: freebsd-net@freebsd.org X-Mailman-Version: 2.1.5 Precedence: list Reply-To: d@delphij.net List-Id: Networking and TCP/IP with FreeBSD List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Wed, 03 Oct 2007 21:45:52 -0000 This is an OpenPGP/MIME signed message (RFC 2440 and 3156) --------------enig19778564B8230D4BDB26AB74 Content-Type: multipart/mixed; boundary="------------060708020602090500090901" This is a multi-part message in MIME format. --------------060708020602090500090901 Content-Type: text/plain; charset=ISO-8859-1 Content-Transfer-Encoding: quoted-printable Hi Valdimir and Jack, I have ported Valdimir's 1.16 revision of their driver to -CURRENT code as of today, but I don't have a box that is suitable for testing right now as I just moved, and the server I used to do FreeBSD coding stuff is located several thousand miles away :-) I hope that this would be useful for adoption to the official em(4) driver, and thanks Valdimir and Yandex for their work on this. Cheers, --=20 Xin LI http://www.delphij.net/ FreeBSD - The Power to Serve! --------------060708020602090500090901 Content-Type: text/plain; x-mac-type="0"; x-mac-creator="0"; name="em.diff" Content-Transfer-Encoding: quoted-printable Content-Disposition: inline; filename="em.diff" Index: e1000_defines.h =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/em/e1000_defines.h,v retrieving revision 1.3 diff -u -p -r1.3 e1000_defines.h --- e1000_defines.h 16 May 2007 00:14:23 -0000 1.3 +++ e1000_defines.h 3 Oct 2007 21:36:07 -0000 @@ -746,7 +746,6 @@ */ #define IMS_ENABLE_MASK ( \ E1000_IMS_RXT0 | \ - E1000_IMS_TXDW | \ E1000_IMS_RXDMT0 | \ E1000_IMS_RXSEQ | \ E1000_IMS_LSC) Index: if_em.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/em/if_em.c,v retrieving revision 1.184 diff -u -p -r1.184 if_em.c --- if_em.c 10 Sep 2007 21:50:40 -0000 1.184 +++ if_em.c 3 Oct 2007 21:41:12 -0000 @@ -240,14 +240,16 @@ static void em_initialize_transmit_unit( static int em_setup_receive_structures(struct adapter *); static void em_initialize_receive_unit(struct adapter *); static void em_enable_intr(struct adapter *); +static void em_enable_intr_rx(struct adapter *); static void em_disable_intr(struct adapter *); +static void em_disable_intr_rx(struct adapter *); static void em_free_transmit_structures(struct adapter *); static void em_free_receive_structures(struct adapter *); static void em_update_stats_counters(struct adapter *); static void em_txeof(struct adapter *); static int em_allocate_receive_structures(struct adapter *); static int em_allocate_transmit_structures(struct adapter *); -static int em_rxeof(struct adapter *, int); +static int em_rxeof(struct adapter *, int, int); #ifndef __NO_STRICT_ALIGNMENT static int em_fixup_rx(struct adapter *); #endif @@ -292,14 +294,19 @@ static void em_get_hw_control(struct static void em_release_hw_control(struct adapter *); static void em_enable_wakeup(device_t); =20 + +/* + * Fast interrupt handler and legacy ithread/polling modes are + * mutually exclusive. + */ #ifdef DEVICE_POLLING static poll_handler_t em_poll; static void em_intr(void *); #else +static void em_add_int_rx_kthread_priority(struct adapter *, const char = *, + const char *, int *, int); static int em_intr_fast(void *); -static void em_add_rx_process_limit(struct adapter *, const char *, - const char *, int *, int); -static void em_handle_rxtx(void *context, int pending); +static void em_kthread_rx(void *arg); static void em_handle_link(void *context, int pending); #endif =20 @@ -351,9 +358,8 @@ TUNABLE_INT("hw.em.rxd", &em_rxd); TUNABLE_INT("hw.em.txd", &em_txd); TUNABLE_INT("hw.em.smart_pwr_down", &em_smart_pwr_down); #ifndef DEVICE_POLLING -/* How many packets rxeof tries to clean at a time */ -static int em_rx_process_limit =3D 100; -TUNABLE_INT("hw.em.rx_process_limit", &em_rx_process_limit); +static int em_rx_kthread_priority =3D PRI_MAX_KERN; +TUNABLE_INT("hw.em.rx_kthread_priority", &em_rx_kthread_priority); #endif /* Global used in WOL setup with multiport cards */ static int global_quad_port_a =3D 0; @@ -370,7 +376,7 @@ static int global_quad_port_a =3D 0; static int em_probe(device_t dev) { - char adapter_name[60]; + char adapter_name[1024]; /* XXX why? */ uint16_t pci_vendor_id =3D 0; uint16_t pci_device_id =3D 0; uint16_t pci_subvendor_id =3D 0; @@ -431,7 +437,8 @@ em_attach(device_t dev) =20 adapter =3D device_get_softc(dev); adapter->dev =3D adapter->osdep.dev =3D dev; - EM_LOCK_INIT(adapter, device_get_nameunit(dev)); + EM_RXLOCK_INIT(adapter, device_get_nameunit(dev)); + EM_TXLOCK_INIT(adapter, device_get_nameunit(dev)); =20 /* SYSCTL stuff */ SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), @@ -444,8 +451,8 @@ em_attach(device_t dev) OID_AUTO, "stats", CTLTYPE_INT|CTLFLAG_RW, adapter, 0, em_sysctl_stats, "I", "Statistics"); =20 - callout_init_mtx(&adapter->timer, &adapter->mtx, 0); - callout_init_mtx(&adapter->tx_fifo_timer, &adapter->mtx, 0); + callout_init_mtx(&adapter->timer, &adapter->txmtx, 0); + callout_init_mtx(&adapter->tx_fifo_timer, &adapter->txmtx, 0); =20 /* Determine hardware and mac info */ em_identify_hardware(adapter); @@ -506,10 +513,10 @@ em_attach(device_t dev) } =20 #ifndef DEVICE_POLLING - /* Sysctls for limiting the amount of work done in the taskqueue */ - em_add_rx_process_limit(adapter, "rx_processing_limit", - "max number of rx packets to process", &adapter->rx_process_limit, - em_rx_process_limit); + /* Sysctls for set the RX kthreads' priority */ + em_add_int_rx_kthread_priority(adapter, "rx_kthread_priority", + "priority of RX handler kthread", &adapter->rx_kthread_priority, + em_rx_kthread_priority); #endif =20 /* @@ -517,25 +524,14 @@ em_attach(device_t dev) * must not exceed hardware maximum, and must be multiple * of E1000_DBA_ALIGN. */ - if (((em_txd * sizeof(struct e1000_tx_desc)) % EM_DBA_ALIGN) !=3D 0 || - (adapter->hw.mac.type >=3D e1000_82544 && em_txd > EM_MAX_TXD) || - (adapter->hw.mac.type < e1000_82544 && em_txd > EM_MAX_TXD_82543) |= | - (em_txd < EM_MIN_TXD)) { - device_printf(dev, "Using %d TX descriptors instead of %d!\n", - EM_DEFAULT_TXD, em_txd); - adapter->num_tx_desc =3D EM_DEFAULT_TXD; - } else - adapter->num_tx_desc =3D em_txd; - if (((em_rxd * sizeof(struct e1000_rx_desc)) % EM_DBA_ALIGN) !=3D 0 || - (adapter->hw.mac.type >=3D e1000_82544 && em_rxd > EM_MAX_RXD) || - (adapter->hw.mac.type < e1000_82544 && em_rxd > EM_MAX_RXD_82543) |= | - (em_rxd < EM_MIN_RXD)) { - device_printf(dev, "Using %d RX descriptors instead of %d!\n", - EM_DEFAULT_RXD, em_rxd); - adapter->num_rx_desc =3D EM_DEFAULT_RXD; - } else - adapter->num_rx_desc =3D em_rxd; - + if (adapter->hw.mac.type >=3D e1000_82544) { + adapter->num_tx_desc =3D EM_MAX_TXD; + adapter->num_rx_desc =3D EM_MAX_RXD; + } else { + adapter->num_tx_desc =3D EM_MAX_TXD_82543; + adapter->num_rx_desc =3D EM_MAX_RXD_82543; + } +=09 adapter->hw.mac.autoneg =3D DO_AUTO_NEG; adapter->hw.phy.wait_for_link =3D FALSE; adapter->hw.phy.autoneg_advertised =3D AUTONEG_ADV_DEFAULT; @@ -736,7 +732,9 @@ err_tx_desc: err_pci: em_free_intr(adapter); em_free_pci_resources(adapter); - EM_LOCK_DESTROY(adapter); + /* XXX */ + EM_TXLOCK_DESTROY(adapter); + EM_RXLOCK_DESTROY(adapter); =20 return (error); } @@ -766,7 +764,8 @@ em_detach(device_t dev) =20 em_disable_intr(adapter); em_free_intr(adapter); - EM_LOCK(adapter); + EM_RXLOCK(adapter); + EM_TXLOCK(adapter); adapter->in_detach =3D 1; em_stop(adapter); e1000_phy_hw_reset(&adapter->hw); @@ -785,7 +784,8 @@ em_detach(device_t dev) em_enable_wakeup(dev); } =20 - EM_UNLOCK(adapter); + EM_TXUNLOCK(adapter); + EM_RXUNLOCK(adapter); ether_ifdetach(adapter->ifp); =20 callout_drain(&adapter->timer); @@ -811,7 +811,8 @@ em_detach(device_t dev) adapter->rx_desc_base =3D NULL; } =20 - EM_LOCK_DESTROY(adapter); + EM_TXLOCK_DESTROY(adapter); + EM_RXLOCK_DESTROY(adapter); =20 return (0); } @@ -836,7 +837,8 @@ em_suspend(device_t dev) { struct adapter *adapter =3D device_get_softc(dev); =20 - EM_LOCK(adapter); + EM_RXLOCK(adapter); + EM_TXLOCK(adapter); em_stop(adapter); =20 em_release_manageability(adapter); @@ -853,7 +855,8 @@ em_suspend(device_t dev) em_enable_wakeup(dev); } =20 - EM_UNLOCK(adapter); + EM_TXUNLOCK(adapter); + EM_RXUNLOCK(adapter); =20 return bus_generic_suspend(dev); } @@ -864,7 +867,8 @@ em_resume(device_t dev) struct adapter *adapter =3D device_get_softc(dev); struct ifnet *ifp =3D adapter->ifp; =20 - EM_LOCK(adapter); + EM_RXLOCK(adapter); + EM_TXLOCK(adapter); em_init_locked(adapter); em_init_manageability(adapter); =20 @@ -872,7 +876,8 @@ em_resume(device_t dev) (ifp->if_drv_flags & IFF_DRV_RUNNING)) em_start_locked(ifp); =20 - EM_UNLOCK(adapter); + EM_TXUNLOCK(adapter); + EM_RXUNLOCK(adapter); =20 return bus_generic_resume(dev); } @@ -894,7 +899,7 @@ em_start_locked(struct ifnet *ifp) struct adapter *adapter =3D ifp->if_softc; struct mbuf *m_head; =20 - EM_LOCK_ASSERT(adapter); + EM_TXLOCK_ASSERT(adapter); =20 if ((ifp->if_drv_flags & (IFF_DRV_RUNNING|IFF_DRV_OACTIVE)) !=3D IFF_DRV_RUNNING) @@ -906,7 +911,7 @@ em_start_locked(struct ifnet *ifp) =20 IFQ_DRV_DEQUEUE(&ifp->if_snd, m_head); if (m_head =3D=3D NULL) - break; + continue; /* * Encapsulation can modify our pointer, and or make it * NULL on failure. In that event, we can't requeue. @@ -926,7 +931,12 @@ em_start_locked(struct ifnet *ifp) ETHER_BPF_MTAP(ifp, m_head); =20 /* Set timeout in case hardware has problems transmitting. */ - adapter->watchdog_timer =3D EM_TX_TIMEOUT; + adapter->tx_counter ++; + } + + if (adapter->num_tx_desc - adapter->num_tx_desc_avail > 32) { + /* it's time to clean a little bit */ + em_txeof (adapter); } } =20 @@ -935,10 +945,10 @@ em_start(struct ifnet *ifp) { struct adapter *adapter =3D ifp->if_softc; =20 - EM_LOCK(adapter); + EM_TXLOCK(adapter); if (ifp->if_drv_flags & IFF_DRV_RUNNING) em_start_locked(ifp); - EM_UNLOCK(adapter); + EM_TXUNLOCK(adapter); } =20 /********************************************************************* @@ -973,9 +983,11 @@ em_ioctl(struct ifnet *ifp, u_long comma */ ifp->if_flags |=3D IFF_UP; if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) { - EM_LOCK(adapter); + EM_RXLOCK(adapter); + EM_TXLOCK(adapter); em_init_locked(adapter); - EM_UNLOCK(adapter); + EM_TXUNLOCK(adapter); + EM_RXUNLOCK(adapter); } arp_ifinit(ifp, ifa); } else @@ -988,7 +1000,8 @@ em_ioctl(struct ifnet *ifp, u_long comma =20 IOCTL_DEBUGOUT("ioctl rcv'd: SIOCSIFMTU (Set Interface MTU)"); =20 - EM_LOCK(adapter); + EM_RXLOCK(adapter); + EM_TXLOCK(adapter); switch (adapter->hw.mac.type) { case e1000_82573: /* @@ -1019,7 +1032,8 @@ em_ioctl(struct ifnet *ifp, u_long comma } if (ifr->ifr_mtu > max_frame_size - ETHER_HDR_LEN - ETHER_CRC_LEN) { - EM_UNLOCK(adapter); + EM_TXUNLOCK(adapter); + EM_RXUNLOCK(adapter); error =3D EINVAL; break; } @@ -1028,13 +1042,15 @@ em_ioctl(struct ifnet *ifp, u_long comma adapter->hw.mac.max_frame_size =3D ifp->if_mtu + ETHER_HDR_LEN + ETHER_CRC_LEN; em_init_locked(adapter); - EM_UNLOCK(adapter); + EM_TXUNLOCK(adapter); + EM_RXUNLOCK(adapter); break; } case SIOCSIFFLAGS: IOCTL_DEBUGOUT("ioctl rcv'd:\ SIOCSIFFLAGS (Set Interface Flags)"); - EM_LOCK(adapter); + EM_RXLOCK(adapter); + EM_TXLOCK(adapter); if (ifp->if_flags & IFF_UP) { if ((ifp->if_drv_flags & IFF_DRV_RUNNING)) { if ((ifp->if_flags ^ adapter->if_flags) & @@ -1048,13 +1064,15 @@ em_ioctl(struct ifnet *ifp, u_long comma if (ifp->if_drv_flags & IFF_DRV_RUNNING) em_stop(adapter); adapter->if_flags =3D ifp->if_flags; - EM_UNLOCK(adapter); + EM_TXUNLOCK(adapter); + EM_RXUNLOCK(adapter); break; case SIOCADDMULTI: case SIOCDELMULTI: IOCTL_DEBUGOUT("ioctl rcv'd: SIOC(ADD|DEL)MULTI"); if (ifp->if_drv_flags & IFF_DRV_RUNNING) { - EM_LOCK(adapter); + EM_RXLOCK(adapter); + EM_TXLOCK(adapter); em_disable_intr(adapter); em_set_multi(adapter); if (adapter->hw.mac.type =3D=3D e1000_82542 &&=20 @@ -1065,19 +1083,23 @@ em_ioctl(struct ifnet *ifp, u_long comma if (!(ifp->if_capenable & IFCAP_POLLING)) #endif em_enable_intr(adapter); - EM_UNLOCK(adapter); + EM_TXUNLOCK(adapter); + EM_RXUNLOCK(adapter); } break; case SIOCSIFMEDIA: /* Check SOL/IDER usage */ - EM_LOCK(adapter); + EM_RXLOCK(adapter); + EM_TXLOCK(adapter); if (e1000_check_reset_block(&adapter->hw)) { - EM_UNLOCK(adapter); + EM_TXUNLOCK(adapter); + EM_RXUNLOCK(adapter); device_printf(adapter->dev, "Media change is" " blocked due to SOL/IDER session.\n"); break; } - EM_UNLOCK(adapter); + EM_TXUNLOCK(adapter); + EM_RXUNLOCK(adapter); case SIOCGIFMEDIA: IOCTL_DEBUGOUT("ioctl rcv'd: \ SIOCxIFMEDIA (Get/Set Interface Media)"); @@ -1096,17 +1118,21 @@ em_ioctl(struct ifnet *ifp, u_long comma error =3D ether_poll_register(em_poll, ifp); if (error) return (error); - EM_LOCK(adapter); + EM_RXLOCK(adapter); + EM_TXLOCK(adapter); em_disable_intr(adapter); ifp->if_capenable |=3D IFCAP_POLLING; - EM_UNLOCK(adapter); + EM_TXUNLOCK(adapter); + EM_RXUNLOCK(adapter); } else { error =3D ether_poll_deregister(ifp); /* Enable interrupt even in error case */ - EM_LOCK(adapter); + EM_RXLOCK(adapter); + EM_TXLOCK(adapter); em_enable_intr(adapter); ifp->if_capenable &=3D ~IFCAP_POLLING; - EM_UNLOCK(adapter); + EM_TXUNLOCK(adapter); + EM_RXUNLOCK(adapter); } } #endif @@ -1149,29 +1175,49 @@ static void em_watchdog(struct adapter *adapter) { =20 - EM_LOCK_ASSERT(adapter); + EM_TXLOCK_ASSERT(adapter); =20 - /* - ** The timer is set to 5 every time start queues a packet. - ** Then txeof keeps resetting to 5 as long as it cleans at - ** least one descriptor. - ** Finally, anytime all descriptors are clean the timer is - ** set to 0. - */ - if (adapter->watchdog_timer =3D=3D 0 || --adapter->watchdog_timer) - return; + if (E1000_READ_REG(&adapter->hw, E1000_TDH) =3D=3D + E1000_READ_REG(&adapter->hw, E1000_TDT)) { + /* TX queue is clean. Nothing to wait */ + adapter->tx_counter_watchdog_mark =3D 0; + } =20 /* If we are in this routine because of pause frames, then * don't reset the hardware. */ if (E1000_READ_REG(&adapter->hw, E1000_STATUS) & E1000_STATUS_TXOFF) { - adapter->watchdog_timer =3D EM_TX_TIMEOUT; + /* XOFF received */ + adapter->tx_counter_watchdog_mark =3D 0; + return; + } + + if (!adapter->tx_counter_watchdog_mark) { + /* watchdog isn't started yet, let's do it */ + adapter->tx_counter_watchdog_mark =3D adapter->tx_counter; + adapter->tx_tdh_watchdog_mark =3D E1000_READ_REG(&adapter->hw, E1000_T= DH); + return; + } + + if (adapter->tx_counter - adapter->tx_counter_watchdog_mark >=3D adapte= r->num_tx_desc) { + /* TX ring has been wrapped, clean watchdog condition */ + adapter->tx_counter_watchdog_mark =3D 0; return; } =20 - if (e1000_check_for_link(&adapter->hw) =3D=3D 0) + if (adapter->tx_tdh_watchdog_mark !=3D E1000_READ_REG(&adapter->hw, E10= 00_TDH)) { + /* Something were sent */ + adapter->tx_counter_watchdog_mark =3D 0; + return; + } + + if (e1000_check_for_link(&adapter->hw) =3D=3D 0) { device_printf(adapter->dev, "watchdog timeout -- resetting\n"); + em_print_hw_stats(adapter); + em_print_debug_info(adapter); + } + adapter->ifp->if_drv_flags &=3D ~IFF_DRV_RUNNING; adapter->watchdog_events++; =20 @@ -1198,7 +1244,8 @@ em_init_locked(struct adapter *adapter) =20 INIT_DEBUGOUT("em_init: begin"); =20 - EM_LOCK_ASSERT(adapter); + EM_RXLOCK_ASSERT(adapter); + EM_TXLOCK_ASSERT(adapter); =20 em_stop(adapter); =20 @@ -1337,9 +1384,11 @@ em_init(void *arg) { struct adapter *adapter =3D arg; =20 - EM_LOCK(adapter); + EM_RXLOCK(adapter); + EM_TXLOCK(adapter); em_init_locked(adapter); - EM_UNLOCK(adapter); + EM_TXUNLOCK(adapter); + EM_RXUNLOCK(adapter); } =20 =20 @@ -1355,9 +1404,11 @@ em_poll(struct ifnet *ifp, enum poll_cmd struct adapter *adapter =3D ifp->if_softc; uint32_t reg_icr; =20 - EM_LOCK(adapter); + EM_RXLOCK(adapter); + EM_TXLOCK(adapter); if ((ifp->if_drv_flags & IFF_DRV_RUNNING) =3D=3D 0) { - EM_UNLOCK(adapter); + EM_TXUNLOCK(adapter); + EM_RXUNLOCK(adapter); return; } =20 @@ -1372,12 +1423,13 @@ em_poll(struct ifnet *ifp, enum poll_cmd em_local_timer, adapter); } } - em_rxeof(adapter, count); + em_rxeof(adapter, count, 0); em_txeof(adapter); =20 if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd)) em_start_locked(ifp); - EM_UNLOCK(adapter); + EM_TXUNLOCK(adapter); + EM_RXUNLOCK(adapter); } =20 /********************************************************************* @@ -1393,11 +1445,11 @@ em_intr(void *arg) struct ifnet *ifp; uint32_t reg_icr; =20 - EM_LOCK(adapter); + /* XXX EM_LOCK(adapter); */ ifp =3D adapter->ifp; =20 if (ifp->if_capenable & IFCAP_POLLING) { - EM_UNLOCK(adapter); + /* EM_UNLOCK(adapter); */ return; } =20 @@ -1419,29 +1471,35 @@ em_intr(void *arg) if (reg_icr =3D=3D 0xffffffff) break; =20 - if (ifp->if_drv_flags & IFF_DRV_RUNNING) { - em_rxeof(adapter, -1); - em_txeof(adapter); - } - /* Link status change */ if (reg_icr & (E1000_ICR_RXSEQ | E1000_ICR_LSC)) { + EM_RXLOCK(adapter); + EM_TXLOCK(adapter); callout_stop(&adapter->timer); adapter->hw.mac.get_link_status =3D 1; e1000_check_for_link(&adapter->hw); em_update_link_status(adapter); callout_reset(&adapter->timer, hz, em_local_timer, adapter); + EM_TXUNLOCK(adapter); + EM_RXUNLOCK(adapter); + } + if (ifp->if_drv_flags & IFF_DRV_RUNNING) { + if (reg_icr & (E1000_ICR_RXDMT0|E1000_ICR_RXO|E1000_ICR_RXT0)) { + EM_RXLOCK(adapter); + em_rxeof(adapter, -1,0); + EM_RXUNLOCK(adapter); + } + if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd)) { + EM_TXLOCK(adapter); + em_start_locked(ifp); + EM_TXUNLOCK(adapter); + } } =20 if (reg_icr & E1000_ICR_RXO) adapter->rx_overruns++; } - - if (ifp->if_drv_flags & IFF_DRV_RUNNING && - !IFQ_DRV_IS_EMPTY(&ifp->if_snd)) - em_start_locked(ifp); - EM_UNLOCK(adapter); } =20 #else /* if not DEVICE_POLLING, then fast interrupt routines only */ @@ -1454,9 +1512,11 @@ em_handle_link(void *context, int pendin =20 ifp =3D adapter->ifp; =20 - EM_LOCK(adapter); + EM_RXLOCK(adapter); + EM_TXLOCK(adapter); if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) { - EM_UNLOCK(adapter); + EM_TXUNLOCK(adapter); + EM_RXUNLOCK(adapter); return; } =20 @@ -1465,33 +1525,37 @@ em_handle_link(void *context, int pendin e1000_check_for_link(&adapter->hw); em_update_link_status(adapter); callout_reset(&adapter->timer, hz, em_local_timer, adapter); - EM_UNLOCK(adapter); + + wakeup (&adapter->rxmtx); + wakeup (&adapter->txmtx); + EM_TXUNLOCK(adapter); + EM_RXUNLOCK(adapter); } =20 static void -em_handle_rxtx(void *context, int pending) +em_kthread_rx(void *arg) { - struct adapter *adapter =3D context; - struct ifnet *ifp; + struct adapter *adapter =3D arg; + struct ifnet *ifp =3D adapter->ifp; + int myKthreadNo =3D 0; =20 - ifp =3D adapter->ifp; + EM_RXLOCK(adapter); + myKthreadNo =3D adapter -> rxKthreadNo ++; + adapter -> rxIpBeingProcessed[myKthreadNo] =3D 0; + adapter -> waitedBy[myKthreadNo] =3D 0; + EM_RXUNLOCK(adapter); =20 - /* - * TODO: - * It should be possible to run the tx clean loop without the lock. - */ - if (ifp->if_drv_flags & IFF_DRV_RUNNING) { - if (em_rxeof(adapter, adapter->rx_process_limit) !=3D 0) - taskqueue_enqueue(adapter->tq, &adapter->rxtx_task); - EM_LOCK(adapter); - em_txeof(adapter); - - if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd)) - em_start_locked(ifp); - EM_UNLOCK(adapter); + while (!adapter->rx_shutdown_flag) { + tsleep(&adapter->rxmtx, adapter->rx_kthread_priority, "em_rx", hz); + if (ifp->if_drv_flags & IFF_DRV_RUNNING) { + EM_RXLOCK(adapter); + em_rxeof(adapter,-1, myKthreadNo); + EM_RXUNLOCK(adapter); + } + em_enable_intr_rx(adapter); } =20 - em_enable_intr(adapter); + kthread_exit(0); } =20 /********************************************************************* @@ -1526,13 +1590,17 @@ em_intr_fast(void *arg) (reg_icr & E1000_ICR_INT_ASSERTED) =3D=3D 0) return (FILTER_STRAY); =20 - /* - * Mask interrupts until the taskqueue is finished running. This is - * cheap, just assume that it is needed. This also works around the - * MSI message reordering errata on certain systems. - */ - em_disable_intr(adapter); - taskqueue_enqueue(adapter->tq, &adapter->rxtx_task); + if (ifp->if_drv_flags & IFF_DRV_RUNNING) { + if (reg_icr & (E1000_ICR_RXDMT0|E1000_ICR_RXO|E1000_ICR_RXT0)) { + /* + * Mask interrupts until the taskqueue is finished running. This is + * cheap, just assume that it is needed. This also works around the + * MSI message reordering errata on certain systems. + */ + em_disable_intr_rx (adapter); + wakeup (&adapter->rxmtx); + } + } =20 /* Link status change */ if (reg_icr & (E1000_ICR_RXSEQ | E1000_ICR_LSC)) @@ -1560,7 +1628,8 @@ em_media_status(struct ifnet *ifp, struc =20 INIT_DEBUGOUT("em_media_status: begin"); =20 - EM_LOCK(adapter); + EM_RXLOCK(adapter); + EM_TXLOCK(adapter); e1000_check_for_link(&adapter->hw); em_update_link_status(adapter); =20 @@ -1568,7 +1637,8 @@ em_media_status(struct ifnet *ifp, struc ifmr->ifm_active =3D IFM_ETHER; =20 if (!adapter->link_active) { - EM_UNLOCK(adapter); + EM_TXUNLOCK(adapter); + EM_RXUNLOCK(adapter); return; } =20 @@ -1596,7 +1666,8 @@ em_media_status(struct ifnet *ifp, struc else ifmr->ifm_active |=3D IFM_HDX; } - EM_UNLOCK(adapter); + EM_TXUNLOCK(adapter); + EM_RXUNLOCK(adapter); } =20 /********************************************************************* @@ -1618,7 +1689,8 @@ em_media_change(struct ifnet *ifp) if (IFM_TYPE(ifm->ifm_media) !=3D IFM_ETHER) return (EINVAL); =20 - EM_LOCK(adapter); + EM_RXLOCK(adapter); + EM_TXLOCK(adapter); switch (IFM_SUBTYPE(ifm->ifm_media)) { case IFM_AUTO: adapter->hw.mac.autoneg =3D DO_AUTO_NEG; @@ -1656,7 +1728,8 @@ em_media_change(struct ifnet *ifp) adapter->hw.phy.reset_disable =3D FALSE; =20 em_init_locked(adapter); - EM_UNLOCK(adapter); + EM_TXUNLOCK(adapter); + EM_RXUNLOCK(adapter); =20 return (0); } @@ -2130,7 +2203,8 @@ em_82547_move_tail(void *arg) uint16_t length =3D 0; boolean_t eop =3D 0; =20 - EM_LOCK_ASSERT(adapter); + EM_RXLOCK_ASSERT(adapter); + EM_TXLOCK_ASSERT(adapter); =20 hw_tdt =3D E1000_READ_REG(&adapter->hw, E1000_TDT); sw_tdt =3D adapter->next_avail_tx_desc; @@ -2337,7 +2411,8 @@ em_local_timer(void *arg) struct adapter *adapter =3D arg; struct ifnet *ifp =3D adapter->ifp; =20 - EM_LOCK_ASSERT(adapter); + EM_RXLOCK(adapter); + EM_TXLOCK(adapter); =20 e1000_check_for_link(&adapter->hw); em_update_link_status(adapter); @@ -2359,6 +2434,9 @@ em_local_timer(void *arg) em_watchdog(adapter); =20 callout_reset(&adapter->timer, hz, em_local_timer, adapter); + + EM_TXUNLOCK(adapter); + EM_RXUNLOCK(adapter); } =20 static void @@ -2419,7 +2497,8 @@ em_stop(void *arg) struct adapter *adapter =3D arg; struct ifnet *ifp =3D adapter->ifp; =20 - EM_LOCK_ASSERT(adapter); + EM_RXLOCK_ASSERT(adapter); + EM_TXLOCK_ASSERT(adapter); =20 INIT_DEBUGOUT("em_stop: begin"); =20 @@ -2606,19 +2685,22 @@ em_allocate_intr(struct adapter *adapter * Try allocating a fast interrupt and the associated deferred * processing contexts. */ - TASK_INIT(&adapter->rxtx_task, 0, em_handle_rxtx, adapter); - TASK_INIT(&adapter->link_task, 0, em_handle_link, adapter); - adapter->tq =3D taskqueue_create_fast("em_taskq", M_NOWAIT, - taskqueue_thread_enqueue, &adapter->tq); - taskqueue_start_threads(&adapter->tq, 1, PI_NET, "%s taskq", - device_get_nameunit(adapter->dev)); + TASK_INIT(&adapter->link_task, INTR_TYPE_NET | INTR_MPSAFE, em_handle_l= ink, adapter); + + adapter->rx_shutdown_flag=3DFALSE; + adapter->rxKthreadNo=3D0; + adapter->reorder_cnt=3D0; + for (int i =3D 0; i < RX_KTHREADS_NUM; i++) { + adapter->rx_kthreads_handles[i] =3D NULL; + kthread_create (em_kthread_rx, adapter, adapter->rx_kthreads_handles += i,=20 + INTR_TYPE_NET | INTR_FAST | INTR_MPSAFE, 0, "%s_rx_kthread_%d",device= _get_nameunit(dev),i); + } + if ((error =3D bus_setup_intr(dev, adapter->res_interrupt, - INTR_TYPE_NET, em_intr_fast, NULL, adapter, + INTR_TYPE_NET | INTR_FAST | INTR_MPSAFE, em_intr_fast, NULL, adapte= r, &adapter->int_handler_tag)) !=3D 0) { device_printf(dev, "Failed to register fast interrupt " "handler: %d\n", error); - taskqueue_free(adapter->tq); - adapter->tq =3D NULL; return (error); } #endif=20 @@ -2637,11 +2719,12 @@ em_free_intr(struct adapter *adapter) adapter->int_handler_tag); adapter->int_handler_tag =3D NULL; } - if (adapter->tq !=3D NULL) { - taskqueue_drain(adapter->tq, &adapter->rxtx_task); - taskqueue_drain(taskqueue_fast, &adapter->link_task); - taskqueue_free(adapter->tq); - adapter->tq =3D NULL; + taskqueue_drain(taskqueue_fast, &adapter->link_task); + + adapter->rx_shutdown_flag=3DTRUE; + for (int i =3D 0; i < RX_KTHREADS_NUM; i++) { + if (adapter->rx_kthreads_handles[i]) + tsleep(adapter->rx_kthreads_handles[i], 0, "RXSTOP", 3*hz); } } =20 @@ -3138,7 +3221,7 @@ em_initialize_transmit_unit(struct adapt E1000_WRITE_REG(&adapter->hw, E1000_TIDV, adapter->tx_int_delay.value);= if(adapter->hw.mac.type >=3D e1000_82540) E1000_WRITE_REG(&adapter->hw, E1000_TADV, - adapter->tx_abs_int_delay.value); + EM_USECS_TO_TICKS(adapter->tx_abs_int_delay.value)); =20 if ((adapter->hw.mac.type =3D=3D e1000_82571) || (adapter->hw.mac.type =3D=3D e1000_82572)) { @@ -3364,6 +3447,10 @@ em_transmit_checksum_setup(struct adapte =20 adapter->num_tx_desc_avail--; adapter->next_avail_tx_desc =3D curr_txd; + + adapter->tx_counter=3D0; + adapter->tx_counter_watchdog_mark=3D0; + adapter->tx_tdh_watchdog_mark=3D0; } =20 /********************************************************************** @@ -3736,7 +3823,7 @@ em_txeof(struct adapter *adapter) struct e1000_tx_desc *tx_desc, *eop_desc; struct ifnet *ifp =3D adapter->ifp; =20 - EM_LOCK_ASSERT(adapter); + EM_TXLOCK_ASSERT(adapter); =20 if (adapter->num_tx_desc_avail =3D=3D adapter->num_tx_desc) return; @@ -3809,15 +3896,8 @@ em_txeof(struct adapter *adapter) * If there are no pending descriptors, clear the timeout. Other= wise, * if some descriptors have been freed, restart the timeout. */ - if (num_avail > EM_TX_CLEANUP_THRESHOLD) { =20 + if (num_avail > EM_TX_CLEANUP_THRESHOLD) ifp->if_drv_flags &=3D ~IFF_DRV_OACTIVE; - /* All clean, turn off the timer */ - if (num_avail =3D=3D adapter->num_tx_desc) - adapter->watchdog_timer =3D 0; - /* Some cleaned, reset the timer */ - else if (num_avail !=3D adapter->num_tx_desc_avail) - adapter->watchdog_timer =3D EM_TX_TIMEOUT; - } adapter->num_tx_desc_avail =3D num_avail; return; } @@ -4144,7 +4224,7 @@ em_free_receive_structures(struct adapte * *********************************************************************/ static int -em_rxeof(struct adapter *adapter, int count) +em_rxeof(struct adapter *adapter, int count, int myKthreadNo) { struct ifnet *ifp; struct mbuf *mp; @@ -4298,15 +4378,57 @@ discard: if (++i =3D=3D adapter->num_rx_desc) i =3D 0; if (m !=3D NULL) { + struct ip *ip =3D mtod(m, struct ip *); + adapter->next_rx_desc_to_check =3D i; -#ifdef DEVICE_POLLING - EM_UNLOCK(adapter); - (*ifp->if_input)(ifp, m); - EM_LOCK(adapter); -#else - /* Already running unlocked */ + + /* + * Trick to avoid reorder: + * + * Don't allow change order of tcp packets + * in same session. In order to make this + * easier, we will not allow to process packets + * from one same source with more than one CPU. + */ + int hlen =3D ip->ip_hl << 2; + if (hlen >=3D sizeof(struct ip)) { /* minimum header length */ + adapter -> rxIpBeingProcessed[myKthreadNo]=3Dip->ip_src.s_addr; + + if (ip->ip_src.s_addr) + for (int k=3D0; k < RX_KTHREADS_NUM; k++) { + if ((adapter->rxIpBeingProcessed[k] =3D=3D ip->ip_src.s_addr)=20 + && !adapter->waitedBy[k]) { + /* + * Packet from the same IP is being processed + * by another thread, wait until that was done. + */ + adapter->reorder_cnt++;=20 + adapter->waitedBy[k] =3D myKthreadNo; + msleep(adapter->rxIpBeingProcessed+k, + &adapter->rxmtx, + adapter->rx_kthread_priority, + "RORDER", -1); + } + } + } else=20 + ip =3D NULL; + + EM_RXUNLOCK(adapter); + (*ifp->if_input)(ifp, m); -#endif + + EM_RXLOCK(adapter); + + adapter->rxIpBeingProcessed[myKthreadNo]=3D0; + + if (adapter->waitedBy[myKthreadNo]) { + /* + * Wakeup threads blocking on our packet process + * procedure due to the reorder prevention check + */ + wakeup(adapter->rxIpBeingProcessed+myKthreadNo); + adapter->waitedBy[myKthreadNo] =3D 0; + } i =3D adapter->next_rx_desc_to_check; } current_desc =3D &adapter->rx_desc_base[i]; @@ -4438,6 +4560,18 @@ em_disable_intr(struct adapter *adapter) E1000_WRITE_REG(&adapter->hw, E1000_IMC, 0xffffffff); } =20 +static void +em_enable_intr_rx(struct adapter *adapter) +{ + E1000_WRITE_REG(&adapter->hw, E1000_IMS, E1000_IMS_RXT0 | E1000_IMS_RXD= MT0 | E1000_IMS_RXO); +} + +static void +em_disable_intr_rx(struct adapter *adapter) +{ + E1000_WRITE_REG(&adapter->hw, E1000_IMC, E1000_IMS_RXT0 | E1000_IMS_RXD= MT0 | E1000_IMS_RXO); +} + /* * Bit of a misnomer, what this really means is * to enable OS management of the system... aka @@ -4878,6 +5012,8 @@ em_print_debug_info(struct adapter *adap adapter->dropped_pkts); device_printf(dev, "Driver tx dma failure in encap =3D %ld\n", adapter->no_tx_dma_setup); + device_printf(dev, "Packets pended due to reorder =3D %ld\n", + adapter->reorder_cnt); } =20 static void @@ -4996,7 +5132,8 @@ em_sysctl_int_delay(SYSCTL_HANDLER_ARGS) =20 adapter =3D info->adapter; =09 - EM_LOCK(adapter); + EM_RXLOCK(adapter); + EM_TXLOCK(adapter); regval =3D E1000_READ_OFFSET(&adapter->hw, info->offset); regval =3D (regval & ~0xffff) | (ticks & 0xffff); /* Handle a few special cases. */ @@ -5014,7 +5151,8 @@ em_sysctl_int_delay(SYSCTL_HANDLER_ARGS) break; } E1000_WRITE_OFFSET(&adapter->hw, info->offset, regval); - EM_UNLOCK(adapter); + EM_TXUNLOCK(adapter); + EM_RXUNLOCK(adapter); return (0); } =20 @@ -5034,7 +5172,7 @@ em_add_int_delay_sysctl(struct adapter * =20 #ifndef DEVICE_POLLING static void -em_add_rx_process_limit(struct adapter *adapter, const char *name, +em_add_int_rx_kthread_priority(struct adapter *adapter, const char *name= , const char *description, int *limit, int value) { *limit =3D value; Index: if_em.h =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/em/if_em.h,v retrieving revision 1.62 diff -u -p -r1.62 if_em.h --- if_em.h 10 Sep 2007 21:50:40 -0000 1.62 +++ if_em.h 3 Oct 2007 21:35:44 -0000 @@ -82,7 +82,7 @@ POSSIBILITY OF SUCH DAMAGE. * system is reporting dropped transmits, this value may be set too hi= gh * causing the driver to run out of available transmit descriptors. */ -#define EM_TIDV 64 +#define EM_TIDV 65535 =20 /* * EM_TADV - Transmit Absolute Interrupt Delay Value @@ -96,7 +96,7 @@ POSSIBILITY OF SUCH DAMAGE. * along with EM_TIDV, may improve traffic throughput in specific * network conditions. */ -#define EM_TADV 64 +#define EM_TADV 65535 =20 /* * EM_RDTR - Receive Interrupt Delay Timer (Packet Timer) @@ -130,12 +130,12 @@ POSSIBILITY OF SUCH DAMAGE. * along with EM_RDTR, may improve traffic throughput in specific netw= ork * conditions. */ -#define EM_RADV 64 +#define EM_RADV 977 =20 /* * This parameter controls the duration of transmit watchdog timer. */ -#define EM_TX_TIMEOUT 5 /* set to 5 seconds */ +#define EM_TX_TIMEOUT 2 /* set to 2 seconds */ =20 /* * This parameter controls when the driver calls the routine to reclaim @@ -270,15 +270,31 @@ struct adapter { struct ifmedia media; struct callout timer; struct callout tx_fifo_timer; - int watchdog_timer; + + unsigned tx_counter; + unsigned tx_counter_watchdog_mark; + unsigned tx_tdh_watchdog_mark; + int io_rid; int msi; int if_flags; - struct mtx mtx; int em_insert_vlan_header; +=09 + /* RX/TX locks */ + struct mtx rxmtx; + struct mtx txmtx; + struct task link_task; - struct task rxtx_task; - struct taskqueue *tq; /* private task queue */ + +#define RX_KTHREADS_NUM 2 + struct proc *rx_kthreads_handles[RX_KTHREADS_NUM]; + int rx_shutdown_flag; + + in_addr_t rxIpBeingProcessed[RX_KTHREADS_NUM]; + int waitedBy[RX_KTHREADS_NUM]; + int rxKthreadNo; + unsigned long reorder_cnt; + /* Management and WOL features */ int wol; int has_manage; @@ -333,7 +349,7 @@ struct adapter { uint32_t next_rx_desc_to_check; uint32_t rx_buffer_len; uint16_t num_rx_desc; - int rx_process_limit; + int rx_kthread_priority; struct em_buffer *rx_buffer_area; bus_dma_tag_t rxtag; bus_dmamap_t rx_sparemap; @@ -413,11 +429,20 @@ typedef struct _DESCRIPTOR_PAIR uint32_t elements; } DESC_ARRAY, *PDESC_ARRAY; =20 -#define EM_LOCK_INIT(_sc, _name) \ - mtx_init(&(_sc)->mtx, _name, MTX_NETWORK_LOCK, MTX_DEF) -#define EM_LOCK_DESTROY(_sc) mtx_destroy(&(_sc)->mtx) -#define EM_LOCK(_sc) mtx_lock(&(_sc)->mtx) -#define EM_UNLOCK(_sc) mtx_unlock(&(_sc)->mtx) -#define EM_LOCK_ASSERT(_sc) mtx_assert(&(_sc)->mtx, MA_OWNED) +#define EM_RXLOCK_INIT(_sc, _name) \ + mtx_init(&(_sc)->rxmtx, _name, MTX_NETWORK_LOCK, MTX_DEF) +#define EM_RXLOCK_DESTROY(_sc) mtx_destroy(&(_sc)->rxmtx) +#define EM_RXLOCK(_sc) mtx_lock(&(_sc)->rxmtx) +#define EM_RXTRYLOCK(_sc) mtx_trylock(&(_sc)->rxmtx) +#define EM_RXUNLOCK(_sc) mtx_unlock(&(_sc)->rxmtx) +#define EM_RXLOCK_ASSERT(_sc) mtx_assert(&(_sc)->rxmtx, MA_OWNED) + +#define EM_TXLOCK_INIT(_sc, _name) \ + mtx_init(&(_sc)->txmtx, _name, MTX_NETWORK_LOCK, MTX_DEF) +#define EM_TXLOCK_DESTROY(_sc) mtx_destroy(&(_sc)->txmtx) +#define EM_TXLOCK(_sc) mtx_lock(&(_sc)->txmtx) +#define EM_TXTRYLOCK(_sc) mtx_trylock(&(_sc)->txmtx) +#define EM_TXUNLOCK(_sc) mtx_unlock(&(_sc)->txmtx) +#define EM_TXLOCK_ASSERT(_sc) mtx_assert(&(_sc)->txmtx, MA_OWNED) =20 #endif /* _EM_H_DEFINED_ */ --------------060708020602090500090901-- --------------enig19778564B8230D4BDB26AB74 Content-Type: application/pgp-signature; name="signature.asc" Content-Description: OpenPGP digital signature Content-Disposition: attachment; filename="signature.asc" -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.7 (Darwin) Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org iD8DBQFHBA2DOfuToMruuMARCkW+AJ9c5eeADIOjd342XJj+h7rv/kiANACcD/g9 Lm8kC0mqO1eP/nH33NN1NAc= =rPFn -----END PGP SIGNATURE----- --------------enig19778564B8230D4BDB26AB74--