carl9170 firmware: Wake-on-LAN support
authorChristian Lamparter <chunkeey@googlemail.com>
Sun, 16 Jan 2011 00:11:18 +0000 (01:11 +0100)
committerChristian Lamparter <chunkeey@googlemail.com>
Sun, 16 Jan 2011 00:48:15 +0000 (01:48 +0100)
With this feature enabled, the device will stay
active while the host goes into System Standby
[Suspend to RAM!].

Furthermore, it scans all incoming 802.11 data frames
for a MAGIC WOL pattern and - when found - propagates
the wake-up call to the USB Host.

Notes/Limitations:
* Your host has to support USB remote wake-up.
  [In some cases, /proc/acpi/wakeup has to be
   configured as well - see Google for more
   details]

* The MAC address is taken from the device EEPROM.
  [so, ifconfig dev hw ether overrides don't
   work (yet)]

* The magic pattern has to be within the first
  320 bytes of the data frame.

* The magic frames has to be send unencrypted.
  Therefore the WOL initiator has to support
  frame injection [not a problem for most
  mac80211-based drivers].

* The initatior need to be in range and on the
  same channel as well.

Signed-off-by: Christian Lamparter <chunkeey@googlemail.com>
carlfw/Kconfig
carlfw/include/carl9170.h
carlfw/include/usb.h
carlfw/include/wl.h
carlfw/src/fw.c
carlfw/src/main.c
carlfw/src/wlan.c
carlfw/usb/main.c
carlfw/usb/usb.c
tools/src/fwinfo.c

index f3d35e765f6dcd958a17dd62c7871b3182523623..9a980e36268ad211186ed0e7c28597e3d1f1823b 100644 (file)
@@ -157,6 +157,15 @@ config CARL9170FW_TX_AMPDU
        prompt "Firmware-supported ampdu scheduling"
        depends on CARL9170FW_EXPERIMENTAL
 
+config CARL9170FW_WOL
+       def_bool n
+       prompt "Wakeup on WLAN"
+       depends on CARL9170FW_EXPERIMENTAL
+       ---help---
+        With this option enabled, the firmware can wake-up
+        suspended hosts... As long as they fully support
+        USB remote wakeup.
+
 config CARL9170FW_VIFS_NUM
        default 1
        int
index b61189bb2bf0530867cb7e661d23dc2c5ed6c893..b57993d2d7e70abafc582aa86f64fee80564416e 100644 (file)
@@ -63,6 +63,12 @@ enum carl9170_mac_reset_state {
        CARL9170_MAC_RESET_FORCE,
 };
 
+enum carl9170_suspend_mode {
+       CARL9170_HOST_AWAKE                     = 0,
+       CARL9170_HOST_SUSPENDED,
+       CARL9170_AWAKE_HOST,
+};
+
 /*
  * This platform - being an odd 32-bit architecture - prefers to
  * have 32-Bit variables.
@@ -76,6 +82,7 @@ struct firmware_context_struct {
        /* misc */
        unsigned int watchdog_enable;
        unsigned int reboot;
