From: Christian Lamparter Date: Sun, 16 Jan 2011 00:11:18 +0000 (+0100) Subject: carl9170 firmware: Wake-on-LAN support X-Git-Tag: 1.9.3~18 X-Git-Url: https://jxself.org/git/?p=carl9170fw.git;a=commitdiff_plain;h=6476369c2c6d4ba487408cb5daff8df0480d6b4a carl9170 firmware: Wake-on-LAN support 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 --- diff --git a/carlfw/Kconfig b/carlfw/Kconfig index f3d35e7..9a980e3 100644 --- a/carlfw/Kconfig +++ b/carlfw/Kconfig @@ -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 diff --git a/carlfw/include/carl9170.h b/carlfw/include/carl9170.h index b61189b..b57993d 100644 --- a/carlfw/include/carl9170.h +++ b/carlfw/include/carl9170.h @@ -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; diff --git a/carlfw/include/usb.h b/carlfw/include/usb.h index 3f35567..2098638 100644 --- a/carlfw/include/usb.h +++ b/carlfw/include/usb.h @@ -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); diff --git a/carlfw/include/wl.h b/carlfw/include/wl.h index 4f0c76d..8c06f6a 100644 --- a/carlfw/include/wl.h +++ b/carlfw/include/wl.h @@ -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); diff --git a/carlfw/src/fw.c b/carlfw/src/fw.c index fe16975..e71425f 100644 --- a/carlfw/src/fw.c +++ b/carlfw/src/fw.c @@ -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), diff --git a/carlfw/src/main.c b/carlfw/src/main.c index bf6602a..a88338a 100644 --- a/carlfw/src/main.c +++ b/carlfw/src/main.c @@ -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 */ diff --git a/carlfw/src/wlan.c b/carlfw/src/wlan.c index 794aa86..9eb336a 100644 --- a/carlfw/src/wlan.c +++ b/carlfw/src/wlan.c @@ -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. + * + * + */ + + 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; diff --git a/carlfw/usb/main.c b/carlfw/usb/main.c index 95aac88..c2ad6c3 100644 --- a/carlfw/usb/main.c +++ b/carlfw/usb/main.c @@ -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 */ +} diff --git a/carlfw/usb/usb.c b/carlfw/usb/usb.c index 4e387d7..f4b95d8 100644 --- a/carlfw/usb/usb.c +++ b/carlfw/usb/usb.c @@ -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: diff --git a/tools/src/fwinfo.c b/tools/src/fwinfo.c index b42852d..18ca8be 100644 --- a/tools/src/fwinfo.c +++ b/tools/src/fwinfo.c @@ -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,