/* * arch/arm/mach-tegra/tegra_wakeup_monitor.c * * 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, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #define MAX_PACKET_NUM 6 #define MONITOR_HTABLE_SIZE 256 #define TWM_TCP_IPV4 1 #define TWM_UDP_IPV4 2 #define TWM_TCP_IPV6 3 #define TWM_UDP_IPV6 4 #define CMDLINE_LENGTH 80 struct packet_info { struct hlist_nulls_node node; unsigned short dport; atomic_t *wakeup; atomic_t unmonitored; unsigned int valid_counter; }; struct program_info { struct hlist_nulls_node node; unsigned int hash; char program[CMDLINE_LENGTH]; 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 monitor_enable; bool nf_enable; bool am_enable; int wakeup_source; struct completion suspend_prepare_done; }; struct _monitor_table { struct twm_hslot *program_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 unsigned int string_hash(const char *str) { unsigned int i, hash = 0; for (i = 0; str[i] && i < CMDLINE_LENGTH; i++) hash = 31*hash + str[i]; return hash; } static ssize_t show_monitor_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->monitor_enable ? 1 : 0); } static ssize_t store_monitor_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, "monitor enable = %d\n", enable); twm->monitor_enable = enable; return count; } /* MUST hold hslot->lock when call this func */ static struct program_info *get_program_info(struct device *dev, char *program, struct twm_hslot *hslot, bool auto_alloc) { struct program_info *cmd_info; struct hlist_nulls_node *node; if (hslot->count == 0) goto alloc; hlist_nulls_for_each_entry(cmd_info, node, &hslot->head, node) if (!strncmp(cmd_info->program, program, CMDLINE_LENGTH)) return cmd_info; alloc: if (!auto_alloc) return NULL; cmd_info = devm_kzalloc(dev, sizeof(*cmd_info), GFP_ATOMIC); if (!cmd_info) { dev_err(dev, "could not allocate a program_info"); return NULL; } strncpy(cmd_info->program, program, CMDLINE_LENGTH); cmd_info->hash = string_hash(program); atomic_set(&cmd_info->wakeup, 0); cmd_info->valid_counter = monitor_table.valid_counter; hlist_nulls_add_head_rcu(&cmd_info->node, &hslot->head); hslot->count++; return cmd_info; } /* 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, "get done cmd\n"); complete(&twm->suspend_prepare_done); return count; } static int tegra_wakeup_monitor_pm_notifier(struct notifier_block *notifier, unsigned long pm_event, void *unused) { struct tegra_wakeup_monitor *twm = container_of(notifier, struct tegra_wakeup_monitor, pm_notifier); char *envp[2]; unsigned long const timeout = msecs_to_jiffies(TEGRA_WAKEUP_MONITOR_CMD_TIMEOUT_MS); envp[1] = NULL; switch (pm_event) { case PM_SUSPEND_PREPARE: if (twm->monitor_enable) { dev_info(&twm->pdev->dev, "enter suspend_prepare\n"); switch (twm->wakeup_source) { case TEGRA_WAKEUP_SOURCE_WIFI: envp[0] = TEGRA_SUSPEND_PREPARE_UEVENT_WIFI; break; 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); /* clean the wakeup source flag */ twm->wakeup_source = TEGRA_WAKEUP_SOURCE_OTHERS; /* waiting for cmd feedback */ 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"); envp[0] = TEGRA_POST_SUSPEND_UEVENT; /* send out a uevent to boardcast post suspend*/ kobject_uevent_env(&twm->pdev->dev.kobj, KOBJ_CHANGE, envp); } return NOTIFY_OK; } 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, _tcph; struct udphdr *pudphdr, _udph; struct timeval tv; unsigned short sport; unsigned short dport; unsigned char protocol; __le16 fragoff; int offset; struct ipv6hdr _ipv6h, *ip6; 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"); if (nf_counter++ >= MAX_PACKET_NUM) nf_monitor = false; if (ip_hdr(skb)->version == 4) { 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 transport layer trpacket"); return NF_ACCEPT; } } else if (ip_hdr(skb)->version == 6) { offset = skb_network_offset(skb); ip6 = skb_header_pointer(skb, offset, sizeof(struct ipv6hdr), &_ipv6h); if (ip6 == NULL) return NF_ACCEPT; protocol = ip6->nexthdr; offset += sizeof(struct ipv6hdr); offset = ipv6_skip_exthdr(skb, offset, &protocol, &fragoff); if (offset < 0) return NF_ACCEPT; switch (protocol) { case IPPROTO_TCP: { ptcphdr = skb_header_pointer(skb, offset, sizeof(struct tcphdr), &_tcph); if (ptcphdr == NULL) return NF_ACCEPT; sport = ntohs(ptcphdr->source); dport = ntohs(ptcphdr->dest); hslot_table = monitor_table.tcp_hash; break; } case IPPROTO_UDP: { pudphdr = skb_header_pointer(skb, offset, sizeof(struct udphdr), &_udph); if (pudphdr == NULL) return NF_ACCEPT; sport = ntohs(pudphdr->source); dport = ntohs(pudphdr->dest); hslot_table = monitor_table.udp_hash; break; } default: dev_dbg(&twm->pdev->dev, "unexpected transport layer packet"); return NF_ACCEPT; } } else { dev_dbg(&twm->pdev->dev, "unexpected not-IPV4/IPV6 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) { char *program = NULL; unsigned int proto, port, off, hash; struct program_info *cmd_info; struct packet_info *packet_info; struct twm_hslot *program_hslot_table, *pkt_hslot_table; struct twm_hslot *program_hslot, *pkt_hslot; struct tegra_wakeup_monitor *twm = dev_get_drvdata(dev); if (!twm || !monitor_table.program_hash) { dev_err(dev, "monitor_table is not initialized!"); return 0; } off = strlen(buf); if (off >= PAGE_SIZE) { dev_err(dev, "buffer size is over %lu!", PAGE_SIZE); return 0; } program = kmalloc(PAGE_SIZE, GFP_KERNEL); if (program == NULL) { dev_err(dev, "can't not alloate buffer!"); return 0; } monitor_table.valid_counter++; program_hslot_table = monitor_table.program_hash; while (3 == sscanf(buf, "%s %u %u;%n", program, &proto, &port, &off)) { if (off == 0) break; buf += off; switch (proto) { case TWM_TCP_IPV4: case TWM_TCP_IPV6: pkt_hslot_table = monitor_table.tcp_hash; break; case TWM_UDP_IPV4: case TWM_UDP_IPV6: pkt_hslot_table = monitor_table.udp_hash; break; default: dev_err(dev, "%s: invalid proto type", __func__); continue; } if (port > 0xffff) { dev_err(dev, "%s: invalid port number", __func__); continue; } hash = string_hash(program); program_hslot = &program_hslot_table[hash & monitor_table.mask]; spin_lock_bh(&program_hslot->lock); cmd_info = get_program_info(dev, program, program_hslot, true); if (cmd_info == NULL) { dev_err(dev, "%s: cannot get a program_info", __func__); spin_unlock_bh(&program_hslot->lock); continue; } cmd_info->hash = hash; atomic_set(&cmd_info->wakeup, 0); cmd_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(&program_hslot->lock); continue; } packet_info->dport = port; packet_info->wakeup = &cmd_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(&program_hslot->lock); } kfree(program); return count; } static ssize_t store_add_ports(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { char *program = NULL; unsigned int proto, port, off, hash; struct program_info *cmd_info; struct packet_info *packet_info; struct twm_hslot *program_hslot_table, *pkt_hslot_table; struct twm_hslot *program_hslot, *pkt_hslot; struct tegra_wakeup_monitor *twm = dev_get_drvdata(dev); if (!twm || !monitor_table.program_hash) { dev_err(dev, "monitor_table is not initialized!"); return 0; } off = strlen(buf); if (off >= PAGE_SIZE) { dev_err(dev, "buffer size is over %lu!", PAGE_SIZE); return 0; } program = kmalloc(PAGE_SIZE, GFP_KERNEL); if (program == NULL) { dev_err(dev, "can't not alloate buffer!"); return 0; } program_hslot_table = monitor_table.program_hash; while (3 == sscanf(buf, "%s %u %u;%n", program, &proto, &port, &off)) { if (off == 0) break; buf += off; switch (proto) { case TWM_TCP_IPV4: case TWM_TCP_IPV6: pkt_hslot_table = monitor_table.tcp_hash; break; case TWM_UDP_IPV4: case TWM_UDP_IPV6: pkt_hslot_table = monitor_table.udp_hash; break; default: dev_err(dev, "%s: invalid proto type", __func__); continue; } if (port > 0xffff) { dev_err(dev, "%s: invalid port number", __func__); continue; } hash = string_hash(program); program_hslot = &program_hslot_table[hash & monitor_table.mask]; spin_lock_bh(&program_hslot->lock); cmd_info = get_program_info(dev, program, program_hslot, true); if (cmd_info == NULL) { dev_err(dev, "%s: cannot get a program_info", __func__); spin_unlock_bh(&program_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(&program_hslot->lock); continue; } packet_info->dport = port; packet_info->wakeup = &cmd_info->wakeup; atomic_set(&packet_info->unmonitored, 0); packet_info->valid_counter = cmd_info->valid_counter; spin_unlock_bh(&pkt_hslot->lock); spin_unlock_bh(&program_hslot->lock); } kfree(program); return count; } static ssize_t store_del_ports(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { char *program; unsigned int proto, port, off, hash; struct program_info *cmd_info, *target_program_info; struct packet_info *packet_info; struct twm_hslot *program_hslot_table, *pkt_hslot_table; struct twm_hslot *program_hslot, *pkt_hslot; struct tegra_wakeup_monitor *twm = dev_get_drvdata(dev); if (!twm || !monitor_table.program_hash) { dev_err(dev, "monitor_table is not initialized!"); return 0; } off = strlen(buf); if (off >= PAGE_SIZE) { dev_err(dev, "buffer size is over %lu!", PAGE_SIZE); return 0; } program = kmalloc(PAGE_SIZE, GFP_KERNEL); if (program == NULL) { dev_err(dev, "can't not alloate buffer!"); return 0; } program_hslot_table = monitor_table.program_hash; while (3 == sscanf(buf, "%s %u %u;%n", program, &proto, &port, &off)) { if (off == 0) break; buf += off; switch (proto) { case TWM_TCP_IPV4: case TWM_TCP_IPV6: pkt_hslot_table = monitor_table.tcp_hash; break; case TWM_UDP_IPV4: case TWM_UDP_IPV6: pkt_hslot_table = monitor_table.udp_hash; break; default: dev_err(dev, "%s: invalid proto type", __func__); continue; } if (port > 0xffff) { dev_err(dev, "%s: invalid port number", __func__); continue; } hash = string_hash(program); program_hslot = &program_hslot_table[hash & monitor_table.mask]; spin_lock_bh(&program_hslot->lock); cmd_info = get_program_info(dev, program, program_hslot, false); if (cmd_info == NULL) { dev_err(dev, "%s: cannot get a program_info", __func__); spin_unlock_bh(&program_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(&program_hslot->lock); continue; } target_program_info = container_of(packet_info->wakeup, struct program_info, wakeup); if (!strncmp(target_program_info->program, program, CMDLINE_LENGTH)) { hlist_nulls_del(&packet_info->node); devm_kfree(dev, packet_info); pkt_hslot->count--; } spin_unlock_bh(&pkt_hslot->lock); spin_unlock_bh(&program_hslot->lock); } kfree(program); 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->program_hash = devm_kzalloc(&twm->pdev->dev, MONITOR_HTABLE_SIZE * 3 * sizeof(struct twm_hslot), GFP_KERNEL); if (!table->program_hash) return -ENOMEM; table->twm = twm; table->mask = MONITOR_HTABLE_SIZE - 1; table->tcp_hash = table->program_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->program_hash[i].head, i); table->program_hash[i].count = 0; spin_lock_init(&table->program_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_show(struct seq_file *m, void *v) { int i = 0; struct twm_hslot *hslot; struct program_info *cmd_info; struct packet_info *packet_info; struct hlist_nulls_node *node; struct device *dev; if (!monitor_table.twm || !monitor_table.program_hash) { seq_printf(m, "monitor table not initialized\n"); return 0; } 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.program_hash[i]; if (hslot->count == 0) continue; spin_lock_bh(&hslot->lock); hlist_nulls_for_each_entry(cmd_info, node, &hslot->head, node) { if (cmd_info->valid_counter != monitor_table.valid_counter) { hlist_nulls_del(&cmd_info->node); devm_kfree(dev, cmd_info); hslot->count--; continue; } seq_printf(m, "program %s, wakeup times %u\n", cmd_info->program, atomic_read(&cmd_info->wakeup)); } spin_unlock_bh(&hslot->lock); } seq_printf(m, "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; seq_printf(m, "port %u, wakeup times %u\n", packet_info->dport, atomic_read(&packet_info->unmonitored)); } spin_unlock_bh(&hslot->lock); } seq_printf(m, "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; seq_printf(m, "port %u, wakeup times %u\n", packet_info->dport, atomic_read(&packet_info->unmonitored)); } spin_unlock_bh(&hslot->lock); } return 0; } static int twm_offender_stat_open(struct inode *inode, struct file *file) { return single_open(file, twm_offender_stat_show, PDE_DATA(inode)); } static const struct file_operations twm_offender_stat_fops = { .open = twm_offender_stat_open, .read = seq_read, .llseek = seq_lseek, .release = seq_release, }; 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; int ret = 0; twm->pdata = pdata; twm->pdev = pdev; twm->monitor_enable = false; twm->nf_enable = false; twm->wakeup_source = TEGRA_WAKEUP_SOURCE_OTHERS; twm->pm_notifier.notifier_call = tegra_wakeup_monitor_pm_notifier; 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; } if (!proc_create("twm_offender_stat", 0, init_net.proc_net, &twm_offender_stat_fops)) { 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: 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; } static int tegra_wakeup_monitor_probe(struct platform_device *pdev) { struct tegra_wakeup_monitor_platform_data *pdata = pdev->dev.platform_data; struct tegra_wakeup_monitor *twm; int ret = 0; if (!pdata) { dev_dbg(&pdev->dev, "platform_data not available\n"); return -EINVAL; } twm = devm_kzalloc(&pdev->dev, sizeof(struct tegra_wakeup_monitor), GFP_KERNEL); if (!twm) { dev_dbg(&pdev->dev, "failed to allocate memory\n"); return -ENOMEM; } 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; } nf_counter = 0; return 0; } static int tegra_wakeup_monitor_resume(struct platform_device *pdev) { struct tegra_wakeup_monitor *twm = platform_get_drvdata(pdev); /* read and save wake status */ u64 wake_status = tegra_read_pmc_wake_status(); 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; } dev_info(&pdev->dev, "wakeup source = %d\n", twm->wakeup_source); return 0; } static struct platform_driver tegra_wakeup_monitor_driver = { .driver = { .name = "tegra_wakeup_monitor", .owner = THIS_MODULE, }, .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 }; static int __init tegra_wakeup_monitor_init(void) { return platform_driver_register(&tegra_wakeup_monitor_driver); } subsys_initcall(tegra_wakeup_monitor_init); static void __exit tegra_wakeup_monitor_exit(void) { platform_driver_unregister(&tegra_wakeup_monitor_driver); } module_exit(tegra_wakeup_monitor_exit); MODULE_DESCRIPTION("Tegra Wakeup Monitor driver"); MODULE_LICENSE("GPL");