+       unsigned int suspend_mode;
 
        struct {
                /* Host Interface DMA queues */
@@ -139,7 +146,8 @@ struct firmware_context_struct {
        struct {
                unsigned int config,
                             interface_setting,
-                            alternate_interface_setting;
+                            alternate_interface_setting,
+                            device_feature;
                enum carl9170_ep0_action ep0_action;
 
                void *ep0_txrx_buffer;
index 3f35567f8b9910133cb45fb401a28c56ab39a859..2098638cf7d6a8d0b01a17535b1b7beaa6f703dd 100644 (file)
@@ -50,11 +50,16 @@ static inline __inline bool usb_configured(void)
                  AR9170_USB_DEVICE_ADDRESS_CONFIGURE);
 }
 
-static inline __inline void usb_remote_wakeup(void)
+static inline __inline void usb_enable_remote_wakeup(void)
 {
        orb(AR9170_USB_REG_MAIN_CTRL, AR9170_USB_MAIN_CTRL_REMOTE_WAKEUP);
 }
 
+static inline __inline void usb_disable_remote_wakeup(void)
+{
+       andb(AR9170_USB_REG_MAIN_CTRL, ~AR9170_USB_MAIN_CTRL_REMOTE_WAKEUP);
+}
+
 static inline __inline void usb_enable_global_int(void)
 {
        orb(AR9170_USB_REG_MAIN_CTRL, AR9170_USB_MAIN_CTRL_ENABLE_GLOBAL_INT);
index 4f0c76de42fd324ac4553da504916aad69bf3d4a..8c06f6a12ca0673bcd8a016bfd15723df125bb98 100644 (file)
@@ -265,6 +265,15 @@ void wlan_cab_modify_dtim_beacon(const unsigned int vif,
                                 const unsigned int bcn_addr,
                                 const unsigned int bcn_len);
 
+static inline void wlan_prepare_wol(void)
+{
+       /* set filter policy to: discard everything */
+       fw.wlan.rx_filter = CARL9170_RX_FILTER_EVERYTHING;
+
+       /* reenable rx dma */
+       wlan_trigger(AR9170_DMA_TRIGGER_RXQ);
+}
+
 static inline void __check_wlantx(void)
 {
        BUILD_BUG_ON(CARL9170_TX_SUPERDESC_LEN & 3);
index fe169757e4758a3020685152a0518a40974454a1..e71425f3c94d8b87e5137e7cfddd909f84134ef0 100644 (file)
@@ -66,6 +66,9 @@ const struct carl9170_firmware_descriptor __section(fwdsc) carl9170fw_desc = {
                                        BIT(CARL9170FW_PSM) |
 #endif /* CONFIG_CARL9170FW_PSM */
                                        BIT(CARL9170FW_RX_FILTER) |
+#ifdef CONFIG_CARL9170FW_WOL
+                                       BIT(CARL9170FW_WOL) |
+#endif /* CONFIG_CARL9170FW_WOL */
                                           (0)),
 
             .miniboot_size = cpu_to_le16(0),
index bf6602a8d8cc29af60916edb97737b4facf9d84f..a88338ac79bfd7b7e839295fc9fc6800bc1a63ef 100644 (file)
@@ -29,6 +29,7 @@
 #include "printf.h"
 #include "gpio.h"
 #include "wl.h"
+#include "usb.h"
 
 #define AR9170_WATCH_DOG_TIMER            0x100
 
@@ -105,6 +106,8 @@ static void timer0_isr(void)
        gpio_timer();
 #endif /* CONFIG_CARL9170FW_GPIO_INTERRUPT */
 
+       usb_timer();
+
 #ifdef CONFIG_CARL9170FW_DEBUG_LED_HEARTBEAT
        set(AR9170_GPIO_REG_PORT_DATA, get(AR9170_GPIO_REG_PORT_DATA) ^ 1);
 #endif /* CONFIG_CARL9170FW_DEBUG_LED_HEARTBEAT */
index 794aa86f44ba9d29a37a7209f514c054cc4be038..9eb336a267eee7c4858d1e057e18358cede4b7c8 100644 (file)
@@ -31,6 +31,7 @@
 #include "printf.h"
 #include "rf.h"
 #include "linux/ieee80211.h"
+#include "rom.h"
 
 static void wlan_txunstuck(unsigned int queue)
 {
@@ -629,6 +630,83 @@ static void wlan_check_rx_overrun(void)
        }
 }
 
+#ifdef CONFIG_CARL9170FW_WOL
+static void wlan_rx_wol(struct ieee80211_hdr *hdr, unsigned int len)
+{
+       const unsigned char *data, *end, *mac;
+       unsigned int found = 0;
+
+       /*
+        * LIMITATION:
+        * We can only scan the first AR9170_BLOCK_SIZE [=~320] bytes
+        * for MAGIC patterns!
+        */
+
+       /*
+        * TODO:
+        * Currently, the MAGIC MAC Address is fixed to the EEPROM default.
+        * It's possible to make it fully configurable, e.g:
+        *
+        * mac = (const unsigned char *) AR9170_MAC_REG_MAC_ADDR_L;
+        * But this will clash with the driver's suspend path, because it
+        * needs to reset the registers.
+        */
+       mac = rom.sys.mac_address;
+
+       data = (u8 *)((unsigned long)hdr + ieee80211_hdrlen(hdr->frame_control));
+       end = (u8 *)((unsigned long)hdr + len);
+
+       /*
+        * scan for standard WOL Magic frame
+        *
+        * "A physical WakeOnLAN (Magic Packet) will look like this:
+        * ---------------------------------------------------------------
+        * | Synchronization Stream |  Target MAC |  Password (optional) |
+        * |    6 octets            |   96 octets |   0, 4 or 6          |
+        * ---------------------------------------------------------------
+        *
+        * The Synchronization Stream is defined as 6 bytes of FFh.
+        * The Target MAC block contains 16 duplications of the IEEEaddress
+        * of the target, with no breaks or interruptions.
+        *
+        * The Password field is optional, but if present, contains either
+        * 4 bytes or 6 bytes. The WakeOnLAN dissector was implemented to
+        * dissect the password, if present, according to the command-line
+        * format that ether-wake uses, therefore, if a 4-byte password is
+        * present, it will be dissected as an IPv4 address and if a 6-byte
+        * password is present, it will be dissected as an Ethernet address.
+        *
+        * <http://wiki.wireshark.org/WakeOnLAN>
+        */
+
+       while (data < end) {
+               if (found >= 6) {
+                       if (*data == mac[found % 6])
+                               found++;
+                       else
+                               found = 0;
+               }
+
+               /* previous check might reset found counter */
+               if (found < 6) {
+                       if (*data == 0xff)
+                               found++;
+                       else
+                               found = 0;
+               }
+
+               if (found == (6 + 16 * 6)) {
+                       fw.suspend_mode = CARL9170_AWAKE_HOST;
+                       return;
+               }
+
+               data++;
+       }
+
+       return;
+}
+#endif /* CONFIG_CARL9170FW_WOL */
+
 static unsigned int wlan_rx_filter(struct dma_desc *desc)
 {
        struct ieee80211_hdr *hdr;
@@ -683,6 +761,14 @@ static unsigned int wlan_rx_filter(struct dma_desc *desc)
                rx_filter |= CARL9170_RX_FILTER_MGMT;
        }
 
+#ifdef CONFIG_CARL9170FW_WOL
+       if (unlikely(fw.suspend_mode == CARL9170_HOST_SUSPENDED)) {
+               if (rx_filter & CARL9170_RX_FILTER_DATA)
+                       wlan_rx_wol(hdr, min(data_len,
+                                   (unsigned int)AR9170_BLOCK_SIZE));
+       }
+#endif /* CONFIG_CARL9170FW_WOL */
+
 #undef AR9170_RX_ERROR_BAD
 
        return rx_filter;
index 95aac88b86b1d8d2a5d9f0027260495fa6365196..c2ad6c3d71fcc79fc4b1bc082430f796931745cd 100644 (file)
@@ -27,6 +27,7 @@
 #include "printf.h"
 #include "timer.h"
 #include "rom.h"
+#include "wl.h"
 #include "shared/phy.h"
 
 #ifdef CONFIG_CARL9170FW_DEBUG_USB
@@ -381,13 +382,22 @@ static void usb_handler(uint8_t usb_interrupt_level1)
                if (usb_interrupt_level2 & AR9170_USB_INTR_SRC7_USB_SUSPEND) {
                        usb_suspend_ack();
 
-                       disable_watchdog();
+                       fw.suspend_mode = CARL9170_HOST_SUSPENDED;
 
-                       /* GO_TO_SUSPEND stops the CPU clock too. */
-                       orb(AR9170_USB_REG_MAIN_CTRL, AR9170_USB_MAIN_CTRL_GO_TO_SUSPEND);
+                       if (!(fw.usb.device_feature & USB_DEVICE_REMOTE_WAKEUP)) {
+                               disable_watchdog();
+
+                               /* GO_TO_SUSPEND stops the CPU clock too. */
+                               orb(AR9170_USB_REG_MAIN_CTRL, AR9170_USB_MAIN_CTRL_GO_TO_SUSPEND);
+                       } else {
+                               wlan_prepare_wol();
+                       }
                }
 
                if (usb_interrupt_level2 & AR9170_USB_INTR_SRC7_USB_RESUME) {
+                       fw.suspend_mode = CARL9170_HOST_AWAKE;
+                       andl(AR9170_USB_REG_WAKE_UP, AR9170_USB_WAKE_UP_WAKE);
+
                        usb_resume_ack();
                        reboot();
                }
@@ -407,3 +417,11 @@ void handle_usb(void)
                usb_trigger_in();
 }
 
+void usb_timer(void)
+{
+#ifdef CONFIG_CARL9170FW_WOL
+       if (fw.suspend_mode == CARL9170_AWAKE_HOST) {
+               orl(AR9170_USB_REG_WAKE_UP, AR9170_USB_WAKE_UP_WAKE);
+       }
+#endif /* CONFIG_CARL9170FW_WOL */
+}
index 4e387d74f974805c54c628cf15f7d6f0ac18e3c3..f4b95d8b7e6d13cac23c39f17f210ad34e717d3b 100644 (file)
@@ -40,7 +40,11 @@ static struct ar9170_usb_config usb_config_highspeed = {
                .bNumInterfaces = 1,
                .bConfigurationValue = 1,
                .iConfiguration = 0,
-               .bmAttributes = USB_CONFIG_ATT_ONE,
+               .bmAttributes = USB_CONFIG_ATT_ONE |
+#ifdef CONFIG_CARL9170FW_WOL
+                               USB_CONFIG_ATT_WAKEUP |
+#endif /* CONFIG_CARL9170FW_WOL */
+                               0,
                .bMaxPower = 0xfa, /* 500 mA */
        },
 
@@ -103,7 +107,11 @@ static struct ar9170_usb_config usb_config_fullspeed = {
                .bNumInterfaces = 1,
                .bConfigurationValue = 1,
                .iConfiguration = 0,
-               .bmAttributes = USB_CONFIG_ATT_ONE,
+               .bmAttributes = USB_CONFIG_ATT_ONE |
+#ifdef CONFIG_CARL9170FW_WOL
+                               USB_CONFIG_ATT_WAKEUP |
+#endif /* CONFIG_CARL9170FW_WOL */
+                               0,
                .bMaxPower = 0xfa, /* 500 mA */
        },
 
@@ -258,7 +266,13 @@ void usb_init(void)
         *
         * fw.usb.interface_setting = 0;
         * fw.usb.alternate_interface_setting = 0;
+        * fw.usb.device_feature = 0;
         */
+
+#ifdef CONFIG_CARL9170FW_WOL
+       fw.usb.device_feature |= USB_DEVICE_REMOTE_WAKEUP;
+       usb_enable_remote_wakeup();
+#endif /* CONFIG_CARL9170FW_WOL */
 }
 
 #define GET_ARRAY(a, o)        ((uint32_t *) (((unsigned long) data) + offset))
