j1939: proc: re-add proc interface

Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
diff --git a/Documentation/networking/j1939.txt b/Documentation/networking/j1939.txt
index d2cdfa9..a0503ff 100644
--- a/Documentation/networking/j1939.txt
+++ b/Documentation/networking/j1939.txt
@@ -38,7 +38,13 @@
 
   6 can-j1939 SYSCTL
 
-  7 Credits
+  7 can-j1939 procfs interface
+    7.1 /proc/net/can-j1939/ecu
+    7.2 /proc/net/can-j1939/filter
+    7.3 /proc/net/can-j1939/sock
+    7.4 /proc/net/can-j1939/transport
+
+  8 Credits
 
 ============================================================================
 
@@ -468,7 +474,50 @@
   See section "SAE J1939" in "Documentation/sysctl/net.txt".
 
 
-7. Credits
+7. /proc/net/can-j1939 Interface.
+--------------------------------
+
+  Files giving you a view on the in-kernel operation of J1939 are located at:
+  /proc/net/j1939.
+
+7.1 /proc/net/can-j1939/ecu
+
+  This file gives an overview of the known ECU's to the kernel.
+  - iface : network interface they operate on.
+  - SA : current address.
+  - name : 64bit NAME
+  - flags : 'L' = local, 'R' = remote
+
+7.2 /proc/net/can-j1939/filter
+
+7.3 /proc/net/can-j1939/sock
+
+  This file gives a list of all j1939 sockets currently open.
+  - iface : network interface
+  - flags :
+    'b' : bound
+    'c' : connected
+    'P' : PROMISC
+    'o' : RECV_OWN
+    'd' : RECV_DEST
+    'p' : RECV_PRIO
+  - local: [NAME],SA
+  - remote: [NAME]/MASK,DA
+  - pgn : PGN
+  - prio : priority
+  - pending : # packets pending (see MSG_SYN on 4.2.1)
+
+7.4 /proc/net/can-j1939/transport
+
+  This file shows a list of pending transport sessions
+  - iface
+  - src : XX (addr) or XXXXXXXXXXXXXXXX (name)
+  - dst : XX or XXXXXXXXXXXXXXXX or '-' (broadcast)
+  - pgn :
+  - done/total : current # transferred bytes / total
+
+
+8. Credits
 --------------------------------
 
   Kurt Van Dijck (j1939 core, transport protocol, API)
diff --git a/net/can/j1939/j1939-priv.h b/net/can/j1939/j1939-priv.h
index 67a8a06..f799350 100644
--- a/net/can/j1939/j1939-priv.h
+++ b/net/can/j1939/j1939-priv.h
@@ -18,6 +18,7 @@
 #include <linux/kref.h>
 #include <linux/list.h>
 #include <linux/module.h>
+#include <linux/proc_fs.h>
 #include <linux/can/can-ml.h>
 #include <linux/can/j1939.h>
 
@@ -181,6 +182,8 @@ struct j1939_ecu *j1939_ecu_find_by_name(name_t name, int ifindex);
 /* find_by_name, with kref & read_lock taken */
 struct j1939_ecu *j1939_ecu_find_priv_default_tx(int ifindex, name_t *pname,
 						 u8 *paddr);
