diff options
-rw-r--r-- | arch/arm/mach-tegra/include/mach/tegra_wakeup_monitor.h | 11 | ||||
-rw-r--r-- | arch/arm/mach-tegra/tegra_wakeup_monitor.c | 790 |
2 files changed, 709 insertions, 92 deletions
diff --git a/arch/arm/mach-tegra/include/mach/tegra_wakeup_monitor.h b/arch/arm/mach-tegra/include/mach/tegra_wakeup_monitor.h index 433123b381d4..5734441a3b3e 100644 --- a/arch/arm/mach-tegra/include/mach/tegra_wakeup_monitor.h +++ b/arch/arm/mach-tegra/include/mach/tegra_wakeup_monitor.h @@ -1,7 +1,7 @@ /* * arch/arm/mach-tegra/include/mach/tegra_wakeup_monitor.h * - * Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved. + * Copyright (c) 2012-2013, NVIDIA CORPORATION. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, @@ -22,9 +22,10 @@ /* Wakeup source */ #define TEGRA_WAKEUP_SOURCE_OTHERS 0 #define TEGRA_WAKEUP_SOURCE_WIFI 1 +#define TEGRA_WAKEUP_SOURCE_RTC 2 /* Wow wakeup event*/ -#define TEGRA_WOW_WAKEUP_ENABLE "TEGRA_WOW_WAKEUP_ENABLE=1" +#define TEGRA_WOW_WAKEUP_ENABLE "TEGRA_WOW_WAKEUP_ENABLE=1" #define TEGRA_WOW_WAKEUP_DISABLE "TEGRA_WOW_WAKEUP_ENABLE=0" /* Suspend prepare uevent string */ @@ -36,11 +37,15 @@ #define TEGRA_POST_SUSPEND_UEVENT "PM_POST_SUSPEND" /* Timeout to get cmd from up-lever */ -#define TEGRA_WAKEUP_MONITOR_CMD_TIMEOUT_MS 100 +#define TEGRA_WAKEUP_MONITOR_CMD_TIMEOUT_MS 1000 /* tegra wakeup monitor platform data */ struct tegra_wakeup_monitor_platform_data { int wifi_wakeup_source; + int rtc_wakeup_source; }; +extern uint32_t get_rtc_wakeup_src(void); +extern void set_rtc_wakeup_src(uint32_t value); + #endif /* __MACH_TEGRA_WAKEUP_MONITOR_H */ diff --git a/arch/arm/mach-tegra/tegra_wakeup_monitor.c b/arch/arm/mach-tegra/tegra_wakeup_monitor.c index e1e6f783a8b9..52bc3d604978 100644 --- a/arch/arm/mach-tegra/tegra_wakeup_monitor.c +++ b/arch/arm/mach-tegra/tegra_wakeup_monitor.c @@ -1,7 +1,7 @@ /* * arch/arm/mach-tegra/tegra_wakeup_monitor.c * - * Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved. + * Copyright (c) 2012-2013, NVIDIA CORPORATION. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, @@ -18,23 +18,70 @@ #include <linux/module.h> #include <linux/platform_device.h> -#include <linux/semaphore.h> +#include <linux/completion.h> #include <linux/suspend.h> #include <linux/slab.h> + +#include <net/ip.h> +#include <linux/netfilter_ipv4.h> +#include <linux/proc_fs.h> + #include <mach/tegra_wakeup_monitor.h> #include "pm-irq.h" +#define MAX_PACKET_NUM 6 +#define MONITOR_HTABLE_SIZE 256 +#define TWM_TCP 1 +#define TWM_UDP 2 + +struct packet_info { + struct hlist_nulls_node node; + unsigned short dport; + atomic_t *wakeup; + atomic_t unmonitored; + unsigned int valid_counter; +}; + +struct uid_info { + struct hlist_nulls_node node; + unsigned int uid; + atomic_t wakeup; + unsigned int valid_counter; +}; + +struct twm_hslot { + struct hlist_nulls_head head; + int count; + spinlock_t lock; +}; + struct tegra_wakeup_monitor { struct tegra_wakeup_monitor_platform_data *pdata; struct notifier_block pm_notifier; struct platform_device *pdev; - bool wow_enabled; bool monitor_enable; + bool nf_enable; + bool am_enable; int wakeup_source; - struct semaphore suspend_prepare_sem; + struct completion suspend_prepare_done; }; +struct _monitor_table { + struct twm_hslot *uid_hash; + struct twm_hslot *tcp_hash; + struct twm_hslot *udp_hash; + unsigned short mask; + unsigned int valid_counter; + struct tegra_wakeup_monitor *twm; +}; + +static struct _monitor_table monitor_table; + +static bool nf_monitor; +static bool nf_valid_flag; +static int nf_counter; + static ssize_t show_monitor_enable(struct device *dev, struct device_attribute *attr, char *buf) { @@ -52,77 +99,59 @@ static ssize_t store_monitor_enable(struct device *dev, if (sscanf(buf, "%d", &enable) != 1 || enable < 0 || enable > 1) return -EINVAL; - dev_info(dev, "wakeup moniter: monitor enable = %d\n", enable); + dev_info(dev, "monitor enable = %d\n", enable); twm->monitor_enable = enable; return count; } -static DEVICE_ATTR(monitor_enable, S_IRUSR | S_IWUSR, show_monitor_enable, - store_monitor_enable); - -static int set_wow(struct tegra_wakeup_monitor *twm, bool enable) +/* MUST hold hslot->lock when call this func */ +static struct uid_info *get_uid_info(struct device *dev, + unsigned int uid, + struct twm_hslot *hslot, + bool auto_alloc) { - char *envp[2]; - - if (enable) - envp[0] = TEGRA_WOW_WAKEUP_ENABLE; - else - envp[0] = TEGRA_WOW_WAKEUP_DISABLE; - envp[1] = NULL; - /* Sent out a uevent to broadcast wow enable change*/ - kobject_uevent_env(&twm->pdev->dev.kobj, KOBJ_CHANGE, envp); - dev_info(&twm->pdev->dev, - "wakeup moniter: set_wow = %d\n", (int)enable); - return 0; -} - -static ssize_t show_wow_enable(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct tegra_wakeup_monitor *twm = dev_get_drvdata(dev); - return sprintf(buf, "%d\n", twm->wow_enabled ? 1 : 0); -} - -static ssize_t store_wow_enable(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - int enable; - struct tegra_wakeup_monitor *twm = dev_get_drvdata(dev); - - if (sscanf(buf, "%d", &enable) != 1 || enable < 0 || enable > 1) - return -EINVAL; + struct uid_info *uid_info; + struct hlist_nulls_node *node; + if (hslot->count == 0) + goto alloc; + + hlist_nulls_for_each_entry(uid_info, node, &hslot->head, node) + if (uid_info->uid == uid) + return uid_info; +alloc: + if (!auto_alloc) + return NULL; + + uid_info = devm_kzalloc(dev, sizeof(*uid_info), GFP_ATOMIC); + if (!uid_info) { + dev_err(dev, "could not allocate a uid_info"); + return NULL; + } - dev_info(dev, "wakeup moniter: wow_enable = %d\n", enable); - set_wow(twm, enable); - twm->wow_enabled = enable; + uid_info->uid = uid; - return count; + hlist_nulls_add_head_rcu(&uid_info->node, &hslot->head); + hslot->count++; + return uid_info; } -static DEVICE_ATTR(wow_enable, S_IRUSR | S_IWUSR, show_wow_enable, - store_wow_enable); - /* if the wakeup monitor is enabled, it will receive a command before suspend */ static ssize_t store_cmd(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - struct tegra_wakeup_monitor *twm = dev_get_drvdata(dev); if (strncmp(buf, "0", 1)) return -EINVAL; - dev_info(dev, "wakeup moniter: get done cmd\n"); - up(&twm->suspend_prepare_sem); + dev_info(dev, "get done cmd\n"); + complete(&twm->suspend_prepare_done); return count; } -static DEVICE_ATTR(cmd, S_IWUSR, NULL, store_cmd); - static int tegra_wakeup_monitor_pm_notifier(struct notifier_block *notifier, unsigned long pm_event, void *unused) { @@ -144,6 +173,10 @@ static int tegra_wakeup_monitor_pm_notifier(struct notifier_block *notifier, default: envp[0] = TEGRA_SUSPEND_PREPARE_UEVENT_OTHERS; } + /* If we have more comletion, clean it */ + if (try_wait_for_completion(&twm->suspend_prepare_done)) + dev_warn(&twm->pdev->dev, + "completion is not empty\n"); /* send out a uevent to boardcast suspend prepare */ kobject_uevent_env(&twm->pdev->dev.kobj, KOBJ_CHANGE, envp); @@ -151,18 +184,14 @@ static int tegra_wakeup_monitor_pm_notifier(struct notifier_block *notifier, twm->wakeup_source = TEGRA_WAKEUP_SOURCE_OTHERS; /* waiting for cmd feedback */ - if (down_timeout(&twm->suspend_prepare_sem, - timeout) != 0) - dev_err(&twm->pdev->dev, "wakeup monitor: cmd time out\n"); + if (wait_for_completion_timeout( + &twm->suspend_prepare_done, timeout) == 0) + dev_err(&twm->pdev->dev, "cmd time out\n"); } return NOTIFY_OK; case PM_POST_SUSPEND: if (twm->monitor_enable) { dev_info(&twm->pdev->dev, "enter post_suspend\n"); - if (twm->wow_enabled == false) { - set_wow(twm, true); - twm->wow_enabled = true; - } envp[0] = TEGRA_POST_SUSPEND_UEVENT; /* send out a uevent to boardcast post suspend*/ kobject_uevent_env(&twm->pdev->dev.kobj, KOBJ_CHANGE, @@ -173,43 +202,578 @@ static int tegra_wakeup_monitor_pm_notifier(struct notifier_block *notifier, return NOTIFY_DONE; } + +/* MUST hold hslot->lock when call this func */ +static struct packet_info *get_packet_info(struct device *dev, + unsigned short port, + struct twm_hslot *hslot, + bool auto_alloc) +{ + struct packet_info *packet_info; + struct hlist_nulls_node *node; + if (hslot->count == 0) + goto alloc; + + hlist_nulls_for_each_entry(packet_info, node, &hslot->head, node) + if (packet_info->dport == port) + return packet_info; + +alloc: + if (!auto_alloc) + return NULL; + + packet_info = devm_kzalloc(dev, sizeof(*packet_info), GFP_ATOMIC); + if (!packet_info) { + dev_err(dev, "could not allocate a packet_info"); + return NULL; + } + + packet_info->dport = port; + + hlist_nulls_add_head_rcu(&packet_info->node, &hslot->head); + hslot->count++; + return packet_info; +} + +/* Note: this function must be SMP safe */ +static unsigned int twm_nf_hook(unsigned int hook, + struct sk_buff *skb, + const struct net_device *indev, + const struct net_device *outdev, + int (*okfn) (struct sk_buff *)) +{ + struct tcphdr *ptcphdr; + struct udphdr *pudphdr; + struct timeval tv; + unsigned short sport; + unsigned short dport; + unsigned char protocol; + struct packet_info *packet_info; + struct twm_hslot *hslot_table, *hslot; + struct tegra_wakeup_monitor *twm = monitor_table.twm; + + if (nf_monitor == false) + return NF_ACCEPT; + + if (!(skb) || !(ip_hdr(skb))) { + dev_err(&twm->pdev->dev, "unexpected skb"); + return NF_ACCEPT; + } + + if (nf_counter == 0) + dev_dbg(&twm->pdev->dev, "a new begin of monitoring"); + + nf_counter++; + if (nf_counter >= MAX_PACKET_NUM) + nf_monitor = false; + + protocol = ip_hdr(skb)->protocol; + if (protocol == IPPROTO_TCP) { + ptcphdr = (struct tcphdr *) + ((char *)ip_hdr(skb) + (ip_hdr(skb)->ihl << 2)); + sport = ntohs(ptcphdr->source); + dport = ntohs(ptcphdr->dest); + hslot_table = monitor_table.tcp_hash; + } else if (protocol == IPPROTO_UDP) { + pudphdr = (struct udphdr *) + ((char *)ip_hdr(skb) + (ip_hdr(skb)->ihl << 2)); + sport = ntohs(pudphdr->source); + dport = ntohs(pudphdr->dest); + hslot_table = monitor_table.udp_hash; + } else { + dev_dbg(&twm->pdev->dev, "unexpected packet"); + return NF_ACCEPT; + } + + do_gettimeofday(&tv); + + if (nf_valid_flag == true && nf_counter == 1) { + hslot = &hslot_table[dport & monitor_table.mask]; + spin_lock_bh(&hslot->lock); + packet_info = get_packet_info(&twm->pdev->dev, + dport, hslot, true); + if (packet_info == NULL) { + dev_err(&twm->pdev->dev, + "%s: cannot get a packet_info", __func__); + spin_unlock_bh(&hslot->lock); + return NF_ACCEPT; + } else if (packet_info->wakeup) { + atomic_inc(packet_info->wakeup); + } else { + atomic_inc(&packet_info->unmonitored); + packet_info->valid_counter = + monitor_table.valid_counter; + } + + spin_unlock_bh(&hslot->lock); + } + + dev_dbg(&twm->pdev->dev, "%d packet received from %s", + nf_counter, indev->name); + dev_dbg(&twm->pdev->dev, + "%s ip:%pI4 port:%u -> ip:%pI4 port:%u at time:%u,%u", + (protocol == IPPROTO_TCP) ? "TCP" : "UDP", + &ip_hdr(skb)->saddr, sport, &ip_hdr(skb)->daddr, dport, + (unsigned int)tv.tv_sec, (unsigned int)tv.tv_usec); + + return NF_ACCEPT; +} + +static struct nf_hook_ops twm_nf_ops __read_mostly = { + .hook = twm_nf_hook, + .pf = PF_INET, + .hooknum = NF_INET_PRE_ROUTING, + .priority = NF_IP_PRI_MANGLE, +}; + +static ssize_t show_nf_enable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tegra_wakeup_monitor *twm = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", twm->nf_enable ? 1 : 0); +} + +static ssize_t store_nf_enable(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int err; + int enable; + struct tegra_wakeup_monitor *twm = dev_get_drvdata(dev); + + if (sscanf(buf, "%d", &enable) != 1 || enable < 0 || enable > 1) + return -EINVAL; + + if (enable == 1 && twm->nf_enable == false) { + err = nf_register_hook(&twm_nf_ops); + if (err < 0) { + dev_err(dev, "hook registration error\n"); + return err; + } + } else if (enable == 0 && twm->nf_enable == true) { + nf_unregister_hook(&twm_nf_ops); + } + + twm->nf_enable = enable; + dev_info(dev, "netfilter enable = %d\n", enable); + + return count; +} + +static ssize_t show_am_enable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct tegra_wakeup_monitor *twm = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", twm->am_enable ? 1 : 0); +} + +static ssize_t store_am_enable(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int enable; + struct tegra_wakeup_monitor *twm = dev_get_drvdata(dev); + + if (sscanf(buf, "%d", &enable) != 1 || enable < 0 || enable > 1) + return -EINVAL; + + dev_info(dev, "am_enable = %d\n", enable); + twm->am_enable = enable; + + return count; +} + +static ssize_t store_init_ports(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned int uid, proto, port, off; + struct uid_info *uid_info; + struct packet_info *packet_info; + struct twm_hslot *uid_hslot_table, *pkt_hslot_table; + struct twm_hslot *uid_hslot, *pkt_hslot; + struct tegra_wakeup_monitor *twm = dev_get_drvdata(dev); + + if (!twm || !monitor_table.uid_hash) { + dev_err(dev, "monitor_table is not initialized!"); + return 0; + } + + monitor_table.valid_counter++; + + uid_hslot_table = monitor_table.uid_hash; + while (3 == sscanf(buf, "%u,%u,%u;%n", &uid, &proto, &port, &off)) { + if (off == 0) + break; + buf += off; + + if (proto == TWM_TCP) + pkt_hslot_table = monitor_table.tcp_hash; + else if (proto == TWM_UDP) + pkt_hslot_table = monitor_table.udp_hash; + else { + dev_err(dev, "%s: invalid proto type", __func__); + continue; + } + + if (port > 0xffff) { + dev_err(dev, "%s: invalid port number", __func__); + continue; + } + + uid_hslot = &uid_hslot_table[uid & monitor_table.mask]; + spin_lock_bh(&uid_hslot->lock); + uid_info = get_uid_info(dev, uid, uid_hslot, true); + if (uid_info == NULL) { + dev_err(dev, "%s: cannot get a uid_info", __func__); + spin_unlock_bh(&uid_hslot->lock); + continue; + } + uid_info->uid = uid; + atomic_set(&uid_info->wakeup, 0); + uid_info->valid_counter = monitor_table.valid_counter; + + pkt_hslot = &pkt_hslot_table[port & monitor_table.mask]; + spin_lock_bh(&pkt_hslot->lock); + packet_info = get_packet_info(dev, port, pkt_hslot, true); + if (packet_info == NULL) { + dev_err(dev, "%s: cannot get a packet_info", __func__); + spin_unlock_bh(&pkt_hslot->lock); + spin_unlock_bh(&uid_hslot->lock); + continue; + } + packet_info->dport = port; + packet_info->wakeup = &uid_info->wakeup; + atomic_set(&packet_info->unmonitored, 0); + packet_info->valid_counter = monitor_table.valid_counter; + + spin_unlock_bh(&pkt_hslot->lock); + spin_unlock_bh(&uid_hslot->lock); + } + + return count; +} + +static ssize_t store_add_ports(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned int uid, proto, port, off; + struct uid_info *uid_info; + struct packet_info *packet_info; + struct twm_hslot *uid_hslot_table, *pkt_hslot_table; + struct twm_hslot *uid_hslot, *pkt_hslot; + struct tegra_wakeup_monitor *twm = dev_get_drvdata(dev); + + if (!twm || !monitor_table.uid_hash) { + dev_err(dev, "monitor_table is not initialized!"); + return 0; + } + + uid_hslot_table = monitor_table.uid_hash; + while (3 == sscanf(buf, "%u,%u,%u;%n", &uid, &proto, &port, &off)) { + if (off == 0) + break; + buf += off; + + if (proto == TWM_TCP) + pkt_hslot_table = monitor_table.tcp_hash; + else if (proto == TWM_UDP) + pkt_hslot_table = monitor_table.udp_hash; + else { + dev_err(dev, "%s: invalid proto type", __func__); + continue; + } + + if (port > 0xffff) { + dev_err(dev, "%s: invalid port number", __func__); + continue; + } + + uid_hslot = &uid_hslot_table[uid & monitor_table.mask]; + spin_lock_bh(&uid_hslot->lock); + uid_info = get_uid_info(dev, uid, uid_hslot, false); + if (uid_info == NULL) { + dev_err(dev, "%s: cannot get a uid_info", __func__); + spin_unlock_bh(&uid_hslot->lock); + continue; + } + + pkt_hslot = &pkt_hslot_table[port & monitor_table.mask]; + spin_lock_bh(&pkt_hslot->lock); + packet_info = get_packet_info(dev, port, pkt_hslot, true); + if (packet_info == NULL) { + dev_err(dev, "%s: cannot get a packet_info", __func__); + spin_unlock_bh(&pkt_hslot->lock); + spin_unlock_bh(&uid_hslot->lock); + continue; + } + packet_info->dport = port; + packet_info->wakeup = &uid_info->wakeup; + atomic_set(&packet_info->unmonitored, 0); + packet_info->valid_counter = uid_info->valid_counter; + + spin_unlock_bh(&pkt_hslot->lock); + spin_unlock_bh(&uid_hslot->lock); + } + + return count; +} + +static ssize_t store_del_ports(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned int uid, proto, port, off; + struct uid_info *uid_info, *target_uid_info; + struct packet_info *packet_info; + struct twm_hslot *uid_hslot_table, *pkt_hslot_table; + struct twm_hslot *uid_hslot, *pkt_hslot; + struct tegra_wakeup_monitor *twm = dev_get_drvdata(dev); + + if (!twm || !monitor_table.uid_hash) { + dev_err(dev, "monitor_table is not initialized!"); + return 0; + } + + uid_hslot_table = monitor_table.uid_hash; + while (3 == sscanf(buf, "%u,%u,%u;%n", &uid, &proto, &port, &off)) { + if (off == 0) + break; + buf += off; + + if (proto == TWM_TCP) + pkt_hslot_table = monitor_table.tcp_hash; + else if (proto == TWM_UDP) + pkt_hslot_table = monitor_table.udp_hash; + else { + dev_err(dev, "%s: invalid proto type", __func__); + continue; + } + + if (port > 0xffff) { + dev_err(dev, "%s: invalid port number", __func__); + continue; + } + + uid_hslot = &uid_hslot_table[uid & monitor_table.mask]; + spin_lock_bh(&uid_hslot->lock); + uid_info = get_uid_info(dev, uid, uid_hslot, false); + if (uid_info == NULL) { + dev_err(dev, "%s: cannot get a uid_info", __func__); + spin_unlock_bh(&uid_hslot->lock); + continue; + } + + pkt_hslot = &pkt_hslot_table[port & monitor_table.mask]; + spin_lock_bh(&pkt_hslot->lock); + packet_info = get_packet_info(dev, port, pkt_hslot, false); + if (packet_info == NULL) { + dev_err(dev, "%s: cannot get a packet_info", __func__); + spin_unlock_bh(&pkt_hslot->lock); + spin_unlock_bh(&uid_hslot->lock); + continue; + } + target_uid_info = container_of(packet_info->wakeup, + struct uid_info, wakeup); + if (uid == target_uid_info->uid) { + hlist_nulls_del(&packet_info->node); + devm_kfree(dev, packet_info); + pkt_hslot->count--; + } + + spin_unlock_bh(&pkt_hslot->lock); + spin_unlock_bh(&uid_hslot->lock); + } + + return count; +} + +static struct device_attribute twm_attrs[] = { + __ATTR(monitor_enable, S_IRUSR | S_IWUSR, + show_monitor_enable, store_monitor_enable), + __ATTR(cmd, S_IWUSR, NULL, store_cmd), + __ATTR(nf_enable, S_IRUSR | S_IWUSR, show_nf_enable, store_nf_enable), + __ATTR(am_enable, S_IRUSR | S_IWUSR, show_am_enable, store_am_enable), + __ATTR(init_ports, S_IWUSR, NULL, store_init_ports), + __ATTR(add_ports, S_IWUSR, NULL, store_add_ports), + __ATTR(del_ports, S_IWUSR, NULL, store_del_ports), +}; + +static inline int monitor_table_init(struct _monitor_table *table, + struct tegra_wakeup_monitor *twm) +{ + unsigned int i; + + table->uid_hash = devm_kzalloc(&twm->pdev->dev, MONITOR_HTABLE_SIZE * + 3 * sizeof(struct twm_hslot), GFP_KERNEL); + if (!table->uid_hash) + return -ENOMEM; + + table->twm = twm; + + table->mask = MONITOR_HTABLE_SIZE - 1; + table->tcp_hash = table->uid_hash + (table->mask + 1); + table->udp_hash = table->tcp_hash + (table->mask + 1); + for (i = 0; i <= table->mask; i++) { + INIT_HLIST_NULLS_HEAD(&table->uid_hash[i].head, i); + table->uid_hash[i].count = 0; + spin_lock_init(&table->uid_hash[i].lock); + } + for (i = 0; i <= table->mask; i++) { + INIT_HLIST_NULLS_HEAD(&table->tcp_hash[i].head, i); + table->tcp_hash[i].count = 0; + spin_lock_init(&table->tcp_hash[i].lock); + } + for (i = 0; i <= table->mask; i++) { + INIT_HLIST_NULLS_HEAD(&table->udp_hash[i].head, i); + table->udp_hash[i].count = 0; + spin_lock_init(&table->udp_hash[i].lock); + } + + return 0; +} + +static int twm_offender_stat(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + int i = 0; + int len = 0; + struct twm_hslot *hslot; + struct uid_info *uid_info; + struct packet_info *packet_info; + struct hlist_nulls_node *node; + struct device *dev; + + if (!monitor_table.twm || !monitor_table.uid_hash) { + len += sprintf(page + len, "monitor table not initialized\n"); + *eof = 1; + return len; + } + + dev = &monitor_table.twm->pdev->dev; + + /* make sure the len does not exceed PAGE_SIZE */ + for (i = 0; i <= monitor_table.mask; i++) { + hslot = &monitor_table.uid_hash[i]; + if (hslot->count == 0) + continue; + + spin_lock_bh(&hslot->lock); + hlist_nulls_for_each_entry(uid_info, node, + &hslot->head, node) { + if (uid_info->valid_counter != + monitor_table.valid_counter) { + hlist_nulls_del(&uid_info->node); + devm_kfree(dev, uid_info); + hslot->count--; + continue; + } + len += sprintf(page + len, + "uid %u, wakeup times %u\n", + uid_info->uid, + atomic_read(&uid_info->wakeup)); + } + spin_unlock_bh(&hslot->lock); + } + len += sprintf(page + len, "Unmonitored wakeups\nTCP statistics:\n"); + for (i = 0; i <= monitor_table.mask; i++) { + hslot = &monitor_table.tcp_hash[i]; + if (hslot->count == 0) + continue; + + spin_lock_bh(&hslot->lock); + hlist_nulls_for_each_entry(packet_info, node, + &hslot->head, node) { + if (packet_info->valid_counter != + monitor_table.valid_counter) { + hlist_nulls_del(&packet_info->node); + devm_kfree(dev, packet_info); + hslot->count--; + continue; + } + if (packet_info->wakeup) + continue; + len += sprintf(page + len, + "port %u, wakeup times %u\n", + packet_info->dport, + atomic_read(&packet_info->unmonitored)); + } + spin_unlock_bh(&hslot->lock); + } + len += sprintf(page + len, "UDP statistics:\n"); + for (i = 0; i <= monitor_table.mask; i++) { + hslot = &monitor_table.udp_hash[i]; + if (hslot->count == 0) + continue; + + spin_lock_bh(&hslot->lock); + hlist_nulls_for_each_entry(packet_info, node, + &hslot->head, node) { + if (packet_info->valid_counter != + monitor_table.valid_counter) { + hlist_nulls_del(&packet_info->node); + devm_kfree(dev, packet_info); + hslot->count--; + continue; + } + if (packet_info->wakeup) + continue; + len += sprintf(page + len, + "port %u, wakeup times %u\n", + packet_info->dport, + atomic_read(&packet_info->unmonitored)); + } + spin_unlock_bh(&hslot->lock); + } + + *eof = 1; + return len; +} + static inline int twm_init(struct tegra_wakeup_monitor *twm, struct platform_device *pdev) { + unsigned int i; struct tegra_wakeup_monitor_platform_data *pdata = pdev->dev.platform_data; + struct proc_dir_entry *e = NULL; int ret = 0; twm->pdata = pdata; twm->pdev = pdev; - - /* create sysfs node */ - ret = device_create_file(&pdev->dev, &dev_attr_monitor_enable); - if (ret) - goto error; - - ret = device_create_file(&pdev->dev, &dev_attr_wow_enable); - if (ret) - goto error; - - ret = device_create_file(&pdev->dev, &dev_attr_cmd); - if (ret) - goto error; - twm->monitor_enable = false; - twm->wow_enabled = true; + twm->nf_enable = false; twm->wakeup_source = TEGRA_WAKEUP_SOURCE_OTHERS; twm->pm_notifier.notifier_call = tegra_wakeup_monitor_pm_notifier; - sema_init(&(twm->suspend_prepare_sem), 1); + init_completion(&(twm->suspend_prepare_done)); ret = register_pm_notifier(&twm->pm_notifier); + if (ret < 0) { + dev_err(&pdev->dev, "pm notifier registration error\n"); + return ret; + } + + e = create_proc_read_entry("twm_offender_stat", 0, init_net.proc_net, + twm_offender_stat, NULL); + if (!e) { + dev_err(&pdev->dev, "proc entry creation error\n"); + return -ENOMEM; + } + + /* create sysfs node */ + for (i = 0; i < ARRAY_SIZE(twm_attrs); i++) { + ret = device_create_file(&pdev->dev, &twm_attrs[i]); + if (ret) + goto error; + } return ret; error: - device_remove_file(&pdev->dev, &dev_attr_cmd); - device_remove_file(&pdev->dev, &dev_attr_wow_enable); - device_remove_file(&pdev->dev, &dev_attr_monitor_enable); + remove_proc_entry("twm_offender_stat", init_net.proc_net); + for (i = 0; i < ARRAY_SIZE(twm_attrs); i++) + device_remove_file(&pdev->dev, &twm_attrs[i]); return ret; } @@ -233,23 +797,60 @@ static int tegra_wakeup_monitor_probe(struct platform_device *pdev) return -ENOMEM; } - twm_init(twm, pdev); - dev_set_drvdata(&pdev->dev, twm); + ret = twm_init(twm, pdev); + if (ret < 0) { + dev_err(&pdev->dev, "failed to init twm\n"); + goto error; + } + + ret = dev_set_drvdata(&pdev->dev, twm); + if (ret < 0) { + dev_err(&pdev->dev, "failed to set driver data\n"); + goto error; + } + + ret = monitor_table_init(&monitor_table, twm); + if (ret < 0) { + dev_err(&pdev->dev, "failed to init monitor table\n"); + goto error; + } + + return 0; +error: + devm_kfree(&pdev->dev, twm); return ret; } static int __exit tegra_wakeup_monitor_remove(struct platform_device *pdev) { + unsigned int i; struct tegra_wakeup_monitor *twm = platform_get_drvdata(pdev); unregister_pm_notifier(&twm->pm_notifier); + nf_unregister_hook(&twm_nf_ops); + + for (i = 0; i < ARRAY_SIZE(twm_attrs); i++) + device_remove_file(&pdev->dev, &twm_attrs[i]); + remove_proc_entry("twm_offender_stat", init_net.proc_net); + + kfree(twm); /* is it needed? */ + return 0; +} + +static int tegra_wakeup_monitor_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct tegra_wakeup_monitor *twm = platform_get_drvdata(pdev); + if (twm->nf_enable) { + nf_monitor = true; + dev_info(&pdev->dev, "start netfilter monitoring\n"); + } else { + nf_monitor = false; + } - device_remove_file(&pdev->dev, &dev_attr_monitor_enable); - device_remove_file(&pdev->dev, &dev_attr_wow_enable); - device_remove_file(&pdev->dev, &dev_attr_cmd); + nf_counter = 0; - kfree(twm); return 0; } @@ -259,13 +860,23 @@ static int tegra_wakeup_monitor_resume(struct platform_device *pdev) /* read and save wake status */ u64 wake_status = tegra_read_pmc_wake_status(); - - if (twm->pdata->wifi_wakeup_source != -1 && - (wake_status & BIT(twm->pdata->wifi_wakeup_source))) - twm->wakeup_source = TEGRA_WAKEUP_SOURCE_WIFI; - else + nf_valid_flag = false; + if (twm->pdata->wifi_wakeup_source != -1) { + if (wake_status & BIT(twm->pdata->wifi_wakeup_source)) { + twm->wakeup_source = TEGRA_WAKEUP_SOURCE_WIFI; + nf_valid_flag = true; + } else if (wake_status & BIT(twm->pdata->rtc_wakeup_source)) { + twm->wakeup_source = TEGRA_WAKEUP_SOURCE_RTC; + if (twm->am_enable) + set_rtc_wakeup_src(1); + } else { + twm->wakeup_source = TEGRA_WAKEUP_SOURCE_OTHERS; + } + } else { twm->wakeup_source = TEGRA_WAKEUP_SOURCE_OTHERS; - pr_info("wakeup monitor: wakeup source =%d\n", twm->wakeup_source); + } + + dev_info(&pdev->dev, "wakeup source = %d\n", twm->wakeup_source); return 0; } @@ -278,6 +889,7 @@ static struct platform_driver tegra_wakeup_monitor_driver = { .probe = tegra_wakeup_monitor_probe, .remove = __exit_p(tegra_wakeup_monitor_remove), #ifdef CONFIG_PM + .suspend = tegra_wakeup_monitor_suspend, .resume = tegra_wakeup_monitor_resume, #endif }; |