arm: tegra: wakeup offender monitoring
Louis Li [Tue, 8 Jan 2013 07:04:42 +0000 (15:04 +0800)]
Find the wakeup offender when the device is waken up by wireless or rtc
alarm. And record how many times the offenders cause the device to wake
up separately.

Bug 1190908

Change-Id: Ic8608448ce84d5c109e3957e0d98c3fcf088989c
Signed-off-by: Louis Li <louli@nvidia.com>
Reviewed-on: http://git-master/r/189852
Reviewed-by: Automatic_Commit_Validation_User
GVS: Gerrit_Virtual_Submit
Tested-by: Jiukai Ma <jiukaim@nvidia.com>
Reviewed-by: Sachin Nikam <snikam@nvidia.com>

arch/arm/mach-tegra/include/mach/tegra_wakeup_monitor.h
arch/arm/mach-tegra/tegra_wakeup_monitor.c

index 433123b..5734441 100644 (file)
@@ -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,
 /* 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 */
 #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 */
index e1e6f78..52bc3d6 100644 (file)
@@ -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,
 
 #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
 };