+extern struct proc_dir_entry *j1939_procdir;
+
 struct j1939_addr {
 	name_t src_name;
 	name_t dst_name;
diff --git a/net/can/j1939/main.c b/net/can/j1939/main.c
index 1412fc2..14932f7 100644
--- a/net/can/j1939/main.c
+++ b/net/can/j1939/main.c
@@ -31,6 +31,9 @@ MODULE_LICENSE("GPL v2");
 MODULE_AUTHOR("EIA Electronics (Kurt Van Dijck & Pieter Beyens)");
 MODULE_ALIAS("can-proto-" __stringify(CAN_J1939));
 
+static const char j1939_procname[] = "can-j1939";
+struct proc_dir_entry *j1939_procdir;
+
 /* LOWLEVEL CAN interface */
 
 /* CAN_HDR: #bytes before can_frame data part */
@@ -347,6 +350,84 @@ static struct notifier_block j1939_netdev_notifier = {
 	.notifier_call = j1939_netdev_notify,
 };
 
+/* proc access to the addr+name database */
+static int j1939_proc_show_addr(struct seq_file *sqf, void *v)
+{
+	struct net_device *netdev;
+	struct j1939_priv *priv;
+	int j;
+
+	seq_puts(sqf, "iface\tsa\t#users\n");
+	rcu_read_lock();
+	for_each_netdev_rcu(&init_net, netdev) {
+		priv = j1939_priv_get(netdev);
+		if (!priv)
+			continue;
+		read_lock_bh(&priv->lock);
+		for (j = 0; j < 0xfe; ++j) {
+			if (!priv->ents[j].nusers)
+				continue;
+			seq_printf(sqf, "%s\t%02x\t%i\n",
+				   netdev->name, j, priv->ents[j].nusers);
+		}
+		read_unlock_bh(&priv->lock);
+		j1939_priv_put(priv);
+	}
+	rcu_read_unlock();
+	return 0;
+}
+
+static int j1939_proc_show_name(struct seq_file *sqf, void *v)
+{
+	struct net_device *netdev;
+	struct j1939_priv *priv;
+	struct j1939_ecu *ecu;
+
+	seq_puts(sqf, "iface\tname\tsa\t#users\n");
+	rcu_read_lock();
+	for_each_netdev_rcu(&init_net, netdev) {
+		priv = j1939_priv_get(netdev);
+		if (!priv)
+			continue;
+		read_lock_bh(&priv->lock);
+		list_for_each_entry(ecu, &priv->ecus, list)
+			seq_printf(sqf, "%s\t%016llx\t%02x%s\t%i\n",
+				   netdev->name, ecu->name, ecu->sa,
+				   (priv->ents[ecu->sa].ecu == ecu) ? "" : "?",
+				   ecu->nusers);
+		read_unlock_bh(&priv->lock);
+		j1939_priv_put(priv);
+	}
+	rcu_read_unlock();
+	return 0;
+}
+
+static int j1939_proc_open_addr(struct inode *inode, struct file *file)
+{
+	return single_open(file, j1939_proc_show_addr, NULL);
+}
+
+static int j1939_proc_open_name(struct inode *inode, struct file *file)
+{
+	return single_open(file, j1939_proc_show_name, NULL);
+}
+
+static const struct file_operations j1939_proc_ops_addr = {
+	.owner		= THIS_MODULE,
+	.open		= j1939_proc_open_addr,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+};
+
+static const struct file_operations j1939_proc_ops_name = {
+	.owner		= THIS_MODULE,
+	.open		= j1939_proc_open_name,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+};
+
 /* MODULE interface */
 static __init int j1939_module_init(void)
 {
@@ -354,6 +435,11 @@ static __init int j1939_module_init(void)
 
 	pr_info("can: SAE J1939\n");
 
+	/* create /proc/net/can directory */
+	j1939_procdir = proc_mkdir(j1939_procname, init_net.proc_net);
+	if (!j1939_procdir)
+		return -EINVAL;
+
 	ret = register_netdevice_notifier(&j1939_netdev_notifier);
 	if (ret)
 		goto fail_notifier;
@@ -367,23 +453,38 @@ static __init int j1939_module_init(void)
 	if (ret < 0)
 		goto fail_tp;
 
+	if (!proc_create("addr", 0444, j1939_procdir, &j1939_proc_ops_addr))
+		goto fail_addr;
+	if (!proc_create("name", 0444, j1939_procdir, &j1939_proc_ops_name))
+		goto fail_name;
 	return 0;
 
+ fail_name:
+	remove_proc_entry("addr", j1939_procdir);
+ fail_addr:
+	j1939tp_module_exit();
  fail_tp:
 	can_proto_unregister(&j1939_can_proto);
  fail_sk:
 	unregister_netdevice_notifier(&j1939_netdev_notifier);
  fail_notifier:
+	proc_remove(j1939_procdir);
+	j1939_procdir = NULL;
 	return ret;
 }
 
 static __exit void j1939_module_exit(void)
 {
+	remove_proc_entry("name", j1939_procdir);
+	remove_proc_entry("addr", j1939_procdir);
 	j1939tp_module_exit();
 
 	can_proto_unregister(&j1939_can_proto);
 
 	unregister_netdevice_notifier(&j1939_netdev_notifier);
+
+	proc_remove(j1939_procdir);
+	j1939_procdir = NULL;
 }
 
 module_init(j1939_module_init);
diff --git a/net/can/j1939/transport.c b/net/can/j1939/transport.c
index 183328f..944016c 100644
--- a/net/can/j1939/transport.c
+++ b/net/can/j1939/transport.c
@@ -1348,6 +1348,53 @@ int j1939tp_rmdev_notifier(struct net_device *netdev)
 	return NOTIFY_DONE;
 }
 
