lists.openwall.net | lists / announce owl-users owl-dev john-users john-dev passwdqc-users yescrypt popa3d-users / oss-security kernel-hardening musl sabotage tlsify passwords / crypt-dev xvendor / Bugtraq Full-Disclosure linux-kernel linux-netdev linux-ext4 linux-hardening PHC | |
Open Source and information security mailing list archives
| ||
|
Date: Thu, 17 May 2007 17:59:48 -0700 From: "Paul E. McKenney" <paulmck@...ux.vnet.ibm.com> To: Urs Thuermann <urs@...ogud.escape.de> Cc: netdev@...r.kernel.org, Thomas Gleixner <tglx@...utronix.de>, Oliver Hartkopp <oliver.hartkopp@...kswagen.de>, Urs Thuermann <urs.thuermann@...kswagen.de> Subject: Re: [patch 2/7] CAN: Add PF_CAN core module On Wed, May 16, 2007 at 04:51:02PM +0200, Urs Thuermann wrote: > This patch adds the CAN core functionality but no protocols or drivers. > No protocol implementations are included here. They come as separate > patches. Protocol numbers are already in include/linux/can.h. Interesting! One question called out below -- why do call_rcu() on each piece of the struct dev_rcv_lists, instead of doing call_rcu() on the whole thing and having the RCU callback free up the pieces? Given that all the pieces are call_rcu()ed separately, there had better not be persistent pointers to the pieces, right? Doing it in one chunk would make the code a bit simpler and also reduce the RCU overhead a bit. Or am I missing something subtle here? Thanx, Paul > Signed-Off-By: Oliver Hartkopp <oliver.hartkopp@...kswagen.de> > Signed-Off-By: Urs Thuermann <urs.thuermann@...kswagen.de> > > --- > include/linux/can.h | 98 ++++ > include/linux/can/core.h | 88 +++ > net/Kconfig | 1 > net/Makefile | 1 > net/can/Kconfig | 25 + > net/can/Makefile | 6 > net/can/af_can.c | 1063 +++++++++++++++++++++++++++++++++++++++++++++++ > net/can/af_can.h | 122 +++++ > net/can/proc.c | 660 +++++++++++++++++++++++++++++ > 9 files changed, 2064 insertions(+) > > Index: linux-2.6.22-rc1-git4/include/linux/can.h > =================================================================== > --- /dev/null 1970-01-01 00:00:00.000000000 +0000 > +++ linux-2.6.22-rc1-git4/include/linux/can.h 2007-05-16 10:02:06.000000000 +0200 > @@ -0,0 +1,98 @@ > +/* > + * linux/can.h > + * > + * Definitions for CAN networklayer (socket addr / CAN frame / CAN filter) > + * > + * Authors: Oliver Hartkopp <oliver.hartkopp@...kswagen.de> > + * Urs Thuermann <urs.thuermann@...kswagen.de> > + * Copyright (c) 2002-2007 Volkswagen Group Electronic Research > + * All rights reserved. > + * > + * Send feedback to <socketcan-users@...ts.berlios.de> > + * > + */ > + > +#ifndef CAN_H > +#define CAN_H > + > +#include <linux/types.h> > +#include <linux/socket.h> > + > +/* controller area network (CAN) kernel definitions */ > + > +/* special address description flags for the CAN_ID */ > +#define CAN_EFF_FLAG 0x80000000U /* EFF/SFF is set in the MSB */ > +#define CAN_RTR_FLAG 0x40000000U /* remote transmission request */ > +#define CAN_ERR_FLAG 0x20000000U /* error frame */ > + > +/* valid bits in CAN ID for frame formats */ > +#define CAN_SFF_MASK 0x000007FFU /* standard frame format (SFF) */ > +#define CAN_EFF_MASK 0x1FFFFFFFU /* extended frame format (EFF) */ > +#define CAN_ERR_MASK 0x1FFFFFFFU /* omit EFF, RTR, ERR flags */ > + > +typedef __u32 canid_t; > +typedef __u32 can_err_mask_t; > + > +/** > + * struct can_frame - basic CAN frame structure > + * @can_id: the CAN ID of the frame and CAN_*_FLAG flags, see above. > + * @can_dlc: the data length field of the CAN frame > + * @data: the CAN frame payload. > + */ > +struct can_frame { > + canid_t can_id; /* 32 bit CAN_ID + EFF/RTR/ERR flags */ > + __u8 can_dlc; /* data length code: 0 .. 8 */ > + __u8 data[8] __attribute__((aligned(8))); > +}; > + > +/* particular protocols of the protocol family PF_CAN */ > +#define CAN_RAW 1 /* RAW sockets */ > +#define CAN_BCM 2 /* Broadcast Manager */ > +#define CAN_TP16 3 /* VAG Transport Protocol v1.6 */ > +#define CAN_TP20 4 /* VAG Transport Protocol v2.0 */ > +#define CAN_MCNET 5 /* Bosch MCNet */ > +#define CAN_ISOTP 6 /* ISO 15765-2 Transport Protocol */ > +#define CAN_BAP 7 /* VAG Bedien- und Anzeigeprotokoll */ > +#define CAN_NPROTO 8 > + > +#define SOL_CAN_BASE 100 > + > +/** > + * struct sockaddr_can - the sockaddr structure for CAN sockets > + * @can_family: address family number AF_CAN. > + * @can_ifindex: CAN network interface index. > + * @can_addr: transport protocol specific address, mostly CAN IDs. > + */ > +struct sockaddr_can { > + sa_family_t can_family; > + int can_ifindex; > + union { > + struct { canid_t rx_id, tx_id; } tp16; > + struct { canid_t rx_id, tx_id; } tp20; > + struct { canid_t rx_id, tx_id; } mcnet; > + struct { canid_t rx_id, tx_id; } isotp; > + struct { int lcu, type; } bap; > + } can_addr; > +}; > + > +/** > + * struct can_filter - CAN ID based filter in can_register(). > + * @can_id: relevant bits of CAN ID which are not masked out. > + * @can_mask: CAN mask (see description) > + * > + * Description: > + * A filter matches, when > + * > + * <received_can_id> & mask == can_id & mask > + * > + * The filter can be inverted (CAN_INV_FILTER bit set in can_id) or it can > + * filter for error frames (CAN_ERR_FLAG bit set in mask). > + */ > +struct can_filter { > + canid_t can_id; > + canid_t can_mask; > +}; > + > +#define CAN_INV_FILTER 0x20000000U /* to be set in can_filter.can_id */ > + > +#endif /* CAN_H */ > Index: linux-2.6.22-rc1-git4/include/linux/can/core.h > =================================================================== > --- /dev/null 1970-01-01 00:00:00.000000000 +0000 > +++ linux-2.6.22-rc1-git4/include/linux/can/core.h 2007-05-16 09:45:00.000000000 +0200 > @@ -0,0 +1,88 @@ > +/* > + * linux/can/core.h > + * > + * Protoypes and definitions for CAN protocol modules using the PF_CAN core > + * > + * Authors: Oliver Hartkopp <oliver.hartkopp@...kswagen.de> > + * Urs Thuermann <urs.thuermann@...kswagen.de> > + * Copyright (c) 2002-2007 Volkswagen Group Electronic Research > + * All rights reserved. > + * > + * Send feedback to <socketcan-users@...ts.berlios.de> > + * > + */ > + > +#ifndef CAN_CORE_H > +#define CAN_CORE_H > + > +#include <linux/can.h> > +#include <linux/skbuff.h> > +#include <linux/netdevice.h> > + > +#define CAN_VERSION "20070508" > + > +/* increment this number each time you change some user-space interface */ > +#define CAN_ABI_VERSION "8" > + > +#define CAN_VERSION_STRING "rev " CAN_VERSION " abi " CAN_ABI_VERSION > + > +#define DNAME(dev) ((dev) ? (dev)->name : "any") > + > +#define CAN_PROC_DIR "net/can" /* /proc/... */ > + > +/** > + * struct can_proto - CAN protocol structure > + * @type: type argument in socket() syscall, e.g. SOCK_DGRAM. > + * @protocol: protocol number in socket() syscall. > + * @capability: capability needed to open the socket, or -1 for no restriction. > + * @ops: pointer to struct proto_ops for sock->ops. > + * @prot: pointer to struct proto structure. > + */ > +struct can_proto { > + int type; > + int protocol; > + int capability; > + struct proto_ops *ops; > + struct proto *prot; > +}; > + > +/* function prototypes for the CAN networklayer core (af_can.c) */ > + > +extern int can_proto_register(struct can_proto *cp); > +extern int can_proto_unregister(struct can_proto *cp); > + > +extern int can_rx_register(struct net_device *dev, canid_t can_id, > + canid_t mask, > + void (*func)(struct sk_buff *, void *), > + void *data, char *ident); > + > +extern int can_rx_unregister(struct net_device *dev, canid_t can_id, > + canid_t mask, > + void (*func)(struct sk_buff *, void *), > + void *data); > + > +extern int can_dev_register(struct net_device *dev, > + void (*func)(unsigned long msg, void *), > + void *data); > + > +extern int can_dev_unregister(struct net_device *dev, > + void (*func)(unsigned long msg, void *), > + void *data); > + > +extern int can_send(struct sk_buff *skb, int loop); > + > +#ifdef CONFIG_CAN_DEBUG_CORE > +extern void can_debug_skb(struct sk_buff *skb); > +extern void can_debug_cframe(const char *msg, struct can_frame *cframe, ...); > +#define DBG(args...) (debug & 1 ? \ > + (printk(KERN_DEBUG "can-%s %s: ", \ > + IDENT, __func__), printk(args)) : 0) > +#define DBG_FRAME(args...) (debug & 2 ? can_debug_cframe(args) : 0) > +#define DBG_SKB(skb) (debug & 4 ? can_debug_skb(skb) : 0) > +#else > +#define DBG(args...) > +#define DBG_FRAME(args...) > +#define DBG_SKB(skb) > +#endif > + > +#endif /* CAN_CORE_H */ > Index: linux-2.6.22-rc1-git4/net/Kconfig > =================================================================== > --- linux-2.6.22-rc1-git4.orig/net/Kconfig 2007-05-16 09:44:54.000000000 +0200 > +++ linux-2.6.22-rc1-git4/net/Kconfig 2007-05-16 09:45:00.000000000 +0200 > @@ -210,6 +210,7 @@ > endmenu > > source "net/ax25/Kconfig" > +source "net/can/Kconfig" > source "net/irda/Kconfig" > source "net/bluetooth/Kconfig" > source "net/rxrpc/Kconfig" > Index: linux-2.6.22-rc1-git4/net/Makefile > =================================================================== > --- linux-2.6.22-rc1-git4.orig/net/Makefile 2007-05-16 09:44:54.000000000 +0200 > +++ linux-2.6.22-rc1-git4/net/Makefile 2007-05-16 09:45:00.000000000 +0200 > @@ -34,6 +34,7 @@ > obj-$(CONFIG_NETROM) += netrom/ > obj-$(CONFIG_ROSE) += rose/ > obj-$(CONFIG_AX25) += ax25/ > +obj-$(CONFIG_CAN) += can/ > obj-$(CONFIG_IRDA) += irda/ > obj-$(CONFIG_BT) += bluetooth/ > obj-$(CONFIG_SUNRPC) += sunrpc/ > Index: linux-2.6.22-rc1-git4/net/can/Kconfig > =================================================================== > --- /dev/null 1970-01-01 00:00:00.000000000 +0000 > +++ linux-2.6.22-rc1-git4/net/can/Kconfig 2007-05-16 10:01:14.000000000 +0200 > @@ -0,0 +1,25 @@ > +# > +# Controller Area Network (CAN) network layer core configuration > +# > + > +menuconfig CAN > + depends on NET > + tristate "CAN bus subsystem support" > + ---help--- > + Controller Area Network (CAN) is a slow (up to 1Mbit/s) serial > + communications protocol, which was developed by Bosch at > + 1991 mainly for automotive, but now widely used in marine > + (NMEA2000), industrial and medical applications. > + More information on the CAN network protocol family PF_CAN > + is contained in <Documentation/networking/can.txt>. > + > + If you want CAN support, you should say Y here and also to the > + specific driver for your controller(s) below. > + > +config CAN_DEBUG_CORE > + bool "CAN Core debugging messages" > + depends on CAN > + ---help--- > + Say Y here if you want the CAN core to produce a bunch of debug > + messages to the system log. Select this if you are having a > + problem with CAN support and want to see more of what is going on. > Index: linux-2.6.22-rc1-git4/net/can/Makefile > =================================================================== > --- /dev/null 1970-01-01 00:00:00.000000000 +0000 > +++ linux-2.6.22-rc1-git4/net/can/Makefile 2007-05-16 10:01:14.000000000 +0200 > @@ -0,0 +1,6 @@ > +# > +# Makefile for the Linux Controller Area Network core. > +# > + > +obj-$(CONFIG_CAN) += can.o > +can-objs := af_can.o proc.o > Index: linux-2.6.22-rc1-git4/net/can/af_can.c > =================================================================== > --- /dev/null 1970-01-01 00:00:00.000000000 +0000 > +++ linux-2.6.22-rc1-git4/net/can/af_can.c 2007-05-16 10:02:06.000000000 +0200 > @@ -0,0 +1,1063 @@ > +/* > + * af_can.c - Protocol family CAN core module > + * (used by different CAN protocol modules) > + * > + * Copyright (c) 2002-2007 Volkswagen Group Electronic Research > + * All rights reserved. > + * > + * Redistribution and use in source and binary forms, with or without > + * modification, are permitted provided that the following conditions > + * are met: > + * 1. Redistributions of source code must retain the above copyright > + * notice, this list of conditions, the following disclaimer and > + * the referenced file 'COPYING'. > + * 2. Redistributions in binary form must reproduce the above copyright > + * notice, this list of conditions and the following disclaimer in the > + * documentation and/or other materials provided with the distribution. > + * 3. Neither the name of Volkswagen nor the names of its contributors > + * may be used to endorse or promote products derived from this software > + * without specific prior written permission. > + * > + * Alternatively, provided that this notice is retained in full, this > + * software may be distributed under the terms of the GNU General > + * Public License ("GPL") version 2 as distributed in the 'COPYING' > + * file from the main directory of the linux kernel source. > + * > + * The provided data structures and external interfaces from this code > + * are not restricted to be used by modules with a GPL compatible license. > + * > + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS > + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT > + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR > + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT > + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, > + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT > + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, > + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY > + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT > + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE > + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH > + * DAMAGE. > + * > + * Send feedback to <socketcan-users@...ts.berlios.de> > + * > + */ > + > +#include <linux/module.h> > +#include <linux/slab.h> > +#include <linux/kmod.h> > +#include <linux/init.h> > +#include <linux/list.h> > +#include <linux/spinlock.h> > +#include <linux/rcupdate.h> > +#include <linux/socket.h> > +#include <linux/if_ether.h> > +#include <linux/if_arp.h> > +#include <linux/skbuff.h> > +#include <linux/net.h> > +#include <linux/netdevice.h> > +#include <linux/can.h> > +#include <linux/can/core.h> > +#include <net/sock.h> > +#include <asm/uaccess.h> > + > +#include "af_can.h" > + > +#define IDENT "core" > +static __initdata const char banner[] = > + KERN_INFO "can: controller area network core # " > + CAN_VERSION_STRING "\n"; > + > +MODULE_DESCRIPTION("Controller Area Network PF_CAN core"); > +MODULE_LICENSE("Dual BSD/GPL"); > +MODULE_AUTHOR("Urs Thuermann <urs.thuermann@...kswagen.de>, " > + "Oliver Hartkopp <oliver.hartkopp@...kswagen.de>"); > + > +MODULE_ALIAS_NETPROTO(PF_CAN); > + > +int stats_timer = 1; /* default: on */ > +module_param(stats_timer, int, S_IRUGO); > +MODULE_PARM_DESC(stats_timer, "enable timer for statistics (default:on)"); > + > +#ifdef CONFIG_CAN_DEBUG_CORE > +static int debug = 0; > +module_param(debug, int, S_IRUGO); > +MODULE_PARM_DESC(debug, "debug print mask: 1:debug, 2:frames, 4:skbs"); > +#endif > + > +struct notifier { > + struct list_head list; > + struct net_device *dev; > + void (*func)(unsigned long msg, void *data); > + void *data; > +}; > + > +static LIST_HEAD(notifier_list); > +static DEFINE_RWLOCK(notifier_lock); > + > +HLIST_HEAD(rx_dev_list); > +static struct dev_rcv_lists rx_alldev_list; > +static DEFINE_SPINLOCK(rcv_lists_lock); > + > +static struct kmem_cache *rcv_cache __read_mostly; > + > +/* table of registered CAN protocols */ > +static struct can_proto *proto_tab[CAN_NPROTO]; > + > +struct timer_list stattimer; /* timer for statistics update */ > +struct s_stats stats; /* packet statistics */ > +struct s_pstats pstats; /* receive list statistics */ > + > +/* > + * af_can socket functions > + */ > + > +static int can_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg) > +{ > + struct sock *sk = sock->sk; > + > + switch (cmd) { > + > + case SIOCGSTAMP: > + return sock_get_timestamp(sk, (struct timeval __user *)arg); > + > + default: > + return -ENOIOCTLCMD; > + } > +} > + > +static void can_sock_destruct(struct sock *sk) > +{ > + DBG("called for sock %p\n", sk); > + > + skb_queue_purge(&sk->sk_receive_queue); > + if (sk->sk_protinfo) > + kfree(sk->sk_protinfo); > +} > + > +static int can_create(struct socket *sock, int protocol) > +{ > + struct sock *sk; > + struct can_proto *cp; > + char module_name[sizeof("can-proto-000")]; > + int ret = 0; > + > + DBG("socket %p, type %d, proto %d\n", sock, sock->type, protocol); > + > + sock->state = SS_UNCONNECTED; > + > + if (protocol < 0 || protocol >= CAN_NPROTO) > + return -EINVAL; > + > + DBG("looking up proto %d in proto_tab[]\n", protocol); > + > + /* try to load protocol module, when CONFIG_KMOD is defined */ > + if (!proto_tab[protocol]) { > + sprintf(module_name, "can-proto-%d", protocol); > + ret = request_module(module_name); > + > + /* In case of error we only print a message but don't > + * return the error code immediately. Below we will > + * return -EPROTONOSUPPORT > + */ > + if (ret == -ENOSYS) > + printk(KERN_INFO "can: request_module(%s) not" > + " implemented.\n", module_name); > + else if (ret) > + printk(KERN_ERR "can: request_module(%s) failed\n", > + module_name); > + } > + > + /* check for success and correct type */ > + cp = proto_tab[protocol]; > + if (!cp || cp->type != sock->type) > + return -EPROTONOSUPPORT; > + > + if (cp->capability >= 0 && !capable(cp->capability)) > + return -EPERM; > + > + sock->ops = cp->ops; > + > + sk = sk_alloc(PF_CAN, GFP_KERNEL, cp->prot, 1); > + if (!sk) > + return -ENOMEM; > + > + sock_init_data(sock, sk); > + sk->sk_destruct = can_sock_destruct; > + > + DBG("created sock: %p\n", sk); > + > + if (sk->sk_prot->init) > + ret = sk->sk_prot->init(sk); > + > + if (ret) { > + /* release sk on errors */ > + sock_orphan(sk); > + sock_put(sk); > + } > + > + return ret; > +} > + > +/* > + * af_can tx path > + */ > + > +/** > + * can_send - transmit a CAN frame (optional with local loopback) > + * @skb: pointer to socket buffer with CAN frame in data section > + * @loop: loopback for listeners on local CAN sockets (recommended default!) > + * > + * Return: > + * 0 on success > + * -ENETDOWN when the selected interface is down > + * -ENOBUFS on full driver queue (see net_xmit_errno()) > + */ > +int can_send(struct sk_buff *skb, int loop) > +{ > + struct sock **tx_sk = (struct sock **)skb->cb; > + int err; > + > + if (skb->dev->type != ARPHRD_CAN) { > + kfree_skb(skb); > + return -EPERM; > + } > + > + if (loop) { > + /* local loopback of sent CAN frames (default) */ > + > + /* indication for the CAN driver: do loopback */ > + *tx_sk = skb->sk; > + > + /* > + * The reference to the originating sock may be also required > + * by the receiving socket to indicate (and ignore) his own > + * sent data. Example: can_raw sockopt CAN_RAW_RECV_OWN_MSGS > + */ > + > + /* interface not capabable to do the loopback itself? */ > + if (!(skb->dev->flags & IFF_LOOPBACK)) { > + struct sk_buff *newskb = skb_clone(skb, GFP_ATOMIC); > + > + /* perform the local loopback here */ > + newskb->protocol = htons(ETH_P_CAN); > + newskb->ip_summed = CHECKSUM_UNNECESSARY; > + netif_rx(newskb); > + } > + } else { > + /* indication for the CAN driver: no loopback required */ > + *tx_sk = NULL; > + } > + > + if (!(skb->dev->flags & IFF_UP)) > + return -ENETDOWN; > + > + /* send to netdevice */ > + err = dev_queue_xmit(skb); > + if (err > 0) > + err = net_xmit_errno(err); > + > + /* update statistics */ > + stats.tx_frames++; > + stats.tx_frames_delta++; > + > + return err; > +} > +EXPORT_SYMBOL(can_send); > + > +/* > + * af_can rx path > + */ > + > +static struct dev_rcv_lists *find_dev_rcv_lists(struct net_device *dev) > +{ > + struct dev_rcv_lists *d; > + struct hlist_node *n; > + > + /* > + * find receive list for this device > + * > + * The hlist_for_each_entry*() macros curse through the list > + * using the pointer variable n and set d to the containing > + * struct in each list iteration. Therefore, after list > + * iteration, d is unmodified when the list is empty, and it > + * points to last list element, when the list is non-empty > + * but no match in the loop body is found. I.e. d is *not* > + * NULL when no match is found. We can, however, use the > + * cursor variable n to decide if a match was found. > + */ > + > + hlist_for_each_entry(d, n, &rx_dev_list, list) { > + if (d->dev == dev) > + break; > + } > + > + return n ? d : NULL; > +} > + > +static struct hlist_head *find_rcv_list(canid_t *can_id, canid_t *mask, > + struct dev_rcv_lists *d) > +{ > + canid_t inv = *can_id & CAN_INV_FILTER; /* save flag before masking */ > + > + /* filter error frames */ > + if (*mask & CAN_ERR_FLAG) { > + /* clear CAN_ERR_FLAG in list entry */ > + *mask &= CAN_ERR_MASK; > + return &d->rx_err; > + } > + > + /* ensure valid values in can_mask */ > + if (*mask & CAN_EFF_FLAG) > + *mask &= (CAN_EFF_MASK | CAN_EFF_FLAG | CAN_RTR_FLAG); > + else > + *mask &= (CAN_SFF_MASK | CAN_RTR_FLAG); > + > + /* reduce condition testing at receive time */ > + *can_id &= *mask; > + > + /* inverse can_id/can_mask filter */ > + if (inv) > + return &d->rx_inv; > + > + /* mask == 0 => no condition testing at receive time */ > + if (!(*mask)) > + return &d->rx_all; > + > + /* use extra filterset for the subscription of exactly *ONE* can_id */ > + if (*can_id & CAN_EFF_FLAG) { > + if (*mask == (CAN_EFF_MASK | CAN_EFF_FLAG)) { > + /* RFC: a use-case for hash-tables in the future? */ > + return &d->rx_eff; > + } > + } else { > + if (*mask == CAN_SFF_MASK) > + return &d->rx_sff[*can_id]; > + } > + > + /* default: filter via can_id/can_mask */ > + return &d->rx_fil; > +} > + > +/** > + * can_rx_register - subscribe CAN frames from a specific interface > + * @dev: pointer to netdevice (NULL => subcribe from 'all' CAN devices list) > + * @can_id: CAN identifier (see description) > + * @mask: CAN mask (see description) > + * @func: callback function on filter match > + * @data: returned parameter for callback function > + * @ident: string for calling module indentification > + * > + * Description: > + * Invokes the callback function with the received sk_buff and the given > + * parameter 'data' on a matching receive filter. A filter matches, when > + * > + * <received_can_id> & mask == can_id & mask > + * > + * The filter can be inverted (CAN_INV_FILTER bit set in can_id) or it can > + * filter for error frames (CAN_ERR_FLAG bit set in mask). > + * > + * Return: > + * 0 on success > + * -ENOMEM on missing cache mem to create subscription entry > + * -ENODEV unknown device > + */ > +int can_rx_register(struct net_device *dev, canid_t can_id, canid_t mask, > + void (*func)(struct sk_buff *, void *), void *data, > + char *ident) > +{ > + struct receiver *r; > + struct hlist_head *rl; > + struct dev_rcv_lists *d; > + int ret = 0; > + > + /* insert new receiver (dev,canid,mask) -> (func,data) */ > + > + DBG("dev %p, id %03X, mask %03X, callback %p, data %p, ident %s\n", > + dev, can_id, mask, func, data, ident); > + > + r = kmem_cache_alloc(rcv_cache, GFP_KERNEL); > + if (!r) > + return -ENOMEM; > + > + spin_lock_bh(&rcv_lists_lock); > + > + d = find_dev_rcv_lists(dev); > + if (d) { > + rl = find_rcv_list(&can_id, &mask, d); > + > + r->can_id = can_id; > + r->mask = mask; > + r->matches = 0; > + r->func = func; > + r->data = data; > + r->ident = ident; > + > + hlist_add_head_rcu(&r->list, rl); > + d->entries++; > + > + pstats.rcv_entries++; > + if (pstats.rcv_entries_max < pstats.rcv_entries) > + pstats.rcv_entries_max = pstats.rcv_entries; > + } else { > + DBG("receive list not found for dev %s, id %03X, mask %03X\n", > + DNAME(dev), can_id, mask); > + kmem_cache_free(rcv_cache, r); > + ret = -ENODEV; > + } > + > + spin_unlock_bh(&rcv_lists_lock); > + > + return ret; > +} > +EXPORT_SYMBOL(can_rx_register); > + > +static void can_rcv_lists_delete(struct rcu_head *rp) > +{ > + struct dev_rcv_lists *d = container_of(rp, struct dev_rcv_lists, rcu); > + > + kfree(d); > +} > + > +static void can_rx_delete(struct rcu_head *rp) > +{ > + struct receiver *r = container_of(rp, struct receiver, rcu); > + > + kmem_cache_free(rcv_cache, r); > +} > + > +static void can_rx_delete_all(struct hlist_head *rl) > +{ > + struct receiver *r; > + struct hlist_node *n; > + > + hlist_for_each_entry_rcu(r, n, rl, list) { > + hlist_del_rcu(&r->list); > + call_rcu(&r->rcu, can_rx_delete); > + } > +} > + > +/** > + * can_rx_unregister - unsubscribe CAN frames from a specific interface > + * @dev: pointer to netdevice (NULL => unsubcribe from 'all' CAN devices list) > + * @can_id: CAN identifier > + * @mask: CAN mask > + * @func: callback function on filter match > + * @data: returned parameter for callback function > + * > + * Description: > + * Removes subscription entry depending on given (subscription) values. > + * > + * Return: > + * 0 on success > + * -EINVAL on missing subscription entry > + * -ENODEV unknown device > + */ > +int can_rx_unregister(struct net_device *dev, canid_t can_id, canid_t mask, > + void (*func)(struct sk_buff *, void *), void *data) > +{ > + struct receiver *r = NULL; > + struct hlist_head *rl; > + struct hlist_node *next; > + struct dev_rcv_lists *d; > + int ret = 0; > + > + DBG("dev %p, id %03X, mask %03X, callback %p, data %p\n", > + dev, can_id, mask, func, data); > + > + spin_lock_bh(&rcv_lists_lock); > + > + d = find_dev_rcv_lists(dev); > + if (!d) { > + DBG("receive list not found for dev %s, id %03X, mask %03X\n", > + DNAME(dev), can_id, mask); > + ret = -ENODEV; > + goto out; > + } > + > + rl = find_rcv_list(&can_id, &mask, d); > + > + /* > + * Search the receiver list for the item to delete. This should > + * exist, since no receiver may be unregistered that hasn't > + * been registered before. > + */ > + > + hlist_for_each_entry(r, next, rl, list) { > + if (r->can_id == can_id && r->mask == mask > + && r->func == func && r->data == data) > + break; > + } > + > + /* > + * Check for bug in CAN protocol implementations: > + * If no matching list item was found, the list cursor variable next > + * will be NULL, while r will point to the last item of the list. > + */ > + > + if (!next) { > + DBG("receive list entry not found for " > + "dev %s, id %03X, mask %03X\n", DNAME(dev), can_id, mask); > + ret = -EINVAL; > + r = NULL; > + goto out; > + } > + > + hlist_del_rcu(&r->list); > + d->entries--; > + > + if (pstats.rcv_entries > 0) > + pstats.rcv_entries--; > + > + out: > + spin_unlock_bh(&rcv_lists_lock); > + > + /* schedule the receiver item for deletion */ > + if (r) > + call_rcu(&r->rcu, can_rx_delete); > + > + return ret; > +} > +EXPORT_SYMBOL(can_rx_unregister); > + > +static inline void deliver(struct sk_buff *skb, struct receiver *r) > +{ > + struct sk_buff *clone = skb_clone(skb, GFP_ATOMIC); > + > + DBG("skbuff %p cloned to %p\n", skb, clone); > + if (clone) { > + r->func(clone, r->data); > + r->matches++; > + } > +} > + > +static int can_rcv_filter(struct dev_rcv_lists *d, struct sk_buff *skb) > +{ > + struct receiver *r; > + struct hlist_node *n; > + int matches = 0; > + struct can_frame *cf = (struct can_frame*)skb->data; > + canid_t can_id = cf->can_id; > + > + if (d->entries == 0) > + return 0; > + > + if (can_id & CAN_ERR_FLAG) { > + /* check for error frame entries only */ > + hlist_for_each_entry_rcu(r, n, &d->rx_err, list) { > + if (can_id & r->mask) { > + DBG("match on rx_err skbuff %p\n", skb); > + deliver(skb, r); > + matches++; > + } > + } > + return matches; > + } > + > + /* check for unfiltered entries */ > + hlist_for_each_entry_rcu(r, n, &d->rx_all, list) { > + DBG("match on rx_all skbuff %p\n", skb); > + deliver(skb, r); > + matches++; > + } > + > + /* check for can_id/mask entries */ > + hlist_for_each_entry_rcu(r, n, &d->rx_fil, list) { > + if ((can_id & r->mask) == r->can_id) { > + DBG("match on rx_fil skbuff %p\n", skb); > + deliver(skb, r); > + matches++; > + } > + } > + > + /* check for inverted can_id/mask entries */ > + hlist_for_each_entry_rcu(r, n, &d->rx_inv, list) { > + if ((can_id & r->mask) != r->can_id) { > + DBG("match on rx_inv skbuff %p\n", skb); > + deliver(skb, r); > + matches++; > + } > + } > + > + /* check CAN_ID specific entries */ > + if (can_id & CAN_EFF_FLAG) { > + hlist_for_each_entry_rcu(r, n, &d->rx_eff, list) { > + if (r->can_id == can_id) { > + DBG("match on rx_eff skbuff %p\n", skb); > + deliver(skb, r); > + matches++; > + } > + } > + } else { > + can_id &= CAN_SFF_MASK; > + hlist_for_each_entry_rcu(r, n, &d->rx_sff[can_id], list) { > + DBG("match on rx_sff skbuff %p\n", skb); > + deliver(skb, r); > + matches++; > + } > + } > + > + return matches; > +} > + > +static int can_rcv(struct sk_buff *skb, struct net_device *dev, > + struct packet_type *pt, struct net_device *orig_dev) > +{ > + struct dev_rcv_lists *d; > + int matches; > + > + DBG("received skbuff on device %s, ptype %04x\n", > + dev->name, ntohs(pt->type)); > + DBG_SKB(skb); > + DBG_FRAME("af_can: can_rcv: received CAN frame", > + (struct can_frame *)skb->data); > + > + if (dev->type != ARPHRD_CAN) { > + kfree_skb(skb); > + return 0; > + } > + > + /* update statistics */ > + stats.rx_frames++; > + stats.rx_frames_delta++; > + > + rcu_read_lock(); > + > + /* deliver the packet to sockets listening on all devices */ > + matches = can_rcv_filter(&rx_alldev_list, skb); > + > + /* find receive list for this device */ > + d = find_dev_rcv_lists(dev); > + if (d) > + matches += can_rcv_filter(d, skb); > + > + rcu_read_unlock(); > + > + /* free the skbuff allocated by the netdevice driver */ > + DBG("freeing skbuff %p\n", skb); > + kfree_skb(skb); > + > + if (matches > 0) { > + stats.matches++; > + stats.matches_delta++; > + } > + > + return 0; > +} > + > +/* > + * af_can debugging stuff > + */ > + > +#ifdef CONFIG_CAN_DEBUG_CORE > + > +#define DBG_BSIZE 1024 > + > +/** > + * can_debug_cframe - print CAN frame > + * @msg: pointer to message printed before the given CAN frame > + * @cf: pointer to CAN frame > + */ > +void can_debug_cframe(const char *msg, struct can_frame *cf, ...) > +{ > + va_list ap; > + int len; > + int dlc, i; > + char *buf; > + > + buf = kmalloc(DBG_BSIZE, GFP_ATOMIC); > + if (!buf) > + return; > + > + len = sprintf(buf, KERN_DEBUG); > + va_start(ap, cf); > + len += snprintf(buf + len, DBG_BSIZE - 64, msg, ap); > + buf[len++] = ':'; > + buf[len++] = ' '; > + va_end(ap); > + > + dlc = cf->can_dlc; > + if (dlc > 8) > + dlc = 8; > + > + if (cf->can_id & CAN_EFF_FLAG) > + len += sprintf(buf + len, "<%08X> [%X] ", > + cf->can_id & CAN_EFF_MASK, dlc); > + else > + len += sprintf(buf + len, "<%03X> [%X] ", > + cf->can_id & CAN_SFF_MASK, dlc); > + > + for (i = 0; i < dlc; i++) > + len += sprintf(buf + len, "%02X ", cf->data[i]); > + > + if (cf->can_id & CAN_RTR_FLAG) > + len += sprintf(buf + len, "(RTR)"); > + > + buf[len++] = '\n'; > + buf[len] = '\0'; > + printk(buf); > + kfree(buf); > +} > +EXPORT_SYMBOL(can_debug_cframe); > + > +/** > + * can_debug_skb - print socket buffer content to kernel log > + * @skb: pointer to socket buffer > + */ > +void can_debug_skb(struct sk_buff *skb) > +{ > + int len, nbytes, i; > + char *buf; > + > + buf = kmalloc(DBG_BSIZE, GFP_ATOMIC); > + if (!buf) > + return; > + > + len = sprintf(buf, > + KERN_DEBUG " skbuff at %p, dev: %d, proto: %04x\n" > + KERN_DEBUG " users: %d, dataref: %d, nr_frags: %d, " > + "h,d,t,e,l: %p %+d %+d %+d, %d", > + skb, skb->dev ? skb->dev->ifindex : -1, > + ntohs(skb->protocol), > + atomic_read(&skb->users), > + atomic_read(&(skb_shinfo(skb)->dataref)), > + skb_shinfo(skb)->nr_frags, > + skb->head, skb->data - skb->head, > + skb->tail - skb->head, skb->end - skb->head, skb->len); > + nbytes = skb->end - skb->head; > + for (i = 0; i < nbytes; i++) { > + if (i % 16 == 0) > + len += sprintf(buf + len, "\n" KERN_DEBUG " "); > + if (len < DBG_BSIZE - 16) { > + len += sprintf(buf + len, " %02x", skb->head[i]); > + } else { > + len += sprintf(buf + len, "..."); > + break; > + } > + } > + buf[len++] = '\n'; > + buf[len] = '\0'; > + printk(buf); > + kfree(buf); > +} > +EXPORT_SYMBOL(can_debug_skb); > + > +#endif > + > +/* > + * af_can protocol functions > + */ > + > +/** > + * can_proto_register - register CAN transport protocol > + * @cp: pointer to CAN protocol structure > + * > + * Return: > + * 0 on success > + * -EINVAL invalid (out of range) protocol number > + * -EBUSY protocol already in use > + * -ENOBUF if proto_register() fails > + */ > +int can_proto_register(struct can_proto *cp) > +{ > + int proto = cp->protocol; > + int err = 0; > + > + if (proto < 0 || proto >= CAN_NPROTO) { > + printk(KERN_ERR "can: protocol number %d out " > + "of range\n", proto); > + return -EINVAL; > + } > + if (proto_tab[proto]) { > + printk(KERN_ERR "can: protocol %d already " > + "registered\n", proto); > + return -EBUSY; > + } > + > + err = proto_register(cp->prot, 0); > + if (err < 0) > + return err; > + > + proto_tab[proto] = cp; > + > + /* use generic ioctl function if the module doesn't bring its own */ > + if (!cp->ops->ioctl) > + cp->ops->ioctl = can_ioctl; > + > + return err; > +} > +EXPORT_SYMBOL(can_proto_register); > + > +/** > + * can_proto_unregister - unregister CAN transport protocol > + * @cp: pointer to CAN protocol structure > + * > + * Return: > + * 0 on success > + * -ESRCH protocol number was not registered > + */ > +int can_proto_unregister(struct can_proto *cp) > +{ > + int proto = cp->protocol; > + > + if (!proto_tab[proto]) { > + printk(KERN_ERR "can: protocol %d is not registered\n", proto); > + return -ESRCH; > + } > + proto_unregister(cp->prot); > + proto_tab[proto] = NULL; > + > + return 0; > +} > +EXPORT_SYMBOL(can_proto_unregister); > + > +/** > + * can_dev_register - subscribe notifier for CAN device status changes > + * @dev: pointer to netdevice > + * @func: callback function on status change > + * @data: returned parameter for callback function > + * > + * Description: > + * Invokes the callback function with the status 'msg' and the given > + * parameter 'data' on a status change of the given CAN network device. > + * > + * Return: > + * 0 on success > + * -ENOMEM on missing mem to create subscription entry > + * -ENODEV unknown device > + */ > +int can_dev_register(struct net_device *dev, > + void (*func)(unsigned long msg, void *), void *data) > +{ > + struct notifier *n; > + > + DBG("called for %s\n", dev->name); > + > + if (!dev || dev->type != ARPHRD_CAN) > + return -ENODEV; > + > + n = kmalloc(sizeof(*n), GFP_KERNEL); > + if (!n) > + return -ENOMEM; > + > + n->dev = dev; > + n->func = func; > + n->data = data; > + > + write_lock(¬ifier_lock); > + list_add(&n->list, ¬ifier_list); > + write_unlock(¬ifier_lock); > + > + return 0; > +} > +EXPORT_SYMBOL(can_dev_register); > + > +/** > + * can_dev_unregister - unsubscribe notifier for CAN device status changes > + * @dev: pointer to netdevice > + * @func: callback function on filter match > + * @data: returned parameter for callback function > + * > + * Description: > + * Removes subscription entry depending on given (subscription) values. > + * > + * Return: > + * 0 on success > + * -EINVAL on missing subscription entry > + */ > +int can_dev_unregister(struct net_device *dev, > + void (*func)(unsigned long msg, void *), void *data) > +{ > + struct notifier *n, *next; > + int ret = -EINVAL; > + > + DBG("called for %s\n", dev->name); > + > + write_lock(¬ifier_lock); > + list_for_each_entry_safe(n, next, ¬ifier_list, list) { > + if (n->dev == dev && n->func == func && n->data == data) { > + list_del(&n->list); > + kfree(n); > + ret = 0; > + break; > + } > + } > + write_unlock(¬ifier_lock); > + > + return ret; > +} > +EXPORT_SYMBOL(can_dev_unregister); > + > +static int can_notifier(struct notifier_block *nb, > + unsigned long msg, void *data) > +{ > + struct net_device *dev = (struct net_device *)data; > + struct notifier *n; > + struct dev_rcv_lists *d; > + int i; > + > + DBG("called for %s, msg = %lu\n", dev->name, msg); > + > + if (dev->type != ARPHRD_CAN) > + return NOTIFY_DONE; > + > + switch (msg) { > + > + case NETDEV_REGISTER: > + > + /* > + * create new dev_rcv_lists for this device > + * > + * N.B. zeroing the struct is the correct initialization > + * for the embedded hlist_head structs. > + * Another list type, e.g. list_head, would require > + * explicit initialization. > + */ > + > + DBG("creating new dev_rcv_lists for %s\n", dev->name); > + > + d = kzalloc(sizeof(*d), > + in_interrupt() ? GFP_ATOMIC : GFP_KERNEL); > + if (!d) { > + printk(KERN_ERR "can: allocation of receive " > + "list failed\n"); > + return NOTIFY_DONE; > + } > + d->dev = dev; > + > + spin_lock_bh(&rcv_lists_lock); > + hlist_add_head_rcu(&d->list, &rx_dev_list); > + spin_unlock_bh(&rcv_lists_lock); > + > + break; > + > + case NETDEV_UNREGISTER: > + spin_lock_bh(&rcv_lists_lock); > + > + d = find_dev_rcv_lists(dev); > + if (d) { > + hlist_del_rcu(&d->list); > + > + /* remove all receivers hooked at this netdevice */ > + can_rx_delete_all(&d->rx_err); > + can_rx_delete_all(&d->rx_all); > + can_rx_delete_all(&d->rx_fil); > + can_rx_delete_all(&d->rx_inv); > + can_rx_delete_all(&d->rx_eff); > + for (i = 0; i < 2048; i++) > + can_rx_delete_all(&d->rx_sff[i]); Urk!!! That could be one heaping pile of callbacks!!! Why not just do the single call_rcu() below, and take the struct rcu_dev_lists apart in the RCU callback? Unless I am missing something, the can_rx_delete_all() function feeds all the pieces to call_rcu() anyway, so there should not be other CPUs digging through this stuff. Or am I missing something subtle here? > + } else > + printk(KERN_ERR "can: notifier: receive list not " > + "found for dev %s\n", dev->name); > + > + spin_unlock_bh(&rcv_lists_lock); > + > + if (d) > + call_rcu(&d->rcu, can_rcv_lists_delete); > + > + break; > + } > + > + read_lock(¬ifier_lock); > + list_for_each_entry(n, ¬ifier_list, list) { > + if (n->dev == dev) > + n->func(msg, n->data); > + } > + read_unlock(¬ifier_lock); > + > + return NOTIFY_DONE; > +} > + > +/* > + * af_can module init/exit functions > + */ > + > +static struct packet_type can_packet = { > + .type = __constant_htons(ETH_P_CAN), > + .dev = NULL, > + .func = can_rcv, > +}; > + > +static struct net_proto_family can_family_ops = { > + .family = PF_CAN, > + .create = can_create, > + .owner = THIS_MODULE, > +}; > + > +/* notifier block for netdevice event */ > +static struct notifier_block can_netdev_notifier = { > + .notifier_call = can_notifier, > +}; > + > +static __init int can_init(void) > +{ > + printk(banner); > + > + rcv_cache = kmem_cache_create("can_receiver", sizeof(struct receiver), > + 0, 0, NULL, NULL); > + if (!rcv_cache) > + return -ENOMEM; > + > + /* > + * Insert struct dev_rcv_lists for reception on all devices. > + * This struct is zero initialized which is correct for the > + * embedded hlist heads, the dev pointer, and the entries counter. > + */ > + > + spin_lock_bh(&rcv_lists_lock); > + hlist_add_head_rcu(&rx_alldev_list.list, &rx_dev_list); > + spin_unlock_bh(&rcv_lists_lock); > + > + if (stats_timer) { > + /* the statistics are updated every second (timer triggered) */ > + init_timer(&stattimer); > + stattimer.function = can_stat_update; > + stattimer.data = 0; > + /* update every second */ > + stattimer.expires = jiffies + HZ; > + /* start statistics timer */ > + add_timer(&stattimer); > + } else > + stattimer.function = NULL; > + > + /* procfs init */ > + can_init_proc(); > + > + /* protocol register */ > + sock_register(&can_family_ops); > + register_netdevice_notifier(&can_netdev_notifier); > + dev_add_pack(&can_packet); > + > + return 0; > +} > + > +static __exit void can_exit(void) > +{ > + struct dev_rcv_lists *d; > + struct hlist_node *n, *next; > + > + if (stats_timer) > + del_timer(&stattimer); > + > + /* procfs remove */ > + can_remove_proc(); > + > + /* protocol unregister */ > + dev_remove_pack(&can_packet); > + unregister_netdevice_notifier(&can_netdev_notifier); > + sock_unregister(PF_CAN); > + > + /* remove rx_dev_list */ > + spin_lock_bh(&rcv_lists_lock); > + hlist_del(&rx_alldev_list.list); > + hlist_for_each_entry_safe(d, n, next, &rx_dev_list, list) { > + hlist_del(&d->list); > + kfree(d); > + } > + spin_unlock_bh(&rcv_lists_lock); > + > + kmem_cache_destroy(rcv_cache); > +} > + > +module_init(can_init); > +module_exit(can_exit); > Index: linux-2.6.22-rc1-git4/net/can/af_can.h > =================================================================== > --- /dev/null 1970-01-01 00:00:00.000000000 +0000 > +++ linux-2.6.22-rc1-git4/net/can/af_can.h 2007-05-16 10:02:06.000000000 +0200 > @@ -0,0 +1,122 @@ > +/* > + * Copyright (c) 2002-2007 Volkswagen Group Electronic Research > + * All rights reserved. > + * > + * Redistribution and use in source and binary forms, with or without > + * modification, are permitted provided that the following conditions > + * are met: > + * 1. Redistributions of source code must retain the above copyright > + * notice, this list of conditions, the following disclaimer and > + * the referenced file 'COPYING'. > + * 2. Redistributions in binary form must reproduce the above copyright > + * notice, this list of conditions and the following disclaimer in the > + * documentation and/or other materials provided with the distribution. > + * 3. Neither the name of Volkswagen nor the names of its contributors > + * may be used to endorse or promote products derived from this software > + * without specific prior written permission. > + * > + * Alternatively, provided that this notice is retained in full, this > + * software may be distributed under the terms of the GNU General > + * Public License ("GPL") version 2 as distributed in the 'COPYING' > + * file from the main directory of the linux kernel source. > + * > + * The provided data structures and external interfaces from this code > + * are not restricted to be used by modules with a GPL compatible license. > + * > + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS > + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT > + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR > + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT > + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, > + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT > + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, > + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY > + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT > + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE > + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH > + * DAMAGE. > + * > + * Send feedback to <socketcan-users@...ts.berlios.de> > + * > + */ > + > +#ifndef AF_CAN_H > +#define AF_CAN_H > + > +#include <linux/skbuff.h> > +#include <linux/netdevice.h> > +#include <linux/list.h> > +#include <linux/rcupdate.h> > +#include <linux/can.h> > + > +/* af_can rx dispatcher structures */ > + > +struct receiver { > + struct hlist_node list; > + struct rcu_head rcu; > + canid_t can_id; > + canid_t mask; > + unsigned long matches; > + void (*func)(struct sk_buff *, void *); > + void *data; > + char *ident; > +}; > + > +struct dev_rcv_lists { > + struct hlist_node list; > + struct rcu_head rcu; > + struct net_device *dev; > + struct hlist_head rx_err; > + struct hlist_head rx_all; > + struct hlist_head rx_fil; > + struct hlist_head rx_inv; > + struct hlist_head rx_sff[0x800]; > + struct hlist_head rx_eff; > + int entries; > +}; > + > +/* statistic structures */ > + > +struct s_stats { > + unsigned long jiffies_init; > + > + unsigned long rx_frames; > + unsigned long tx_frames; > + unsigned long matches; > + > + unsigned long total_rx_rate; > + unsigned long total_tx_rate; > + unsigned long total_rx_match_ratio; > + > + unsigned long current_rx_rate; > + unsigned long current_tx_rate; > + unsigned long current_rx_match_ratio; > + > + unsigned long max_rx_rate; > + unsigned long max_tx_rate; > + unsigned long max_rx_match_ratio; > + > + unsigned long rx_frames_delta; > + unsigned long tx_frames_delta; > + unsigned long matches_delta; > +}; /* can be reset e.g. by can_init_stats() */ > + > +struct s_pstats { > + unsigned long stats_reset; > + unsigned long user_reset; > + unsigned long rcv_entries; > + unsigned long rcv_entries_max; > +}; /* persistent statistics */ > + > +/* function prototypes for the CAN networklayer procfs (proc.c) */ > +extern void can_init_proc(void); > +extern void can_remove_proc(void); > +extern void can_stat_update(unsigned long data); > + > +/* structures and variables from af_can.c needed in proc.c for reading */ > +extern struct timer_list stattimer; /* timer for statistics update */ > +extern struct s_stats stats; /* packet statistics */ > +extern struct s_pstats pstats; /* receive list statistics */ > +extern struct hlist_head rx_dev_list; /* rx dispatcher structures */ > + > +#endif /* AF_CAN_H */ > Index: linux-2.6.22-rc1-git4/net/can/proc.c > =================================================================== > --- /dev/null 1970-01-01 00:00:00.000000000 +0000 > +++ linux-2.6.22-rc1-git4/net/can/proc.c 2007-05-16 10:02:06.000000000 +0200 > @@ -0,0 +1,660 @@ > +/* > + * proc.c - procfs support for Protocol family CAN core module > + * > + * Copyright (c) 2002-2007 Volkswagen Group Electronic Research > + * All rights reserved. > + * > + * Redistribution and use in source and binary forms, with or without > + * modification, are permitted provided that the following conditions > + * are met: > + * 1. Redistributions of source code must retain the above copyright > + * notice, this list of conditions, the following disclaimer and > + * the referenced file 'COPYING'. > + * 2. Redistributions in binary form must reproduce the above copyright > + * notice, this list of conditions and the following disclaimer in the > + * documentation and/or other materials provided with the distribution. > + * 3. Neither the name of Volkswagen nor the names of its contributors > + * may be used to endorse or promote products derived from this software > + * without specific prior written permission. > + * > + * Alternatively, provided that this notice is retained in full, this > + * software may be distributed under the terms of the GNU General > + * Public License ("GPL") version 2 as distributed in the 'COPYING' > + * file from the main directory of the linux kernel source. > + * > + * The provided data structures and external interfaces from this code > + * are not restricted to be used by modules with a GPL compatible license. > + * > + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS > + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT > + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR > + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT > + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, > + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT > + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, > + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY > + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT > + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE > + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH > + * DAMAGE. > + * > + * Send feedback to <socketcan-users@...ts.berlios.de> > + * > + */ > + > +#include <linux/module.h> > +#include <linux/proc_fs.h> > +#include <linux/list.h> > +#include <linux/rcupdate.h> > +#include <linux/can/core.h> > + > +#include "af_can.h" > + > +/* > + * proc filenames for the PF_CAN core > + */ > + > +#define CAN_PROC_VERSION "version" > +#define CAN_PROC_STATS "stats" > +#define CAN_PROC_RESET_STATS "reset_stats" > +#define CAN_PROC_RCVLIST_ALL "rcvlist_all" > +#define CAN_PROC_RCVLIST_FIL "rcvlist_fil" > +#define CAN_PROC_RCVLIST_INV "rcvlist_inv" > +#define CAN_PROC_RCVLIST_SFF "rcvlist_sff" > +#define CAN_PROC_RCVLIST_EFF "rcvlist_eff" > +#define CAN_PROC_RCVLIST_ERR "rcvlist_err" > + > +static struct proc_dir_entry *can_dir = NULL; > +static struct proc_dir_entry *pde_version = NULL; > +static struct proc_dir_entry *pde_stats = NULL; > +static struct proc_dir_entry *pde_reset_stats = NULL; > +static struct proc_dir_entry *pde_rcvlist_all = NULL; > +static struct proc_dir_entry *pde_rcvlist_fil = NULL; > +static struct proc_dir_entry *pde_rcvlist_inv = NULL; > +static struct proc_dir_entry *pde_rcvlist_sff = NULL; > +static struct proc_dir_entry *pde_rcvlist_eff = NULL; > +static struct proc_dir_entry *pde_rcvlist_err = NULL; > + > +static int user_reset = 0; > + > +/* > + * af_can statistics stuff > + */ > + > +static void can_init_stats(void) > +{ > + /* > + * This memset function is called from a timer context (when > + * stattimer is active which is the default) OR in a process > + * context (reading the proc_fs when stattimer is disabled). > + */ > + memset(&stats, 0, sizeof(stats)); > + stats.jiffies_init = jiffies; > + > + pstats.stats_reset++; > + > + if (user_reset) { > + user_reset = 0; > + pstats.user_reset++; > + } > +} > + > +static unsigned long calc_rate(unsigned long oldjif, unsigned long newjif, > + unsigned long count) > +{ > + unsigned long ret = 0; > + > + if (oldjif == newjif) > + return 0; > + > + /* see can_rcv() - this should NEVER happen! */ > + if (count > (ULONG_MAX / HZ)) { > + printk(KERN_ERR "can: calc_rate: count exceeded! %ld\n", > + count); > + return 99999999; > + } > + > + ret = (count * HZ) / (newjif - oldjif); > + > + return ret; > +} > + > +void can_stat_update(unsigned long data) > +{ > + unsigned long j = jiffies; /* snapshot */ > + > + /* restart counting in timer context on user request */ > + if (user_reset) > + can_init_stats(); > + > + /* restart counting on jiffies overflow */ > + if (j < stats.jiffies_init) > + can_init_stats(); > + > + /* stats.rx_frames is the definitively max. statistic value */ > + > + /* prevent overflow in calc_rate() */ > + if (stats.rx_frames > (ULONG_MAX / HZ)) > + can_init_stats(); > + > + /* matches overflow - very improbable */ > + if (stats.matches > (ULONG_MAX / 100)) > + can_init_stats(); > + > + /* calc total values */ > + if (stats.rx_frames) > + stats.total_rx_match_ratio = (stats.matches * 100) / > + stats.rx_frames; > + > + stats.total_tx_rate = calc_rate(stats.jiffies_init, j, > + stats.tx_frames); > + stats.total_rx_rate = calc_rate(stats.jiffies_init, j, > + stats.rx_frames); > + > + /* calc current values */ > + if (stats.rx_frames_delta) > + stats.current_rx_match_ratio = > + (stats.matches_delta * 100) / stats.rx_frames_delta; > + > + stats.current_tx_rate = calc_rate(0, HZ, stats.tx_frames_delta); > + stats.current_rx_rate = calc_rate(0, HZ, stats.rx_frames_delta); > + > + /* check / update maximum values */ > + if (stats.max_tx_rate < stats.current_tx_rate) > + stats.max_tx_rate = stats.current_tx_rate; > + > + if (stats.max_rx_rate < stats.current_rx_rate) > + stats.max_rx_rate = stats.current_rx_rate; > + > + if (stats.max_rx_match_ratio < stats.current_rx_match_ratio) > + stats.max_rx_match_ratio = stats.current_rx_match_ratio; > + > + /* clear values for 'current rate' calculation */ > + stats.tx_frames_delta = 0; > + stats.rx_frames_delta = 0; > + stats.matches_delta = 0; > + > + /* restart timer (one second) */ > + stattimer.expires = jiffies + HZ; > + add_timer(&stattimer); > +} > + > +/* > + * proc read functions > + * > + * From known use-cases we expect about 10 entries in a receive list to be > + * printed in the proc_fs. So PAGE_SIZE is definitely enough space here. > + * > + */ > + > +static int can_print_rcvlist(char *page, int len, struct hlist_head *rx_list, > + struct net_device *dev) > +{ > + struct receiver *r; > + struct hlist_node *n; > + > + rcu_read_lock(); > + hlist_for_each_entry_rcu(r, n, rx_list, list) { > + char *fmt = (r->can_id & CAN_EFF_FLAG)? > + " %-5s %08X %08x %08x %08x %8ld %s\n" : > + " %-5s %03X %08x %08x %08x %8ld %s\n"; > + > + len += snprintf(page + len, PAGE_SIZE - len, fmt, > + DNAME(dev), r->can_id, r->mask, > + (unsigned int)r->func, (unsigned int)r->data, > + r->matches, r->ident); > + > + /* does a typical line fit into the current buffer? */ > + > + /* 100 Bytes before end of buffer */ > + if (len > PAGE_SIZE - 100) { > + /* mark output cut off */ > + len += snprintf(page + len, PAGE_SIZE - len, > + " (..)\n"); > + break; > + } > + } > + rcu_read_unlock(); > + > + return len; > +} > + > +static int can_print_recv_banner(char *page, int len) > +{ > + /* > + * can1. 00000000 00000000 00000000 > + * ....... 0 tp20 > + */ > + len += snprintf(page + len, PAGE_SIZE - len, > + " device can_id can_mask function" > + " userdata matches ident\n"); > + > + return len; > +} > + > +static int can_proc_read_stats(char *page, char **start, off_t off, > + int count, int *eof, void *data) > +{ > + int len = 0; > + > + len += snprintf(page + len, PAGE_SIZE - len, "\n"); > + len += snprintf(page + len, PAGE_SIZE - len, > + " %8ld transmitted frames (TXF)\n", stats.tx_frames); > + len += snprintf(page + len, PAGE_SIZE - len, > + " %8ld received frames (RXF)\n", stats.rx_frames); > + len += snprintf(page + len, PAGE_SIZE - len, > + " %8ld matched frames (RXMF)\n", stats.matches); > + > + len += snprintf(page + len, PAGE_SIZE - len, "\n"); > + > + if (stattimer.function == can_stat_update) { > + len += snprintf(page + len, PAGE_SIZE - len, > + " %8ld %% total match ratio (RXMR)\n", > + stats.total_rx_match_ratio); > + > + len += snprintf(page + len, PAGE_SIZE - len, > + " %8ld frames/s total tx rate (TXR)\n", > + stats.total_tx_rate); > + len += snprintf(page + len, PAGE_SIZE - len, > + " %8ld frames/s total rx rate (RXR)\n", > + stats.total_rx_rate); > + > + len += snprintf(page + len, PAGE_SIZE - len, "\n"); > + > + len += snprintf(page + len, PAGE_SIZE - len, > + " %8ld %% current match ratio (CRXMR)\n", > + stats.current_rx_match_ratio); > + > + len += snprintf(page + len, PAGE_SIZE - len, > + " %8ld frames/s current tx rate (CTXR)\n", > + stats.current_tx_rate); > + len += snprintf(page + len, PAGE_SIZE - len, > + " %8ld frames/s current rx rate (CRXR)\n", > + stats.current_rx_rate); > + > + len += snprintf(page + len, PAGE_SIZE - len, "\n"); > + > + len += snprintf(page + len, PAGE_SIZE - len, > + " %8ld %% max match ratio (MRXMR)\n", > + stats.max_rx_match_ratio); > + > + len += snprintf(page + len, PAGE_SIZE - len, > + " %8ld frames/s max tx rate (MTXR)\n", > + stats.max_tx_rate); > + len += snprintf(page + len, PAGE_SIZE - len, > + " %8ld frames/s max rx rate (MRXR)\n", > + stats.max_rx_rate); > + > + len += snprintf(page + len, PAGE_SIZE - len, "\n"); > + } > + > + len += snprintf(page + len, PAGE_SIZE - len, > + " %8ld current receive list entries (CRCV)\n", > + pstats.rcv_entries); > + len += snprintf(page + len, PAGE_SIZE - len, > + " %8ld maximum receive list entries (MRCV)\n", > + pstats.rcv_entries_max); > + > + if (pstats.stats_reset) > + len += snprintf(page + len, PAGE_SIZE - len, > + "\n %8ld statistic resets (STR)\n", > + pstats.stats_reset); > + > + if (pstats.user_reset) > + len += snprintf(page + len, PAGE_SIZE - len, > + " %8ld user statistic resets (USTR)\n", > + pstats.user_reset); > + > + len += snprintf(page + len, PAGE_SIZE - len, "\n"); > + > + *eof = 1; > + return len; > +} > + > +static int can_proc_read_reset_stats(char *page, char **start, off_t off, > + int count, int *eof, void *data) > +{ > + int len = 0; > + > + user_reset = 1; > + > + if (stattimer.function == can_stat_update) { > + len += snprintf(page + len, PAGE_SIZE - len, > + "Scheduled statistic reset #%ld.\n", > + pstats.stats_reset + 1); > + > + } else { > + if (stats.jiffies_init != jiffies) > + can_init_stats(); > + > + len += snprintf(page + len, PAGE_SIZE - len, > + "Performed statistic reset #%ld.\n", > + pstats.stats_reset); > + } > + > + *eof = 1; > + return len; > +} > + > +static int can_proc_read_version(char *page, char **start, off_t off, > + int count, int *eof, void *data) > +{ > + int len = 0; > + > + len += snprintf(page + len, PAGE_SIZE - len, "%s\n", > + CAN_VERSION_STRING); > + *eof = 1; > + return len; > +} > + > +static int can_proc_read_rcvlist_all(char *page, char **start, off_t off, > + int count, int *eof, void *data) > +{ > + int len = 0; > + struct dev_rcv_lists *d; > + struct hlist_node *n; > + > + /* RX_ALL */ > + len += snprintf(page + len, PAGE_SIZE - len, > + "\nreceive list 'rx_all':\n"); > + > + /* find receive list for this device */ > + rcu_read_lock(); > + hlist_for_each_entry_rcu(d, n, &rx_dev_list, list) { > + > + if (!hlist_empty(&d->rx_all)) { > + len = can_print_recv_banner(page, len); > + len = can_print_rcvlist(page, len, &d->rx_all, d->dev); > + } else > + len += snprintf(page + len, PAGE_SIZE - len, > + " (%s: no entry)\n", DNAME(d->dev)); > + > + /* exit on end of buffer? */ > + if (len > PAGE_SIZE - 100) > + break; > + } > + rcu_read_unlock(); > + > + len += snprintf(page + len, PAGE_SIZE - len, "\n"); > + > + *eof = 1; > + return len; > +} > + > +static int can_proc_read_rcvlist_fil(char *page, char **start, off_t off, > + int count, int *eof, void *data) > +{ > + int len = 0; > + struct dev_rcv_lists *d; > + struct hlist_node *n; > + > + /* RX_FIL */ > + len += snprintf(page + len, PAGE_SIZE - len, > + "\nreceive list 'rx_fil':\n"); > + > + /* find receive list for this device */ > + rcu_read_lock(); > + hlist_for_each_entry_rcu(d, n, &rx_dev_list, list) { > + > + if (!hlist_empty(&d->rx_fil)) { > + len = can_print_recv_banner(page, len); > + len = can_print_rcvlist(page, len, &d->rx_fil, d->dev); > + } else > + len += snprintf(page + len, PAGE_SIZE - len, > + " (%s: no entry)\n", DNAME(d->dev)); > + > + /* exit on end of buffer? */ > + if (len > PAGE_SIZE - 100) > + break; > + } > + rcu_read_unlock(); > + > + len += snprintf(page + len, PAGE_SIZE - len, "\n"); > + > + *eof = 1; > + return len; > +} > + > +static int can_proc_read_rcvlist_inv(char *page, char **start, off_t off, > + int count, int *eof, void *data) > +{ > + int len = 0; > + struct dev_rcv_lists *d; > + struct hlist_node *n; > + > + /* RX_INV */ > + len += snprintf(page + len, PAGE_SIZE - len, > + "\nreceive list 'rx_inv':\n"); > + > + /* find receive list for this device */ > + rcu_read_lock(); > + hlist_for_each_entry_rcu(d, n, &rx_dev_list, list) { > + > + if (!hlist_empty(&d->rx_inv)) { > + len = can_print_recv_banner(page, len); > + len = can_print_rcvlist(page, len, &d->rx_inv, d->dev); > + } else > + len += snprintf(page + len, PAGE_SIZE - len, > + " (%s: no entry)\n", DNAME(d->dev)); > + > + /* exit on end of buffer? */ > + if (len > PAGE_SIZE - 100) > + break; > + } > + rcu_read_unlock(); > + > + len += snprintf(page + len, PAGE_SIZE - len, "\n"); > + > + *eof = 1; > + return len; > +} > + > +static int can_proc_read_rcvlist_sff(char *page, char **start, off_t off, > + int count, int *eof, void *data) > +{ > + int len = 0; > + struct dev_rcv_lists *d; > + struct hlist_node *n; > + > + /* RX_SFF */ > + len += snprintf(page + len, PAGE_SIZE - len, > + "\nreceive list 'rx_sff':\n"); > + > + /* find receive list for this device */ > + rcu_read_lock(); > + hlist_for_each_entry_rcu(d, n, &rx_dev_list, list) { > + int i, all_empty = 1; > + /* check wether at least one list is non-empty */ > + for (i = 0; i < 0x800; i++) > + if (!hlist_empty(&d->rx_sff[i])) { > + all_empty = 0; > + break; > + } > + > + if (!all_empty) { > + len = can_print_recv_banner(page, len); > + for (i = 0; i < 0x800; i++) { > + if (!hlist_empty(&d->rx_sff[i]) && > + len < PAGE_SIZE - 100) > + len = can_print_rcvlist(page, len, > + &d->rx_sff[i], > + d->dev); > + } > + } else > + len += snprintf(page + len, PAGE_SIZE - len, > + " (%s: no entry)\n", DNAME(d->dev)); > + > + /* exit on end of buffer? */ > + if (len > PAGE_SIZE - 100) > + break; > + } > + rcu_read_unlock(); > + > + len += snprintf(page + len, PAGE_SIZE - len, "\n"); > + > + *eof = 1; > + return len; > +} > + > +static int can_proc_read_rcvlist_eff(char *page, char **start, off_t off, > + int count, int *eof, void *data) > +{ > + int len = 0; > + struct dev_rcv_lists *d; > + struct hlist_node *n; > + > + /* RX_EFF */ > + len += snprintf(page + len, PAGE_SIZE - len, > + "\nreceive list 'rx_eff':\n"); > + > + /* find receive list for this device */ > + rcu_read_lock(); > + hlist_for_each_entry_rcu(d, n, &rx_dev_list, list) { > + > + if (!hlist_empty(&d->rx_eff)) { > + len = can_print_recv_banner(page, len); > + len = can_print_rcvlist(page, len, &d->rx_eff, d->dev); > + } else > + len += snprintf(page + len, PAGE_SIZE - len, > + " (%s: no entry)\n", DNAME(d->dev)); > + > + /* exit on end of buffer? */ > + if (len > PAGE_SIZE - 100) > + break; > + } > + rcu_read_unlock(); > + > + len += snprintf(page + len, PAGE_SIZE - len, "\n"); > + > + *eof = 1; > + return len; > +} > + > +static int can_proc_read_rcvlist_err(char *page, char **start, off_t off, > + int count, int *eof, void *data) > +{ > + int len = 0; > + struct dev_rcv_lists *d; > + struct hlist_node *n; > + > + /* RX_ERR */ > + len += snprintf(page + len, PAGE_SIZE - len, > + "\nreceive list 'rx_err':\n"); > + > + /* find receive list for this device */ > + rcu_read_lock(); > + hlist_for_each_entry_rcu(d, n, &rx_dev_list, list) { > + > + if (!hlist_empty(&d->rx_err)) { > + len = can_print_recv_banner(page, len); > + len = can_print_rcvlist(page, len, &d->rx_err, d->dev); > + } else > + len += snprintf(page + len, PAGE_SIZE - len, > + " (%s: no entry)\n", DNAME(d->dev)); > + > + /* exit on end of buffer? */ > + if (len > PAGE_SIZE - 100) > + break; > + } > + rcu_read_unlock(); > + > + len += snprintf(page + len, PAGE_SIZE - len, "\n"); > + > + *eof = 1; > + return len; > +} > + > +/* > + * proc utility functions > + */ > + > +static struct proc_dir_entry *can_create_proc_readentry(const char *name, > + mode_t mode, > + read_proc_t* read_proc, > + void *data) > +{ > + if (can_dir) > + return create_proc_read_entry(name, mode, can_dir, read_proc, > + data); > + else > + return NULL; > +} > + > +static void can_remove_proc_readentry(const char *name) > +{ > + if (can_dir) > + remove_proc_entry(name, can_dir); > +} > + > +/* > + * can_init_proc - create main CAN proc directory and procfs entries > + */ > +void can_init_proc(void) > +{ > + /* create /proc/net/can directory */ > + can_dir = proc_mkdir(CAN_PROC_DIR, NULL); > + > + if (!can_dir) { > + printk(KERN_INFO "can: failed to create /proc/%s . " > + "CONFIG_PROC_FS missing?\n", CAN_PROC_DIR); > + return; > + } > + > + can_dir->owner = THIS_MODULE; > + > + /* own procfs entries from the AF_CAN core */ > + pde_version = can_create_proc_readentry(CAN_PROC_VERSION, > + 0644, can_proc_read_version, NULL); > + pde_stats = can_create_proc_readentry(CAN_PROC_STATS, > + 0644, can_proc_read_stats, NULL); > + pde_reset_stats = can_create_proc_readentry(CAN_PROC_RESET_STATS, > + 0644, can_proc_read_reset_stats, NULL); > + pde_rcvlist_all = can_create_proc_readentry(CAN_PROC_RCVLIST_ALL, > + 0644, can_proc_read_rcvlist_all, NULL); > + pde_rcvlist_fil = can_create_proc_readentry(CAN_PROC_RCVLIST_FIL, > + 0644, can_proc_read_rcvlist_fil, NULL); > + pde_rcvlist_inv = can_create_proc_readentry(CAN_PROC_RCVLIST_INV, > + 0644, can_proc_read_rcvlist_inv, NULL); > + pde_rcvlist_sff = can_create_proc_readentry(CAN_PROC_RCVLIST_SFF, > + 0644, can_proc_read_rcvlist_sff, NULL); > + pde_rcvlist_eff = can_create_proc_readentry(CAN_PROC_RCVLIST_EFF, > + 0644, can_proc_read_rcvlist_eff, NULL); > + pde_rcvlist_err = can_create_proc_readentry(CAN_PROC_RCVLIST_ERR, > + 0644, can_proc_read_rcvlist_err, NULL); > +} > + > +/* > + * can_remove_proc - remove procfs entries and main CAN proc directory > + */ > +void can_remove_proc(void) > +{ > + if (pde_version) > + can_remove_proc_readentry(CAN_PROC_VERSION); > + > + if (pde_stats) > + can_remove_proc_readentry(CAN_PROC_STATS); > + > + if (pde_reset_stats) > + can_remove_proc_readentry(CAN_PROC_RESET_STATS); > + > + if (pde_rcvlist_all) > + can_remove_proc_readentry(CAN_PROC_RCVLIST_ALL); > + > + if (pde_rcvlist_fil) > + can_remove_proc_readentry(CAN_PROC_RCVLIST_FIL); > + > + if (pde_rcvlist_inv) > + can_remove_proc_readentry(CAN_PROC_RCVLIST_INV); > + > + if (pde_rcvlist_sff) > + can_remove_proc_readentry(CAN_PROC_RCVLIST_SFF); > + > + if (pde_rcvlist_eff) > + can_remove_proc_readentry(CAN_PROC_RCVLIST_EFF); > + > + if (pde_rcvlist_err) > + can_remove_proc_readentry(CAN_PROC_RCVLIST_ERR); > + > + if (can_dir) > + remove_proc_entry(CAN_PROC_DIR, NULL); > +} > > -- > - > To unsubscribe from this list: send the line "unsubscribe netdev" in > the body of a message to majordomo@...r.kernel.org > More majordomo info at http://vger.kernel.org/majordomo-info.html - To unsubscribe from this list: send the line "unsubscribe netdev" in the body of a message to majordomo@...r.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
Powered by blists - more mailing lists