@@ -311,7 +325,7 @@ static int usb_ep0tx_data(const void *data, const unsigned int len)
 #ifdef CONFIG_CARL9170FW_USB_STANDARD_CMDS
 static int usb_get_status(const struct usb_ctrlrequest *ctrl)
 {
-       __le16 status = cpu_to_le16(0);
+       __le16 status = cpu_to_le16(fw.usb.device_feature);
 
        if ((ctrl->bRequestType & USB_DIR_MASK) != USB_DIR_IN)
                return -1;
@@ -558,6 +572,34 @@ static int usb_get_interface(const struct usb_ctrlrequest *ctrl)
        return usb_ep0tx_data(&fw.usb.alternate_interface_setting, 1);
 }
 
+static int usb_manipulate_feature(const struct usb_ctrlrequest *ctrl, bool __unused clear)
+{
+       unsigned int feature;
+        if (USB_CHECK_REQTYPE(ctrl, USB_RECIP_DEVICE, USB_DIR_OUT))
+               return -1;
+
+       if (usb_configured() == false)
+               return -1;
+
+       feature = le16_to_cpu(ctrl->wValue);
+
+#ifdef CONFIG_CARL9170FW_WOL
+       if (feature & USB_DEVICE_REMOTE_WAKEUP) {
+               if (clear)
+                       usb_disable_remote_wakeup();
+               else
+                       usb_enable_remote_wakeup();
+       }
+#endif /* CONFIG_CARL9170FW_WOL */
+
+       if (clear)
+               fw.usb.device_feature &= ~feature;
+       else
+               fw.usb.device_feature |= feature;
+
+       return 1;
+}
+
 #ifdef CONFIG_CARL9170FW_USB_MODESWITCH
 static int usb_set_interface(const struct usb_ctrlrequest *ctrl)
 {
@@ -606,9 +648,8 @@ static int usb_standard_command(const struct usb_ctrlrequest *ctrl __unused)
                break;
 
        case USB_REQ_CLEAR_FEATURE:
-               break;
-
        case USB_REQ_SET_FEATURE:
+               usb_manipulate_feature(ctrl, ctrl->bRequest == USB_REQ_CLEAR_FEATURE);
                break;
 
        case USB_REQ_SET_ADDRESS:
index b42852d519f85dbba432b52b30b5a81c37fec0bd..18ca8be67bf21f9cc779f178e0e132d304fea0fe 100644 (file)
@@ -64,6 +64,7 @@ static const struct feature_list known_otus_features_v1[] = {
        CHECK_FOR_FEATURE(CARL9170FW_GPIO_INTERRUPT),
        CHECK_FOR_FEATURE(CARL9170FW_PSM),
        CHECK_FOR_FEATURE(CARL9170FW_RX_FILTER),
+       CHECK_FOR_FEATURE(CARL9170FW_WOL),
 };
 
 static void check_feature_list(const struct carl9170fw_desc_head *head,