+/* PROC */
+static int j1939tp_proc_show_session(struct seq_file *sqf,
+				     struct session *session)
+{
+	seq_printf(sqf, "%i", session->skb_iif);
+	if (session->cb->addr.src_name)
+		seq_printf(sqf, "\t%016llx", session->cb->addr.src_name);
+	else
+		seq_printf(sqf, "\t%02x", session->cb->addr.sa);
+	if (session->cb->addr.dst_name)
+		seq_printf(sqf, "\t%016llx", session->cb->addr.dst_name);
+	else if (j1939_address_is_unicast(session->cb->addr.da))
+		seq_printf(sqf, "\t%02x", session->cb->addr.da);
+	else
+		seq_puts(sqf, "\t-");
+	seq_printf(sqf, "\t%05x\t%u/%u\n", session->cb->addr.pgn,
+		   session->pkt.done * 7, session->skb->len);
+	return 0;
+}
+
+static int j1939tp_proc_show(struct seq_file *sqf, void *v)
+{
+	struct session *session;
+
+	seq_puts(sqf, "iface\tsrc\tdst\tpgn\tdone/total\n");
+	j1939_sessionlist_lock();
+	list_for_each_entry(session, &tp_sessionq, list)
+		j1939tp_proc_show_session(sqf, session);
+	list_for_each_entry(session, &tp_extsessionq, list)
+		j1939tp_proc_show_session(sqf, session);
+	j1939_sessionlist_unlock();
+	return 0;
+}
+
+static int j1939tp_proc_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, j1939tp_proc_show, NULL);
+}
+
+static const struct file_operations j1939tp_proc_ops = {
+	.owner = THIS_MODULE,
+	.open = j1939tp_proc_open,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.release = single_release,
+};
+
 static struct ctl_table canj1939_sysctl_table[] = {
 	{
 		.procname	= "transport_burst_count",
@@ -1399,11 +1446,15 @@ static struct ctl_table_header *sysctl_hdr;
 /* module init */
 int __init j1939tp_module_init(void)
 {
-	sysctl_hdr = register_net_sysctl(&init_net, "net/can-j1939",
-					 canj1939_sysctl_table);
-	if (!sysctl_hdr)
+	if (!proc_create("transport", 0444, j1939_procdir, &j1939tp_proc_ops))
 		return -ENOMEM;
 
+	sysctl_hdr = register_net_sysctl(&init_net, "net/can-j1939",
+					 canj1939_sysctl_table);
+	if (!sysctl_hdr) {
+		remove_proc_entry("transport", j1939_procdir);
+		return -ENOMEM;
+	}
 	return 0;
 }
 
@@ -1414,6 +1465,7 @@ void j1939tp_module_exit(void)
 	wake_up_all(&tp_wait);
 
 	unregister_net_sysctl_table(sysctl_hdr);
+	remove_proc_entry("transport", j1939_procdir);
 	j1939_sessionlist_lock();
 	list_for_each_entry_safe(session, saved, &tp_extsessionq, list) {
 		list_del_init(&session->list);