Date: Sun, 25 Nov 2007 22:48:50 +0100 From: Stefan Sperling <stsp@stsp.name> To: hackers@freebsd.org Cc: David Leslie <me@jdavidleslie.com> Subject: How to add wake on lan support for your card (was: Re: FreeBSD WOL sis on) Message-ID: <20071125214850.GY1463@ted.stsp.lan> In-Reply-To: <452840.43857.qm@web38013.mail.mud.yahoo.com> References: <20071125174204.GT1463@ted.stsp.lan> <452840.43857.qm@web38013.mail.mud.yahoo.com>
next in thread | previous in thread | raw e-mail | index | archive | help
--3mUD2hqWbnBptYHy Content-Type: text/plain; charset=iso-8859-1 Content-Disposition: inline Content-Transfer-Encoding: quoted-printable On Sun, Nov 25, 2007 at 10:53:59AM -0800, David Leslie wrote: > Linux does support WOL for (at least) this SiS900 NIC > (I can verify that it does work on this board), so > maybe it will be supported in FreeBSD in the future? Sure, it's possible. Adding support for a card is not that hard actually. It can be done in one or two evenings by someone who has messed with any kind of driver before, and maybe a week by someone who hasn't and is willing to learn basics about device drivers, e.g. how to access and manipulate registers from C. Knowing C is a prerequisite of course. I myself had no idea at all about drivers either before I started working on the WOL patch. But I currently rarely have time to work on this (except the odd Sunday like today), and I keep getting requests to support more chipsets that queue up faster than I can handle :-/ Maybe if I provide a little guide some people will get interested and help a bit. Here it goes: =3D How to add WOL support for your card =3D To add WOL support for your card, and you have no data sheet for the card, first look at the Linux driver to find out whether the card can do WOL under Linux. There should be some routines for configuring WOL. Look at the ethtool_ops struct, if the driver sets pointers to get_wol and set_wol routines inside it, it supports WOL. For example, the "via-rhine" driver on Linux (if_vr on FreeBSD) does this: static struct ethtool_ops netdev_ethtool_ops =3D { // snip .get_wol =3D rhine_get_wol, .set_wol =3D rhine_set_wol, // snip Now that you know that you can get this to work, apply the WOL patch to your system: http://stsp.n=E6me/wol/ See the README.txt for instructions. =3D=3D How do I tell the chip on my card to do WOL? =3D=3D You can do this in a new driver routine called dev_enable_wol(), where dev_ is the usual driver name prefix, e.g. xl_enable_wol() for if_xl. If you have a data sheet, it will probably tell you how to enable WOL in detail. The data sheet for my NatSemi card is freely available on the web and has a very good section on configuring wake on lan: http://www.national.com/ds/DP/DP83815.pdf Look at page 90 in that PDF. Even if your chip is different the general procedure is likely the same on most chips so this serves as a good example (along with a working implementation for the if_sis driver in my patch). That page helped me getting started at lot. How exactly WOL is configured depends on the device, but usually involves a) configuring the receive filter b) some WOL configuration register=20 c) putting the device into D3 ("sleep") power state =3D=3D=3D The receive filter =3D=3D The receive filter will simply need to accept *any* kind of packet. There is usually a register in the chip with bits that define what kind packets the receiver on the card should hand on to the driver and which it should just ignore -- typically the filter understands unicast, multicast and broadcast packet types. In case of the 3com chip, the Linux driver (3c59x) does this in the acpi_set_WOL() function to set the bits in the receive register: /* The RxFilter must accept the WOL frames. */ iowrite16(SetRxFilter|RxStation|RxMulticast|RxBroadcast, ioaddr + EL3_CMD); iowrite16(RxEnable, ioaddr + EL3_CMD); =20 The FreeBSD (almost) equivalent in xl_enable_wol() is this: /* Configure the receive filter to accept any kind of packet. */ XL_SEL_WIN(5); rxfilt =3D CSR_READ_1(sc, XL_W5_RX_FILTER); rxfilt |=3D XL_RXFILTER_INDIVIDUAL | XL_RXFILTER_ALLMULTI | XL_RXFILTER_BROADCAST | XL_RXFILTER_ALLFRAMES; CSR_WRITE_2(sc, XL_COMMAND, XL_CMD_RX_SET_FILT | rxfilt); Of course, register business is highly chip specific. For example, other chips I've dealt with didn't have register windows at all so the XL_SEL_WIN isn't needed there, and the bit mask macros for the receive filter config register will of course be named somewhat differently in each driver. Of course, a data sheet helps. If you don't have one, looking at what the Linux driver does and matching register offsets and bit masks with the FreeBSD driver will probably help. =3D=3D=3D The WOL config register =3D=3D=3D The WOL config register is usually a 16bit (e.g. if_xl) or 32bit (e.g. if_sis) register somewhere in the chip memory with each bit corresponding to a certain type of wake event -- magic packet, link status change, unicast packet reception, broadcast packet reception, etc. The device will only wake the system if an event happens that it has been configured for. Using the NatSemi card an example, there is a 32bit WOL configuration register at offset 0x40 in the chip register space and bit number 9 (the 8th bit from the right) tells the chip to wake up if it receives a magic packet. See the DP83815 data sheet again, page 55. So to add wake on magic packet support, we add the follwing to /usr/src/sys/dev/if_sisreg.h: /* NS DP83815/6 registers */ // other registers omitted... #define NS_WCSR 0x40 =20 /* Bits in DP83815 Wake On Lan Command/Status register */ #define NS_WCSR_WAKE_MAGIC 0x00000200 Of course there are cards that can do more than just magic packets, and no two users want to wake up their boxes the same way, and some don't want WOL at all, so ifconfig has been extended to allow WOL events to be configured (default is to not wake the box at all). Each driver adds a small field to its softc where it remembers what WOL events the user wants configured. For example, the xl_softc (defined in /usr/src/sys/pci/if_xlreg.h on RELENG_6, and in /usr/src/sys/pci/if_xl.c on RELENG_7) got the following additional field: struct xl_softc { // lots of other fields omitted... uint32_t xl_wol_events; }; Also, we define a macro shorthand that defines what kind of wake events our driver can tell the card to look out for, for example: #define XL_SUPPORTED_WOL_EVENTS (IFWOL_WAKE_ON_MAGIC | IFWOL_WAKE_ON_LINK) The wake event types known to the system are listed in /usr/include/net/if.h: IFWOL_WAKE_ON_UNICAST IFWOL_WAKE_ON_MULTICAST IFWOL_WAKE_ON_BROADCAST IFWOL_WAKE_ON_MAGIC IFWOL_WAKE_ON_LINL (Note that I plan to remove the IFWOL_ENABLE_SOPASSWD flag, so while it is still listed in if.h currently, I omitted it above.) The ifconfig<->driver communication is done via a set of new ioctls, defined in /usr/include/sys/sockio.h: #define SIOCGIFWOLOPTS _IOWR('i', 125, struct ifreq) /* get wake on lan options */ #define SIOCSIFWOLOPTS _IOW('i', 126, struct ifreq) /* set wake on lan options */ #define SIOCGIFWOLSUPP _IOWR('i', 127, struct ifreq) /* get wake on lan modes supported by device */ So whenever the user runs one of ifconfig xl0 ifconfig xl0 wakeon magic or similar your driver's ioctl handler will be called and you will get one of the three requests above. ioctl arguments are usually evaluated in a big switch statement. The following was added to the if_xl driver's xl_ioctl() routine so that the driver remembers what events the user wants to be configured in the chip (SIOCGIFWOLOPTS and SIOCSIFWOLOPTS), ^Get ^Set and allowing ifconfig to determine what type of events the device supports (SIOCGIFWOLSUPP): =20 case SIOCGIFWOLSUPP: if (sc->xl_flags & XL_FLAG_SUPPORTS_WOL) ifr->ifr_wolopts.ifwol_supported =3D XL_SUPPORTED_WOL_EVENTS; else ifr->ifr_wolopts.ifwol_supported =3D 0; error =3D 0; break; case SIOCGIFWOLOPTS: XL_LOCK(sc); xl_get_wolopts(sc, &ifr->ifr_wolopts); XL_UNLOCK(sc); error =3D 0; break; case SIOCSIFWOLOPTS: XL_LOCK(sc); error =3D xl_set_wolopts(sc, &ifr->ifr_wolopts); XL_UNLOCK(sc); break; =20 The xl_(get|set)_wolopts() routines do some sanity checking and store/return the value of ifr_wolopts in the xl_wol_events field in the softc. [The naming is horribly inconsistent here, I plan to clean this up in the future so everything is called simply "wolcfg" everywhere instead of "wolopts" at some places and "wol_events" at others.] After dealing with the receive filter, xl_enable_wol() checks what the user wants configured and writes the corresponding values into the WOL config register (in this case using the CSR_WRITE_2 macro to access registers the 16bit WOL config register on the chip): /* Configure wake on lan events. */ config =3D 0; XL_SEL_WIN(7); if (sc->xl_wol_events & IFWOL_WAKE_ON_MAGIC) config |=3D XL_WAKE_ON_MAGIC; if (sc->xl_wol_events & IFWOL_WAKE_ON_LINK) config |=3D XL_WAKE_ON_LINK; CSR_WRITE_2(sc, XL_W7_BM_WOL, config); =09 =3D=3D=3D Putting the device into D3 power state =3D=3D=3D This is not strictly device specific, at least for PCI devices there is an API that drivers can (should?) use to set the device into D3 power state, see pci_set_powerstate(9) Try that and see if it works. Currently the if_xl driver uses this instead, which should have the same effect: /* Make sure power management is enabled and set the card * into D3hot power state, so it stays active after system shutdown. */ config =3D pci_read_config(dev, XL_PCI_PWRMGMTCTRL, 2); config |=3D XL_PME_EN | XL_PSTATE_D3; pci_write_config(dev, XL_PCI_PWRMGMTCTRL, config | XL_PME_EN, 2); However, doing it manually as done in if_sis works fine, too. (Except I guess there may probably be races if the bus is currently busy or so, maybe some PCI expert can shed some light on whether the following is good practice or not...) /* Set appropriate power state, so the card stays active * after system shutdown. */ CSR_WRITE_4(sc, NS_CLKRUN, NS_CLKRUN_PMESTS | NS_CLKRUN_PMEENB); =3D=3D=3D Where to call dev_enable_wol() =3D=3D=3D Every driver has a shutdown routine. Add your enable_wol() routine to the very end of it so that it is the very last routine that runs in the driver at system shutdown time. For example, the xl_shutdown() routine looks like this: static void xl_shutdown(device_t dev) { struct xl_softc *sc; =20 sc =3D device_get_softc(dev); =20 XL_LOCK(sc); xl_reset(sc); xl_stop(sc); xl_enable_wol(dev); XL_UNLOCK(sc); } Make sure that you do not call the routine outside of a LOCK() and UNLOCK() scope, otherwise people with more than one CPU in their computer might complain. =3D=3D=3D That was it =3D=3D=3D Well, I hope I haven't missed anything important, but I guess that's about it. This should get every aspiring kernel hacker started. For more details look at the patch and see what it does for the drivers it already supports. You should be able to understand the patch fairly well now. Especially the sis_enable_wol() routine in if_sis should be relatively clear quickly with the help of the data sheet. If you're already a hardcore kernel hacker why are you still reading this guide and not hacking up a patch already? :) I will happily accept diffs against drivers. If you have questions just ask. Good luck, --=20 stefan http://stsp.name PGP Key: 0xF59D25F0 --3mUD2hqWbnBptYHy Content-Type: application/pgp-signature Content-Disposition: inline -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.7 (FreeBSD) iD8DBQFHSe3C5dMCc/WdJfARArhqAKCCTjILLDaosN8gC33NMwNKf7dYswCfeVEv 1UF8Dwdrp+t69PZevYWmS3c= =Dcjy -----END PGP SIGNATURE----- --3mUD2hqWbnBptYHy--
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?20071125214850.GY1463>