You will want use wireless-testing, then git checkout -b wow master-2009-07-21 Then git am this file. Read: http://wireless.kernel.org/en/users/Documentation/WoW To find out if your cards *should* support Wow you can run something like this and see something like this: sudo modprobe ath9k debug=0x00000200 sudo dmesg | grep WoW [ 2057.841090] ath9k: WoW capabler device: yes [ 2057.841095] ath9k: EEPROM indicates WoW hw changes in place: yes [ 2057.841100] ath9k: WoW exact match pattern support: yes [ 2057.841105] ath9k: WoW match pattern first dword requirement: yes To test: use wireshark on your AP and a filter rule like: wlan.sa == 00:03:7f:11:a4:c8 || wlan.da == 00:03:7f:11:a4:c8 where 00:03:7f:11:a4:c8 is your STA MAC address. Upon suspend you should see null func frames, if you don't then your AP will probably disassociate you... You can use wpa_supplicant to keep your connection alive to the AP, this will keep it alive accross pm-suspend/resume. cat mosca.conf ctrl_interface=/var/run/wpa_supplicant ap_scan=1 network={ ssid="mosca" scan_ssid=1 key_mgmt=NONE } wpa_supplicant -D nl80211 -i wlan1 -c mosca.conf To generate the WoW (WOL) frame, you can use something like this from the AP: sudo etherwake -D -i wlan3 00:03:7f:11:a4:c8 TODO: * Figure out what is being scheduled on the workqueue during suspend (the WARN). If we remove the interface nothing is issued but note that we *need* to leave the interface as other as per mac80211 API documentation we should not be ACKing frames for peers we are connected to. ath9k doesn't do this but for drivers upon remove interface (but should) but other drivers may and that would mean WoW would not work for them. * Figure out why we get these upon resume: ath9k: timeout (100000 us) on reg 0x806c: 0xdeadbeef & 0x01f00000 != 0x00000000 ath9k: RX failed to go idle in 10 ms RXSM=0xdeadbeef * Figure out why sending a WOL will trigger the harware to send nullfunc frames ... I've seen this * Add WoW pattern support. We should add diassoc pattern and deauth frame patterns, and let users program the rest. * Test beacon loss trigger * Test link change trigger. This may require adding the disassociate pattern and deauth pattern, not sure. To generate a wow pattern you can use something like this: int ath9k_wow_create_pattern(ath_hw *ah) { struct ath_softc *sc = hw->sc; int pattern_num; int pattern_count = 0; u8 * bssid; u32 i, byte_cnt; u8 dis_deauth_pattern[MAX_PATTERN_SIZE]; u8 dis_deauth_mask[MAX_PATTERN_SIZE]; WOW_PATTERN *pattern; struct wow_info *wowInfo = sc->sc_wowInfo; memset(dis_deauth_pattern, 0, MAX_PATTERN_SIZE); memset(dis_deauth_mask, 0, MAX_PATTERN_SIZE); 138,5 25% /* Create Dissassociate / Deauthenticate packet filter * 2 bytes 2 byte 6 bytes 6 bytes 6 bytes * +--------------+----------+---------+--------+--------+---- * + Frame Control+ Duration + DA + SA + BSSID + * +--------------+----------+---------+--------+--------+---- * * The above is the management frame format for disassociate/deauthenticate * pattern, from this we need to match the first byte of 'Frame Control' * and DA, SA, and BSSID fields (skipping 2nd byte of FC and Duration feild. * * Disassociate pattern * -------------------- * Frame control = 00 00 1010 * DA, SA, BSSID = x:x:x:x:x:x * Pattern will be A0000000 | x:x:x:x:x:x | x:x:x:x:x:x | x:x:x:x:x:x -- 22 bytes * Deauthenticate pattern * ---------------------- * Frame control = 00 00 1100 * DA, SA, BSSID = x:x:x:x:x:x * Pattern will be C0000000 | x:x:x:x:x:x | x:x:x:x:x:x | x:x:x:x:x:x -- 22 bytes */ /* Create Disassociate Pattern first */ byte_cnt = 0; memset((char*)&dis_deauth_pattern[0], 0, MAX_PATTERN_SIZE); /* Fill out the mask with all FF's */ for (i = 0; i < MAX_PATTERN_MASK_SIZE; i++) { dis_deauth_mask[i] = 0xff; } /* Copy the 1st byte of frame control field */ dis_deauth_pattern[byte_cnt] = 0xA0; byte_cnt++; /* Skip 2nd byte of frame control and Duration field */ byte_cnt += 3; /* Need not match the destination mac address, it can be a broadcast * mac address or an unicast to this staion */ byte_cnt += 6; /* Copy the Source mac address */ bssid = sc->sc_curbssid; for (i = 0; i < IEEE80211_ADDR_LEN; i++) { dis_deauth_pattern[byte_cnt] = bssid[i]; byte_cnt++; } /* Copy the BSSID , it's same as the source mac address */ for (i = 0; i < IEEE80211_ADDR_LEN; i++) { dis_deauth_pattern[byte_cnt] = bssid[i]; byte_cnt++; } /* Create Disassociate pattern mask */ if (ath9k_hw_MatchPatternExact(ah)) { if (ath9k_wowMatchPatternDword(ah)) { /* * This is required for AR9280.. The first 4 bytes have to be * matched for all patterns. The mask * for disassociation and de-auth pattern matching need to enable the * first 4 bytes. Also the duration field needs to be filled. We assume * the duration of de-auth/disassoc frames and association resp are the same. */ dis_deauth_mask[0] = 0xf0; /* Fill in duration field */ dis_deauth_pattern[2] = wowInfo->wowDuration & 0xff; dis_deauth_pattern[3] = (wowInfo->wowDuration >> 8) & 0xff; } else { dis_deauth_mask[0] = 0xfe; } dis_deauth_mask[1] = 0x03; dis_deauth_mask[2] = 0xc0; } else { dis_deauth_mask[0] = 0xef; dis_deauth_mask[1] = 0x3f; dis_deauth_mask[2] = 0x00; dis_deauth_mask[3] = 0xfc; } ath9k_hw_wowApplyPattern(ah, dis_deauth_pattern, dis_deauth_mask, pattern_count, byte_cnt); pattern_count++; /* For de-authenticate pattern, only the 1st byte of the frame control * feild gets changed from 0xA0 to 0xC0 */ dis_deauth_pattern[0] = 0xC0; /* Now configure the Deauthenticate pattern to the pattern 1 registries */ ath9k_wowApplyPattern(ah, dis_deauth_pattern, dis_deauth_mask, pattern_count, byte_cnt); pattern_count++; for (pattern_num = 0; pattern_num < MAX_NUM_USER_PATTERN; pattern_num++) { pattern = &sc->sc_wowInfo->patterns[pattern_num]; if (pattern->valid) { ath9k_hw_wowApplyPattern(ah, pattern->patternBytes, pattern->maskBytes, pattern_count, pattern->patternLen); pattern_count++; } } /* * For WOW AR9280 support the first 4 bytes all need to be matched. We could * receive a de-auth frame that has been retransmitted. Add another de-auth pattern with * retry bit set in frame control if there is a space available. */ if (ath9k_hw_wowMatchPatternDword(ah) && (pattern_count < MAX_NUM_PATTERN)) { dis_deauth_pattern[1] = 0x08; ath9k_hw_wowApplyPattern(ah, dis_deauth_pattern, dis_deauth_mask, pattern_count, byte_cnt); pattern_count++; } return 0; } From b3af96a4b3f2c8a28bce1b482b1ca4ff0ad53454 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Mon, 20 Jul 2009 17:18:26 -0700 Subject: [PATCH] wireless extensions: make netns aware This makes wireless extensions netns aware. The tasklet sending the events is converted to a work struct so that we can rtnl_lock() in it. Signed-off-by: Johannes Berg --- include/net/net_namespace.h | 3 ++ net/wireless/wext.c | 61 ++++++++++++++++++++---------------------- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/include/net/net_namespace.h b/include/net/net_namespace.h index ded434b..b3646db 100644 --- a/include/net/net_namespace.h +++ b/include/net/net_namespace.h @@ -78,6 +78,9 @@ struct net { #ifdef CONFIG_XFRM struct netns_xfrm xfrm; #endif +#ifdef CONFIG_WIRELESS_EXT + struct sk_buff_head wext_nlevents; +#endif struct net_generic *gen; }; diff --git a/net/wireless/wext.c b/net/wireless/wext.c index 425f7d5..db8351a 100644 --- a/net/wireless/wext.c +++ b/net/wireless/wext.c @@ -1257,48 +1257,48 @@ int compat_wext_handle_ioctl(struct net *net, unsigned int cmd, } #endif -/************************* EVENT PROCESSING *************************/ -/* - * Process events generated by the wireless layer or the driver. - * Most often, the event will be propagated through rtnetlink - */ +static int __net_init wext_pernet_init(struct net *net) +{ + skb_queue_head_init(&net->wext_nlevents); + return 0; +} -/* ---------------------------------------------------------------- */ -/* - * Locking... - * ---------- - * - * Thanks to Herbert Xu for fixing - * the locking issue in here and implementing this code ! - * - * The issue : wireless_send_event() is often called in interrupt context, - * while the Netlink layer can never be called in interrupt context. - * The fully formed RtNetlink events are queued, and then a tasklet is run - * to feed those to Netlink. - * The skb_queue is interrupt safe, and its lock is not held while calling - * Netlink, so there is no possibility of dealock. - * Jean II - */ +static void __net_exit wext_pernet_exit(struct net *net) +{ + skb_queue_purge(&net->wext_nlevents); +} -static struct sk_buff_head wireless_nlevent_queue; +static struct pernet_operations wext_pernet_ops = { + .init = wext_pernet_init, + .exit = wext_pernet_exit, +}; static int __init wireless_nlevent_init(void) { - skb_queue_head_init(&wireless_nlevent_queue); + return register_pernet_subsys(&wext_pernet_ops); return 0; } subsys_initcall(wireless_nlevent_init); -static void wireless_nlevent_process(unsigned long data) +/* Process events generated by the wireless layer or the driver. */ +static void wireless_nlevent_process(struct work_struct *work) { struct sk_buff *skb; + struct net *net; + + rtnl_lock(); - while ((skb = skb_dequeue(&wireless_nlevent_queue))) - rtnl_notify(skb, &init_net, 0, RTNLGRP_LINK, NULL, GFP_ATOMIC); + for_each_net(net) { + while ((skb = skb_dequeue(&net->wext_nlevents))) + rtnl_notify(skb, net, 0, RTNLGRP_LINK, NULL, + GFP_KERNEL); + } + + rtnl_unlock(); } -static DECLARE_TASKLET(wireless_nlevent_tasklet, wireless_nlevent_process, 0); +static DECLARE_WORK(wireless_nlevent_work, wireless_nlevent_process); /* ---------------------------------------------------------------- */ /* @@ -1348,9 +1348,6 @@ static void rtmsg_iwinfo(struct net_device *dev, char *event, int event_len) struct sk_buff *skb; int err; - if (!net_eq(dev_net(dev), &init_net)) - return; - skb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_ATOMIC); if (!skb) return; @@ -1363,8 +1360,8 @@ static void rtmsg_iwinfo(struct net_device *dev, char *event, int event_len) } NETLINK_CB(skb).dst_group = RTNLGRP_LINK; - skb_queue_tail(&wireless_nlevent_queue, skb); - tasklet_schedule(&wireless_nlevent_tasklet); + skb_queue_tail(&dev_net(dev)->wext_nlevents, skb); + schedule_work(&wireless_nlevent_work); } /* ---------------------------------------------------------------- */ -- 1.6.0.4 From 991c58ea8bd1a12697a42e5e7372714d94ac175d Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Mon, 20 Jul 2009 17:19:43 -0700 Subject: [PATCH] wext: optimise, comment and fix event sending The current function for sending events first allocates the event stream buffer, and then an skb to copy the event stream into. This can be done in one go. Also, the current function leaks kernel data to userspace in a 4 uninitialised bytes, initialise those explicitly. Finally also add a few useful comments, as opposed to the current comments. Signed-off-by: Johannes Berg --- net/wireless/wext.c | 114 +++++++++++++++++++++++++------------------------- 1 files changed, 57 insertions(+), 57 deletions(-) diff --git a/net/wireless/wext.c b/net/wireless/wext.c index db8351a..ee35e64 100644 --- a/net/wireless/wext.c +++ b/net/wireless/wext.c @@ -1300,22 +1300,15 @@ static void wireless_nlevent_process(struct work_struct *work) static DECLARE_WORK(wireless_nlevent_work, wireless_nlevent_process); -/* ---------------------------------------------------------------- */ -/* - * Fill a rtnetlink message with our event data. - * Note that we propage only the specified event and don't dump the - * current wireless config. Dumping the wireless config is far too - * expensive (for each parameter, the driver need to query the hardware). - */ -static int rtnetlink_fill_iwinfo(struct sk_buff *skb, struct net_device *dev, - int type, char *event, int event_len) +static struct nlmsghdr *rtnetlink_ifinfo_prep(struct net_device *dev, + struct sk_buff *skb) { struct ifinfomsg *r; struct nlmsghdr *nlh; - nlh = nlmsg_put(skb, 0, 0, type, sizeof(*r), 0); - if (nlh == NULL) - return -EMSGSIZE; + nlh = nlmsg_put(skb, 0, 0, RTM_NEWLINK, sizeof(*r), 0); + if (!nlh) + return NULL; r = nlmsg_data(nlh); r->ifi_family = AF_UNSPEC; @@ -1326,45 +1319,14 @@ static int rtnetlink_fill_iwinfo(struct sk_buff *skb, struct net_device *dev, r->ifi_change = 0; /* Wireless changes don't affect those flags */ NLA_PUT_STRING(skb, IFLA_IFNAME, dev->name); - /* Add the wireless events in the netlink packet */ - NLA_PUT(skb, IFLA_WIRELESS, event_len, event); - - return nlmsg_end(skb, nlh); -nla_put_failure: + return nlh; + nla_put_failure: nlmsg_cancel(skb, nlh); - return -EMSGSIZE; + return NULL; } -/* ---------------------------------------------------------------- */ -/* - * Create and broadcast and send it on the standard rtnetlink socket - * This is a pure clone rtmsg_ifinfo() in net/core/rtnetlink.c - * Andrzej Krzysztofowicz mandated that I used a IFLA_XXX field - * within a RTM_NEWLINK event. - */ -static void rtmsg_iwinfo(struct net_device *dev, char *event, int event_len) -{ - struct sk_buff *skb; - int err; - - skb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_ATOMIC); - if (!skb) - return; - err = rtnetlink_fill_iwinfo(skb, dev, RTM_NEWLINK, event, event_len); - if (err < 0) { - WARN_ON(err == -EMSGSIZE); - kfree_skb(skb); - return; - } - - NETLINK_CB(skb).dst_group = RTNLGRP_LINK; - skb_queue_tail(&dev_net(dev)->wext_nlevents, skb); - schedule_work(&wireless_nlevent_work); -} - -/* ---------------------------------------------------------------- */ /* * Main event dispatcher. Called from other parts and drivers. * Send the event on the appropriate channels. @@ -1383,6 +1345,9 @@ void wireless_send_event(struct net_device * dev, int wrqu_off = 0; /* Offset in wrqu */ /* Don't "optimise" the following variable, it will crash */ unsigned cmd_index; /* *MUST* be unsigned */ + struct sk_buff *skb; + struct nlmsghdr *nlh; + struct nlattr *nla; /* Get the description of the Event */ if (cmd <= SIOCIWLAST) { @@ -1430,25 +1395,60 @@ void wireless_send_event(struct net_device * dev, hdr_len = event_type_size[descr->header_type]; event_len = hdr_len + extra_len; - /* Create temporary buffer to hold the event */ - event = kmalloc(event_len, GFP_ATOMIC); - if (event == NULL) + /* + * The problem for 64/32 bit. + * + * On 64-bit, a regular event is laid out as follows: + * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | + * | event.len | event.cmd | p a d d i n g | + * | wrqu data ... (with the correct size) | + * + * This padding exists because we manipulate event->u, + * and 'event' is not packed. + * + * An iw_point event is laid out like this instead: + * | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | + * | event.len | event.cmd | p a d d i n g | + * | iwpnt.len | iwpnt.flg | p a d d i n g | + * | extra data ... + * + * The second padding exists because struct iw_point is extended, + * but this depends on the platform... + * + * On 32-bit, all the padding shouldn't be there. + */ + + skb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_ATOMIC); + if (!skb) + return; + + /* Send via the RtNetlink event channel */ + nlh = rtnetlink_ifinfo_prep(dev, skb); + if (WARN_ON(!nlh)) { + kfree_skb(skb); + return; + } + + /* Add the wireless events in the netlink packet */ + nla = nla_reserve(skb, IFLA_WIRELESS, event_len); + if (!nla) { + kfree_skb(skb); return; + } + event = nla_data(nla); - /* Fill event */ + /* Fill event - first clear to avoid data leaking */ + memset(event, 0, hdr_len); event->len = event_len; event->cmd = cmd; memcpy(&event->u, ((char *) wrqu) + wrqu_off, hdr_len - IW_EV_LCP_LEN); - if (extra) + if (extra_len) memcpy(((char *) event) + hdr_len, extra, extra_len); - /* Send via the RtNetlink event channel */ - rtmsg_iwinfo(dev, (char *) event, event_len); - - /* Cleanup */ - kfree(event); + nlmsg_end(skb, nlh); - return; /* Always success, I guess ;-) */ + skb_queue_tail(&dev_net(dev)->wext_nlevents, skb); + schedule_work(&wireless_nlevent_work); } EXPORT_SYMBOL(wireless_send_event); -- 1.6.0.4 From e33785504dc6f9d0212ab69cbe538d7b3be2ee44 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Mon, 20 Jul 2009 17:20:02 -0700 Subject: [PATCH] net/compat/wext: send different messages to compat tasks Wireless extensions have the unfortunate problem that events are multicast netlink messages, and are not independent of pointer size. Thus, currently 32-bit tasks on 64-bit platforms cannot properly receive events and fail with all kinds of strange problems, for instance wpa_supplicant never notices disassociations, due to the way the 64-bit event looks (to a 32-bit process), the fact that the address is all zeroes is lost, it thinks instead it is 00:00:00:00:01:00. The same problem existed with the ioctls, until David Miller fixed those some time ago in an heroic effort. A different problem caused by this is that we cannot send the ASSOCREQIE/ASSOCRESPIE events because sending them causes a 32-bit wpa_supplicant on a 64-bit system to overwrite its internal information, which is worse than it not getting the information at all -- so we currently resort to sending a custom string event that it then parses. This, however, has a severe size limitation we are frequently hitting with modern access points; this limitation would can be lifted after this patch by sending the correct binary, not custom, event. A similar problem apparently happens for some other netlink users on x86_64 with 32-bit tasks due to the alignment for 64-bit quantities. In order to fix these problems, I have implemented a way to send compat messages to tasks. When sending an event, we send the non-compat event data together with a compat event data in skb_shinfo(main_skb)->frag_list. Then, when the event is read from the socket, the netlink code makes sure to pass out only the skb that is compatible with the task. This approach was suggested by David Miller, my original approach required always sending two skbs but that had various small problems. To determine whether compat is needed or not, I have used the MSG_CMSG_COMPAT flag, and adjusted the call path for recv and recvfrom to include it, even if those calls do not have a cmsg parameter. I have not solved one small part of the problem, and I don't think it is necessary to: if a 32-bit application uses read() rather than any form of recvmsg() it will still get the wrong (64-bit) event. However, neither do applications actually do this, nor would it be a regression. Signed-off-by: Johannes Berg --- arch/mips/kernel/scall64-n32.S | 2 +- arch/mips/kernel/scall64-o32.S | 4 +- arch/sparc/kernel/sys32.S | 2 +- include/linux/wireless.h | 8 ++++ net/Kconfig | 20 ++++++++++ net/compat.c | 17 ++++++++- net/netlink/af_netlink.c | 36 ++++++++++++++++++- net/wireless/wext.c | 78 ++++++++++++++++++++++++++++++++++++++++ 8 files changed, 160 insertions(+), 7 deletions(-) diff --git a/arch/mips/kernel/scall64-n32.S b/arch/mips/kernel/scall64-n32.S index 15874f9..7c4a94f 100644 --- a/arch/mips/kernel/scall64-n32.S +++ b/arch/mips/kernel/scall64-n32.S @@ -164,7 +164,7 @@ EXPORT(sysn32_call_table) PTR sys_connect PTR sys_accept PTR sys_sendto - PTR sys_recvfrom + PTR compat_sys_recvfrom PTR compat_sys_sendmsg /* 6045 */ PTR compat_sys_recvmsg PTR sys_shutdown diff --git a/arch/mips/kernel/scall64-o32.S b/arch/mips/kernel/scall64-o32.S index 781e0f1..821fc97 100644 --- a/arch/mips/kernel/scall64-o32.S +++ b/arch/mips/kernel/scall64-o32.S @@ -378,8 +378,8 @@ sys_call_table: PTR sys_getsockname PTR sys_getsockopt PTR sys_listen - PTR sys_recv /* 4175 */ - PTR sys_recvfrom + PTR compat_sys_recv /* 4175 */ + PTR compat_sys_recvfrom PTR compat_sys_recvmsg PTR sys_send PTR compat_sys_sendmsg diff --git a/arch/sparc/kernel/sys32.S b/arch/sparc/kernel/sys32.S index f061c4d..3762f6c 100644 --- a/arch/sparc/kernel/sys32.S +++ b/arch/sparc/kernel/sys32.S @@ -121,7 +121,7 @@ SIGN2(sys32_syslog, sys_syslog, %o0, %o2) SIGN1(sys32_umask, sys_umask, %o0) SIGN3(sys32_tgkill, sys_tgkill, %o0, %o1, %o2) SIGN1(sys32_sendto, sys_sendto, %o0) -SIGN1(sys32_recvfrom, sys_recvfrom, %o0) +SIGN1(sys32_recvfrom, compat_sys_recvfrom, %o0) SIGN3(sys32_socket, sys_socket, %o0, %o1, %o2) SIGN2(sys32_connect, sys_connect, %o0, %o2) SIGN2(sys32_bind, sys_bind, %o0, %o2) diff --git a/include/linux/wireless.h b/include/linux/wireless.h index cb24204..5b4c6c7 100644 --- a/include/linux/wireless.h +++ b/include/linux/wireless.h @@ -1132,6 +1132,14 @@ struct __compat_iw_event { }; #define IW_EV_COMPAT_LCP_LEN offsetof(struct __compat_iw_event, pointer) #define IW_EV_COMPAT_POINT_OFF offsetof(struct compat_iw_point, length) + +/* Size of the various events for compat */ +#define IW_EV_COMPAT_CHAR_LEN (IW_EV_COMPAT_LCP_LEN + IFNAMSIZ) +#define IW_EV_COMPAT_UINT_LEN (IW_EV_COMPAT_LCP_LEN + sizeof(__u32)) +#define IW_EV_COMPAT_FREQ_LEN (IW_EV_COMPAT_LCP_LEN + sizeof(struct iw_freq)) +#define IW_EV_COMPAT_PARAM_LEN (IW_EV_COMPAT_LCP_LEN + sizeof(struct iw_param)) +#define IW_EV_COMPAT_ADDR_LEN (IW_EV_COMPAT_LCP_LEN + sizeof(struct sockaddr)) +#define IW_EV_COMPAT_QUAL_LEN (IW_EV_COMPAT_LCP_LEN + sizeof(struct iw_quality)) #define IW_EV_COMPAT_POINT_LEN \ (IW_EV_COMPAT_LCP_LEN + sizeof(struct compat_iw_point) - \ IW_EV_COMPAT_POINT_OFF) diff --git a/net/Kconfig b/net/Kconfig index 7051b97..041c35e 100644 --- a/net/Kconfig +++ b/net/Kconfig @@ -23,6 +23,26 @@ menuconfig NET if NET +config WANT_COMPAT_NETLINK_MESSAGES + bool + help + This option can be selected by other options that need compat + netlink messages. + +config COMPAT_NETLINK_MESSAGES + def_bool y + depends on COMPAT + depends on WIRELESS_EXT || WANT_COMPAT_NETLINK_MESSAGES + help + This option makes it possible to send different netlink messages + to tasks depending on whether the task is a compat task or not. To + achieve this, you need to set skb_shinfo(skb)->frag_list to the + compat skb before sending the skb, the netlink code will sort out + which message to actually pass to the task. + + Newly written code should NEVER need this option but do + compat-independent messages instead! + menu "Networking options" source "net/packet/Kconfig" diff --git a/net/compat.c b/net/compat.c index 8d73905..12728b1 100644 --- a/net/compat.c +++ b/net/compat.c @@ -743,6 +743,18 @@ asmlinkage long compat_sys_recvmsg(int fd, struct compat_msghdr __user *msg, uns return sys_recvmsg(fd, (struct msghdr __user *)msg, flags | MSG_CMSG_COMPAT); } +asmlinkage long compat_sys_recv(int fd, void __user *buf, size_t len, unsigned flags) +{ + return sys_recv(fd, buf, len, flags | MSG_CMSG_COMPAT); +} + +asmlinkage long compat_sys_recvfrom(int fd, void __user *buf, size_t len, + unsigned flags, struct sockaddr __user *addr, + int __user *addrlen) +{ + return sys_recvfrom(fd, buf, len, flags | MSG_CMSG_COMPAT, addr, addrlen); +} + asmlinkage long compat_sys_socketcall(int call, u32 __user *args) { int ret; @@ -788,10 +800,11 @@ asmlinkage long compat_sys_socketcall(int call, u32 __user *args) ret = sys_sendto(a0, compat_ptr(a1), a[2], a[3], compat_ptr(a[4]), a[5]); break; case SYS_RECV: - ret = sys_recv(a0, compat_ptr(a1), a[2], a[3]); + ret = compat_sys_recv(a0, compat_ptr(a1), a[2], a[3]); break; case SYS_RECVFROM: - ret = sys_recvfrom(a0, compat_ptr(a1), a[2], a[3], compat_ptr(a[4]), compat_ptr(a[5])); + ret = compat_sys_recvfrom(a0, compat_ptr(a1), a[2], a[3], + compat_ptr(a[4]), compat_ptr(a[5])); break; case SYS_SHUTDOWN: ret = sys_shutdown(a0,a1); diff --git a/net/netlink/af_netlink.c b/net/netlink/af_netlink.c index 2936fa3..336b53e 100644 --- a/net/netlink/af_netlink.c +++ b/net/netlink/af_netlink.c @@ -1356,7 +1356,7 @@ static int netlink_recvmsg(struct kiocb *kiocb, struct socket *sock, struct netlink_sock *nlk = nlk_sk(sk); int noblock = flags&MSG_DONTWAIT; size_t copied; - struct sk_buff *skb; + struct sk_buff *skb, *frag __maybe_unused = NULL; int err; if (flags&MSG_OOB) @@ -1368,6 +1368,35 @@ static int netlink_recvmsg(struct kiocb *kiocb, struct socket *sock, if (skb == NULL) goto out; +#ifdef CONFIG_COMPAT_NETLINK_MESSAGES + if (unlikely(skb_shinfo(skb)->frag_list)) { + bool need_compat = !!(flags & MSG_CMSG_COMPAT); + + /* + * If this skb has a frag_list, then here that means that + * we will have to use the frag_list skb for compat tasks + * and the regular skb for non-compat tasks. + * + * The skb might (and likely will) be cloned, so we can't + * just reset frag_list and go on with things -- we need to + * keep that. For the compat case that's easy -- simply get + * a reference to the compat skb and free the regular one + * including the frag. For the non-compat case, we need to + * avoid sending the frag to the user -- so assign NULL but + * restore it below before freeing the skb. + */ + if (need_compat) { + struct sk_buff *compskb = skb_shinfo(skb)->frag_list; + skb_get(compskb); + kfree_skb(skb); + skb = compskb; + } else { + frag = skb_shinfo(skb)->frag_list; + skb_shinfo(skb)->frag_list = NULL; + } + } +#endif + msg->msg_namelen = 0; copied = skb->len; @@ -1398,6 +1427,11 @@ static int netlink_recvmsg(struct kiocb *kiocb, struct socket *sock, siocb->scm->creds = *NETLINK_CREDS(skb); if (flags & MSG_TRUNC) copied = skb->len; + +#ifdef CONFIG_COMPAT_NETLINK_MESSAGES + skb_shinfo(skb)->frag_list = frag; +#endif + skb_free_datagram(sk, skb); if (nlk->cb && atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf / 2) diff --git a/net/wireless/wext.c b/net/wireless/wext.c index ee35e64..3fe3c2c 100644 --- a/net/wireless/wext.c +++ b/net/wireless/wext.c @@ -417,6 +417,21 @@ static const int event_type_size[] = { IW_EV_QUAL_LEN, /* IW_HEADER_TYPE_QUAL */ }; +#ifdef CONFIG_COMPAT +static const int compat_event_type_size[] = { + IW_EV_COMPAT_LCP_LEN, /* IW_HEADER_TYPE_NULL */ + 0, + IW_EV_COMPAT_CHAR_LEN, /* IW_HEADER_TYPE_CHAR */ + 0, + IW_EV_COMPAT_UINT_LEN, /* IW_HEADER_TYPE_UINT */ + IW_EV_COMPAT_FREQ_LEN, /* IW_HEADER_TYPE_FREQ */ + IW_EV_COMPAT_ADDR_LEN, /* IW_HEADER_TYPE_ADDR */ + 0, + IW_EV_COMPAT_POINT_LEN, /* Without variable payload */ + IW_EV_COMPAT_PARAM_LEN, /* IW_HEADER_TYPE_PARAM */ + IW_EV_COMPAT_QUAL_LEN, /* IW_HEADER_TYPE_QUAL */ +}; +#endif /************************ COMMON SUBROUTINES ************************/ /* @@ -1348,6 +1363,22 @@ void wireless_send_event(struct net_device * dev, struct sk_buff *skb; struct nlmsghdr *nlh; struct nlattr *nla; +#ifdef CONFIG_COMPAT + struct __compat_iw_event *compat_event; + struct compat_iw_point compat_wrqu; + struct sk_buff *compskb; +#endif + + /* + * Nothing in the kernel sends scan events with data, be safe. + * This is necessary because we cannot fix up scan event data + * for compat, due to being contained in 'extra', but normally + * applications are required to retrieve the scan data anyway + * and no data is included in the event, this codifies that + * practice. + */ + if (WARN_ON(cmd == SIOCGIWSCAN && extra)) + extra = NULL; /* Get the description of the Event */ if (cmd <= SIOCIWLAST) { @@ -1446,7 +1477,54 @@ void wireless_send_event(struct net_device * dev, memcpy(((char *) event) + hdr_len, extra, extra_len); nlmsg_end(skb, nlh); +#ifdef CONFIG_COMPAT + hdr_len = compat_event_type_size[descr->header_type]; + event_len = hdr_len + extra_len; + compskb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_ATOMIC); + if (!compskb) { + kfree_skb(skb); + return; + } + + /* Send via the RtNetlink event channel */ + nlh = rtnetlink_ifinfo_prep(dev, compskb); + if (WARN_ON(!nlh)) { + kfree_skb(skb); + kfree_skb(compskb); + return; + } + + /* Add the wireless events in the netlink packet */ + nla = nla_reserve(compskb, IFLA_WIRELESS, event_len); + if (!nla) { + kfree_skb(skb); + kfree_skb(compskb); + return; + } + compat_event = nla_data(nla); + + compat_event->len = event_len; + compat_event->cmd = cmd; + if (descr->header_type == IW_HEADER_TYPE_POINT) { + compat_wrqu.length = wrqu->data.length; + compat_wrqu.flags = wrqu->data.flags; + memcpy(&compat_event->pointer, + ((char *) &compat_wrqu) + IW_EV_COMPAT_POINT_OFF, + hdr_len - IW_EV_COMPAT_LCP_LEN); + if (extra_len) + memcpy(((char *) compat_event) + hdr_len, + extra, extra_len); + } else { + /* extra_len must be zero, so no if (extra) needed */ + memcpy(&compat_event->pointer, wrqu, + hdr_len - IW_EV_COMPAT_LCP_LEN); + } + + nlmsg_end(compskb, nlh); + + skb_shinfo(skb)->frag_list = compskb; +#endif skb_queue_tail(&dev_net(dev)->wext_nlevents, skb); schedule_work(&wireless_nlevent_work); } -- 1.6.0.4 From 12fd43db1ccb3ec687bd856ef784eaa8f28736a3 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Mon, 20 Jul 2009 16:56:12 -0700 Subject: [PATCH] netlink: use call_rcu for netlink_change_ngroups For the network namespace work in generic netlink I need to be able to call this function under rcu_read_lock(), otherwise the locking becomes a nightmare and more locks would be needed. Instead, just embed a struct rcu_head (actually a struct listeners_rcu_head that also carries the pointer to the memory block) into the listeners memory so we can use call_rcu() instead of synchronising and then freeing. No rcu_barrier() is needed since this code cannot be modular. Signed-off-by: Johannes Berg --- net/netlink/af_netlink.c | 34 ++++++++++++++++++++++++++++++---- 1 files changed, 30 insertions(+), 4 deletions(-) diff --git a/net/netlink/af_netlink.c b/net/netlink/af_netlink.c index 336b53e..57db069 100644 --- a/net/netlink/af_netlink.c +++ b/net/netlink/af_netlink.c @@ -83,6 +83,11 @@ struct netlink_sock { struct module *module; }; +struct listeners_rcu_head { + struct rcu_head rcu_head; + void *ptr; +}; + #define NETLINK_KERNEL_SOCKET 0x1 #define NETLINK_RECV_PKTINFO 0x2 #define NETLINK_BROADCAST_SEND_ERROR 0x4 @@ -1487,7 +1492,8 @@ netlink_kernel_create(struct net *net, int unit, unsigned int groups, if (groups < 32) groups = 32; - listeners = kzalloc(NLGRPSZ(groups), GFP_KERNEL); + listeners = kzalloc(NLGRPSZ(groups) + sizeof(struct listeners_rcu_head), + GFP_KERNEL); if (!listeners) goto out_sock_release; @@ -1535,6 +1541,14 @@ netlink_kernel_release(struct sock *sk) EXPORT_SYMBOL(netlink_kernel_release); +static void netlink_free_old_listeners(struct rcu_head *rcu_head) +{ + struct listeners_rcu_head *lrh; + + lrh = container_of(rcu_head, struct listeners_rcu_head, rcu_head); + kfree(lrh->ptr); +} + /** * netlink_change_ngroups - change number of multicast groups * @@ -1550,6 +1564,7 @@ EXPORT_SYMBOL(netlink_kernel_release); int netlink_change_ngroups(struct sock *sk, unsigned int groups) { unsigned long *listeners, *old = NULL; + struct listeners_rcu_head *old_rcu_head; struct netlink_table *tbl = &nl_table[sk->sk_protocol]; int err = 0; @@ -1558,7 +1573,9 @@ int netlink_change_ngroups(struct sock *sk, unsigned int groups) netlink_table_grab(); if (NLGRPSZ(tbl->groups) < NLGRPSZ(groups)) { - listeners = kzalloc(NLGRPSZ(groups), GFP_ATOMIC); + listeners = kzalloc(NLGRPSZ(groups) + + sizeof(struct listeners_rcu_head), + GFP_ATOMIC); if (!listeners) { err = -ENOMEM; goto out_ungrab; @@ -1566,13 +1583,22 @@ int netlink_change_ngroups(struct sock *sk, unsigned int groups) old = tbl->listeners; memcpy(listeners, old, NLGRPSZ(tbl->groups)); rcu_assign_pointer(tbl->listeners, listeners); + /* + * Free the old memory after an RCU grace period so we + * don't leak it. We use call_rcu() here in order to be + * able to call this function from atomic contexts. The + * allocation of this memory will have reserved enough + * space for struct listeners_rcu_head at the end. + */ + old_rcu_head = (void *)(tbl->listeners + + NLGRPLONGS(tbl->groups)); + old_rcu_head->ptr = old; + call_rcu(&old_rcu_head->rcu_head, netlink_free_old_listeners); } tbl->groups = groups; out_ungrab: netlink_table_ungrab(); - synchronize_rcu(); - kfree(old); return err; } EXPORT_SYMBOL(netlink_change_ngroups); -- 1.6.0.4 From 614ae086d8a8b300c2a4595964d294bdb5be5ba8 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Mon, 20 Jul 2009 16:56:34 -0700 Subject: [PATCH] net: make namespace iteration possible under RCU All we need to take care of is using proper RCU list add/del primitives and inserting a synchronize_rcu() at one place to make sure the exit notifiers are run after everybody has stopped iterating the list. Signed-off-by: Johannes Berg --- include/net/net_namespace.h | 3 +++ net/core/net_namespace.c | 14 +++++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/include/net/net_namespace.h b/include/net/net_namespace.h index b3646db..ba781b0 100644 --- a/include/net/net_namespace.h +++ b/include/net/net_namespace.h @@ -211,6 +211,9 @@ static inline struct net *read_pnet(struct net * const *pnet) #define for_each_net(VAR) \ list_for_each_entry(VAR, &net_namespace_list, list) +#define for_each_net_rcu(VAR) \ + list_for_each_entry_rcu(VAR, &net_namespace_list, list) + #ifdef CONFIG_NET_NS #define __net_init #define __net_exit diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c index b7292a2..5cd0b22 100644 --- a/net/core/net_namespace.c +++ b/net/core/net_namespace.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -127,7 +128,7 @@ static struct net *net_create(void) rv = setup_net(net); if (rv == 0) { rtnl_lock(); - list_add_tail(&net->list, &net_namespace_list); + list_add_tail_rcu(&net->list, &net_namespace_list); rtnl_unlock(); } mutex_unlock(&net_mutex); @@ -156,9 +157,16 @@ static void cleanup_net(struct work_struct *work) /* Don't let anyone else find us. */ rtnl_lock(); - list_del(&net->list); + list_del_rcu(&net->list); rtnl_unlock(); + /* + * Another CPU might be rcu-iterating the list, wait for it. + * This needs to be before calling the exit() notifiers, so + * the rcu_barrier() below isn't sufficient alone. + */ + synchronize_rcu(); + /* Run all of the network namespace exit methods */ list_for_each_entry_reverse(ops, &pernet_list, list) { if (ops->exit) @@ -219,7 +227,7 @@ static int __init net_ns_init(void) panic("Could not setup the initial network namespace"); rtnl_lock(); - list_add_tail(&init_net.list, &net_namespace_list); + list_add_tail_rcu(&init_net.list, &net_namespace_list); rtnl_unlock(); mutex_unlock(&net_mutex); -- 1.6.0.4 From b11b2965308b63d184f23c180221275ac5ac9a1c Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Mon, 20 Jul 2009 16:56:37 -0700 Subject: [PATCH] genetlink: make netns aware This makes generic netlink network namespace aware. No generic netlink families except for the controller family are made namespace aware, they need to be checked one by one and then set the family->netnsok member to true. A new function genlmsg_multicast_netns() is introduced to allow sending a multicast message in a given namespace, for example when it applies to an object that lives in that namespace, a new function genlmsg_multicast_allns() to send a message to all network namespaces (for objects that do not have an associated netns). The function genlmsg_multicast() is changed to multicast the message in just init_net, which is currently correct for all generic netlink families since they only work in init_net right now. Some will later want to work in all net namespaces because they do not care about the netns at all -- those will have to be converted to use one of the new functions genlmsg_multicast_allns() or genlmsg_multicast_netns() whenever they are made netns aware in some way. After this patch families can easily decide whether or not they should be available in all net namespaces. Many genl families us it for objects not related to networking and should therefore be available in all namespaces, but that will have to be done on a per family basis. Note that this doesn't touch on the checkpoint/restart problem where network namespaces could be used, genl families and multicast groups are numbered globally and I see no easy way of changing that, especially since it must be possible to multicast to all network namespaces for those families that do not care about netns. Signed-off-by: Johannes Berg --- fs/dlm/netlink.c | 2 +- include/net/genetlink.h | 66 +++++++++++++-- include/net/net_namespace.h | 2 + kernel/taskstats.c | 10 +- net/irda/irnetlink.c | 2 +- net/netfilter/ipvs/ip_vs_ctl.c | 2 +- net/netlink/genetlink.c | 186 +++++++++++++++++++++++++++++++--------- net/tipc/netlink.c | 2 +- net/wireless/nl80211.c | 14 ++-- 9 files changed, 223 insertions(+), 63 deletions(-) diff --git a/fs/dlm/netlink.c b/fs/dlm/netlink.c index ccc9d62..55ea369 100644 --- a/fs/dlm/netlink.c +++ b/fs/dlm/netlink.c @@ -63,7 +63,7 @@ static int send_data(struct sk_buff *skb) return rv; } - return genlmsg_unicast(skb, listener_nlpid); + return genlmsg_unicast(&init_net, skb, listener_nlpid); } static int user_cmd(struct sk_buff *skb, struct genl_info *info) diff --git a/include/net/genetlink.h b/include/net/genetlink.h index 1b0e3ee..2a1c068 100644 --- a/include/net/genetlink.h +++ b/include/net/genetlink.h @@ -3,6 +3,7 @@ #include #include +#include /** * struct genl_multicast_group - generic netlink multicast group @@ -27,6 +28,8 @@ struct genl_multicast_group * @name: name of family * @version: protocol version * @maxattr: maximum number of attributes supported + * @netnsok: set to true if the family can handle network + * namespaces and should be presented in all of them * @attrbuf: buffer to store parsed attributes * @ops_list: list of all assigned operations * @family_list: family list @@ -39,6 +42,7 @@ struct genl_family char name[GENL_NAMSIZ]; unsigned int version; unsigned int maxattr; + bool netnsok; struct nlattr ** attrbuf; /* private */ struct list_head ops_list; /* private */ struct list_head family_list; /* private */ @@ -62,8 +66,32 @@ struct genl_info struct genlmsghdr * genlhdr; void * userhdr; struct nlattr ** attrs; +#ifdef CONFIG_NET_NS + struct net * _net; +#endif }; +#ifdef CONFIG_NET_NS +static inline struct net *genl_info_net(struct genl_info *info) +{ + return info->_net; +} + +static inline void genl_info_net_set(struct genl_info *info, struct net *net) +{ + info->_net = net; +} +#else +static inline struct net *genl_info_net(struct genl_info *info) +{ + return &init_net; +} + +static inline void genl_info_net_set(struct genl_info *info, struct net *net) +{ +} +#endif + /** * struct genl_ops - generic netlink operations * @cmd: command identifier @@ -98,8 +126,6 @@ extern int genl_register_mc_group(struct genl_family *family, extern void genl_unregister_mc_group(struct genl_family *family, struct genl_multicast_group *grp); -extern struct sock *genl_sock; - /** * genlmsg_put - Add generic netlink header to netlink message * @skb: socket buffer holding the message @@ -170,7 +196,21 @@ static inline void genlmsg_cancel(struct sk_buff *skb, void *hdr) } /** - * genlmsg_multicast - multicast a netlink message + * genlmsg_multicast_netns - multicast a netlink message to a specific netns + * @net: the net namespace + * @skb: netlink message as socket buffer + * @pid: own netlink pid to avoid sending to yourself + * @group: multicast group id + * @flags: allocation flags + */ +static inline int genlmsg_multicast_netns(struct net *net, struct sk_buff *skb, + u32 pid, unsigned int group, gfp_t flags) +{ + return nlmsg_multicast(net->genl_sock, skb, pid, group, flags); +} + +/** + * genlmsg_multicast - multicast a netlink message to the default netns * @skb: netlink message as socket buffer * @pid: own netlink pid to avoid sending to yourself * @group: multicast group id @@ -179,17 +219,29 @@ static inline void genlmsg_cancel(struct sk_buff *skb, void *hdr) static inline int genlmsg_multicast(struct sk_buff *skb, u32 pid, unsigned int group, gfp_t flags) { - return nlmsg_multicast(genl_sock, skb, pid, group, flags); + return genlmsg_multicast_netns(&init_net, skb, pid, group, flags); } /** + * genlmsg_multicast_allns - multicast a netlink message to all net namespaces + * @skb: netlink message as socket buffer + * @pid: own netlink pid to avoid sending to yourself + * @group: multicast group id + * @flags: allocation flags + * + * This function must hold the RTNL or rcu_read_lock(). + */ +int genlmsg_multicast_allns(struct sk_buff *skb, u32 pid, + unsigned int group, gfp_t flags); + +/** * genlmsg_unicast - unicast a netlink message * @skb: netlink message as socket buffer * @pid: netlink pid of the destination socket */ -static inline int genlmsg_unicast(struct sk_buff *skb, u32 pid) +static inline int genlmsg_unicast(struct net *net, struct sk_buff *skb, u32 pid) { - return nlmsg_unicast(genl_sock, skb, pid); + return nlmsg_unicast(net->genl_sock, skb, pid); } /** @@ -199,7 +251,7 @@ static inline int genlmsg_unicast(struct sk_buff *skb, u32 pid) */ static inline int genlmsg_reply(struct sk_buff *skb, struct genl_info *info) { - return genlmsg_unicast(skb, info->snd_pid); + return genlmsg_unicast(genl_info_net(info), skb, info->snd_pid); } /** diff --git a/include/net/net_namespace.h b/include/net/net_namespace.h index ba781b0..33da326 100644 --- a/include/net/net_namespace.h +++ b/include/net/net_namespace.h @@ -26,6 +26,7 @@ struct net_device; struct sock; struct ctl_table_header; struct net_generic; +struct sock; struct net { atomic_t count; /* To decided when the network @@ -57,6 +58,7 @@ struct net { spinlock_t rules_mod_lock; struct sock *rtnl; /* rtnetlink socket */ + struct sock *genl_sock; struct netns_core core; struct netns_mib mib; diff --git a/kernel/taskstats.c b/kernel/taskstats.c index 888adbc..ea8384d 100644 --- a/kernel/taskstats.c +++ b/kernel/taskstats.c @@ -108,7 +108,7 @@ static int prepare_reply(struct genl_info *info, u8 cmd, struct sk_buff **skbp, /* * Send taskstats data in @skb to listener with nl_pid @pid */ -static int send_reply(struct sk_buff *skb, pid_t pid) +static int send_reply(struct sk_buff *skb, struct genl_info *info) { struct genlmsghdr *genlhdr = nlmsg_data(nlmsg_hdr(skb)); void *reply = genlmsg_data(genlhdr); @@ -120,7 +120,7 @@ static int send_reply(struct sk_buff *skb, pid_t pid) return rc; } - return genlmsg_unicast(skb, pid); + return genlmsg_reply(skb, info); } /* @@ -150,7 +150,7 @@ static void send_cpu_listeners(struct sk_buff *skb, if (!skb_next) break; } - rc = genlmsg_unicast(skb_cur, s->pid); + rc = genlmsg_unicast(&init_net, skb_cur, s->pid); if (rc == -ECONNREFUSED) { s->valid = 0; delcount++; @@ -418,7 +418,7 @@ static int cgroupstats_user_cmd(struct sk_buff *skb, struct genl_info *info) goto err; } - rc = send_reply(rep_skb, info->snd_pid); + rc = send_reply(rep_skb, info); err: fput_light(file, fput_needed); @@ -487,7 +487,7 @@ free_return_rc: } else goto err; - return send_reply(rep_skb, info->snd_pid); + return send_reply(rep_skb, info); err: nlmsg_free(rep_skb); return rc; diff --git a/net/irda/irnetlink.c b/net/irda/irnetlink.c index 8dd7ed7..476b307 100644 --- a/net/irda/irnetlink.c +++ b/net/irda/irnetlink.c @@ -115,7 +115,7 @@ static int irda_nl_get_mode(struct sk_buff *skb, struct genl_info *info) genlmsg_end(msg, hdr); - return genlmsg_unicast(msg, info->snd_pid); + return genlmsg_reply(msg, info); err_out: nlmsg_free(msg); diff --git a/net/netfilter/ipvs/ip_vs_ctl.c b/net/netfilter/ipvs/ip_vs_ctl.c index 7c1333c..2d24d81 100644 --- a/net/netfilter/ipvs/ip_vs_ctl.c +++ b/net/netfilter/ipvs/ip_vs_ctl.c @@ -3231,7 +3231,7 @@ static int ip_vs_genl_get_cmd(struct sk_buff *skb, struct genl_info *info) } genlmsg_end(msg, reply); - ret = genlmsg_unicast(msg, info->snd_pid); + ret = genlmsg_reply(msg, info); goto out; nla_put_failure: diff --git a/net/netlink/genetlink.c b/net/netlink/genetlink.c index eed4c6a..575c643 100644 --- a/net/netlink/genetlink.c +++ b/net/netlink/genetlink.c @@ -18,8 +18,6 @@ #include #include -struct sock *genl_sock = NULL; - static DEFINE_MUTEX(genl_mutex); /* serialization of message processing */ static inline void genl_lock(void) @@ -175,10 +173,31 @@ int genl_register_mc_group(struct genl_family *family, mc_groups_longs++; } - err = netlink_change_ngroups(genl_sock, - mc_groups_longs * BITS_PER_LONG); - if (err) - goto out; + if (family->netnsok) { + struct net *net; + + rcu_read_lock(); + for_each_net_rcu(net) { + err = netlink_change_ngroups(net->genl_sock, + mc_groups_longs * BITS_PER_LONG); + if (err) { + /* + * No need to roll back, can only fail if + * memory allocation fails and then the + * number of _possible_ groups has been + * increased on some sockets which is ok. + */ + rcu_read_unlock(); + goto out; + } + } + rcu_read_unlock(); + } else { + err = netlink_change_ngroups(init_net.genl_sock, + mc_groups_longs * BITS_PER_LONG); + if (err) + goto out; + } grp->id = id; set_bit(id, mc_groups); @@ -195,8 +214,14 @@ EXPORT_SYMBOL(genl_register_mc_group); static void __genl_unregister_mc_group(struct genl_family *family, struct genl_multicast_group *grp) { + struct net *net; BUG_ON(grp->family != family); - netlink_clear_multicast_users(genl_sock, grp->id); + + rcu_read_lock(); + for_each_net_rcu(net) + netlink_clear_multicast_users(net->genl_sock, grp->id); + rcu_read_unlock(); + clear_bit(grp->id, mc_groups); list_del(&grp->list); genl_ctrl_event(CTRL_CMD_DELMCAST_GRP, grp); @@ -467,6 +492,7 @@ static int genl_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh) { struct genl_ops *ops; struct genl_family *family; + struct net *net = sock_net(skb->sk); struct genl_info info; struct genlmsghdr *hdr = nlmsg_data(nlh); int hdrlen, err; @@ -475,6 +501,10 @@ static int genl_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh) if (family == NULL) return -ENOENT; + /* this family doesn't exist in this netns */ + if (!family->netnsok && !net_eq(net, &init_net)) + return -ENOENT; + hdrlen = GENL_HDRLEN + family->hdrsize; if (nlh->nlmsg_len < nlmsg_msg_size(hdrlen)) return -EINVAL; @@ -492,7 +522,7 @@ static int genl_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh) return -EOPNOTSUPP; genl_unlock(); - err = netlink_dump_start(genl_sock, skb, nlh, + err = netlink_dump_start(net->genl_sock, skb, nlh, ops->dumpit, ops->done); genl_lock(); return err; @@ -514,6 +544,7 @@ static int genl_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh) info.genlhdr = nlmsg_data(nlh); info.userhdr = nlmsg_data(nlh) + GENL_HDRLEN; info.attrs = family->attrbuf; + genl_info_net_set(&info, net); return ops->doit(skb, &info); } @@ -534,6 +565,7 @@ static struct genl_family genl_ctrl = { .name = "nlctrl", .version = 0x2, .maxattr = CTRL_ATTR_MAX, + .netnsok = true, }; static int ctrl_fill_info(struct genl_family *family, u32 pid, u32 seq, @@ -650,6 +682,7 @@ static int ctrl_dumpfamily(struct sk_buff *skb, struct netlink_callback *cb) int i, n = 0; struct genl_family *rt; + struct net *net = sock_net(skb->sk); int chains_to_skip = cb->args[0]; int fams_to_skip = cb->args[1]; @@ -658,6 +691,8 @@ static int ctrl_dumpfamily(struct sk_buff *skb, struct netlink_callback *cb) continue; n = 0; list_for_each_entry(rt, genl_family_chain(i), family_list) { + if (!rt->netnsok && !net_eq(net, &init_net)) + continue; if (++n < fams_to_skip) continue; if (ctrl_fill_info(rt, NETLINK_CB(cb->skb).pid, @@ -729,6 +764,7 @@ static int ctrl_getfamily(struct sk_buff *skb, struct genl_info *info) if (info->attrs[CTRL_ATTR_FAMILY_ID]) { u16 id = nla_get_u16(info->attrs[CTRL_ATTR_FAMILY_ID]); res = genl_family_find_byid(id); + err = -ENOENT; } if (info->attrs[CTRL_ATTR_FAMILY_NAME]) { @@ -736,49 +772,61 @@ static int ctrl_getfamily(struct sk_buff *skb, struct genl_info *info) name = nla_data(info->attrs[CTRL_ATTR_FAMILY_NAME]); res = genl_family_find_byname(name); + err = -ENOENT; } - if (res == NULL) { - err = -ENOENT; - goto errout; + if (res == NULL) + return err; + + if (!res->netnsok && !net_eq(genl_info_net(info), &init_net)) { + /* family doesn't exist here */ + return -ENOENT; } msg = ctrl_build_family_msg(res, info->snd_pid, info->snd_seq, CTRL_CMD_NEWFAMILY); - if (IS_ERR(msg)) { - err = PTR_ERR(msg); - goto errout; - } + if (IS_ERR(msg)) + return PTR_ERR(msg); - err = genlmsg_reply(msg, info); -errout: - return err; + return genlmsg_reply(msg, info); } static int genl_ctrl_event(int event, void *data) { struct sk_buff *msg; + struct genl_family *family; + struct genl_multicast_group *grp; - if (genl_sock == NULL) + /* genl is still initialising */ + if (!init_net.genl_sock) return 0; switch (event) { case CTRL_CMD_NEWFAMILY: case CTRL_CMD_DELFAMILY: - msg = ctrl_build_family_msg(data, 0, 0, event); - if (IS_ERR(msg)) - return PTR_ERR(msg); - - genlmsg_multicast(msg, 0, GENL_ID_CTRL, GFP_KERNEL); + family = data; + msg = ctrl_build_family_msg(family, 0, 0, event); break; case CTRL_CMD_NEWMCAST_GRP: case CTRL_CMD_DELMCAST_GRP: + grp = data; + family = grp->family; msg = ctrl_build_mcgrp_msg(data, 0, 0, event); - if (IS_ERR(msg)) - return PTR_ERR(msg); - - genlmsg_multicast(msg, 0, GENL_ID_CTRL, GFP_KERNEL); break; + default: + return -EINVAL; + } + + if (IS_ERR(msg)) + return PTR_ERR(msg); + + if (!family->netnsok) { + genlmsg_multicast_netns(&init_net, msg, 0, + GENL_ID_CTRL, GFP_KERNEL); + } else { + rcu_read_lock(); + genlmsg_multicast_allns(msg, 0, GENL_ID_CTRL, GFP_ATOMIC); + rcu_read_unlock(); } return 0; @@ -795,6 +843,33 @@ static struct genl_multicast_group notify_grp = { .name = "notify", }; +static int __net_init genl_pernet_init(struct net *net) +{ + /* we'll bump the group number right afterwards */ + net->genl_sock = netlink_kernel_create(net, NETLINK_GENERIC, 0, + genl_rcv, &genl_mutex, + THIS_MODULE); + + if (!net->genl_sock && net_eq(net, &init_net)) + panic("GENL: Cannot initialize generic netlink\n"); + + if (!net->genl_sock) + return -ENOMEM; + + return 0; +} + +static void __net_exit genl_pernet_exit(struct net *net) +{ + netlink_kernel_release(net->genl_sock); + net->genl_sock = NULL; +} + +static struct pernet_operations genl_pernet_ops = { + .init = genl_pernet_init, + .exit = genl_pernet_exit, +}; + static int __init genl_init(void) { int i, err; @@ -804,36 +879,67 @@ static int __init genl_init(void) err = genl_register_family(&genl_ctrl); if (err < 0) - goto errout; + goto problem; err = genl_register_ops(&genl_ctrl, &genl_ctrl_ops); if (err < 0) - goto errout_register; + goto problem; netlink_set_nonroot(NETLINK_GENERIC, NL_NONROOT_RECV); - /* we'll bump the group number right afterwards */ - genl_sock = netlink_kernel_create(&init_net, NETLINK_GENERIC, 0, - genl_rcv, &genl_mutex, THIS_MODULE); - if (genl_sock == NULL) - panic("GENL: Cannot initialize generic netlink\n"); + err = register_pernet_subsys(&genl_pernet_ops); + if (err) + goto problem; err = genl_register_mc_group(&genl_ctrl, ¬ify_grp); if (err < 0) - goto errout_register; + goto problem; return 0; -errout_register: - genl_unregister_family(&genl_ctrl); -errout: +problem: panic("GENL: Cannot register controller: %d\n", err); } subsys_initcall(genl_init); -EXPORT_SYMBOL(genl_sock); EXPORT_SYMBOL(genl_register_ops); EXPORT_SYMBOL(genl_unregister_ops); EXPORT_SYMBOL(genl_register_family); EXPORT_SYMBOL(genl_unregister_family); + +static int genlmsg_mcast(struct sk_buff *skb, u32 pid, unsigned long group, + gfp_t flags) +{ + struct sk_buff *tmp; + struct net *net, *prev = NULL; + int err; + + for_each_net_rcu(net) { + if (prev) { + tmp = skb_clone(skb, flags); + if (!tmp) { + err = -ENOMEM; + goto error; + } + err = nlmsg_multicast(prev->genl_sock, tmp, + pid, group, flags); + if (err) + goto error; + } + + prev = net; + } + + return nlmsg_multicast(prev->genl_sock, skb, pid, group, flags); + error: + kfree_skb(skb); + return err; +} + +int genlmsg_multicast_allns(struct sk_buff *skb, u32 pid, unsigned int group, + gfp_t flags) +{ + return genlmsg_mcast(skb, pid, group, flags); +} +EXPORT_SYMBOL(genlmsg_multicast_allns); diff --git a/net/tipc/netlink.c b/net/tipc/netlink.c index 3c57005..7bda8e3 100644 --- a/net/tipc/netlink.c +++ b/net/tipc/netlink.c @@ -62,7 +62,7 @@ static int handle_cmd(struct sk_buff *skb, struct genl_info *info) rep_nlh = nlmsg_hdr(rep_buf); memcpy(rep_nlh, req_nlh, hdr_space); rep_nlh->nlmsg_len = rep_buf->len; - genlmsg_unicast(rep_buf, NETLINK_CB(skb).pid); + genlmsg_unicast(&init_net, rep_buf, NETLINK_CB(skb).pid); } return 0; diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 9beaed8..da450ef 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -596,7 +596,7 @@ static int nl80211_get_wiphy(struct sk_buff *skb, struct genl_info *info) cfg80211_unlock_rdev(dev); - return genlmsg_unicast(msg, info->snd_pid); + return genlmsg_reply(msg, info); out_free: nlmsg_free(msg); @@ -922,7 +922,7 @@ static int nl80211_get_interface(struct sk_buff *skb, struct genl_info *info) dev_put(netdev); cfg80211_unlock_rdev(dev); - return genlmsg_unicast(msg, info->snd_pid); + return genlmsg_reply(msg, info); out_free: nlmsg_free(msg); @@ -1236,7 +1236,7 @@ static int nl80211_get_key(struct sk_buff *skb, struct genl_info *info) goto nla_put_failure; genlmsg_end(msg, hdr); - err = genlmsg_unicast(msg, info->snd_pid); + err = genlmsg_reply(msg, info); goto out; nla_put_failure: @@ -1812,7 +1812,7 @@ static int nl80211_get_station(struct sk_buff *skb, struct genl_info *info) dev, mac_addr, &sinfo) < 0) goto out_free; - err = genlmsg_unicast(msg, info->snd_pid); + err = genlmsg_reply(msg, info); goto out; out_free: @@ -2281,7 +2281,7 @@ static int nl80211_get_mpath(struct sk_buff *skb, struct genl_info *info) dev, dst, next_hop, &pinfo) < 0) goto out_free; - err = genlmsg_unicast(msg, info->snd_pid); + err = genlmsg_reply(msg, info); goto out; out_free: @@ -2630,7 +2630,7 @@ static int nl80211_get_mesh_params(struct sk_buff *skb, cur_params.dot11MeshHWMPnetDiameterTraversalTime); nla_nest_end(msg, pinfoattr); genlmsg_end(msg, hdr); - err = genlmsg_unicast(msg, info->snd_pid); + err = genlmsg_reply(msg, info); goto out; nla_put_failure: @@ -2818,7 +2818,7 @@ static int nl80211_get_reg(struct sk_buff *skb, struct genl_info *info) nla_nest_end(msg, nl_reg_rules); genlmsg_end(msg, hdr); - err = genlmsg_unicast(msg, info->snd_pid); + err = genlmsg_reply(msg, info); goto out; nla_put_failure: -- 1.6.0.4 From 3c090f2497dd3e482bcd59ee2fb1c429492e2f3a Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Mon, 20 Jul 2009 16:56:40 -0700 Subject: [PATCH] net: move and export get_net_ns_by_pid The function get_net_ns_by_pid(), to get a network namespace from a pid_t, will be required in cfg80211 as well. Therefore, let's move it to net_namespace.c and export it. We can't make it a static inline in the !NETNS case because it needs to verify that the given pid even exists (and return -ESRCH). Signed-off-by: Johannes Berg --- include/net/net_namespace.h | 2 ++ net/core/net_namespace.c | 21 +++++++++++++++++++++ net/core/rtnetlink.c | 21 +-------------------- 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/include/net/net_namespace.h b/include/net/net_namespace.h index 33da326..5c5136f 100644 --- a/include/net/net_namespace.h +++ b/include/net/net_namespace.h @@ -111,6 +111,8 @@ static inline struct net *copy_net_ns(unsigned long flags, struct net *net_ns) extern struct list_head net_namespace_list; +extern struct net *get_net_ns_by_pid(pid_t pid); + #ifdef CONFIG_NET_NS extern void __put_net(struct net *net); diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c index 5cd0b22..ddd2cd2 100644 --- a/net/core/net_namespace.c +++ b/net/core/net_namespace.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -201,6 +202,26 @@ struct net *copy_net_ns(unsigned long flags, struct net *old_net) } #endif +struct net *get_net_ns_by_pid(pid_t pid) +{ + struct task_struct *tsk; + struct net *net; + + /* Lookup the network namespace */ + net = ERR_PTR(-ESRCH); + rcu_read_lock(); + tsk = find_task_by_vpid(pid); + if (tsk) { + struct nsproxy *nsproxy; + nsproxy = task_nsproxy(tsk); + if (nsproxy) + net = get_net(nsproxy->net_ns); + } + rcu_read_unlock(); + return net; +} +EXPORT_SYMBOL_GPL(get_net_ns_by_pid); + static int __init net_ns_init(void) { struct net_generic *ng; diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c index d78030f..b44775f 100644 --- a/net/core/rtnetlink.c +++ b/net/core/rtnetlink.c @@ -35,7 +35,6 @@ #include #include #include -#include #include #include @@ -52,6 +51,7 @@ #include #include #include +#include struct rtnl_link { @@ -725,25 +725,6 @@ static const struct nla_policy ifla_info_policy[IFLA_INFO_MAX+1] = { [IFLA_INFO_DATA] = { .type = NLA_NESTED }, }; -static struct net *get_net_ns_by_pid(pid_t pid) -{ - struct task_struct *tsk; - struct net *net; - - /* Lookup the network namespace */ - net = ERR_PTR(-ESRCH); - rcu_read_lock(); - tsk = find_task_by_vpid(pid); - if (tsk) { - struct nsproxy *nsproxy; - nsproxy = task_nsproxy(tsk); - if (nsproxy) - net = get_net(nsproxy->net_ns); - } - rcu_read_unlock(); - return net; -} - static int validate_linkmsg(struct net_device *dev, struct nlattr *tb[]) { if (dev) { -- 1.6.0.4 From 0259e79ed7c3cf64f8953b2536668e79fe54ad1c Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 21 Jul 2009 16:55:25 -0700 Subject: [PATCH] mac80211: cooperate more with network namespaces There are still two places in mac80211 that hardcode the initial net namespace (init_net). One of them is mandated by cfg80211 and will be removed by a separate patch, the other one is used for finding the network device of a pending packet via its ifindex. Remove the latter use by keeping track of the device pointer itself, via the vif pointer, and avoid it going stale by dropping pending frames for a given interface when the interface is removed. To keep track of the vif pointer for the correct interface, change the info->control.vif pointer's internal use to always be the correct vif, and only move it to the vif the driver expects (or NULL for monitor interfaces and injected packets) right before giving the packet to the driver. Signed-off-by: Johannes Berg --- net/mac80211/iface.c | 16 +++++++++- net/mac80211/rx.c | 2 +- net/mac80211/tx.c | 78 ++++++++++++++++++++----------------------------- 3 files changed, 48 insertions(+), 48 deletions(-) diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c index 2f797a8..559d698 100644 --- a/net/mac80211/iface.c +++ b/net/mac80211/iface.c @@ -335,7 +335,10 @@ static int ieee80211_stop(struct net_device *dev) struct ieee80211_local *local = sdata->local; struct ieee80211_if_init_conf conf; struct sta_info *sta; + unsigned long flags; + struct sk_buff *skb, *tmp; u32 hw_reconf_flags = 0; + int i; /* * Stop TX on this interface first. @@ -551,6 +554,18 @@ static int ieee80211_stop(struct net_device *dev) if (hw_reconf_flags) ieee80211_hw_config(local, hw_reconf_flags); + spin_lock_irqsave(&local->queue_stop_reason_lock, flags); + for (i = 0; i < IEEE80211_MAX_QUEUES; i++) { + skb_queue_walk_safe(&local->pending[i], skb, tmp) { + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + if (info->control.vif == &sdata->vif) { + __skb_unlink(skb, &local->pending[i]); + dev_kfree_skb_irq(skb); + } + } + } + spin_unlock_irqrestore(&local->queue_stop_reason_lock, flags); + return 0; } @@ -788,7 +803,6 @@ int ieee80211_if_add(struct ieee80211_local *local, const char *name, memcpy(ndev->dev_addr, local->hw.wiphy->perm_addr, ETH_ALEN); SET_NETDEV_DEV(ndev, wiphy_dev(local->hw.wiphy)); - ndev->features |= NETIF_F_NETNS_LOCAL; /* don't use IEEE80211_DEV_TO_SUB_IF because it checks too much */ sdata = netdev_priv(ndev); diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c index 66c797c..d9df819 100644 --- a/net/mac80211/rx.c +++ b/net/mac80211/rx.c @@ -1539,7 +1539,7 @@ ieee80211_rx_h_mesh_fwding(struct ieee80211_rx_data *rx) info = IEEE80211_SKB_CB(fwd_skb); memset(info, 0, sizeof(*info)); info->flags |= IEEE80211_TX_INTFL_NEED_TXPROCESSING; - fwd_skb->iif = rx->dev->ifindex; + info->control.vif = &rx->sdata->vif; ieee80211_select_queue(local, fwd_skb); if (is_multicast_ether_addr(fwd_hdr->addr3)) memcpy(fwd_hdr->addr1, fwd_hdr->addr3, diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index c11cbbd..6847632 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -400,6 +400,7 @@ ieee80211_tx_h_unicast_ps_buf(struct ieee80211_tx_data *tx) sta_info_set_tim_bit(sta); info->control.jiffies = jiffies; + info->control.vif = &tx->sdata->vif; info->flags |= IEEE80211_TX_INTFL_NEED_TXPROCESSING; skb_queue_tail(&sta->ps_tx_buf, tx->skb); return TX_QUEUED; @@ -696,7 +697,7 @@ ieee80211_tx_h_sequence(struct ieee80211_tx_data *tx) * number, if we have no matching interface then we * neither assign one ourselves nor ask the driver to. */ - if (unlikely(!info->control.vif)) + if (unlikely(info->control.vif->type == NL80211_IFTYPE_MONITOR)) return TX_CONTINUE; if (unlikely(ieee80211_is_ctl(hdr->frame_control))) @@ -1092,6 +1093,7 @@ ieee80211_tx_prepare(struct ieee80211_sub_if_data *sdata, } else if (*state != HT_AGG_STATE_IDLE) { /* in progress */ queued = true; + info->control.vif = &sdata->vif; info->flags |= IEEE80211_TX_INTFL_NEED_TXPROCESSING; __skb_queue_tail(&tid_tx->pending, skb); } @@ -1143,6 +1145,7 @@ static int __ieee80211_tx(struct ieee80211_local *local, { struct sk_buff *skb = *skbp, *next; struct ieee80211_tx_info *info; + struct ieee80211_sub_if_data *sdata; unsigned long flags; int ret, len; bool fragm = false; @@ -1167,13 +1170,31 @@ static int __ieee80211_tx(struct ieee80211_local *local, next = skb->next; len = skb->len; + + sdata = vif_to_sdata(info->control.vif); + + switch (sdata->vif.type) { + case NL80211_IFTYPE_MONITOR: + info->control.vif = NULL; + break; + case NL80211_IFTYPE_AP_VLAN: + info->control.vif = &container_of(sdata->bss, + struct ieee80211_sub_if_data, u.ap)->vif; + break; + default: + /* keep */ + break; + } + ret = drv_tx(local, skb); if (WARN_ON(ret != NETDEV_TX_OK && skb->len != len)) { dev_kfree_skb(skb); ret = NETDEV_TX_OK; } - if (ret != NETDEV_TX_OK) + if (ret != NETDEV_TX_OK) { + info->control.vif = &sdata->vif; return IEEE80211_TX_AGAIN; + } *skbp = skb = next; ieee80211_led_tx(local, 1); fragm = true; @@ -1386,11 +1407,6 @@ static void ieee80211_xmit(struct ieee80211_sub_if_data *sdata, struct ieee80211_sub_if_data *tmp_sdata; int headroom; bool may_encrypt; - enum { - NOT_MONITOR, - FOUND_SDATA, - UNKNOWN_ADDRESS, - } monitor_iface = NOT_MONITOR; dev_hold(sdata->dev); @@ -1424,7 +1440,6 @@ static void ieee80211_xmit(struct ieee80211_sub_if_data *sdata, u16 len_rthdr; info->flags |= IEEE80211_TX_CTL_INJECTED; - monitor_iface = UNKNOWN_ADDRESS; len_rthdr = ieee80211_get_radiotap_len(skb->data); hdr = (struct ieee80211_hdr *)(skb->data + len_rthdr); @@ -1454,7 +1469,6 @@ static void ieee80211_xmit(struct ieee80211_sub_if_data *sdata, dev_hold(tmp_sdata->dev); dev_put(sdata->dev); sdata = tmp_sdata; - monitor_iface = FOUND_SDATA; break; } } @@ -1476,13 +1490,7 @@ static void ieee80211_xmit(struct ieee80211_sub_if_data *sdata, return; } - tmp_sdata = sdata; - if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN) - tmp_sdata = container_of(sdata->bss, - struct ieee80211_sub_if_data, - u.ap); - if (likely(monitor_iface != UNKNOWN_ADDRESS)) - info->control.vif = &tmp_sdata->vif; + info->control.vif = &sdata->vif; ieee80211_select_queue(local, skb); ieee80211_tx(sdata, skb, false); @@ -1534,9 +1542,6 @@ int ieee80211_monitor_start_xmit(struct sk_buff *skb, if (unlikely(skb->len < len_rthdr)) goto fail; /* skb too short for claimed rt header extent */ - /* needed because we set skb device to master */ - skb->iif = dev->ifindex; - /* * fix up the pointers accounting for the radiotap * header still being in there. We are being given @@ -1810,8 +1815,6 @@ int ieee80211_subif_start_xmit(struct sk_buff *skb, nh_pos += hdrlen; h_pos += hdrlen; - skb->iif = dev->ifindex; - dev->stats.tx_packets++; dev->stats.tx_bytes += skb->len; @@ -1856,32 +1859,13 @@ static bool ieee80211_tx_pending_skb(struct ieee80211_local *local, struct ieee80211_sub_if_data *sdata; struct sta_info *sta; struct ieee80211_hdr *hdr; - struct net_device *dev; int ret; bool result = true; - /* does interface still exist? */ - dev = dev_get_by_index(&init_net, skb->iif); - if (!dev) { - dev_kfree_skb(skb); - return true; - } - - sdata = IEEE80211_DEV_TO_SUB_IF(dev); - if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN) - sdata = container_of(sdata->bss, - struct ieee80211_sub_if_data, - u.ap); - - if (unlikely(info->control.vif && info->control.vif != &sdata->vif)) { - dev_kfree_skb(skb); - result = true; - goto out; - } + sdata = vif_to_sdata(info->control.vif); if (info->flags & IEEE80211_TX_INTFL_NEED_TXPROCESSING) { - /* do not use sdata, it may have been changed above */ - ieee80211_tx(IEEE80211_DEV_TO_SUB_IF(dev), skb, true); + ieee80211_tx(sdata, skb, true); } else { hdr = (struct ieee80211_hdr *)skb->data; sta = sta_info_get(local, hdr->addr1); @@ -1891,9 +1875,6 @@ static bool ieee80211_tx_pending_skb(struct ieee80211_local *local, result = false; } - out: - dev_put(dev); - return result; } @@ -1921,10 +1902,16 @@ void ieee80211_tx_pending(unsigned long data) while (!skb_queue_empty(&local->pending[i])) { struct sk_buff *skb = __skb_dequeue(&local->pending[i]); + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct ieee80211_sub_if_data *sdata; + + sdata = vif_to_sdata(info->control.vif); + dev_hold(sdata->dev); spin_unlock_irqrestore(&local->queue_stop_reason_lock, flags); txok = ieee80211_tx_pending_skb(local, skb); + dev_put(sdata->dev); if (!txok) __skb_queue_head(&local->pending[i], skb); spin_lock_irqsave(&local->queue_stop_reason_lock, @@ -2234,7 +2221,6 @@ void ieee80211_tx_skb(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb, skb_set_network_header(skb, 0); skb_set_transport_header(skb, 0); - skb->iif = sdata->dev->ifindex; if (!encrypt) info->flags |= IEEE80211_TX_INTFL_DONT_ENCRYPT; -- 1.6.0.4 From 47d7852c29858b2c98a9c68416b71192e05087ae Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Mon, 20 Jul 2009 17:30:46 -0700 Subject: [PATCH] cfg80211: make aware of net namespaces In order to make cfg80211/nl80211 aware of network namespaces, we have to do the following things: * del_virtual_intf method takes an interface index rather than a netdev pointer - simply change this * nl80211 uses init_net a lot, it changes to use the sender's network namespace * scan requests use the interface index, hold a netdev pointer and reference instead * we want a wiphy and its associated virtual interfaces to be in one netns together, so - we need to be able to change ns for a given interface, so export dev_change_net_namespace() - for each virtual interface set the NETIF_F_NETNS_LOCAL flag, and clear that flag only when the wiphy changes ns, to disallow breaking this invariant * when a network namespace goes away, we need to reparent the wiphy to init_net * cfg80211 users that support creating virtual interfaces must create them in the wiphy's namespace, currently this affects only mac80211 The end result is that you can now switch an entire wiphy into a different network namespace with the new command iw phy# set netns and all virtual interfaces will follow (or the operation fails). Signed-off-by: Johannes Berg --- include/linux/nl80211.h | 9 ++ include/net/cfg80211.h | 40 +++++++++- net/core/dev.c | 1 + net/mac80211/cfg.c | 14 +--- net/wireless/core.c | 75 ++++++++++++++++- net/wireless/core.h | 5 +- net/wireless/nl80211.c | 202 +++++++++++++++++++++++++++++++++-------------- net/wireless/scan.c | 21 ++--- net/wireless/sme.c | 3 +- 9 files changed, 272 insertions(+), 98 deletions(-) diff --git a/include/linux/nl80211.h b/include/linux/nl80211.h index 962e223..cb3dc60 100644 --- a/include/linux/nl80211.h +++ b/include/linux/nl80211.h @@ -262,6 +262,9 @@ * reasons, for this the %NL80211_ATTR_DISCONNECTED_BY_AP and * %NL80211_ATTR_REASON_CODE attributes are used. * + * @NL80211_CMD_SET_WIPHY_NETNS: Set a wiphy's netns. Note that all devices + * associated with this wiphy must be down and will follow. + * * @NL80211_CMD_MAX: highest used command number * @__NL80211_CMD_AFTER_LAST: internal use */ @@ -336,6 +339,8 @@ enum nl80211_commands { NL80211_CMD_ROAM, NL80211_CMD_DISCONNECT, + NL80211_CMD_SET_WIPHY_NETNS, + /* add new commands above here */ /* used to define NL80211_CMD_MAX below */ @@ -573,6 +578,8 @@ enum nl80211_commands { * and join_ibss(), key information is in a nested attribute each * with %NL80211_KEY_* sub-attributes * + * @NL80211_ATTR_PID: Process ID of a network namespace. + * * @NL80211_ATTR_MAX: highest attribute number currently defined * @__NL80211_ATTR_AFTER_LAST: internal use */ @@ -701,6 +708,8 @@ enum nl80211_attrs { NL80211_ATTR_KEY, NL80211_ATTR_KEYS, + NL80211_ATTR_PID, + /* add attributes here, update the policy in nl80211.c */ __NL80211_ATTR_AFTER_LAST, diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index a981ca8..0d27877 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -542,7 +542,7 @@ struct cfg80211_ssid { * @ie: optional information element(s) to add into Probe Request or %NULL * @ie_len: length of ie in octets * @wiphy: the wiphy this was for - * @ifidx: the interface index + * @dev: the interface */ struct cfg80211_scan_request { struct cfg80211_ssid *ssids; @@ -554,7 +554,7 @@ struct cfg80211_scan_request { /* internal */ struct wiphy *wiphy; - int ifidx; + struct net_device *dev; bool aborted; }; @@ -845,7 +845,8 @@ struct cfg80211_bitrate_mask { * @resume: wiphy device needs to be resumed * * @add_virtual_intf: create a new virtual interface with the given name, - * must set the struct wireless_dev's iftype. + * must set the struct wireless_dev's iftype. Beware: You must create + * the new netdev in the wiphy's network namespace! * * @del_virtual_intf: remove the virtual interface determined by ifindex. * @@ -937,7 +938,7 @@ struct cfg80211_ops { int (*add_virtual_intf)(struct wiphy *wiphy, char *name, enum nl80211_iftype type, u32 *flags, struct vif_params *params); - int (*del_virtual_intf)(struct wiphy *wiphy, int ifindex); + int (*del_virtual_intf)(struct wiphy *wiphy, struct net_device *dev); int (*change_virtual_intf)(struct wiphy *wiphy, struct net_device *dev, enum nl80211_iftype type, u32 *flags, @@ -1088,6 +1089,9 @@ struct cfg80211_ops { * @frag_threshold: Fragmentation threshold (dot11FragmentationThreshold); * -1 = fragmentation disabled, only odd values >= 256 used * @rts_threshold: RTS threshold (dot11RTSThreshold); -1 = RTS/CTS disabled + * @net: the network namespace this wiphy currently lives in + * @netnsok: if set to false, do not allow changing the netns of this + * wiphy at all */ struct wiphy { /* assign these fields before you register the wiphy */ @@ -1101,6 +1105,8 @@ struct wiphy { bool custom_regulatory; bool strict_regulatory; + bool netnsok; + enum cfg80211_signal_type signal_type; int bss_priv_size; @@ -1139,9 +1145,35 @@ struct wiphy { /* dir in debugfs: ieee80211/ */ struct dentry *debugfsdir; +#ifdef CONFIG_NET_NS + /* the network namespace this phy lives in currently */ + struct net *_net; +#endif + char priv[0] __attribute__((__aligned__(NETDEV_ALIGN))); }; +#ifdef CONFIG_NET_NS +static inline struct net *wiphy_net(struct wiphy *wiphy) +{ + return wiphy->_net; +} + +static inline void wiphy_net_set(struct wiphy *wiphy, struct net *net) +{ + wiphy->_net = net; +} +#else +static inline struct net *wiphy_net(struct wiphy *wiphy) +{ + return &init_net; +} + +static inline void wiphy_net_set(struct wiphy *wiphy, struct net *net) +{ +} +#endif + /** * wiphy_priv - return priv from wiphy * diff --git a/net/core/dev.c b/net/core/dev.c index 901d5f4..50085b2 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -5344,6 +5344,7 @@ int dev_change_net_namespace(struct net_device *dev, struct net *net, const char out: return err; } +EXPORT_SYMBOL_GPL(dev_change_net_namespace); static int dev_cpu_callback(struct notifier_block *nfb, unsigned long action, diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index 52928ad..4bbf500 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -57,19 +57,9 @@ static int ieee80211_add_iface(struct wiphy *wiphy, char *name, return 0; } -static int ieee80211_del_iface(struct wiphy *wiphy, int ifindex) +static int ieee80211_del_iface(struct wiphy *wiphy, struct net_device *dev) { - struct net_device *dev; - struct ieee80211_sub_if_data *sdata; - - /* we're under RTNL */ - dev = __dev_get_by_index(&init_net, ifindex); - if (!dev) - return -ENODEV; - - sdata = IEEE80211_DEV_TO_SUB_IF(dev); - - ieee80211_if_remove(sdata); + ieee80211_if_remove(IEEE80211_DEV_TO_SUB_IF(dev)); return 0; } diff --git a/net/wireless/core.c b/net/wireless/core.c index 6891cd0..442c9f3 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c @@ -106,7 +106,7 @@ __cfg80211_rdev_from_info(struct genl_info *info) if (info->attrs[NL80211_ATTR_IFINDEX]) { ifindex = nla_get_u32(info->attrs[NL80211_ATTR_IFINDEX]); - dev = dev_get_by_index(&init_net, ifindex); + dev = dev_get_by_index(genl_info_net(info), ifindex); if (dev) { if (dev->ieee80211_ptr) byifidx = @@ -151,13 +151,13 @@ cfg80211_get_dev_from_info(struct genl_info *info) } struct cfg80211_registered_device * -cfg80211_get_dev_from_ifindex(int ifindex) +cfg80211_get_dev_from_ifindex(struct net *net, int ifindex) { struct cfg80211_registered_device *rdev = ERR_PTR(-ENODEV); struct net_device *dev; mutex_lock(&cfg80211_mutex); - dev = dev_get_by_index(&init_net, ifindex); + dev = dev_get_by_index(net, ifindex); if (!dev) goto out; if (dev->ieee80211_ptr) { @@ -222,6 +222,42 @@ int cfg80211_dev_rename(struct cfg80211_registered_device *rdev, return 0; } +int cfg80211_switch_netns(struct cfg80211_registered_device *rdev, + struct net *net) +{ + struct wireless_dev *wdev; + int err = 0; + + if (!rdev->wiphy.netnsok) + return -EOPNOTSUPP; + + list_for_each_entry(wdev, &rdev->netdev_list, list) { + wdev->netdev->features &= ~NETIF_F_NETNS_LOCAL; + err = dev_change_net_namespace(wdev->netdev, net, "wlan%d"); + if (err) + break; + wdev->netdev->features |= NETIF_F_NETNS_LOCAL; + } + + if (err) { + /* failed -- clean up to old netns */ + net = wiphy_net(&rdev->wiphy); + + list_for_each_entry_continue_reverse(wdev, &rdev->netdev_list, + list) { + wdev->netdev->features &= ~NETIF_F_NETNS_LOCAL; + err = dev_change_net_namespace(wdev->netdev, net, + "wlan%d"); + WARN_ON(err); + wdev->netdev->features |= NETIF_F_NETNS_LOCAL; + } + } + + wiphy_net_set(&rdev->wiphy, net); + + return err; +} + static void cfg80211_rfkill_poll(struct rfkill *rfkill, void *data) { struct cfg80211_registered_device *rdev = data; @@ -375,6 +411,8 @@ struct wiphy *wiphy_new(const struct cfg80211_ops *ops, int sizeof_priv) rdev->wiphy.dev.class = &ieee80211_class; rdev->wiphy.dev.platform_data = rdev; + wiphy_net_set(&rdev->wiphy, &init_net); + rdev->rfkill_ops.set_block = cfg80211_rfkill_set_block; rdev->rfkill = rfkill_alloc(dev_name(&rdev->wiphy.dev), &rdev->wiphy.dev, RFKILL_TYPE_WLAN, @@ -615,6 +653,9 @@ static int cfg80211_netdev_notifier_call(struct notifier_block * nb, spin_lock_init(&wdev->event_lock); mutex_lock(&rdev->devlist_mtx); list_add(&wdev->list, &rdev->netdev_list); + /* can only change netns with wiphy */ + dev->features |= NETIF_F_NETNS_LOCAL; + if (sysfs_create_link(&dev->dev.kobj, &rdev->wiphy.dev.kobj, "phy80211")) { printk(KERN_ERR "wireless: failed to add phy80211 " @@ -705,10 +746,32 @@ static struct notifier_block cfg80211_netdev_notifier = { .notifier_call = cfg80211_netdev_notifier_call, }; -static int cfg80211_init(void) +static void __net_exit cfg80211_pernet_exit(struct net *net) +{ + struct cfg80211_registered_device *rdev; + + rtnl_lock(); + mutex_lock(&cfg80211_mutex); + list_for_each_entry(rdev, &cfg80211_rdev_list, list) { + if (net_eq(wiphy_net(&rdev->wiphy), net)) + WARN_ON(cfg80211_switch_netns(rdev, &init_net)); + } + mutex_unlock(&cfg80211_mutex); + rtnl_unlock(); +} + +static struct pernet_operations cfg80211_pernet_ops = { + .exit = cfg80211_pernet_exit, +}; + +static int __init cfg80211_init(void) { int err; + err = register_pernet_device(&cfg80211_pernet_ops); + if (err) + goto out_fail_pernet; + err = wiphy_sysfs_init(); if (err) goto out_fail_sysfs; @@ -736,9 +799,10 @@ out_fail_nl80211: out_fail_notifier: wiphy_sysfs_exit(); out_fail_sysfs: + unregister_pernet_device(&cfg80211_pernet_ops); +out_fail_pernet: return err; } - subsys_initcall(cfg80211_init); static void cfg80211_exit(void) @@ -748,5 +812,6 @@ static void cfg80211_exit(void) unregister_netdevice_notifier(&cfg80211_netdev_notifier); wiphy_sysfs_exit(); regulatory_exit(); + unregister_pernet_device(&cfg80211_pernet_ops); } module_exit(cfg80211_exit); diff --git a/net/wireless/core.h b/net/wireless/core.h index 2ec8ddb..4276b70 100644 --- a/net/wireless/core.h +++ b/net/wireless/core.h @@ -170,7 +170,10 @@ struct wiphy *wiphy_idx_to_wiphy(int wiphy_idx); /* identical to cfg80211_get_dev_from_info but only operate on ifindex */ extern struct cfg80211_registered_device * -cfg80211_get_dev_from_ifindex(int ifindex); +cfg80211_get_dev_from_ifindex(struct net *net, int ifindex); + +int cfg80211_switch_netns(struct cfg80211_registered_device *rdev, + struct net *net); static inline void cfg80211_lock_rdev(struct cfg80211_registered_device *rdev) { diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index da450ef..7880a9c 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -14,8 +14,10 @@ #include #include #include +#include #include #include +#include #include "core.h" #include "nl80211.h" #include "reg.h" @@ -27,24 +29,26 @@ static struct genl_family nl80211_fam = { .hdrsize = 0, /* no private header */ .version = 1, /* no particular meaning now */ .maxattr = NL80211_ATTR_MAX, + .netnsok = true, }; /* internal helper: get rdev and dev */ -static int get_rdev_dev_by_info_ifindex(struct nlattr **attrs, +static int get_rdev_dev_by_info_ifindex(struct genl_info *info, struct cfg80211_registered_device **rdev, struct net_device **dev) { + struct nlattr **attrs = info->attrs; int ifindex; if (!attrs[NL80211_ATTR_IFINDEX]) return -EINVAL; ifindex = nla_get_u32(attrs[NL80211_ATTR_IFINDEX]); - *dev = dev_get_by_index(&init_net, ifindex); + *dev = dev_get_by_index(genl_info_net(info), ifindex); if (!*dev) return -ENODEV; - *rdev = cfg80211_get_dev_from_ifindex(ifindex); + *rdev = cfg80211_get_dev_from_ifindex(genl_info_net(info), ifindex); if (IS_ERR(*rdev)) { dev_put(*dev); return PTR_ERR(*rdev); @@ -133,6 +137,7 @@ static struct nla_policy nl80211_policy[NL80211_ATTR_MAX+1] __read_mostly = { [NL80211_ATTR_PRIVACY] = { .type = NLA_FLAG }, [NL80211_ATTR_CIPHER_SUITE_GROUP] = { .type = NLA_U32 }, [NL80211_ATTR_WPA_VERSIONS] = { .type = NLA_U32 }, + [NL80211_ATTR_PID] = { .type = NLA_U32 }, }; /* policy for the attributes */ @@ -532,6 +537,10 @@ static int nl80211_send_wiphy(struct sk_buff *msg, u32 pid, u32 seq, int flags, CMD(deauth, DEAUTHENTICATE); CMD(disassoc, DISASSOCIATE); CMD(join_ibss, JOIN_IBSS); + if (dev->wiphy.netnsok) { + i++; + NLA_PUT_U32(msg, i, NL80211_CMD_SET_WIPHY_NETNS); + } #undef CMD @@ -562,6 +571,8 @@ static int nl80211_dump_wiphy(struct sk_buff *skb, struct netlink_callback *cb) mutex_lock(&cfg80211_mutex); list_for_each_entry(dev, &cfg80211_rdev_list, list) { + if (!net_eq(wiphy_net(&dev->wiphy), sock_net(skb->sk))) + continue; if (++idx <= start) continue; if (nl80211_send_wiphy(skb, NETLINK_CB(cb->skb).pid, @@ -867,6 +878,8 @@ static int nl80211_dump_interface(struct sk_buff *skb, struct netlink_callback * mutex_lock(&cfg80211_mutex); list_for_each_entry(dev, &cfg80211_rdev_list, list) { + if (!net_eq(wiphy_net(&dev->wiphy), sock_net(skb->sk))) + continue; if (wp_idx < wp_start) { wp_idx++; continue; @@ -907,7 +920,7 @@ static int nl80211_get_interface(struct sk_buff *skb, struct genl_info *info) struct net_device *netdev; int err; - err = get_rdev_dev_by_info_ifindex(info->attrs, &dev, &netdev); + err = get_rdev_dev_by_info_ifindex(info, &dev, &netdev); if (err) return err; @@ -975,7 +988,7 @@ static int nl80211_set_interface(struct sk_buff *skb, struct genl_info *info) rtnl_lock(); - err = get_rdev_dev_by_info_ifindex(info->attrs, &rdev, &dev); + err = get_rdev_dev_by_info_ifindex(info, &rdev, &dev); if (err) goto unlock_rtnl; @@ -1098,26 +1111,25 @@ static int nl80211_new_interface(struct sk_buff *skb, struct genl_info *info) static int nl80211_del_interface(struct sk_buff *skb, struct genl_info *info) { struct cfg80211_registered_device *rdev; - int ifindex, err; + int err; struct net_device *dev; rtnl_lock(); - err = get_rdev_dev_by_info_ifindex(info->attrs, &rdev, &dev); + err = get_rdev_dev_by_info_ifindex(info, &rdev, &dev); if (err) goto unlock_rtnl; - ifindex = dev->ifindex; - dev_put(dev); if (!rdev->ops->del_virtual_intf) { err = -EOPNOTSUPP; goto out; } - err = rdev->ops->del_virtual_intf(&rdev->wiphy, ifindex); + err = rdev->ops->del_virtual_intf(&rdev->wiphy, dev); out: cfg80211_unlock_rdev(rdev); + dev_put(dev); unlock_rtnl: rtnl_unlock(); return err; @@ -1195,7 +1207,7 @@ static int nl80211_get_key(struct sk_buff *skb, struct genl_info *info) rtnl_lock(); - err = get_rdev_dev_by_info_ifindex(info->attrs, &rdev, &dev); + err = get_rdev_dev_by_info_ifindex(info, &rdev, &dev); if (err) goto unlock_rtnl; @@ -1274,7 +1286,7 @@ static int nl80211_set_key(struct sk_buff *skb, struct genl_info *info) rtnl_lock(); - err = get_rdev_dev_by_info_ifindex(info->attrs, &rdev, &dev); + err = get_rdev_dev_by_info_ifindex(info, &rdev, &dev); if (err) goto unlock_rtnl; @@ -1333,7 +1345,7 @@ static int nl80211_new_key(struct sk_buff *skb, struct genl_info *info) rtnl_lock(); - err = get_rdev_dev_by_info_ifindex(info->attrs, &rdev, &dev); + err = get_rdev_dev_by_info_ifindex(info, &rdev, &dev); if (err) goto unlock_rtnl; @@ -1380,7 +1392,7 @@ static int nl80211_del_key(struct sk_buff *skb, struct genl_info *info) rtnl_lock(); - err = get_rdev_dev_by_info_ifindex(info->attrs, &rdev, &dev); + err = get_rdev_dev_by_info_ifindex(info, &rdev, &dev); if (err) goto unlock_rtnl; @@ -1429,7 +1441,7 @@ static int nl80211_addset_beacon(struct sk_buff *skb, struct genl_info *info) rtnl_lock(); - err = get_rdev_dev_by_info_ifindex(info->attrs, &rdev, &dev); + err = get_rdev_dev_by_info_ifindex(info, &rdev, &dev); if (err) goto unlock_rtnl; @@ -1516,7 +1528,7 @@ static int nl80211_del_beacon(struct sk_buff *skb, struct genl_info *info) rtnl_lock(); - err = get_rdev_dev_by_info_ifindex(info->attrs, &rdev, &dev); + err = get_rdev_dev_by_info_ifindex(info, &rdev, &dev); if (err) goto unlock_rtnl; @@ -1726,13 +1738,13 @@ static int nl80211_dump_station(struct sk_buff *skb, rtnl_lock(); - netdev = __dev_get_by_index(&init_net, ifidx); + netdev = __dev_get_by_index(sock_net(skb->sk), ifidx); if (!netdev) { err = -ENODEV; goto out_rtnl; } - dev = cfg80211_get_dev_from_ifindex(ifidx); + dev = cfg80211_get_dev_from_ifindex(sock_net(skb->sk), ifidx); if (IS_ERR(dev)) { err = PTR_ERR(dev); goto out_rtnl; @@ -1791,7 +1803,7 @@ static int nl80211_get_station(struct sk_buff *skb, struct genl_info *info) rtnl_lock(); - err = get_rdev_dev_by_info_ifindex(info->attrs, &rdev, &dev); + err = get_rdev_dev_by_info_ifindex(info, &rdev, &dev); if (err) goto out_rtnl; @@ -1829,14 +1841,16 @@ static int nl80211_get_station(struct sk_buff *skb, struct genl_info *info) /* * Get vlan interface making sure it is on the right wiphy. */ -static int get_vlan(struct nlattr *vlanattr, +static int get_vlan(struct genl_info *info, struct cfg80211_registered_device *rdev, struct net_device **vlan) { + struct nlattr *vlanattr = info->attrs[NL80211_ATTR_STA_VLAN]; *vlan = NULL; if (vlanattr) { - *vlan = dev_get_by_index(&init_net, nla_get_u32(vlanattr)); + *vlan = dev_get_by_index(genl_info_net(info), + nla_get_u32(vlanattr)); if (!*vlan) return -ENODEV; if (!(*vlan)->ieee80211_ptr) @@ -1891,11 +1905,11 @@ static int nl80211_set_station(struct sk_buff *skb, struct genl_info *info) rtnl_lock(); - err = get_rdev_dev_by_info_ifindex(info->attrs, &rdev, &dev); + err = get_rdev_dev_by_info_ifindex(info, &rdev, &dev); if (err) goto out_rtnl; - err = get_vlan(info->attrs[NL80211_ATTR_STA_VLAN], rdev, ¶ms.vlan); + err = get_vlan(info, rdev, ¶ms.vlan); if (err) goto out; @@ -2004,11 +2018,11 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info) rtnl_lock(); - err = get_rdev_dev_by_info_ifindex(info->attrs, &rdev, &dev); + err = get_rdev_dev_by_info_ifindex(info, &rdev, &dev); if (err) goto out_rtnl; - err = get_vlan(info->attrs[NL80211_ATTR_STA_VLAN], rdev, ¶ms.vlan); + err = get_vlan(info, rdev, ¶ms.vlan); if (err) goto out; @@ -2079,7 +2093,7 @@ static int nl80211_del_station(struct sk_buff *skb, struct genl_info *info) rtnl_lock(); - err = get_rdev_dev_by_info_ifindex(info->attrs, &rdev, &dev); + err = get_rdev_dev_by_info_ifindex(info, &rdev, &dev); if (err) goto out_rtnl; @@ -2185,13 +2199,13 @@ static int nl80211_dump_mpath(struct sk_buff *skb, rtnl_lock(); - netdev = __dev_get_by_index(&init_net, ifidx); + netdev = __dev_get_by_index(sock_net(skb->sk), ifidx); if (!netdev) { err = -ENODEV; goto out_rtnl; } - dev = cfg80211_get_dev_from_ifindex(ifidx); + dev = cfg80211_get_dev_from_ifindex(sock_net(skb->sk), ifidx); if (IS_ERR(dev)) { err = PTR_ERR(dev); goto out_rtnl; @@ -2255,7 +2269,7 @@ static int nl80211_get_mpath(struct sk_buff *skb, struct genl_info *info) rtnl_lock(); - err = get_rdev_dev_by_info_ifindex(info->attrs, &rdev, &dev); + err = get_rdev_dev_by_info_ifindex(info, &rdev, &dev); if (err) goto out_rtnl; @@ -2314,7 +2328,7 @@ static int nl80211_set_mpath(struct sk_buff *skb, struct genl_info *info) rtnl_lock(); - err = get_rdev_dev_by_info_ifindex(info->attrs, &rdev, &dev); + err = get_rdev_dev_by_info_ifindex(info, &rdev, &dev); if (err) goto out_rtnl; @@ -2362,7 +2376,7 @@ static int nl80211_new_mpath(struct sk_buff *skb, struct genl_info *info) rtnl_lock(); - err = get_rdev_dev_by_info_ifindex(info->attrs, &rdev, &dev); + err = get_rdev_dev_by_info_ifindex(info, &rdev, &dev); if (err) goto out_rtnl; @@ -2404,7 +2418,7 @@ static int nl80211_del_mpath(struct sk_buff *skb, struct genl_info *info) rtnl_lock(); - err = get_rdev_dev_by_info_ifindex(info->attrs, &rdev, &dev); + err = get_rdev_dev_by_info_ifindex(info, &rdev, &dev); if (err) goto out_rtnl; @@ -2455,7 +2469,7 @@ static int nl80211_set_bss(struct sk_buff *skb, struct genl_info *info) rtnl_lock(); - err = get_rdev_dev_by_info_ifindex(info->attrs, &rdev, &dev); + err = get_rdev_dev_by_info_ifindex(info, &rdev, &dev); if (err) goto out_rtnl; @@ -2574,7 +2588,7 @@ static int nl80211_get_mesh_params(struct sk_buff *skb, rtnl_lock(); /* Look up our device */ - err = get_rdev_dev_by_info_ifindex(info->attrs, &rdev, &dev); + err = get_rdev_dev_by_info_ifindex(info, &rdev, &dev); if (err) goto out_rtnl; @@ -2691,7 +2705,7 @@ static int nl80211_set_mesh_params(struct sk_buff *skb, struct genl_info *info) rtnl_lock(); - err = get_rdev_dev_by_info_ifindex(info->attrs, &rdev, &dev); + err = get_rdev_dev_by_info_ifindex(info, &rdev, &dev); if (err) goto out_rtnl; @@ -2947,7 +2961,7 @@ static int nl80211_trigger_scan(struct sk_buff *skb, struct genl_info *info) rtnl_lock(); - err = get_rdev_dev_by_info_ifindex(info->attrs, &rdev, &dev); + err = get_rdev_dev_by_info_ifindex(info, &rdev, &dev); if (err) goto out_rtnl; @@ -3069,14 +3083,16 @@ static int nl80211_trigger_scan(struct sk_buff *skb, struct genl_info *info) request->ie_len); } - request->ifidx = dev->ifindex; + request->dev = dev; request->wiphy = &rdev->wiphy; rdev->scan_req = request; err = rdev->ops->scan(&rdev->wiphy, dev, request); - if (!err) + if (!err) { nl80211_send_scan_start(rdev, dev); + dev_hold(dev); + } out_free: if (err) { @@ -3198,11 +3214,11 @@ static int nl80211_dump_scan(struct sk_buff *skb, cb->args[0] = ifidx; } - dev = dev_get_by_index(&init_net, ifidx); + dev = dev_get_by_index(sock_net(skb->sk), ifidx); if (!dev) return -ENODEV; - rdev = cfg80211_get_dev_from_ifindex(ifidx); + rdev = cfg80211_get_dev_from_ifindex(sock_net(skb->sk), ifidx); if (IS_ERR(rdev)) { err = PTR_ERR(rdev); goto out_put_netdev; @@ -3312,7 +3328,7 @@ static int nl80211_authenticate(struct sk_buff *skb, struct genl_info *info) rtnl_lock(); - err = get_rdev_dev_by_info_ifindex(info->attrs, &rdev, &dev); + err = get_rdev_dev_by_info_ifindex(info, &rdev, &dev); if (err) goto unlock_rtnl; @@ -3448,7 +3464,7 @@ static int nl80211_associate(struct sk_buff *skb, struct genl_info *info) rtnl_lock(); - err = get_rdev_dev_by_info_ifindex(info->attrs, &rdev, &dev); + err = get_rdev_dev_by_info_ifindex(info, &rdev, &dev); if (err) goto unlock_rtnl; @@ -3531,7 +3547,7 @@ static int nl80211_deauthenticate(struct sk_buff *skb, struct genl_info *info) rtnl_lock(); - err = get_rdev_dev_by_info_ifindex(info->attrs, &rdev, &dev); + err = get_rdev_dev_by_info_ifindex(info, &rdev, &dev); if (err) goto unlock_rtnl; @@ -3593,7 +3609,7 @@ static int nl80211_disassociate(struct sk_buff *skb, struct genl_info *info) rtnl_lock(); - err = get_rdev_dev_by_info_ifindex(info->attrs, &rdev, &dev); + err = get_rdev_dev_by_info_ifindex(info, &rdev, &dev); if (err) goto unlock_rtnl; @@ -3666,7 +3682,7 @@ static int nl80211_join_ibss(struct sk_buff *skb, struct genl_info *info) rtnl_lock(); - err = get_rdev_dev_by_info_ifindex(info->attrs, &rdev, &dev); + err = get_rdev_dev_by_info_ifindex(info, &rdev, &dev); if (err) goto unlock_rtnl; @@ -3739,7 +3755,7 @@ static int nl80211_leave_ibss(struct sk_buff *skb, struct genl_info *info) rtnl_lock(); - err = get_rdev_dev_by_info_ifindex(info->attrs, &rdev, &dev); + err = get_rdev_dev_by_info_ifindex(info, &rdev, &dev); if (err) goto unlock_rtnl; @@ -3924,7 +3940,7 @@ static int nl80211_connect(struct sk_buff *skb, struct genl_info *info) return err; rtnl_lock(); - err = get_rdev_dev_by_info_ifindex(info->attrs, &rdev, &dev); + err = get_rdev_dev_by_info_ifindex(info, &rdev, &dev); if (err) goto unlock_rtnl; @@ -4000,7 +4016,7 @@ static int nl80211_disconnect(struct sk_buff *skb, struct genl_info *info) rtnl_lock(); - err = get_rdev_dev_by_info_ifindex(info->attrs, &rdev, &dev); + err = get_rdev_dev_by_info_ifindex(info, &rdev, &dev); if (err) goto unlock_rtnl; @@ -4024,6 +4040,47 @@ unlock_rtnl: return err; } +static int nl80211_wiphy_netns(struct sk_buff *skb, struct genl_info *info) +{ + struct cfg80211_registered_device *rdev; + struct net *net; + int err; + u32 pid; + + if (!info->attrs[NL80211_ATTR_PID]) + return -EINVAL; + + pid = nla_get_u32(info->attrs[NL80211_ATTR_PID]); + + rtnl_lock(); + + rdev = cfg80211_get_dev_from_info(info); + if (IS_ERR(rdev)) { + err = PTR_ERR(rdev); + goto out; + } + + net = get_net_ns_by_pid(pid); + if (IS_ERR(net)) { + err = PTR_ERR(net); + goto out; + } + + err = 0; + + /* check if anything to do */ + if (net_eq(wiphy_net(&rdev->wiphy), net)) + goto out_put_net; + + err = cfg80211_switch_netns(rdev, net); + out_put_net: + put_net(net); + out: + cfg80211_unlock_rdev(rdev); + rtnl_unlock(); + return err; +} + static struct genl_ops nl80211_ops[] = { { .cmd = NL80211_CMD_GET_WIPHY, @@ -4257,6 +4314,12 @@ static struct genl_ops nl80211_ops[] = { .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, }, + { + .cmd = NL80211_CMD_SET_WIPHY_NETNS, + .doit = nl80211_wiphy_netns, + .policy = nl80211_policy, + .flags = GENL_ADMIN_PERM, + }, }; static struct genl_multicast_group nl80211_mlme_mcgrp = { .name = "mlme", @@ -4288,7 +4351,8 @@ void nl80211_notify_dev_rename(struct cfg80211_registered_device *rdev) return; } - genlmsg_multicast(msg, 0, nl80211_config_mcgrp.id, GFP_KERNEL); + genlmsg_multicast_netns(wiphy_net(&rdev->wiphy), msg, 0, + nl80211_config_mcgrp.id, GFP_KERNEL); } static int nl80211_add_scan_req(struct sk_buff *msg, @@ -4365,7 +4429,8 @@ void nl80211_send_scan_start(struct cfg80211_registered_device *rdev, return; } - genlmsg_multicast(msg, 0, nl80211_scan_mcgrp.id, GFP_KERNEL); + genlmsg_multicast_netns(wiphy_net(&rdev->wiphy), msg, 0, + nl80211_scan_mcgrp.id, GFP_KERNEL); } void nl80211_send_scan_done(struct cfg80211_registered_device *rdev, @@ -4383,7 +4448,8 @@ void nl80211_send_scan_done(struct cfg80211_registered_device *rdev, return; } - genlmsg_multicast(msg, 0, nl80211_scan_mcgrp.id, GFP_KERNEL); + genlmsg_multicast_netns(wiphy_net(&rdev->wiphy), msg, 0, + nl80211_scan_mcgrp.id, GFP_KERNEL); } void nl80211_send_scan_aborted(struct cfg80211_registered_device *rdev, @@ -4401,7 +4467,8 @@ void nl80211_send_scan_aborted(struct cfg80211_registered_device *rdev, return; } - genlmsg_multicast(msg, 0, nl80211_scan_mcgrp.id, GFP_KERNEL); + genlmsg_multicast_netns(wiphy_net(&rdev->wiphy), msg, 0, + nl80211_scan_mcgrp.id, GFP_KERNEL); } /* @@ -4450,7 +4517,10 @@ void nl80211_send_reg_change_event(struct regulatory_request *request) return; } - genlmsg_multicast(msg, 0, nl80211_regulatory_mcgrp.id, GFP_KERNEL); + rtnl_lock(); + genlmsg_multicast_allns(msg, 0, nl80211_regulatory_mcgrp.id, + GFP_KERNEL); + rtnl_unlock(); return; @@ -4486,7 +4556,8 @@ static void nl80211_send_mlme_event(struct cfg80211_registered_device *rdev, return; } - genlmsg_multicast(msg, 0, nl80211_mlme_mcgrp.id, gfp); + genlmsg_multicast_netns(wiphy_net(&rdev->wiphy), msg, 0, + nl80211_mlme_mcgrp.id, gfp); return; nla_put_failure: @@ -4553,7 +4624,8 @@ static void nl80211_send_mlme_timeout(struct cfg80211_registered_device *rdev, return; } - genlmsg_multicast(msg, 0, nl80211_mlme_mcgrp.id, gfp); + genlmsg_multicast_netns(wiphy_net(&rdev->wiphy), msg, 0, + nl80211_mlme_mcgrp.id, gfp); return; nla_put_failure: @@ -4611,7 +4683,8 @@ void nl80211_send_connect_result(struct cfg80211_registered_device *rdev, return; } - genlmsg_multicast(msg, 0, nl80211_mlme_mcgrp.id, gfp); + genlmsg_multicast_netns(wiphy_net(&rdev->wiphy), msg, 0, + nl80211_mlme_mcgrp.id, gfp); return; nla_put_failure: @@ -4651,7 +4724,8 @@ void nl80211_send_roamed(struct cfg80211_registered_device *rdev, return; } - genlmsg_multicast(msg, 0, nl80211_mlme_mcgrp.id, gfp); + genlmsg_multicast_netns(wiphy_net(&rdev->wiphy), msg, 0, + nl80211_mlme_mcgrp.id, gfp); return; nla_put_failure: @@ -4691,7 +4765,8 @@ void nl80211_send_disconnected(struct cfg80211_registered_device *rdev, return; } - genlmsg_multicast(msg, 0, nl80211_mlme_mcgrp.id, GFP_KERNEL); + genlmsg_multicast_netns(wiphy_net(&rdev->wiphy), msg, 0, + nl80211_mlme_mcgrp.id, GFP_KERNEL); return; nla_put_failure: @@ -4726,7 +4801,8 @@ void nl80211_send_ibss_bssid(struct cfg80211_registered_device *rdev, return; } - genlmsg_multicast(msg, 0, nl80211_mlme_mcgrp.id, gfp); + genlmsg_multicast_netns(wiphy_net(&rdev->wiphy), msg, 0, + nl80211_mlme_mcgrp.id, gfp); return; nla_put_failure: @@ -4766,7 +4842,8 @@ void nl80211_michael_mic_failure(struct cfg80211_registered_device *rdev, return; } - genlmsg_multicast(msg, 0, nl80211_mlme_mcgrp.id, gfp); + genlmsg_multicast_netns(wiphy_net(&rdev->wiphy), msg, 0, + nl80211_mlme_mcgrp.id, gfp); return; nla_put_failure: @@ -4819,7 +4896,10 @@ void nl80211_send_beacon_hint_event(struct wiphy *wiphy, return; } - genlmsg_multicast(msg, 0, nl80211_regulatory_mcgrp.id, GFP_ATOMIC); + rcu_read_lock(); + genlmsg_multicast_allns(msg, 0, nl80211_regulatory_mcgrp.id, + GFP_ATOMIC); + rcu_read_unlock(); return; diff --git a/net/wireless/scan.c b/net/wireless/scan.c index 4ad8b4b..b26db78 100644 --- a/net/wireless/scan.c +++ b/net/wireless/scan.c @@ -32,9 +32,7 @@ void __cfg80211_scan_done(struct work_struct *wk) mutex_lock(&rdev->mtx); request = rdev->scan_req; - dev = dev_get_by_index(&init_net, request->ifidx); - if (!dev) - goto out; + dev = request->dev; /* * This must be before sending the other events! @@ -66,17 +64,10 @@ void __cfg80211_scan_done(struct work_struct *wk) void cfg80211_scan_done(struct cfg80211_scan_request *request, bool aborted) { - struct net_device *dev = dev_get_by_index(&init_net, request->ifidx); - if (WARN_ON(!dev)) { - kfree(request); - return; - } - WARN_ON(request != wiphy_to_dev(request->wiphy)->scan_req); request->aborted = aborted; schedule_work(&wiphy_to_dev(request->wiphy)->scan_done_wk); - dev_put(dev); } EXPORT_SYMBOL(cfg80211_scan_done); @@ -592,7 +583,7 @@ int cfg80211_wext_siwscan(struct net_device *dev, if (!netif_running(dev)) return -ENETDOWN; - rdev = cfg80211_get_dev_from_ifindex(dev->ifindex); + rdev = cfg80211_get_dev_from_ifindex(dev_net(dev), dev->ifindex); if (IS_ERR(rdev)) return PTR_ERR(rdev); @@ -617,7 +608,7 @@ int cfg80211_wext_siwscan(struct net_device *dev, } creq->wiphy = wiphy; - creq->ifidx = dev->ifindex; + creq->dev = dev; creq->ssids = (void *)(creq + 1); creq->channels = (void *)(creq->ssids + 1); creq->n_channels = n_channels; @@ -654,8 +645,10 @@ int cfg80211_wext_siwscan(struct net_device *dev, if (err) { rdev->scan_req = NULL; kfree(creq); - } else + } else { nl80211_send_scan_start(rdev, dev); + dev_hold(dev); + } out: cfg80211_unlock_rdev(rdev); return err; @@ -948,7 +941,7 @@ int cfg80211_wext_giwscan(struct net_device *dev, if (!netif_running(dev)) return -ENETDOWN; - rdev = cfg80211_get_dev_from_ifindex(dev->ifindex); + rdev = cfg80211_get_dev_from_ifindex(dev_net(dev), dev->ifindex); if (IS_ERR(rdev)) return PTR_ERR(rdev); diff --git a/net/wireless/sme.c b/net/wireless/sme.c index e7a8851..fb42860 100644 --- a/net/wireless/sme.c +++ b/net/wireless/sme.c @@ -86,7 +86,7 @@ static int cfg80211_conn_scan(struct wireless_dev *wdev) wdev->conn->params.ssid_len); request->ssids[0].ssid_len = wdev->conn->params.ssid_len; - request->ifidx = wdev->netdev->ifindex; + request->dev = wdev->netdev; request->wiphy = &rdev->wiphy; rdev->scan_req = request; @@ -95,6 +95,7 @@ static int cfg80211_conn_scan(struct wireless_dev *wdev) if (!err) { wdev->conn->state = CFG80211_CONN_SCANNING; nl80211_send_scan_start(rdev, wdev->netdev); + dev_hold(wdev->netdev); } else { rdev->scan_req = NULL; kfree(request); -- 1.6.0.4 From c5705b9c5fe3ecf3e04a36f3f874456ed1fe33a7 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Mon, 20 Jul 2009 17:30:49 -0700 Subject: [PATCH] mac80211: allow using network namespaces This finally opens up the ability to put mac80211 devices into different network namespaces. As long as you don't have sysfs, that is. Signed-off-by: Johannes Berg --- net/mac80211/iface.c | 1 + net/mac80211/main.c | 1 + 2 files changed, 2 insertions(+), 0 deletions(-) diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c index 559d698..0cb29df 100644 --- a/net/mac80211/iface.c +++ b/net/mac80211/iface.c @@ -787,6 +787,7 @@ int ieee80211_if_add(struct ieee80211_local *local, const char *name, name, ieee80211_if_setup); if (!ndev) return -ENOMEM; + dev_net_set(ndev, wiphy_net(local->hw.wiphy)); ndev->needed_headroom = local->tx_headroom + 4*6 /* four MAC addresses */ diff --git a/net/mac80211/main.c b/net/mac80211/main.c index 3234f37..02cabbf 100644 --- a/net/mac80211/main.c +++ b/net/mac80211/main.c @@ -620,6 +620,7 @@ struct ieee80211_hw *ieee80211_alloc_hw(size_t priv_data_len, if (!wiphy) return NULL; + wiphy->netnsok = true; wiphy->privid = mac80211_wiphy_privid; /* Yes, putting cfg80211_bss into ieee80211_bss is a hack */ -- 1.6.0.4 From c630c4fc6b58126e6aac8f74c2be05845a7f85cc Mon Sep 17 00:00:00 2001 From: Luis R. Rodriguez Date: Sun, 10 May 2009 01:53:06 -0700 Subject: [PATCH] ath9k: do not stop the queues in driver stop mac80211 will have disabled the queues for us when needed. Signed-off-by: Luis R. Rodriguez --- drivers/net/wireless/ath/ath9k/main.c | 2 -- 1 files changed, 0 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/ath/ath9k/main.c b/drivers/net/wireless/ath/ath9k/main.c index 254e787..3436295 100644 --- a/drivers/net/wireless/ath/ath9k/main.c +++ b/drivers/net/wireless/ath/ath9k/main.c @@ -2101,8 +2101,6 @@ static void ath9k_stop(struct ieee80211_hw *hw) mutex_lock(&sc->mutex); - ieee80211_stop_queues(hw); - if (ath9k_wiphy_started(sc)) { mutex_unlock(&sc->mutex); return; /* another wiphy still in use */ -- 1.6.0.4 From d245127d480b0cd95eb17499e8ba2605980572bf Mon Sep 17 00:00:00 2001 From: Luis R. Rodriguez Date: Mon, 11 May 2009 02:00:17 -0700 Subject: [PATCH] adm8211: remove uneeded code during suspend/resume mac80211 drivers do not need to stop the software queues or call their own stop() callback upon suspend as we do it for drivers. Equally drivers don't have to call their own start() or start the queues as mac80211 will do it for us. Signed-off-by: Luis R. Rodriguez --- drivers/net/wireless/adm8211.c | 17 ----------------- 1 files changed, 0 insertions(+), 17 deletions(-) diff --git a/drivers/net/wireless/adm8211.c b/drivers/net/wireless/adm8211.c index ecc9383..5695911 100644 --- a/drivers/net/wireless/adm8211.c +++ b/drivers/net/wireless/adm8211.c @@ -1964,14 +1964,6 @@ static void __devexit adm8211_remove(struct pci_dev *pdev) #ifdef CONFIG_PM static int adm8211_suspend(struct pci_dev *pdev, pm_message_t state) { - struct ieee80211_hw *dev = pci_get_drvdata(pdev); - struct adm8211_priv *priv = dev->priv; - - if (priv->mode != NL80211_IFTYPE_UNSPECIFIED) { - ieee80211_stop_queues(dev); - adm8211_stop(dev); - } - pci_save_state(pdev); pci_set_power_state(pdev, pci_choose_state(pdev, state)); return 0; @@ -1979,17 +1971,8 @@ static int adm8211_suspend(struct pci_dev *pdev, pm_message_t state) static int adm8211_resume(struct pci_dev *pdev) { - struct ieee80211_hw *dev = pci_get_drvdata(pdev); - struct adm8211_priv *priv = dev->priv; - pci_set_power_state(pdev, PCI_D0); pci_restore_state(pdev); - - if (priv->mode != NL80211_IFTYPE_UNSPECIFIED) { - adm8211_start(dev); - ieee80211_wake_queues(dev); - } - return 0; } #endif /* CONFIG_PM */ -- 1.6.0.4 From 3603cc2ecf03a3b3c3068dde862a6ac50341a6b5 Mon Sep 17 00:00:00 2001 From: Luis R. Rodriguez Date: Tue, 12 May 2009 01:58:56 -0700 Subject: [PATCH] cfg80211: add WoW support We support a set and get for WoW parameters. We aware of 4 type of triggers: * Magic Packet * Beacon miss * Link change (no longer associated) * User pattern match We currently only support Magic Packet. The user pattern match will need a parser as patterns can be customized. WoW will require suspend to S3 Hot (power cable plugged), and proper BIOS support to receive PCI PMEs and kick the box on, and of course a capable device. For USB this may be easier and no BIOS intervention should be required. Drivers which support WoW must set their supported hardware/driver triggers on the wiphy's cfg80211 wow_triggers_supported. cfg80211 drivers will be informed of the enabled wow parameters through the suspend() and resume() cfg80211 callback. By default we disable all triggers. To enable WoW you must first be associated to an AP, WoW triggers will also be disabled upon disconnect due to the security reasons and the natural roaming nature of wireless. Userspace is expected to enable WoW per BSS, and users running things manual will have to enabe WoW every time they connect to the AP. Signed-off-by: Luis R. Rodriguez --- include/linux/nl80211.h | 36 +++++++++++ include/net/cfg80211.h | 36 +++++++++- net/mac80211/cfg.c | 4 +- net/wireless/core.h | 3 + net/wireless/nl80211.c | 161 +++++++++++++++++++++++++++++++++++++++++++++++ net/wireless/sme.c | 12 ++++ net/wireless/sysfs.c | 4 +- 7 files changed, 248 insertions(+), 8 deletions(-) diff --git a/include/linux/nl80211.h b/include/linux/nl80211.h index cb3dc60..b92ab06 100644 --- a/include/linux/nl80211.h +++ b/include/linux/nl80211.h @@ -265,6 +265,18 @@ * @NL80211_CMD_SET_WIPHY_NETNS: Set a wiphy's netns. Note that all devices * associated with this wiphy must be down and will follow. * + * @NL80211_CMD_GET_WOW: get Wake-on-Wireless-LAN (WoW) settings. + * @NL80211_CMD_SET_WOW: set Wake-on-Wireless-LAN (Wow) settings. Wake on + * wireless makes use of standard Wake-on-LAN (WoL) frames, you receive + * a WoW frame when your AP sends you a regular WOL frame. The difference + * difference WoL is you need to be associated to an AP in order to + * receive WoW frames, so additional triggers are available for a wakeup. + * A driver capable of WoW should initialize the wiphy with its supported + * WoW triggers. Upon suspend cfg80211 will inform the driver of the user + * enabled triggers. By default no WoW triggers are enabled. + * For more information see: + * http://wireless.kernel.org/en/users/Documentation/WoW + * * @NL80211_CMD_MAX: highest used command number * @__NL80211_CMD_AFTER_LAST: internal use */ @@ -341,6 +353,9 @@ enum nl80211_commands { NL80211_CMD_SET_WIPHY_NETNS, + NL80211_CMD_GET_WOW, + NL80211_CMD_SET_WOW, + /* add new commands above here */ /* used to define NL80211_CMD_MAX below */ @@ -360,6 +375,8 @@ enum nl80211_commands { #define NL80211_CMD_DEAUTHENTICATE NL80211_CMD_DEAUTHENTICATE #define NL80211_CMD_DISASSOCIATE NL80211_CMD_DISASSOCIATE #define NL80211_CMD_REG_BEACON_HINT NL80211_CMD_REG_BEACON_HINT +#define NL80211_CMD_GET_WOW NL80211_CMD_GET_WOW +#define NL80211_CMD_SET_WOW NL80211_CMD_SET_WOW /** * enum nl80211_attrs - nl80211 netlink attributes @@ -580,6 +597,12 @@ enum nl80211_commands { * * @NL80211_ATTR_PID: Process ID of a network namespace. * + * @NL80211_ATTR_WOW_TRIGGERS_SUPPORTED: the supported WoW triggers + * @NL80211_ATTR_WOW_TRIGGERS_ENABLED: used by %NL80211_CMD_SET_WOW to + * indicate which WoW triggers should be enabled. This is also + * used by %NL80211_CMD_GET_WOW to get the currently enabled WoW + * triggers. + * * @NL80211_ATTR_MAX: highest attribute number currently defined * @__NL80211_ATTR_AFTER_LAST: internal use */ @@ -710,6 +733,9 @@ enum nl80211_attrs { NL80211_ATTR_PID, + NL80211_ATTR_WOW_TRIGGERS_SUPPORTED, + NL80211_ATTR_WOW_TRIGGERS_ENABLED, + /* add attributes here, update the policy in nl80211.c */ __NL80211_ATTR_AFTER_LAST, @@ -1381,4 +1407,14 @@ enum nl80211_key_attributes { NL80211_KEY_MAX = __NL80211_KEY_AFTER_LAST - 1 }; +/** + * enum nl80211_wow_triggers - Wake-on-Wireless-LAN triggers + * + * NL80211_WOW_TRIGGER_MAGIC_PACKET: a wake signal will be sent to the + * devices if a magic packet is received. + */ +enum nl80211_wow_triggers { + NL80211_WOW_TRIGGER_MAGIC_PACKET = 1 << 1, +}; + #endif /* __LINUX_NL80211_H */ diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 0d27877..177f194 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -828,6 +828,25 @@ struct cfg80211_bitrate_mask { u32 maxrate; /* in kbps, 0 == no limit */ }; +/* + * struct cfg80211_wow - Wake on Wireless-LAN support info + * + * This structure defines the WoW triggers enabled and set for the device. + * For now we only carry the supported tiggers but this is expected to grow + * once we add support for user patterns. + * + * @triggers_enabled: enabled triggers by the user. Default + * is to disable all triggers. The flags for this bitmask + * are %NL80211_WOW_TRIGGER_*. + * @netdev: net_device where we enabled WoW. This is only used to clear + * the @triggers_enabled upon disassociation and to prevent settings + * onto other netdevices from the same rdev. + */ +struct cfg80211_wow { + u32 triggers_enabled; + struct net_device *netdev; +}; + /** * struct cfg80211_ops - backend description for wireless configuration * @@ -841,8 +860,11 @@ struct cfg80211_bitrate_mask { * wireless extensions but this is subject to reevaluation as soon as this * code is used more widely and we have a first user without wext. * - * @suspend: wiphy device needs to be suspended - * @resume: wiphy device needs to be resumed + * @suspend: wiphy device needs to be suspended. We pass the struct cfg80211_wow + * so the device enables the appropriate triggers during suspend. + * @resume: wiphy device needs to be resumed. We pass the struct cfg80211_wow + * so the device can opt out from adding new information for the AP we + * we were last associated to when WoW was enabled. * * @add_virtual_intf: create a new virtual interface with the given name, * must set the struct wireless_dev's iftype. Beware: You must create @@ -932,8 +954,8 @@ struct cfg80211_bitrate_mask { * @testmode_cmd: run a test mode command */ struct cfg80211_ops { - int (*suspend)(struct wiphy *wiphy); - int (*resume)(struct wiphy *wiphy); + int (*suspend)(struct wiphy *wiphy, struct cfg80211_wow *wow); + int (*resume)(struct wiphy *wiphy, struct cfg80211_wow *wow); int (*add_virtual_intf)(struct wiphy *wiphy, char *name, enum nl80211_iftype type, u32 *flags, @@ -1089,6 +1111,10 @@ struct cfg80211_ops { * @frag_threshold: Fragmentation threshold (dot11FragmentationThreshold); * -1 = fragmentation disabled, only odd values >= 256 used * @rts_threshold: RTS threshold (dot11RTSThreshold); -1 = RTS/CTS disabled + * @wow_triggers_supported: supported bitmask of Wake-on-Wireless triggers. + * The flags for this bitmask are %NL80211_WOW_TRIGGER_*. The driver + * should set the capabilities before registering the wiphy. WoW triggers + * which should be used are passed to the driver upon suspend. * @net: the network namespace this wiphy currently lives in * @netnsok: if set to false, do not allow changing the netns of this * wiphy at all @@ -1121,6 +1147,8 @@ struct wiphy { u32 frag_threshold; u32 rts_threshold; + u32 wow_triggers_supported; + /* If multiple wiphys are registered and you're handed e.g. * a regular netdev with assigned ieee80211_ptr, you won't * know whether it points to a wiphy your driver has registered diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index 4bbf500..457887f 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -1128,12 +1128,12 @@ static int ieee80211_set_channel(struct wiphy *wiphy, } #ifdef CONFIG_PM -static int ieee80211_suspend(struct wiphy *wiphy) +static int ieee80211_suspend(struct wiphy *wiphy, struct cfg80211_wow *wow) { return __ieee80211_suspend(wiphy_priv(wiphy)); } -static int ieee80211_resume(struct wiphy *wiphy) +static int ieee80211_resume(struct wiphy *wiphy, struct cfg80211_wow *wow) { return __ieee80211_resume(wiphy_priv(wiphy)); } diff --git a/net/wireless/core.h b/net/wireless/core.h index 4276b70..3f0163f 100644 --- a/net/wireless/core.h +++ b/net/wireless/core.h @@ -66,6 +66,9 @@ struct cfg80211_registered_device { struct work_struct conn_work; struct work_struct event_work; + /* Used to keep track of user configurable triggers */ + struct cfg80211_wow wow; + #ifdef CONFIG_CFG80211_DEBUGFS /* Debugfs entries */ struct wiphy_debugfsdentries { diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 7880a9c..c3812d2 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -138,6 +138,8 @@ static struct nla_policy nl80211_policy[NL80211_ATTR_MAX+1] __read_mostly = { [NL80211_ATTR_CIPHER_SUITE_GROUP] = { .type = NLA_U32 }, [NL80211_ATTR_WPA_VERSIONS] = { .type = NLA_U32 }, [NL80211_ATTR_PID] = { .type = NLA_U32 }, + [NL80211_ATTR_WOW_TRIGGERS_SUPPORTED] = { .type = NLA_U32 }, + [NL80211_ATTR_WOW_TRIGGERS_ENABLED] = { .type = NLA_U32 }, }; /* policy for the attributes */ @@ -4078,6 +4080,153 @@ static int nl80211_wiphy_netns(struct sk_buff *skb, struct genl_info *info) out: cfg80211_unlock_rdev(rdev); rtnl_unlock(); + + return err; +} +static int nl80211_get_wow(struct sk_buff *skb, + struct genl_info *info) +{ + struct cfg80211_registered_device *rdev; + struct net_device *dev; + struct wiphy *wiphy; + int err; + void *hdr; + struct sk_buff *msg; + + rtnl_lock(); + + /* Look up our device */ + err = get_rdev_dev_by_info_ifindex(info, &rdev, &dev); + if (err) + goto out_rtnl; + + wiphy = &rdev->wiphy; + + /* Draw up a netlink message to send back */ + msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (!msg) { + err = -ENOBUFS; + goto out; + } + hdr = nl80211hdr_put(msg, info->snd_pid, info->snd_seq, 0, + NL80211_CMD_GET_WOW); + if (!hdr) + goto nla_put_failure; + NLA_PUT_U32(msg, NL80211_ATTR_WOW_TRIGGERS_SUPPORTED, + wiphy->wow_triggers_supported); + NLA_PUT_U32(msg, NL80211_ATTR_WOW_TRIGGERS_ENABLED, + rdev->wow.triggers_enabled); + + genlmsg_end(msg, hdr); + err = genlmsg_reply(msg, info); + goto out; + + nla_put_failure: + genlmsg_cancel(msg, hdr); + err = -EMSGSIZE; + out: + /* Cleanup */ + cfg80211_unlock_rdev(rdev); + dev_put(dev); + out_rtnl: + rtnl_unlock(); + + return err; +} + +static int nl80211_set_wow(struct sk_buff *skb, struct genl_info *info) +{ + int err; + struct cfg80211_registered_device *rdev; + struct net_device *dev; + struct wiphy *wiphy; + struct wireless_dev *wdev; + u32 triggers_requested; + + if (!info->attrs[NL80211_ATTR_WOW_TRIGGERS_ENABLED]) + return -EINVAL; + + rtnl_lock(); + + err = get_rdev_dev_by_info_ifindex(info, &rdev, &dev); + if (err) + goto out_rtnl; + + wiphy = &rdev->wiphy; + + if (!wiphy->wow_triggers_supported) { + err = -EOPNOTSUPP; + goto out; + } + + triggers_requested = + nla_get_u32(info->attrs[NL80211_ATTR_WOW_TRIGGERS_ENABLED]); + + if (rdev->wow.netdev) { + if (rdev->wow.netdev != dev) { + err = -EALREADY; + goto out; + } + if (!triggers_requested) { + rdev->wow.triggers_enabled = 0; + rdev->wow.netdev = NULL; + goto out; + } + } + + /* This would be a no-op as the rdev->wow should be unconfigured */ + if (!triggers_requested) + goto out; + + /* We only support magic packet right now */ + if (triggers_requested & ~NL80211_WOW_TRIGGER_MAGIC_PACKET) { + err = -EOPNOTSUPP; + goto out; + } + + if ((triggers_requested & wiphy->wow_triggers_supported) != + triggers_requested) { + err = -EOPNOTSUPP; + goto out; + } + + /* + * It only makes sense to enable WoW if we're associated as a STA, + * the AP should be buffering frames for us. We'll discard all frames + * and only process the frames which will trigger us on in hardware. + */ + wdev = dev->ieee80211_ptr; + + wdev_lock(wdev); + + if (wdev->iftype != NL80211_IFTYPE_STATION) { + err = -EINVAL; + wdev_unlock(wdev); + goto out; + } + + if (wdev->sme_state != CFG80211_SME_CONNECTED) { + err = -ENOTCONN; + wdev_unlock(wdev); + goto out; + } + + wdev_unlock(wdev); + + /* + * Apply changes. This information gets passed to the + * drivers during suspend. + */ + rdev->wow.netdev = dev; + rdev->wow.triggers_enabled = triggers_requested; + + out: + /* cleanup */ + cfg80211_unlock_rdev(rdev); + dev_put(dev); + out_rtnl: + rtnl_unlock(); + return err; } @@ -4320,6 +4469,18 @@ static struct genl_ops nl80211_ops[] = { .policy = nl80211_policy, .flags = GENL_ADMIN_PERM, }, + { + .cmd = NL80211_CMD_GET_WOW, + .doit = nl80211_get_wow, + .policy = nl80211_policy, + /* can be retrieved by unprivileged users */ + }, + { + .cmd = NL80211_CMD_SET_WOW, + .doit = nl80211_set_wow, + .policy = nl80211_policy, + .flags = GENL_ADMIN_PERM, + }, }; static struct genl_multicast_group nl80211_mlme_mcgrp = { .name = "mlme", diff --git a/net/wireless/sme.c b/net/wireless/sme.c index fb42860..0b68092 100644 --- a/net/wireless/sme.c +++ b/net/wireless/sme.c @@ -570,6 +570,18 @@ void __cfg80211_disconnected(struct net_device *dev, const u8 *ie, wrqu.ap_addr.sa_family = ARPHRD_ETHER; wireless_send_event(dev, SIOCGIWAP, &wrqu, NULL); #endif + + /* + * If we're not associated we cannot possibly get frames + * to generate a WoW trigger. We also do this for security + * purposes as with 802.11 we can roam to untrusted networks. + * We require userspace to send a request to enable WoW for + * each BSS. + */ + if (rdev->wow.netdev && rdev->wow.netdev == dev) { + rdev->wow.netdev = NULL; + rdev->wow.triggers_enabled = 0; + } } void cfg80211_disconnected(struct net_device *dev, u16 reason, diff --git a/net/wireless/sysfs.c b/net/wireless/sysfs.c index efe3c5c..e86a3b0 100644 --- a/net/wireless/sysfs.c +++ b/net/wireless/sysfs.c @@ -64,7 +64,7 @@ static int wiphy_suspend(struct device *dev, pm_message_t state) if (rdev->ops->suspend) { rtnl_lock(); - ret = rdev->ops->suspend(&rdev->wiphy); + ret = rdev->ops->suspend(&rdev->wiphy, &rdev->wow); rtnl_unlock(); } @@ -83,7 +83,7 @@ static int wiphy_resume(struct device *dev) if (rdev->ops->resume) { rtnl_lock(); - ret = rdev->ops->resume(&rdev->wiphy); + ret = rdev->ops->resume(&rdev->wiphy, &rdev->wow); rtnl_unlock(); } -- 1.6.0.4 From 152c039b3cb336e2bec2cee5b0c3b98782c4ccf1 Mon Sep 17 00:00:00 2001 From: Luis R. Rodriguez Date: Tue, 12 May 2009 17:31:32 -0700 Subject: [PATCH] mac80211: add WoW support If we're associated and have not already gone into PS mode we ensure send a nullfunc frame before going to suspend. Upon resume we ensure we restore PS mode if it was enabled and inform the driver accordingly, also notify the AP we're awake. WoW requires the radio to be left on during suspend, to do this we inform drivers of the requested wow triggers on the mac80211 stop callback. When using WoW we do not remove the interface for the device we are using as a STA in mac80211. Signed-off-by: Luis R. Rodriguez --- drivers/net/wireless/adm8211.c | 2 +- drivers/net/wireless/at76c50x-usb.c | 3 +- drivers/net/wireless/ath/ar9170/main.c | 2 +- drivers/net/wireless/ath/ath5k/base.c | 4 +- drivers/net/wireless/ath/ath9k/main.c | 2 +- drivers/net/wireless/b43/main.c | 2 +- drivers/net/wireless/b43legacy/main.c | 2 +- drivers/net/wireless/iwlwifi/iwl-agn.c | 2 +- drivers/net/wireless/iwlwifi/iwl3945-base.c | 2 +- drivers/net/wireless/libertas_tf/main.c | 2 +- drivers/net/wireless/mac80211_hwsim.c | 2 +- drivers/net/wireless/mwl8k.c | 2 +- drivers/net/wireless/p54/main.c | 2 +- drivers/net/wireless/rt2x00/rt2x00.h | 2 +- drivers/net/wireless/rt2x00/rt2x00mac.c | 2 +- drivers/net/wireless/rtl818x/rtl8180_dev.c | 2 +- drivers/net/wireless/rtl818x/rtl8187_dev.c | 2 +- drivers/net/wireless/wl12xx/wl1251_main.c | 2 +- drivers/net/wireless/zd1211rw/zd_mac.c | 2 +- include/net/mac80211.h | 15 ++++--- net/mac80211/cfg.c | 5 +- net/mac80211/debugfs.c | 4 +- net/mac80211/driver-ops.h | 6 +- net/mac80211/driver-trace.h | 6 ++- net/mac80211/ieee80211_i.h | 19 ++++++-- net/mac80211/iface.c | 6 +- net/mac80211/main.c | 2 +- net/mac80211/pm.c | 61 +++++++++++++++++++++++++- net/mac80211/util.c | 26 ++++++++++-- 29 files changed, 140 insertions(+), 51 deletions(-) diff --git a/drivers/net/wireless/adm8211.c b/drivers/net/wireless/adm8211.c index 5695911..776a715 100644 --- a/drivers/net/wireless/adm8211.c +++ b/drivers/net/wireless/adm8211.c @@ -1547,7 +1547,7 @@ fail: return retval; } -static void adm8211_stop(struct ieee80211_hw *dev) +static void adm8211_stop(struct ieee80211_hw *dev, struct cfg80211_wow *wow) { struct adm8211_priv *priv = dev->priv; diff --git a/drivers/net/wireless/at76c50x-usb.c b/drivers/net/wireless/at76c50x-usb.c index 13303fa..8357fef 100644 --- a/drivers/net/wireless/at76c50x-usb.c +++ b/drivers/net/wireless/at76c50x-usb.c @@ -1767,7 +1767,8 @@ error: return 0; } -static void at76_mac80211_stop(struct ieee80211_hw *hw) +static void at76_mac80211_stop(struct ieee80211_hw *hw, + struct cfg80211_wow *wow) { struct at76_priv *priv = hw->priv; diff --git a/drivers/net/wireless/ath/ar9170/main.c b/drivers/net/wireless/ath/ar9170/main.c index c7287a8..c2d307e 100644 --- a/drivers/net/wireless/ath/ar9170/main.c +++ b/drivers/net/wireless/ath/ar9170/main.c @@ -1282,7 +1282,7 @@ out: return err; } -static void ar9170_op_stop(struct ieee80211_hw *hw) +static void ar9170_op_stop(struct ieee80211_hw *hw, struct cfg80211_wow *wow) { struct ar9170 *ar = hw->priv; unsigned int i; diff --git a/drivers/net/wireless/ath/ath5k/base.c b/drivers/net/wireless/ath/ath5k/base.c index 7db32ce..035e713 100644 --- a/drivers/net/wireless/ath/ath5k/base.c +++ b/drivers/net/wireless/ath/ath5k/base.c @@ -223,7 +223,7 @@ static int ath5k_tx_queue(struct ieee80211_hw *hw, struct sk_buff *skb, static int ath5k_reset(struct ath5k_softc *sc, struct ieee80211_channel *chan); static int ath5k_reset_wake(struct ath5k_softc *sc); static int ath5k_start(struct ieee80211_hw *hw); -static void ath5k_stop(struct ieee80211_hw *hw); +static void ath5k_stop(struct ieee80211_hw *hw, struct cfg80211_wow *wow); static int ath5k_add_interface(struct ieee80211_hw *hw, struct ieee80211_if_init_conf *conf); static void ath5k_remove_interface(struct ieee80211_hw *hw, @@ -2735,7 +2735,7 @@ static int ath5k_start(struct ieee80211_hw *hw) return ath5k_init(hw->priv); } -static void ath5k_stop(struct ieee80211_hw *hw) +static void ath5k_stop(struct ieee80211_hw *hw, struct cfg80211_wow *wow) { ath5k_stop_hw(hw->priv); } diff --git a/drivers/net/wireless/ath/ath9k/main.c b/drivers/net/wireless/ath/ath9k/main.c index 3436295..baef04d 100644 --- a/drivers/net/wireless/ath/ath9k/main.c +++ b/drivers/net/wireless/ath/ath9k/main.c @@ -2087,7 +2087,7 @@ exit: return 0; } -static void ath9k_stop(struct ieee80211_hw *hw) +static void ath9k_stop(struct ieee80211_hw *hw, struct cfg80211_wow *wow) { struct ath_wiphy *aphy = hw->priv; struct ath_softc *sc = aphy->sc; diff --git a/drivers/net/wireless/b43/main.c b/drivers/net/wireless/b43/main.c index 3f4360a..105c4ec 100644 --- a/drivers/net/wireless/b43/main.c +++ b/drivers/net/wireless/b43/main.c @@ -4366,7 +4366,7 @@ static int b43_op_start(struct ieee80211_hw *hw) return err; } -static void b43_op_stop(struct ieee80211_hw *hw) +static void b43_op_stop(struct ieee80211_hw *hw, struct cfg80211_wow *wow) { struct b43_wl *wl = hw_to_b43_wl(hw); struct b43_wldev *dev = wl->current_dev; diff --git a/drivers/net/wireless/b43legacy/main.c b/drivers/net/wireless/b43legacy/main.c index c4973c1..9235578 100644 --- a/drivers/net/wireless/b43legacy/main.c +++ b/drivers/net/wireless/b43legacy/main.c @@ -3469,7 +3469,7 @@ out_mutex_unlock: return err; } -static void b43legacy_op_stop(struct ieee80211_hw *hw) +static void b43legacy_op_stop(struct ieee80211_hw *hw, struct cfg80211_wow *wow) { struct b43legacy_wl *wl = hw_to_b43legacy_wl(hw); struct b43legacy_wldev *dev = wl->current_dev; diff --git a/drivers/net/wireless/iwlwifi/iwl-agn.c b/drivers/net/wireless/iwlwifi/iwl-agn.c index 44c7f23..5d28cde 100644 --- a/drivers/net/wireless/iwlwifi/iwl-agn.c +++ b/drivers/net/wireless/iwlwifi/iwl-agn.c @@ -2196,7 +2196,7 @@ out: return 0; } -static void iwl_mac_stop(struct ieee80211_hw *hw) +static void iwl_mac_stop(struct ieee80211_hw *hw, struct cfg80211_wow *wow) { struct iwl_priv *priv = hw->priv; diff --git a/drivers/net/wireless/iwlwifi/iwl3945-base.c b/drivers/net/wireless/iwlwifi/iwl3945-base.c index 2cc7e30..ca98680 100644 --- a/drivers/net/wireless/iwlwifi/iwl3945-base.c +++ b/drivers/net/wireless/iwlwifi/iwl3945-base.c @@ -3129,7 +3129,7 @@ out_release_irq: return ret; } -static void iwl3945_mac_stop(struct ieee80211_hw *hw) +static void iwl3945_mac_stop(struct ieee80211_hw *hw, struct cfg80211_wow *wow) { struct iwl_priv *priv = hw->priv; diff --git a/drivers/net/wireless/libertas_tf/main.c b/drivers/net/wireless/libertas_tf/main.c index 4872345..8557ca4 100644 --- a/drivers/net/wireless/libertas_tf/main.c +++ b/drivers/net/wireless/libertas_tf/main.c @@ -291,7 +291,7 @@ err_prog_firmware: return ret; } -static void lbtf_op_stop(struct ieee80211_hw *hw) +static void lbtf_op_stop(struct ieee80211_hw *hw, struct cfg80211_wow *wow) { struct lbtf_private *priv = hw->priv; unsigned long flags; diff --git a/drivers/net/wireless/mac80211_hwsim.c b/drivers/net/wireless/mac80211_hwsim.c index e7b3c07..d39e225 100644 --- a/drivers/net/wireless/mac80211_hwsim.c +++ b/drivers/net/wireless/mac80211_hwsim.c @@ -488,7 +488,7 @@ static int mac80211_hwsim_start(struct ieee80211_hw *hw) } -static void mac80211_hwsim_stop(struct ieee80211_hw *hw) +static void mac80211_hwsim_stop(struct ieee80211_hw *hw, struct cfg80211_wow *wow) { struct mac80211_hwsim_data *data = hw->priv; data->started = 0; diff --git a/drivers/net/wireless/mwl8k.c b/drivers/net/wireless/mwl8k.c index 4f72547..2703b26 100644 --- a/drivers/net/wireless/mwl8k.c +++ b/drivers/net/wireless/mwl8k.c @@ -2916,7 +2916,7 @@ static int mwl8k_stop_wt(struct work_struct *wt) return rc; } -static void mwl8k_stop(struct ieee80211_hw *hw) +static void mwl8k_stop(struct ieee80211_hw *hw, struct cfg80211_wow *wow) { int rc; struct mwl8k_stop_worker *worker; diff --git a/drivers/net/wireless/p54/main.c b/drivers/net/wireless/p54/main.c index 955f6d7..e0faf9e 100644 --- a/drivers/net/wireless/p54/main.c +++ b/drivers/net/wireless/p54/main.c @@ -190,7 +190,7 @@ out: return err; } -static void p54_stop(struct ieee80211_hw *dev) +static void p54_stop(struct ieee80211_hw *dev, struct cfg80211_wow *wow) { struct p54_common *priv = dev->priv; int i; diff --git a/drivers/net/wireless/rt2x00/rt2x00.h b/drivers/net/wireless/rt2x00/rt2x00.h index cbec91e..d1639e8 100644 --- a/drivers/net/wireless/rt2x00/rt2x00.h +++ b/drivers/net/wireless/rt2x00/rt2x00.h @@ -952,7 +952,7 @@ void rt2x00lib_rxdone(struct rt2x00_dev *rt2x00dev, */ int rt2x00mac_tx(struct ieee80211_hw *hw, struct sk_buff *skb); int rt2x00mac_start(struct ieee80211_hw *hw); -void rt2x00mac_stop(struct ieee80211_hw *hw); +void rt2x00mac_stop(struct ieee80211_hw *hw, struct cfg80211_wow *wow); int rt2x00mac_add_interface(struct ieee80211_hw *hw, struct ieee80211_if_init_conf *conf); void rt2x00mac_remove_interface(struct ieee80211_hw *hw, diff --git a/drivers/net/wireless/rt2x00/rt2x00mac.c b/drivers/net/wireless/rt2x00/rt2x00mac.c index 9d31c23..211562a 100644 --- a/drivers/net/wireless/rt2x00/rt2x00mac.c +++ b/drivers/net/wireless/rt2x00/rt2x00mac.c @@ -179,7 +179,7 @@ int rt2x00mac_start(struct ieee80211_hw *hw) } EXPORT_SYMBOL_GPL(rt2x00mac_start); -void rt2x00mac_stop(struct ieee80211_hw *hw) +void rt2x00mac_stop(struct ieee80211_hw *hw, struct cfg80211_wow *wow) { struct rt2x00_dev *rt2x00dev = hw->priv; diff --git a/drivers/net/wireless/rtl818x/rtl8180_dev.c b/drivers/net/wireless/rtl818x/rtl8180_dev.c index 09f46ab..807605c 100644 --- a/drivers/net/wireless/rtl818x/rtl8180_dev.c +++ b/drivers/net/wireless/rtl818x/rtl8180_dev.c @@ -628,7 +628,7 @@ static int rtl8180_start(struct ieee80211_hw *dev) return ret; } -static void rtl8180_stop(struct ieee80211_hw *dev) +static void rtl8180_stop(struct ieee80211_hw *dev, struct cfg80211_wow *wow) { struct rtl8180_priv *priv = dev->priv; u8 reg; diff --git a/drivers/net/wireless/rtl818x/rtl8187_dev.c b/drivers/net/wireless/rtl818x/rtl8187_dev.c index c9b9dbe..fd1e8f8 100644 --- a/drivers/net/wireless/rtl818x/rtl8187_dev.c +++ b/drivers/net/wireless/rtl818x/rtl8187_dev.c @@ -989,7 +989,7 @@ static int rtl8187_start(struct ieee80211_hw *dev) return 0; } -static void rtl8187_stop(struct ieee80211_hw *dev) +static void rtl8187_stop(struct ieee80211_hw *dev, struct cfg80211_wow *wow) { struct rtl8187_priv *priv = dev->priv; struct sk_buff *skb; diff --git a/drivers/net/wireless/wl12xx/wl1251_main.c b/drivers/net/wireless/wl12xx/wl1251_main.c index 509cbef..90f02f8 100644 --- a/drivers/net/wireless/wl12xx/wl1251_main.c +++ b/drivers/net/wireless/wl12xx/wl1251_main.c @@ -376,7 +376,7 @@ out: return ret; } -static void wl1251_op_stop(struct ieee80211_hw *hw) +static void wl1251_op_stop(struct ieee80211_hw *hw, struct cfg80211_wow *wow) { struct wl1251 *wl = hw->priv; diff --git a/drivers/net/wireless/zd1211rw/zd_mac.c b/drivers/net/wireless/zd1211rw/zd_mac.c index 9600b72..70dcf01 100644 --- a/drivers/net/wireless/zd1211rw/zd_mac.c +++ b/drivers/net/wireless/zd1211rw/zd_mac.c @@ -256,7 +256,7 @@ out: return r; } -static void zd_op_stop(struct ieee80211_hw *hw) +static void zd_op_stop(struct ieee80211_hw *hw, struct cfg80211_wow *wow) { struct zd_mac *mac = zd_hw_mac(hw); struct zd_chip *chip = &mac->chip; diff --git a/include/net/mac80211.h b/include/net/mac80211.h index 7dd67a1..101e450 100644 --- a/include/net/mac80211.h +++ b/include/net/mac80211.h @@ -1294,11 +1294,14 @@ enum ieee80211_ampdu_mlme_action { * Must be implemented. * * @stop: Called after last netdevice attached to the hardware - * is disabled. This should turn off the hardware (at least - * it must turn off frame reception.) - * May be called right after add_interface if that rejects - * an interface. - * Must be implemented. + * is disabled or during suspend. This should turn off the + * hardware (at least it must turn off frame reception) unless + * the device wants to enable Wake-on-Wireless-LAN. In order to + * process WoW triggers the radio must be left on and the driver + * must enable the triggers in hardware. This callback may be + * called right after add_interface if that rejects an interface. + * Must be implemented. WoW triggers which should be enabled prior + * suspend are passed in the callback. * * @add_interface: Called when a netdevice attached to the hardware is * enabled. Because it is not called for monitor mode devices, @start @@ -1425,7 +1428,7 @@ enum ieee80211_ampdu_mlme_action { struct ieee80211_ops { int (*tx)(struct ieee80211_hw *hw, struct sk_buff *skb); int (*start)(struct ieee80211_hw *hw); - void (*stop)(struct ieee80211_hw *hw); + void (*stop)(struct ieee80211_hw *hw, struct cfg80211_wow *wow); int (*add_interface)(struct ieee80211_hw *hw, struct ieee80211_if_init_conf *conf); void (*remove_interface)(struct ieee80211_hw *hw, diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index 457887f..cd248b4 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -1130,13 +1130,14 @@ static int ieee80211_set_channel(struct wiphy *wiphy, #ifdef CONFIG_PM static int ieee80211_suspend(struct wiphy *wiphy, struct cfg80211_wow *wow) { - return __ieee80211_suspend(wiphy_priv(wiphy)); + return __ieee80211_suspend(wiphy_priv(wiphy), wow); } static int ieee80211_resume(struct wiphy *wiphy, struct cfg80211_wow *wow) { - return __ieee80211_resume(wiphy_priv(wiphy)); + return __ieee80211_resume(wiphy_priv(wiphy), wow); } + #else #define ieee80211_suspend NULL #define ieee80211_resume NULL diff --git a/net/mac80211/debugfs.c b/net/mac80211/debugfs.c index 96991b6..f4eb259 100644 --- a/net/mac80211/debugfs.c +++ b/net/mac80211/debugfs.c @@ -115,8 +115,8 @@ static ssize_t reset_write(struct file *file, const char __user *user_buf, struct ieee80211_local *local = file->private_data; rtnl_lock(); - __ieee80211_suspend(&local->hw); - __ieee80211_resume(&local->hw); + __ieee80211_suspend(&local->hw, NULL); + __ieee80211_resume(&local->hw, NULL); rtnl_unlock(); return count; diff --git a/net/mac80211/driver-ops.h b/net/mac80211/driver-ops.h index 4100c36..f12488f 100644 --- a/net/mac80211/driver-ops.h +++ b/net/mac80211/driver-ops.h @@ -17,10 +17,10 @@ static inline int drv_start(struct ieee80211_local *local) return ret; } -static inline void drv_stop(struct ieee80211_local *local) +static inline void drv_stop(struct ieee80211_local *local, struct cfg80211_wow *wow) { - local->ops->stop(&local->hw); - trace_drv_stop(local); + local->ops->stop(&local->hw, wow); + trace_drv_stop(local, wow); } static inline int drv_add_interface(struct ieee80211_local *local, diff --git a/net/mac80211/driver-trace.h b/net/mac80211/driver-trace.h index 48c93d1..f83f913 100644 --- a/net/mac80211/driver-trace.h +++ b/net/mac80211/driver-trace.h @@ -51,16 +51,18 @@ TRACE_EVENT(drv_start, ); TRACE_EVENT(drv_stop, - TP_PROTO(struct ieee80211_local *local), + TP_PROTO(struct ieee80211_local *local, struct cfg80211_wow *wow), - TP_ARGS(local), + TP_ARGS(local, wow), TP_STRUCT__entry( LOCAL_ENTRY + __field(u32, triggers_enabled) ), TP_fast_assign( LOCAL_ASSIGN; + __entry->triggers_enabled = wow->triggers_enabled; ), TP_printk( diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index 6a01771..0181959 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -612,6 +612,14 @@ struct ieee80211_local { */ bool quiescing; + /* + * This will be true if we had enabled PS before going to suspend. + * We use this to help resume restore the PS state to what it was. + * Note that this only makes sense if we're associated and are a + * station. + */ + bool ps_before_suspend; + int tx_headroom; /* required headroom for hardware/radiotap */ /* Tasklet and skb queue to process calls from IRQ mode. All frames @@ -1032,17 +1040,18 @@ void ieee80211_process_measurement_req(struct ieee80211_sub_if_data *sdata, size_t len); /* Suspend/resume and hw reconfiguration */ -int ieee80211_reconfig(struct ieee80211_local *local); +int ieee80211_reconfig(struct ieee80211_local *local, struct cfg80211_wow *wow); #ifdef CONFIG_PM -int __ieee80211_suspend(struct ieee80211_hw *hw); +int __ieee80211_suspend(struct ieee80211_hw *hw, struct cfg80211_wow *wow); -static inline int __ieee80211_resume(struct ieee80211_hw *hw) +static inline int __ieee80211_resume(struct ieee80211_hw *hw, + struct cfg80211_wow *wow) { - return ieee80211_reconfig(hw_to_local(hw)); + return ieee80211_reconfig(hw_to_local(hw), wow); } #else -static inline int __ieee80211_suspend(struct ieee80211_hw *hw) +static inline int __ieee80211_suspend(struct ieee80211_hw *hw, struct cfg80211_wow *wow) { return 0; } diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c index 0cb29df..e89198f 100644 --- a/net/mac80211/iface.c +++ b/net/mac80211/iface.c @@ -195,7 +195,7 @@ static int ieee80211_open(struct net_device *dev) */ if (!is_valid_ether_addr(dev->dev_addr)) { if (!local->open_count) - drv_stop(local); + drv_stop(local, NULL); return -EADDRNOTAVAIL; } @@ -321,7 +321,7 @@ static int ieee80211_open(struct net_device *dev) drv_remove_interface(local, &conf); err_stop: if (!local->open_count) - drv_stop(local); + drv_stop(local, NULL); err_del_bss: sdata->bss = NULL; if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN) @@ -537,7 +537,7 @@ static int ieee80211_stop(struct net_device *dev) ieee80211_recalc_ps(local, -1); if (local->open_count == 0) { - drv_stop(local); + drv_stop(local, NULL); ieee80211_led_radio(local, false); diff --git a/net/mac80211/main.c b/net/mac80211/main.c index 02cabbf..99d5a8e 100644 --- a/net/mac80211/main.c +++ b/net/mac80211/main.c @@ -575,7 +575,7 @@ static void ieee80211_restart_work(struct work_struct *work) container_of(work, struct ieee80211_local, restart_work); rtnl_lock(); - ieee80211_reconfig(local); + ieee80211_reconfig(local, NULL); rtnl_unlock(); } diff --git a/net/mac80211/pm.c b/net/mac80211/pm.c index 7a549f9..bac507c 100644 --- a/net/mac80211/pm.c +++ b/net/mac80211/pm.c @@ -6,16 +6,66 @@ #include "driver-ops.h" #include "led.h" -int __ieee80211_suspend(struct ieee80211_hw *hw) +int __ieee80211_suspend(struct ieee80211_hw *hw, struct cfg80211_wow *wow) { struct ieee80211_local *local = hw_to_local(hw); struct ieee80211_sub_if_data *sdata; struct ieee80211_if_init_conf conf; struct sta_info *sta; unsigned long flags; + bool wow_netdev_found = false; ieee80211_scan_cancel(local); + local->ps_before_suspend = !!(local->hw.conf.flags & IEEE80211_CONF_PS); + + if (wow->triggers_enabled) { + if (!local->ps_before_suspend) { + local->hw.conf.flags |= IEEE80211_CONF_PS; + ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS); + } + + del_timer_sync(&local->dynamic_ps_timer); + cancel_work_sync(&local->dynamic_ps_enable_work); + + /* + * Send nullfunc PS frame to AP so it can buffer frames for us, + * unless we already are in that PS state. We'll discard all + * frames except the ones our harware will process and use for + * triggering a wake up. + */ + list_for_each_entry(sdata, &local->interfaces, list) { + struct ieee80211_if_managed *ifmgd; + if (sdata->dev != wow->netdev) + continue; + /* WoW only applies to STAs */ + if (WARN_ON(sdata->vif.type != NL80211_IFTYPE_STATION)) + continue; + ifmgd = &sdata->u.mgd; + /* + * cfg80211 should have cleared the + * triggers upon disassociation. + */ + if (WARN_ON(!ifmgd->associated)) + continue; + if (!local->ps_before_suspend) + ieee80211_send_nullfunc(local, sdata, 1); + wow_netdev_found = true; + break; + } + + /* + * WoW triggers should be cleared if the netdev claiming + * it no longer exists + */ + WARN_ON(!wow_netdev_found); + + } + + if (wow_netdev_found) + printk(KERN_DEBUG "mac80211: WoW enabled interface found, " + "will try to enable WoW during suspend\n"); + ieee80211_stop_queues_by_reason(hw, IEEE80211_QUEUE_STOP_REASON_SUSPEND); @@ -61,7 +111,10 @@ int __ieee80211_suspend(struct ieee80211_hw *hw) /* stop hardware - this must stop RX */ if (local->open_count) { ieee80211_led_radio(local, false); - drv_stop(local); + if (wow_netdev_found) + drv_stop(local, wow); + else + drv_stop(local, NULL); } /* remove STAs */ @@ -73,7 +126,6 @@ int __ieee80211_suspend(struct ieee80211_hw *hw) sdata = container_of(sdata->bss, struct ieee80211_sub_if_data, u.ap); - drv_sta_notify(local, &sdata->vif, STA_NOTIFY_REMOVE, &sta->sta); } @@ -87,6 +139,9 @@ int __ieee80211_suspend(struct ieee80211_hw *hw) switch(sdata->vif.type) { case NL80211_IFTYPE_STATION: ieee80211_sta_quiesce(sdata); + /* Don't remove the interface when WoW is enabled */ + if (sdata->dev != wow->netdev) + continue; break; case NL80211_IFTYPE_ADHOC: ieee80211_ibss_quiesce(sdata); diff --git a/net/mac80211/util.c b/net/mac80211/util.c index 7fc5584..4dbf8d3 100644 --- a/net/mac80211/util.c +++ b/net/mac80211/util.c @@ -954,7 +954,7 @@ u32 ieee80211_sta_get_rates(struct ieee80211_local *local, return supp_rates; } -int ieee80211_reconfig(struct ieee80211_local *local) +int ieee80211_reconfig(struct ieee80211_local *local, struct cfg80211_wow *wow) { struct ieee80211_hw *hw = &local->hw; struct ieee80211_sub_if_data *sdata; @@ -970,6 +970,9 @@ int ieee80211_reconfig(struct ieee80211_local *local) */ local->suspended = false; + if (from_suspend && local->ps_before_suspend) + local->hw.conf.flags |= IEEE80211_CONF_PS; + /* restart hardware */ if (local->open_count) { res = drv_start(local); @@ -977,11 +980,16 @@ int ieee80211_reconfig(struct ieee80211_local *local) ieee80211_led_radio(local, true); } - /* add interfaces */ + /* + * Add all interfaces back again. If we had enabled WoW we + * don't need to add the interface as we never removed it + * at suspend. + */ list_for_each_entry(sdata, &local->interfaces, list) { if (sdata->vif.type != NL80211_IFTYPE_AP_VLAN && sdata->vif.type != NL80211_IFTYPE_MONITOR && - netif_running(sdata->dev)) { + netif_running(sdata->dev) && + sdata->dev != wow->netdev) { conf.vif = &sdata->vif; conf.type = sdata->vif.type; conf.mac_addr = sdata->dev->dev_addr; @@ -998,7 +1006,6 @@ int ieee80211_reconfig(struct ieee80211_local *local) sdata = container_of(sdata->bss, struct ieee80211_sub_if_data, u.ap); - drv_sta_notify(local, &sdata->vif, STA_NOTIFY_ADD, &sta->sta); } @@ -1072,6 +1079,17 @@ int ieee80211_reconfig(struct ieee80211_local *local) return 0; #ifdef CONFIG_PM + if (!local->ps_before_suspend) { + list_for_each_entry(sdata, &local->interfaces, list) { + struct ieee80211_if_managed *ifmgd; + if (sdata->vif.type != NL80211_IFTYPE_STATION) + continue; + ifmgd = &sdata->u.mgd; + if (ifmgd->associated) + ieee80211_send_nullfunc(local, sdata, 0); + } + } + local->suspended = false; list_for_each_entry(sdata, &local->interfaces, list) { -- 1.6.0.4 From 321f2f7d6734fd292dc949a1c540cf073f47b3e2 Mon Sep 17 00:00:00 2001 From: Luis R. Rodriguez Date: Tue, 14 Jul 2009 06:51:47 -0700 Subject: [PATCH] ath9k: Add Wake-on-Wireless-LAN support This adds WoW suppport for ath9k. Note that not all cards have WoW support available; you also need BIOS support to trigger a wake from the device's PCI PME. We enable only Magic Packet triggers for now, note that if you disconnect you won't get a wake up. Beacon miss and link change triggers need more testing before being enabled. Tested on AR9280 with Magic Packet. Signed-off-by: Luis R. Rodriguez --- drivers/net/wireless/ath/ath9k/Makefile | 1 + drivers/net/wireless/ath/ath9k/ath9k.h | 18 + drivers/net/wireless/ath/ath9k/debug.c | 1 - drivers/net/wireless/ath/ath9k/eeprom.c | 2 + drivers/net/wireless/ath/ath9k/eeprom.h | 1 + drivers/net/wireless/ath/ath9k/hw.c | 45 +++ drivers/net/wireless/ath/ath9k/hw.h | 57 ++++ drivers/net/wireless/ath/ath9k/initvals.h | 31 ++ drivers/net/wireless/ath/ath9k/main.c | 23 ++ drivers/net/wireless/ath/ath9k/pci.c | 122 +++++++ drivers/net/wireless/ath/ath9k/reg.h | 154 +++++++++ drivers/net/wireless/ath/ath9k/wow.c | 514 +++++++++++++++++++++++++++++ 12 files changed, 968 insertions(+), 1 deletions(-) create mode 100644 drivers/net/wireless/ath/ath9k/wow.c diff --git a/drivers/net/wireless/ath/ath9k/Makefile b/drivers/net/wireless/ath/ath9k/Makefile index 783bc39..d4a598d 100644 --- a/drivers/net/wireless/ath/ath9k/Makefile +++ b/drivers/net/wireless/ath/ath9k/Makefile @@ -14,5 +14,6 @@ ath9k-y += hw.o \ ath9k-$(CONFIG_PCI) += pci.o ath9k-$(CONFIG_ATHEROS_AR71XX) += ahb.o ath9k-$(CONFIG_ATH9K_DEBUG) += debug.o +ath9k-$(CONFIG_PM) += wow.o obj-$(CONFIG_ATH9K) += ath9k.o diff --git a/drivers/net/wireless/ath/ath9k/ath9k.h b/drivers/net/wireless/ath/ath9k/ath9k.h index 751885a..efa185a 100644 --- a/drivers/net/wireless/ath/ath9k/ath9k.h +++ b/drivers/net/wireless/ath/ath9k/ath9k.h @@ -604,6 +604,12 @@ struct ath_softc { struct ath_ani ani; struct ath9k_node_stats nodestats; +#ifdef CONFIG_PM + bool wow_got_bmiss_intr; + bool wow_sleep_proc_intr; + u32 wow_intr_before_sleep; + u32 wow_triggers_enabled; +#endif #ifdef CONFIG_ATH9K_DEBUG struct ath9k_debug debug; #endif @@ -612,6 +618,18 @@ struct ath_softc { struct delayed_work tx_complete_work; }; +#ifdef CONFIG_PM +/* + * WoW trigger types + */ +#define AH_WOW_USER_PATTERN_EN 0x1 +#define AH_WOW_MAGIC_PATTERN_EN 0x2 +#define AH_WOW_LINK_CHANGE 0x4 +#define AH_WOW_BEACON_MISS 0x8 +#define AH_WOW_MAX_EVENTS 4 + +#endif + struct ath_wiphy { struct ath_softc *sc; /* shared for all virtual wiphys */ struct ieee80211_hw *hw; diff --git a/drivers/net/wireless/ath/ath9k/debug.c b/drivers/net/wireless/ath/ath9k/debug.c index 9f99f00..8705c35 100644 --- a/drivers/net/wireless/ath/ath9k/debug.c +++ b/drivers/net/wireless/ath/ath9k/debug.c @@ -486,7 +486,6 @@ static const struct file_operations fops_wiphy = { .owner = THIS_MODULE }; - int ath9k_init_debug(struct ath_softc *sc) { sc->debug.debug_mask = ath9k_debug; diff --git a/drivers/net/wireless/ath/ath9k/eeprom.c b/drivers/net/wireless/ath/ath9k/eeprom.c index df41ed5..1b5d7ce 100644 --- a/drivers/net/wireless/ath/ath9k/eeprom.c +++ b/drivers/net/wireless/ath/ath9k/eeprom.c @@ -1612,6 +1612,8 @@ static u32 ath9k_hw_def_get_eeprom(struct ath_hw *ah, return pBase->macAddr[2] << 8 | pBase->macAddr[3]; case AR_EEPROM_MAC(2): return pBase->macAddr[4] << 8 | pBase->macAddr[5]; + case EEP_WOW: + return pBase->eepMisc & BIT(1); case EEP_REG_0: return pBase->regDmn[0]; case EEP_REG_1: diff --git a/drivers/net/wireless/ath/ath9k/eeprom.h b/drivers/net/wireless/ath/ath9k/eeprom.h index 67b8bd1..dce2f51 100644 --- a/drivers/net/wireless/ath/ath9k/eeprom.h +++ b/drivers/net/wireless/ath/ath9k/eeprom.h @@ -182,6 +182,7 @@ enum eeprom_param { EEP_MAC_MSW, EEP_MAC_MID, EEP_MAC_LSW, + EEP_WOW, EEP_REG_0, EEP_REG_1, EEP_OP_CAP, diff --git a/drivers/net/wireless/ath/ath9k/hw.c b/drivers/net/wireless/ath/ath9k/hw.c index b9d1a13..190adb1 100644 --- a/drivers/net/wireless/ath/ath9k/hw.c +++ b/drivers/net/wireless/ath/ath9k/hw.c @@ -718,6 +718,12 @@ static struct ath_hw *ath9k_hw_do_attach(u16 devid, struct ath_softc *sc, ARRAY_SIZE(ar9285PciePhy_clkreq_always_on_L1_9285_1_2), 2); } +#ifdef CONFIG_PM + /* SerDes values during WOW sleep */ + INIT_INI_ARRAY(&ah->iniPcieSerdesWow, + ar9285PciePhy_AWOW_9285_1_2, + ARRAY_SIZE(ar9285PciePhy_AWOW_9285_1_2), 2); +#endif } else if (AR_SREV_9285_10_OR_LATER(ah)) { INIT_INI_ARRAY(&ah->iniModes, ar9285Modes_9285, ARRAY_SIZE(ar9285Modes_9285), 6); @@ -748,6 +754,12 @@ static struct ath_hw *ath9k_hw_do_attach(u16 devid, struct ath_softc *sc, ar9280PciePhy_clkreq_always_on_L1_9280, ARRAY_SIZE(ar9280PciePhy_clkreq_always_on_L1_9280), 2); } +#ifdef CONFIG_PM + /* SerDes values during WOW sleep */ + INIT_INI_ARRAY(&ah->iniPcieSerdesWow, ar9280PciePhy_AWOW_9280, + ARRAY_SIZE(ar9280PciePhy_AWOW_9280), 2); +#endif + INIT_INI_ARRAY(&ah->iniModesAdditional, ar9280Modes_fast_clock_9280_2, ARRAY_SIZE(ar9280Modes_fast_clock_9280_2), 3); @@ -893,6 +905,16 @@ static struct ath_hw *ath9k_hw_do_attach(u16 devid, struct ath_softc *sc, goto bad; } +#ifdef CONFIG_PM + /* WOW */ + ath9k_wow_set_gpio_reset_low(ah); + /* Clear the Wow Status */ + REG_WRITE(ah, AR_PCIE_PM_CTRL, REG_READ(ah, AR_PCIE_PM_CTRL) | + AR_PMCTRL_WOW_PME_CLR); + REG_WRITE(ah, AR_WOW_PATTERN_REG, + AR_WOW_CLEAR_EVENTS(REG_READ(ah, AR_WOW_PATTERN_REG))); +#endif + if (AR_SREV_9285(ah)) ah->tx_trig_level = (AR_FTRIG_256B >> AR_FTRIG_S); else @@ -3336,6 +3358,29 @@ void ath9k_hw_fill_cap_info(struct ath_hw *ah) "regdomain mapped to 0x%x\n", ah->regulatory.current_rd); } + eeval = ah->eep_ops->get_eeprom(ah, EEP_WOW); + if (AR_SREV_9280_10_OR_LATER(ah)) { + /* + * Devices >= AR9280 rev 10 are capable of WoW but require + * a few hardware changes. The EEPROM tells us whether these + * hardware changes are in place on the device. Just enabling + * WoW on the EEPROM is not enough to enable WoW. + */ + pCap->wow |= ATH9K_WOW_DEVICE_CAPABLE; + if (eeval) { + pCap->wow |= ATH9K_WOW_HW_ENABLED; + pCap->wow |= ATH9K_WOW_PATTERN_MATCH_EXACT; + /* + * First 4 bytes of all patterns must match, + * requires some wide masks to ensure proper + * functionality for the disassociation and + * deauthentication frames. + */ + if (AR_SREV_9280(ah)) + pCap->wow |= ATH9K_WOW_PATTERN_MATCH_DWORD; + } + } + eeval = ah->eep_ops->get_eeprom(ah, EEP_OP_MODE); bitmap_zero(pCap->wireless_modes, ATH9K_MODE_MAX); diff --git a/drivers/net/wireless/ath/ath9k/hw.h b/drivers/net/wireless/ath/ath9k/hw.h index 9a4570d..6669bbc 100644 --- a/drivers/net/wireless/ath/ath9k/hw.h +++ b/drivers/net/wireless/ath/ath9k/hw.h @@ -154,6 +154,41 @@ enum ath9k_capability_type { ATH9K_CAP_DS }; +/** + * enum ath9k_hw_wow_caps - WoW device capabilities + * + * @ATH9K_WOW_DEVICE_CAPABLE: device revision is capable of WoW + * @ATH9K_WOW_HW_ENABLED: EEPROM indicates hardware changes for WoW + * *should be* are present. Devices need some the PCI Wake signal to go + * through, this signal is typically capped with a resistor. + * Devices with WoW physically enabled have this resistor + * shorted to 0 ohm and also have the register that PCI core + * picks up as PCI PME capable enabled. + * ATH9K_WOW_PATTERN_MATCH_EXACT: device is capable of matching + * an exact pattern for de-authentication and disassocation. + * ATH9K_WOW_PATTERN_MATCH_DWORD: device requires the first four + * bytes of the pattern for deauthentication and disassocation + * to match for all types of possible frames received of those + * types. + */ +enum ath9k_hw_wow_caps { + ATH9K_WOW_DEVICE_CAPABLE = BIT(0), + ATH9K_WOW_HW_ENABLED = BIT(1), + ATH9K_WOW_PATTERN_MATCH_EXACT = BIT(2), + ATH9K_WOW_PATTERN_MATCH_DWORD = BIT(3), +}; + + +/** + * struct ath9k_hw_capabilities - device capabilities + * + * This structure contains the device capabilities. The EEPROM will + * be read and specific device capabilities are mapped accordingly + * for easy access. + * + * wow: Wake-on-Wireless LAN capabilities, can be any of the + * %ATH9K_WOW_*. + */ struct ath9k_hw_capabilities { u32 hw_caps; /* ATH9K_HW_CAP_* from ath9k_hw_caps */ DECLARE_BITMAP(wireless_modes, ATH9K_MODE_MAX); /* ATH9K_MODE_* */ @@ -169,6 +204,7 @@ struct ath9k_hw_capabilities { u8 num_gpio_pins; u8 num_antcfg_2ghz; u8 num_antcfg_5ghz; + u32 wow; }; struct ath9k_ops_config { @@ -520,6 +556,11 @@ struct ath_hw { int initPDADC; int PDADCdelta; +#ifdef CONFIG_PM + /* WoW mask -- used to indicate which WoW we have enabled */ + u32 ah_wowEventMask; +#endif + struct ar5416IniArray iniModes; struct ar5416IniArray iniCommon; struct ar5416IniArray iniBank0; @@ -535,6 +576,10 @@ struct ath_hw { struct ar5416IniArray iniModesAdditional; struct ar5416IniArray iniModesRxGain; struct ar5416IniArray iniModesTxGain; +#ifdef CONFIG_PM + /* SerDes values during WOW sleep */ + struct ar5416IniArray iniPcieSerdesWow; +#endif }; /* Attach, Detach, Reset */ @@ -613,4 +658,16 @@ enum ath9k_int ath9k_hw_set_interrupts(struct ath_hw *ah, enum ath9k_int ints); void ath9k_hw_btcoex_enable(struct ath_hw *ah); +#ifdef CONFIG_PM + +/* WOW - Wake on Wireless */ +void ath9k_wow_set_gpio_reset_low(struct ath_hw *ah); +/* Called when going to suspend/hibernate */ +void ath9k_hw_wow_enable(struct ath_hw *ah, u32 patternEnable); +/* Called when coming back up from suspend/hibernation */ +u32 ath9k_hw_wow_wake_up(struct ath_hw *ah); +const char *ath9k_hw_wow_event_to_string(u32 wow_event); + +#endif /* CONFIG_PM */ + #endif diff --git a/drivers/net/wireless/ath/ath9k/initvals.h b/drivers/net/wireless/ath/ath9k/initvals.h index f67a2a9..52e1743 100644 --- a/drivers/net/wireless/ath/ath9k/initvals.h +++ b/drivers/net/wireless/ath/ath9k/initvals.h @@ -3439,6 +3439,22 @@ static const u32 ar9280PciePhy_clkreq_always_on_L1_9280[][2] = { {0x00004044, 0x00000000 }, }; +#ifdef CONFIG_PM +/* Auto generated PCI-E PHY config for AR9280 with WOW */ +static const u_int32_t ar9280PciePhy_AWOW_9280[][2] = { + {0x00004040, 0x9248fd00 }, + {0x00004040, 0x24924924 }, + {0x00004040, 0xa8000019 }, + {0x00004040, 0x13160820 }, + {0x00004040, 0xe5980560 }, + {0x00004040, 0xc01ddffd }, + {0x00004040, 0x1aaabe41 }, + {0x00004040, 0xbe105554 }, + {0x00004040, 0x00043007 }, + {0x00004044, 0x00000000 }, +}; +#endif + /* AR9285 Revsion 10*/ static const u_int32_t ar9285Modes_9285[][6] = { { 0x00001030, 0x00000230, 0x00000460, 0x000002c0, 0x00000160, 0x000001e0 }, @@ -4849,3 +4865,18 @@ static const u_int32_t ar9285PciePhy_clkreq_off_L1_9285_1_2[][2] = { {0x00004040, 0x00043007 }, {0x00004044, 0x00000000 }, }; + +#ifdef CONFIG_PM +static const u_int32_t ar9285PciePhy_AWOW_9285_1_2[][2] = { + {0x00004040, 0x9248fd00 }, + {0x00004040, 0x24924924 }, + {0x00004040, 0xa8000019 }, + {0x00004040, 0x13160820 }, + {0x00004040, 0xe5980560 }, + {0x00004040, 0xc01ddffd }, + {0x00004040, 0x1aaabe41 }, + {0x00004040, 0xbe105554 }, + {0x00004040, 0x00043007 }, + {0x00004044, 0x00000000 }, +}; +#endif /* CONFIG_PM */ diff --git a/drivers/net/wireless/ath/ath9k/main.c b/drivers/net/wireless/ath/ath9k/main.c index baef04d..2528571 100644 --- a/drivers/net/wireless/ath/ath9k/main.c +++ b/drivers/net/wireless/ath/ath9k/main.c @@ -596,6 +596,20 @@ irqreturn_t ath_isr(int irq, void *dev) ath9k_hw_set_interrupts(ah, sc->imask); } + if (status & ATH9K_INT_BMISS) { +#ifdef CONFIG_PM + if (sc->wow_sleep_proc_intr) { + DPRINTF(sc, (ATH_DBG_ANY | ATH_DBG_INTERRUPT), + "during WoW we got a BMISS\n"); + sc->wow_got_bmiss_intr = true; + sc->wow_sleep_proc_intr = false; + } +#else + /* BMISS not enabled on ath9k unless WoW is enabled */ + DPRINTF(sc, ATH_DBG_INTERRUPT, + "spurious unattended beacon miss interrupt\n"); +#endif + } if (!(ah->caps.hw_caps & ATH9K_HW_CAP_AUTOSLEEP)) if (status & ATH9K_INT_TIM_TIMER) { /* Clear RxAbort bit so that we can @@ -2094,6 +2108,15 @@ static void ath9k_stop(struct ieee80211_hw *hw, struct cfg80211_wow *wow) aphy->state = ATH_WIPHY_INACTIVE; +#ifdef CONFIG_PM + if (wow && wow->triggers_enabled) { + sc->wow_triggers_enabled = wow->triggers_enabled; + DPRINTF(sc, ATH_DBG_ANY, "Leaving radio on during " + "suspend for WoW\n"); + return; + } +#endif + if (sc->sc_flags & SC_OP_INVALID) { DPRINTF(sc, ATH_DBG_ANY, "Device not present\n"); return; diff --git a/drivers/net/wireless/ath/ath9k/pci.c b/drivers/net/wireless/ath/ath9k/pci.c index 170c5b3..109d3ab 100644 --- a/drivers/net/wireless/ath/ath9k/pci.c +++ b/drivers/net/wireless/ath/ath9k/pci.c @@ -142,6 +142,9 @@ static int ath_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) if ((val & 0x0000ff00) != 0) pci_write_config_dword(pdev, 0x40, val & 0xffff00ff); + device_init_wakeup(&pdev->dev, 1); + device_set_wakeup_enable(&pdev->dev, 0); + ret = pci_request_region(pdev, 0, "ath9k"); if (ret) { dev_err(&pdev->dev, "PCI memory region reserve error\n"); @@ -203,6 +206,32 @@ static int ath_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) ah->hw_version.phyRev, (unsigned long)mem, pdev->irq); + /* WoW is for PCI for ath9k */ + + /* + * Bmiss and link change triggers not yet fully tested, add them + * after properly testing and confirming behaviour. I believe + * we need pattern matching support to enable them as well + * (detect deauth and disassoc). + */ + if (ah->caps.wow & ATH9K_WOW_DEVICE_CAPABLE && + ah->caps.wow & ATH9K_WOW_HW_ENABLED) + hw->wiphy->wow_triggers_supported = + NL80211_WOW_TRIGGER_MAGIC_PACKET; + + DPRINTF(sc, ATH_DBG_CONFIG, + "WoW capabler device: %s\n", + (ah->caps.wow & ATH9K_WOW_DEVICE_CAPABLE) ? "yes" : "no"); + DPRINTF(ah->ah_sc, ATH_DBG_CONFIG, + "EEPROM indicates WoW hw changes in place: %s\n", + (ah->caps.wow & ATH9K_WOW_HW_ENABLED) ? "yes" : "no"); + DPRINTF(ah->ah_sc, ATH_DBG_CONFIG, + "WoW exact match pattern support: %s\n", + (ah->caps.wow & ATH9K_WOW_PATTERN_MATCH_EXACT) ? "yes" : "no"); + DPRINTF(ah->ah_sc, ATH_DBG_CONFIG, + "WoW match pattern first dword requirement: %s\n", + (ah->caps.wow & ATH9K_WOW_PATTERN_MATCH_DWORD) ? "yes" : "no"); + return 0; bad4: ath_detach(sc); @@ -228,21 +257,111 @@ static void ath_pci_remove(struct pci_dev *pdev) #ifdef CONFIG_PM +static u32 ath9k_wow_event_map(u32 cfg_wow_events) +{ + u32 wow_events = 0; + + if (cfg_wow_events & NL80211_WOW_TRIGGER_MAGIC_PACKET) + wow_events |= AH_WOW_MAGIC_PATTERN_EN; +#if 0 + if (cfg_wow_events & NL80211_WOW_TRIGGER_BMISS) + wow_events |= AH_WOW_BEACON_MISS; + if (cfg_wow_events & NL80211_WOW_TRIGGER_LINK_CHANGE) + wow_events |= AH_WOW_LINK_CHANGE; +#endif + + return wow_events; +} + +static void ath9k_pci_wow_enable(struct ath_softc *sc) +{ + struct ath_hw *ah = sc->sc_ah; + u32 wake_up_events; + + wake_up_events = ath9k_wow_event_map(sc->wow_triggers_enabled); + + /* eventually we'll add this... + * if (wake_up_events & AH_WOW_USER_PATTERN_EN) + * ath9k_wow_create_pattern(sc); + */ + + /* + * To avoid false wake, we enable beacon miss interrupt only when + * we go to sleep. We save the current interrupt mask so that + * we can restore it after the system wakes up. + */ + sc->wow_intr_before_sleep = ah->mask_reg; + ath9k_hw_set_interrupts(ah, ATH9K_INT_BMISS | ATH9K_INT_GLOBAL); + sc->imask = ATH9K_INT_BMISS | ATH9K_INT_GLOBAL; + + ath9k_hw_wow_enable(ah, wake_up_events); + device_set_wakeup_enable(sc->dev, 1); + + DPRINTF(sc, ATH_DBG_ANY, + "WoW enabled\n"); + + sc->wow_sleep_proc_intr = true; +} + static int ath_pci_suspend(struct pci_dev *pdev, pm_message_t state) { struct ieee80211_hw *hw = pci_get_drvdata(pdev); struct ath_wiphy *aphy = hw->priv; struct ath_softc *sc = aphy->sc; + if (!!sc->wow_triggers_enabled && device_can_wakeup(&pdev->dev)) + ath9k_pci_wow_enable(sc); + ath9k_hw_set_gpio(sc->sc_ah, ATH_LED_PIN, 1); pci_save_state(pdev); pci_disable_device(pdev); + + if (!!sc->wow_triggers_enabled && device_can_wakeup(&pdev->dev)) { + pci_prepare_to_sleep(pdev); + return 0; + } + + pci_wake_from_d3(pdev, !!sc->wow_triggers_enabled); pci_set_power_state(pdev, PCI_D3hot); return 0; } +static void ath9k_pci_wow_wake(struct ath_softc *sc) +{ + struct ath_hw *ah = sc->sc_ah; + u32 wow_status; + + ath9k_hw_set_interrupts(ah, sc->wow_intr_before_sleep); + sc->imask = sc->wow_intr_before_sleep; + + wow_status = ath9k_hw_wow_wake_up(sc->sc_ah); + + if (sc->wow_got_bmiss_intr) { + /* + * Some devices may not pick beacon miss + * as the reason they woke up so we add that + * here for that shortcoming + */ + wow_status |= AH_WOW_BEACON_MISS; + sc->wow_got_bmiss_intr = false; + DPRINTF(ah->ah_sc, ATH_DBG_ANY, + "Beacon miss interrupt picked up during sleep, " + "adding as possible wake up reason\n"); + } + + if (wow_status) { + DPRINTF(ah->ah_sc, ATH_DBG_ANY, + "Waking up due to WoW trigger%s\n", + ath9k_hw_wow_event_to_string(wow_status)); + DPRINTF(ah->ah_sc, ATH_DBG_ANY, + "WoW status: %d\n WoW reason: %s\n", + wow_status, + ath9k_hw_wow_event_to_string((wow_status & 0x0FFF))); + } +} + static int ath_pci_resume(struct pci_dev *pdev) { struct ieee80211_hw *hw = pci_get_drvdata(pdev); @@ -264,6 +383,9 @@ static int ath_pci_resume(struct pci_dev *pdev) if ((val & 0x0000ff00) != 0) pci_write_config_dword(pdev, 0x40, val & 0xffff00ff); + if (sc->wow_triggers_enabled) + ath9k_pci_wow_wake(sc); + /* Enable LED */ ath9k_hw_cfg_output(sc->sc_ah, ATH_LED_PIN, AR_GPIO_OUTPUT_MUX_AS_OUTPUT); diff --git a/drivers/net/wireless/ath/ath9k/reg.h b/drivers/net/wireless/ath/ath9k/reg.h index 8302aeb..b025800 100644 --- a/drivers/net/wireless/ath/ath9k/reg.h +++ b/drivers/net/wireless/ath/ath9k/reg.h @@ -668,6 +668,11 @@ #define AR_RC_HOSTIF 0x00000100 #define AR_WA 0x4004 +#define AR_WA_UNTIE_RESET_EN (1 << 15) /* Enable PCI Reset to POR (power-on-reset) */ +#define AR_WA_RESET_EN (1 << 18) /* Sw Control to enable PCI-Reset to POR (bit 15) */ +#define AR_WA_ANALOG_SHIFT (1 << 20) +#define AR_WA_POR_SHORT (1 << 21) /* PCI-E Phy reset control */ + #define AR9285_WA_DEFAULT 0x004a05cb #define AR9280_WA_DEFAULT 0x0040073f #define AR_WA_DEFAULT 0x0000073f @@ -1601,4 +1606,153 @@ enum { #define AR_KEYTABLE_MAC0(_n) (AR_KEYTABLE(_n) + 24) #define AR_KEYTABLE_MAC1(_n) (AR_KEYTABLE(_n) + 28) +/* WoW - Wake On Wireless */ + +#define AR_PMCTRL_AUX_PWR_DET 0x10000000 /* Puts Chip in L2 state */ +#define AR_PMCTRL_D3COLD_VAUX 0x00800000 +#define AR_PMCTRL_HOST_PME_EN 0x00400000 /* Send OOB WAKE_L on WoW event */ +#define AR_PMCTRL_WOW_PME_CLR 0x00200000 /* Clear WoW event */ +#define AR_PMCTRL_PWR_STATE_MASK 0x0F000000 /* Power State Mask */ +#define AR_PMCTRL_PWR_STATE_D1D3 0x0F000000 /* Activate D1 and D3 */ +#define AR_PMCTRL_PWR_STATE_D0 0x08000000 /* Activate D0 */ +#define AR_PMCTRL_PWR_PM_CTRL_ENA 0x00008000 /* Enable power management */ + +#define AR_WOW_BEACON_TIMO_MAX 0xFFFFFFFF /* Max. value for Beacon Timeout */ + +/* + * MAC WoW Registers. + */ +#define AR_WOW_PATTERN_REG 0x825C +#define AR_WOW_COUNT_REG 0x8260 +#define AR_WOW_BCN_EN_REG 0x8270 +#define AR_WOW_BCN_TIMO_REG 0x8274 +#define AR_WOW_KEEP_ALIVE_TIMO_REG 0x8278 +#define AR_WOW_KEEP_ALIVE_REG 0x827C +#define AR_WOW_US_SCALAR_REG 0x8284 +#define AR_WOW_KEEP_ALIVE_DELAY_REG 0x8288 +#define AR_WOW_PATTERN_MATCH_REG 0x828C +#define AR_WOW_PATTERN_OFF1_REG 0x8290 /* Pattern bytes 0 -> 3 */ +#define AR_WOW_PATTERN_OFF2_REG 0x8294 /* Pattern bytes 4 -> 7 */ +/* For AR9285 or Later version of chips */ +#define AR_WOW_EXACT_REG 0x829C +#define AR_WOW_LENGTH1_REG 0x8360 +#define AR_WOW_LENGTH2_REG 0x8364 +/* Register to enable pattern match for less than 256 bytes packets */ +#define AR_WOW_PATTERN_MATCH_LT_256B_REG 0x8368 + +/* AR_WOW_PATTERN_REG Values */ +#define AR_WOW_BACK_OFF_SHIFT(x) ((x & 0xf) << 27) /* in usecs */ +#define AR_WOW_MAC_INTR_EN 0x00040000 +#define AR_WOW_MAGIC_EN 0x00010000 +#define AR_WOW_PATTERN_EN(x) ((x & 0xff) << 0) +#define AR_WOW_PATTERN_FOUND_SHIFT 8 +#define AR_WOW_PATTERN_FOUND(x) (x & (0xff << AR_WOW_PATTERN_FOUND_SHIFT)) +#define AR_WOW_PATTERN_FOUND_MASK ((0xff) << AR_WOW_PATTERN_FOUND_SHIFT) +#define AR_WOW_MAGIC_PAT_FOUND 0x00020000 +#define AR_WOW_MAC_INTR 0x00080000 +#define AR_WOW_KEEP_ALIVE_FAIL 0x00100000 +#define AR_WOW_BEACON_FAIL 0x00200000 + +#define AR_WOW_STATUS(x) (x & (AR_WOW_PATTERN_FOUND_MASK | \ + AR_WOW_MAGIC_PAT_FOUND | \ + AR_WOW_KEEP_ALIVE_FAIL | \ + AR_WOW_BEACON_FAIL)) +#define AR_WOW_CLEAR_EVENTS(x) (x & ~(AR_WOW_PATTERN_EN(0xff) | \ + AR_WOW_MAGIC_EN | \ + AR_WOW_MAC_INTR_EN | \ + AR_WOW_BEACON_FAIL | \ + AR_WOW_KEEP_ALIVE_FAIL)) + +/* AR_WOW_COUNT_REG Values */ +#define AR_WOW_AIFS_CNT(x) ((x & 0xff) << 0) +#define AR_WOW_SLOT_CNT(x) ((x & 0xff) << 8) +#define AR_WOW_KEEP_ALIVE_CNT(x) ((x & 0xff) << 16) + +/* AR_WOW_BCN_EN_REG */ +#define AR_WOW_BEACON_FAIL_EN 0x00000001 + +/* AR_WOW_BCN_TIMO_REG */ +#define AR_WOW_BEACON_TIMO 0x40000000 /* Valid if BCN_EN is set */ + +/* AR_WOW_KEEP_ALIVE_TIMO_REG */ +#define AR_WOW_KEEP_ALIVE_TIMO 0x00007A12 +#define AR_WOW_KEEP_ALIVE_NEVER 0xFFFFFFFF + +/* AR_WOW_KEEP_ALIVE_REG */ +#define AR_WOW_KEEP_ALIVE_AUTO_DIS 0x00000001 +#define AR_WOW_KEEP_ALIVE_FAIL_DIS 0x00000002 + +/* AR_WOW_KEEP_ALIVE_DELAY_REG */ +#define AR_WOW_KEEP_ALIVE_DELAY 0x000003E8 /* 1 msec */ + +/* + * Keep it long for Beacon workaround - ensures no false alarm + */ +#define AR_WOW_BMISSTHRESHOLD 0x20 + +/* AR_WOW_PATTERN_MATCH_REG */ +#define AR_WOW_PAT_END_OF_PKT(x) ((x & 0xf) << 0) +#define AR_WOW_PAT_OFF_MATCH(x) ((x & 0xf) << 8) + +/* + * Default values for Wow Configuration for backoff, aifs, slot, keep-alive, etc + * to be programmed into various registers. + */ +#define AR_WOW_PAT_BACKOFF 0x00000004 /* AR_WOW_PATTERN_REG */ +#define AR_WOW_CNT_AIFS_CNT 0x00000022 /* AR_WOW_COUNT_REG */ +#define AR_WOW_CNT_SLOT_CNT 0x00000009 /* AR_WOW_COUNT_REG */ +/* + * Keepalive count applicable for AR9280 2.0 and above. + */ +#define AR_WOW_CNT_KA_CNT 0x00000008 /* AR_WOW_COUNT_REG */ + +/* WoW - Transmit buffer for keep alive frames */ +#define AR_WOW_TRANSMIT_BUFFER 0xE000 /* E000 - EFFC */ + +#define AR_WOW_KA_DESC_WORD2 0xE000 +#define AR_WOW_KA_DESC_WORD3 0xE004 +#define AR_WOW_KA_DESC_WORD4 0xE008 +#define AR_WOW_KA_DESC_WORD5 0xE00C +#define AR_WOW_KA_DESC_WORD6 0xE010 +#define AR_WOW_KA_DESC_WORD7 0xE014 +#define AR_WOW_KA_DESC_WORD8 0xE018 +#define AR_WOW_KA_DESC_WORD9 0xE01C +#define AR_WOW_KA_DESC_WORD10 0xE020 +#define AR_WOW_KA_DESC_WORD11 0xE024 +#define AR_WOW_KA_DESC_WORD12 0xE028 +#define AR_WOW_KA_DESC_WORD13 0xE02C + +#define AR_WOW_KA_DATA_WORD0 0xE030 +#define AR_WOW_KA_DATA_WORD1 0xE034 +#define AR_WOW_KA_DATA_WORD2 0xE038 +#define AR_WOW_KA_DATA_WORD3 0xE03C +#define AR_WOW_KA_DATA_WORD4 0xE040 +#define AR_WOW_KA_DATA_WORD5 0xE044 + +/* WoW Transmit Buffer for patterns */ +#define AR_WOW_TB_PATTERN0 0xE100 +#define AR_WOW_TB_PATTERN1 0xE200 +#define AR_WOW_TB_PATTERN2 0xE300 +#define AR_WOW_TB_PATTERN3 0xE400 +#define AR_WOW_TB_PATTERN4 0xE500 +#define AR_WOW_TB_PATTERN5 0xE600 +#define AR_WOW_TB_PATTERN6 0xE700 +#define AR_WOW_TB_PATTERN7 0xE800 +#define AR_WOW_TB_MASK0 0xEC00 +#define AR_WOW_TB_MASK1 0xEC20 +#define AR_WOW_TB_MASK2 0xEC40 +#define AR_WOW_TB_MASK3 0xEC60 +#define AR_WOW_TB_MASK4 0xEC80 +#define AR_WOW_TB_MASK5 0xECa0 +#define AR_WOW_TB_MASK6 0xECC0 +#define AR_WOW_TB_MASK7 0xECE0 + +/* Currently Pattern 0-7 are supported - so bit 0-7 are set */ +#define AR_WOW_PATTERN_SUPPORTED 0xFF +#define AR_WOW_LENGTH_MAX 0xFF +#define AR_WOW_LENGTH1_SHIFT(_i) ((0x3 - ((_i) & 0x3)) << 0x3) +#define AR_WOW_LENGTH1_MASK(_i) (AR_WOW_LENGTH_MAX << AR_WOW_LENGTH1_SHIFT(_i)) +#define AR_WOW_LENGTH2_SHIFT(_i) ((0x7 - ((_i) & 0x7)) << 0x3) +#define AR_WOW_LENGTH2_MASK(_i) (AR_WOW_LENGTH_MAX << AR_WOW_LENGTH2_SHIFT(_i)) + #endif diff --git a/drivers/net/wireless/ath/ath9k/wow.c b/drivers/net/wireless/ath/ath9k/wow.c new file mode 100644 index 0000000..82d3790 --- /dev/null +++ b/drivers/net/wireless/ath/ath9k/wow.c @@ -0,0 +1,514 @@ +/* + * Copyright (c) 2008-2009 Atheros Communications Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "ath9k.h" +#include "reg.h" + +#ifdef CONFIG_PM + +/* + * This routine is called to configure the SerDes register for the + * AR9280 2.0 and above chip during WOW sleep. + */ +static void +ath9k_ar928xConfigSerDes_WowSleep(struct ath_hw *ah) +{ + unsigned int i; + + /* + * For WOW sleep, we reprogram the SerDes so that the PLL and CHK REQ + * are both enabled. This uses more power but in certain cases this + * is required as otherwise WOW sleep is unstable and chip may + * disappears. + */ + for (i = 0; i < ah->iniPcieSerdesWow.ia_rows; i++) + REG_WRITE(ah, + INI_RA(&ah->iniPcieSerdesWow, i, 0), + INI_RA(&ah->iniPcieSerdesWow, i, 1)); + udelay(1000); +} + +static bool ath9k_wow_create_keep_alive_pattern(struct ath_hw *ah) +{ + u32 frame_len = 28; + u32 tpc = 0x3f; + u32 antenna_mode = 1; + u32 transmit_rate; + u32 frame_type = 0x2; /* Frame Type -> Data */ + u32 sub_type = 0x4; /* Subtype -> Null Data */ + u32 to_ds = 1; + u32 duration_id = 0x3d; + u8 *StaMacAddr, *ApMacAddr; + u8 *addr1, *addr2, *addr3; + u32 ctl[12] = { 0 }; + u32 data_word0 = 0, data_word1 = 0, data_word2 = 0, + data_word3 = 0, data_word4 = 0, data_word5 = 0; + u32 i; + + StaMacAddr = (u8 *)ah->macaddr; + ApMacAddr = (u8 *)ah->ah_sc->curbssid; + addr2 = StaMacAddr; + addr1 = addr3 = ApMacAddr; + + /* + * XXX: we need a way to determine if the AP we're on + * is using CCK only and if so use this: + * transmit_rate = 0x1B; // CCK_1M + * For now we just assume your AP supports OFDM + */ + transmit_rate = 0xB; /* OFDM_6M */ + + /* Set the Transmit Buffer. */ + ctl[0] = (frame_len | (tpc << 16)) + (antenna_mode << 25); + ctl[1] = 0; + ctl[2] = 0x7 << 16; /* tx_tries0 */ + ctl[3] = transmit_rate; + ctl[4] = 0; + ctl[7] = ah->txchainmask << 2; + + for (i = 0; i < 12; i++) + REG_WRITE(ah, (AR_WOW_KA_DESC_WORD2 + i * 4), ctl[i]); + + data_word0 = (frame_type << 2) | (sub_type << 4) | + (to_ds << 8) | (duration_id << 16); + data_word1 = (((u32)addr1[3] << 24) | ((u32)addr1[2] << 16) | + ((u32)addr1[1]) << 8 | ((u32)addr1[0])); + data_word2 = (((u32)addr2[1] << 24) | ((u32)addr2[0] << 16) | + ((u32)addr1[5]) << 8 | ((u32)addr1[4])); + data_word3 = (((u32)addr2[5] << 24) | ((u32)addr2[4] << 16) | + ((u32)addr2[3]) << 8 | ((u32)addr2[2])); + data_word4 = (((u32)addr3[3] << 24) | ((u32)addr3[2] << 16) | + ((u32)addr3[1]) << 8 | (u32)addr3[0]); + data_word5 = (((u32)addr3[5]) << 8 | ((u32)addr3[4])); + + REG_WRITE(ah, AR_WOW_KA_DATA_WORD0, data_word0); + REG_WRITE(ah, AR_WOW_KA_DATA_WORD1, data_word1); + REG_WRITE(ah, AR_WOW_KA_DATA_WORD2, data_word2); + REG_WRITE(ah, AR_WOW_KA_DATA_WORD3, data_word3); + REG_WRITE(ah, AR_WOW_KA_DATA_WORD4, data_word4); + REG_WRITE(ah, AR_WOW_KA_DATA_WORD5, data_word5); + + return true; +} + +/* TBD: Should querying hw for hardware capability */ +#define MAX_PATTERN_SIZE 256 +#define MAX_PATTERN_MASK_SIZE 32 + +/* Deducting the disassociate/deauthenticate packets */ +#define MAX_NUM_USER_PATTERN 6 + +# if 0 +static void ath9k_wow_apply_pattern(struct ath_hw *ah, + u8 *pAthPattern, u8 *pAthMask, + int pattern_count, u32 athPatternLen) +{ + unsigned int i; + u32 reg_pat[] = { + AR_WOW_TB_PATTERN0, + AR_WOW_TB_PATTERN1, + AR_WOW_TB_PATTERN2, + AR_WOW_TB_PATTERN3, + AR_WOW_TB_PATTERN4, + AR_WOW_TB_PATTERN5, + AR_WOW_TB_PATTERN6, + AR_WOW_TB_PATTERN7 + }; + u32 reg_mask[] = { + AR_WOW_TB_MASK0, + AR_WOW_TB_MASK1, + AR_WOW_TB_MASK2, + AR_WOW_TB_MASK3, + AR_WOW_TB_MASK4, + AR_WOW_TB_MASK5, + AR_WOW_TB_MASK6, + AR_WOW_TB_MASK7 + }; + u32 pattern_val; + u32 mask_val; + u8 mask_bit = 0x1; + u8 pattern; + + /* TBD: should check count by querying the hardware capability */ + if (pattern_count >= MAX_NUM_USER_PATTERN) + return; + + pattern = (u8)REG_READ(ah, AR_WOW_PATTERN_REG); + pattern = pattern | (mask_bit << pattern_count); + REG_WRITE(ah, AR_WOW_PATTERN_REG, pattern); + + /* Set the registers for pattern */ + for (i = 0; i < MAX_PATTERN_SIZE; i+=4) { + pattern_val = (((u32)pAthPattern[i]) | + ((u32)pAthPattern[i+1] << 8) | + ((u32)pAthPattern[i+2] << 16) | + ((u32)pAthPattern[i+3] << 24)); + REG_WRITE(ah, (reg_pat[pattern_count] + i), pattern_val); + } + + /* Set the registers for mask */ + for (i = 0; i < MAX_PATTERN_MASK_SIZE; i+=4) { + mask_val = (((u32)pAthMask[i]) | + ((u32)pAthMask[i+1] << 8) | + ((u32)pAthMask[i+2] << 16) | + ((u32)pAthMask[i+3] << 24)); + REG_WRITE(ah, (reg_mask[pattern_count] + i), mask_val); + } + + if (AR_SREV_9285_10_OR_LATER(ah)) { + /* Set the pattern length to be matched */ + u32 val; + if (pattern_count < 4) { + /* Pattern 0-3 uses AR_WOW_LENGTH1_REG register */ + val = REG_READ(ah, AR_WOW_LENGTH1_REG); + val = ((val & (~AR_WOW_LENGTH1_MASK(pattern_count))) | + ((athPatternLen & AR_WOW_LENGTH_MAX) + << AR_WOW_LENGTH1_SHIFT(pattern_count))); + REG_WRITE(ah, AR_WOW_LENGTH1_REG, val); + } else { + /* Pattern 4-7 uses AR_WOW_LENGTH2_REG register */ + val = REG_READ(ah, AR_WOW_LENGTH2_REG); + val = ((val & (~AR_WOW_LENGTH2_MASK(pattern_count))) | + ((athPatternLen & AR_WOW_LENGTH_MAX) + << AR_WOW_LENGTH2_SHIFT(pattern_count))); + REG_WRITE(ah, AR_WOW_LENGTH2_REG, val); + } + } + + ah->ah_wowEventMask |= + (1 << (pattern_count + AR_WOW_PATTERN_FOUND_SHIFT)); +} +#endif + +static bool ath9k_set_power_mode_wow_sleep(struct ath_hw *ah) +{ + REG_SET_BIT(ah, AR_STA_ID1, AR_STA_ID1_PWR_SAV); + + REG_WRITE(ah, AR_CR, AR_CR_RXD); /* Set receive disable bit */ + if (!ath9k_hw_wait(ah, AR_CR, AR_CR_RXE, 0, AH_WAIT_TIMEOUT)) { + DPRINTF(ah->ah_sc, ATH_DBG_CONFIG, + "dma failed to stop in 10ms\n" + "AR_CR=0x%08x\nAR_DIAG_SW=0x%08x\n", + REG_READ(ah, AR_CR), + REG_READ(ah, AR_DIAG_SW)); + return false; + } else { + REG_WRITE(ah, AR_RXDP, 0x0); + + /* AR9280 2.0/2.1 WOW has sleep issue, do not set it to sleep */ + if (AR_SREV_9280_20(ah)) + return true; + else + REG_WRITE(ah, AR_RTC_FORCE_WAKE, + AR_RTC_FORCE_WAKE_ON_INT); + return true; + } +} + +void ath9k_hw_wow_enable(struct ath_hw *ah, u32 patternEnable) +{ + u32 init_val, val, rval = 0; + /* Send a Keep-Alive frame every 900 millisec */ + const int ka_timo = 900; + /* Delay of 4 millisec between two KeepAlive's */ + const int ka_delay = 4; + u32 wow_event_mask; + + /* + * ah_wowEventMask is a mask to the AR_WOW_PATTERN_REG register to + * indicate which WOW events that we have enabled. The WOW Events + * are from the patternEnable in this function and pattern_count of + * ath9k_wow_apply_pattern() + */ + wow_event_mask = ah->ah_wowEventMask; + + /* + * Untie Power-On-Reset from the PCI-E Reset. When we are in WOW sleep, + * we do not want the Reset from the PCI-E to disturb our hw state. + */ + if (AR_SREV_9280_20_OR_LATER(ah) && ah->is_pciexpress) { + /* + * We need to untie the internal POR (power-on-reset) to the + * external PCI-E reset. We also need to tie the PCI-E Phy + * reset to the PCI-E reset. + */ + u32 wa_reg_val; + if (AR_SREV_9285(ah)) + wa_reg_val = AR9285_WA_DEFAULT; + else + wa_reg_val = AR9280_WA_DEFAULT; + wa_reg_val = wa_reg_val & ~(AR_WA_UNTIE_RESET_EN); + wa_reg_val = wa_reg_val | AR_WA_RESET_EN | AR_WA_POR_SHORT; + REG_WRITE(ah, AR_WA, wa_reg_val); + + if (!AR_SREV_9285(ah) || AR_SREV_9285_12_OR_LATER(ah)) { + /* + * For WOW sleep, we reprogram the SerDes so that the + * PLL and CHK REQ are both enabled. This uses more + * power but otherwise in certain cases, WOW sleep is + * unusable and chip may disappears. + */ + ath9k_ar928xConfigSerDes_WowSleep(ah); + } + } + + /* + * Set the power states appropriately and enable pme. + */ + val = REG_READ(ah, AR_PCIE_PM_CTRL); + val |= AR_PMCTRL_HOST_PME_EN | + AR_PMCTRL_PWR_PM_CTRL_ENA | + AR_PMCTRL_AUX_PWR_DET; + val &= ~AR_PMCTRL_WOW_PME_CLR; + REG_WRITE(ah, AR_PCIE_PM_CTRL, val); + + /* + * Setup for: + * - beacon misses + * - magic pattern + * - keep alive timeout + * - pattern matching + */ + + /* + * Program some default values for keep-alives, beacon misses, etc. + */ + init_val = REG_READ(ah, AR_WOW_PATTERN_REG); + val = AR_WOW_BACK_OFF_SHIFT(AR_WOW_PAT_BACKOFF) | init_val; + REG_WRITE(ah, AR_WOW_PATTERN_REG, val); + rval = REG_READ(ah, AR_WOW_PATTERN_REG); + + init_val = REG_READ(ah, AR_WOW_COUNT_REG); + val = AR_WOW_AIFS_CNT(AR_WOW_CNT_AIFS_CNT) | \ + AR_WOW_SLOT_CNT(AR_WOW_CNT_SLOT_CNT) | \ + AR_WOW_KEEP_ALIVE_CNT(AR_WOW_CNT_KA_CNT); + REG_WRITE(ah, AR_WOW_COUNT_REG, val); + rval = REG_READ(ah, AR_WOW_COUNT_REG); + + + init_val = REG_READ(ah, AR_WOW_BCN_TIMO_REG); + if (patternEnable & AH_WOW_BEACON_MISS) + val = AR_WOW_BEACON_TIMO; + else + /* We are not using the beacon miss. Program a large value. */ + val = AR_WOW_BEACON_TIMO_MAX; + REG_WRITE(ah, AR_WOW_BCN_TIMO_REG, val); + + init_val = REG_READ(ah, AR_WOW_KEEP_ALIVE_TIMO_REG); + + /* + * Keep Alive Timo in ms. + */ + if (patternEnable == 0) + val = AR_WOW_KEEP_ALIVE_NEVER; + else + val = ka_timo * 32; + REG_WRITE(ah, AR_WOW_KEEP_ALIVE_TIMO_REG, val); + rval = REG_READ(ah, AR_WOW_KEEP_ALIVE_TIMO_REG); + + init_val = REG_READ(ah, AR_WOW_KEEP_ALIVE_DELAY_REG); + /* + * Keep Alive delay in us. + */ + val = ka_delay * 1000; + REG_WRITE(ah, AR_WOW_KEEP_ALIVE_DELAY_REG, val); + rval = REG_READ(ah, AR_WOW_KEEP_ALIVE_DELAY_REG); + + /* + * Create KeepAlive Pattern to respond to beacons. + */ + ath9k_wow_create_keep_alive_pattern(ah); + + /* + * Configure Mac Wow Registers. + */ + + val = REG_READ(ah, AR_WOW_KEEP_ALIVE_REG); + /* + * Send keep alive timeouts anyway. + */ + val &= ~AR_WOW_KEEP_ALIVE_AUTO_DIS; + + if (patternEnable & AH_WOW_LINK_CHANGE) { + val &= ~ AR_WOW_KEEP_ALIVE_FAIL_DIS; + wow_event_mask |= AR_WOW_KEEP_ALIVE_FAIL; + } else + val |= AR_WOW_KEEP_ALIVE_FAIL_DIS; + + REG_WRITE(ah, AR_WOW_KEEP_ALIVE_REG, val); + val = REG_READ(ah, AR_WOW_KEEP_ALIVE_REG); + val = REG_READ(ah, AR_WOW_BCN_EN_REG); + + /* + * We are relying on a bmiss failure. Ensure we have enough + * threshold to prevent false positives. + */ + REG_RMW_FIELD(ah, AR_RSSI_THR, AR_RSSI_THR_BM_THR, + AR_WOW_BMISSTHRESHOLD); + + /* + * Beacon miss & user pattern events do not work on AR5416. + * We enable beacon miss wow pattern only for AR9280... + */ + if (!AR_SREV_9280_10_OR_LATER(ah)) + patternEnable &= ~AH_WOW_BEACON_MISS; + + if (patternEnable & AH_WOW_BEACON_MISS) { + val |= AR_WOW_BEACON_FAIL_EN; + wow_event_mask |= AR_WOW_BEACON_FAIL; + } else + val &= ~AR_WOW_BEACON_FAIL_EN; + + REG_WRITE(ah, AR_WOW_BCN_EN_REG, val); + val = REG_READ(ah, AR_WOW_BCN_EN_REG); + + /* + * Enable the magic packet registers. + */ + val = REG_READ(ah, AR_WOW_PATTERN_REG); + if (patternEnable & AH_WOW_MAGIC_PATTERN_EN) { + val |= AR_WOW_MAGIC_EN; + wow_event_mask |= AR_WOW_MAGIC_PAT_FOUND; + } else + val &= ~AR_WOW_MAGIC_EN; + + val |= AR_WOW_MAC_INTR_EN; + REG_WRITE(ah, AR_WOW_PATTERN_REG, val); + val = REG_READ(ah, AR_WOW_PATTERN_REG); + + /* + * For AR9285 and later version of the chips + * enable wow pattern match for packets less than + * 256 bytes for all patterns. + */ + if (AR_SREV_9285_10_OR_LATER(ah)) + REG_WRITE(ah, AR_WOW_PATTERN_MATCH_LT_256B_REG, + AR_WOW_PATTERN_SUPPORTED); + + /* + * Set the power states appropriately and enable pme. + */ + val = REG_READ(ah, AR_PCIE_PM_CTRL); + val |= AR_PMCTRL_PWR_STATE_D1D3 | + AR_PMCTRL_HOST_PME_EN | + AR_PMCTRL_PWR_PM_CTRL_ENA; + REG_WRITE(ah, AR_PCIE_PM_CTRL, val); + + ath9k_set_power_mode_wow_sleep(ah); + + ah->ah_wowEventMask = wow_event_mask; +} + +u32 ath9k_hw_wow_wake_up(struct ath_hw *ah) +{ + u32 wowStatus = 0; + u32 val = 0, rval; + + /* + * Read the WOW Status register to know the wakeup reason. + */ + rval = REG_READ(ah, AR_WOW_PATTERN_REG); + val = AR_WOW_STATUS(rval); + + /* + * Mask only the WOW events that we have enabled. Sometimes, we have + * spurious WOW events from the AR_WOW_PATTERN_REG register. This mask + * will clean it up. + */ + val &= ah->ah_wowEventMask; + + if (val) { + if (val & AR_WOW_MAGIC_PAT_FOUND) + wowStatus |= AH_WOW_MAGIC_PATTERN_EN; + if (AR_WOW_PATTERN_FOUND(val)) + wowStatus |= AH_WOW_USER_PATTERN_EN; + if (val & AR_WOW_KEEP_ALIVE_FAIL) + wowStatus |= AH_WOW_LINK_CHANGE; + if (val & AR_WOW_BEACON_FAIL) + wowStatus |= AH_WOW_BEACON_MISS; + } + + /* + * Set and clear WOW_PME_CLEAR registers for the chip to generate next + * wow signal. Disable D3 before accessing other registers ? + */ + val = REG_READ(ah, AR_PCIE_PM_CTRL); + /* Do we have to check the bit value 0x01000000 (7-10) ?? */ + val &= ~AR_PMCTRL_PWR_STATE_D1D3; + val |= AR_PMCTRL_WOW_PME_CLR; + REG_WRITE(ah, AR_PCIE_PM_CTRL, val); + + /* + * Clear all events. + */ + REG_WRITE(ah, AR_WOW_PATTERN_REG, + AR_WOW_CLEAR_EVENTS(REG_READ(ah, AR_WOW_PATTERN_REG))); + + /* + * Tie reset register. + * NB: Not tieing it back might have some repurcussions. + */ + if (AR_SREV_9280_10_OR_LATER(ah)) { + REG_WRITE(ah, AR_WA, REG_READ(ah, AR_WA) | + AR_WA_UNTIE_RESET_EN | + AR_WA_POR_SHORT | + AR_WA_RESET_EN); + } + + /* Restore the Beacon Threshold to init value */ + REG_WRITE(ah, AR_RSSI_THR, INIT_RSSI_THR); + + /* + * Restore the way the PCI-E Reset, Power-On-Reset, external + * PCIE_POR_SHORT pins are tied to its original value. Previously + * just before WOW sleep, we untie the PCI-E Reset to our Chip's + * Power On Reset so that any PCI-E reset from the bus will not + * reset our chip. + */ + if (AR_SREV_9280_20_OR_LATER(ah) && ah->is_pciexpress) + ath9k_hw_configpcipowersave(ah, 0); + + ah->ah_wowEventMask = 0; + + return wowStatus; +} + +void ath9k_wow_set_gpio_reset_low(struct ath_hw *ah) +{ + u32 val; + + val = REG_READ(ah, AR_GPIO_OE_OUT); + val |= (1 << (2 * 2)); + REG_WRITE(ah, AR_GPIO_OE_OUT, val); + val = REG_READ(ah, AR_GPIO_OE_OUT); + val = REG_READ(ah,AR_GPIO_IN_OUT ); +} + +const char * +ath9k_hw_wow_event_to_string(u32 wow_event) +{ + if (wow_event & AH_WOW_MAGIC_PATTERN_EN) + return "Magic pattern"; + if (wow_event & AH_WOW_USER_PATTERN_EN) + return "User pattern"; + if (wow_event & AH_WOW_LINK_CHANGE) + return "Link change"; + if (wow_event & AH_WOW_BEACON_MISS) + return "Beacon miss"; + return "Uknown reason"; +} + +#endif /* CONFIG_PM */ -- 1.6.0.4 From c7d0e317291d0c0a025f095b42c58262afd135e0 Mon Sep 17 00:00:00 2001 From: Christian Lamparter Date: Sun, 19 Jul 2009 22:09:32 +0200 Subject: [PATCH] mac80211: do not monitor the connection while scanning mac80211 constantly monitors the connection to the associated AP in order to check if it is out of reach/dead. This is absolutely fine most of the time. Except when there is a scheduled scan for the whole neighborhood. After all this path could trigger while scanning on different channel. Or even worse: this AP probing triggers a WARN_ON in rate_lowest_index when the scan code did a band transition! ( http://www.kerneloops.org/raw.php?rawid=449304 ) Reported-by: Larry Finger Signed-off-by: Christian Lamparter Tested-by: Larry Finger --- net/mac80211/mlme.c | 3 +++ 1 files changed, 3 insertions(+), 0 deletions(-) diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c index e3b3156..523c0d9 100644 --- a/net/mac80211/mlme.c +++ b/net/mac80211/mlme.c @@ -2213,6 +2213,9 @@ static void ieee80211_sta_monitor_work(struct work_struct *work) container_of(work, struct ieee80211_sub_if_data, u.mgd.monitor_work); + if (sdata->local->sw_scanning || sdata->local->hw_scanning) + return; + ieee80211_mgd_probe_ap(sdata, false); } -- 1.6.0.4