2007-11-17 07:53:52 +08:00
|
|
|
/*
|
|
|
|
* bcm.c - Broadcast Manager to filter/send (cyclic) CAN content
|
|
|
|
*
|
|
|
|
* 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 and the following disclaimer.
|
|
|
|
* 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, in which case the provisions of the
|
|
|
|
* GPL apply INSTEAD OF those given above.
|
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/init.h>
|
2011-06-06 18:43:46 +08:00
|
|
|
#include <linux/interrupt.h>
|
2008-04-16 10:29:14 +08:00
|
|
|
#include <linux/hrtimer.h>
|
2007-11-17 07:53:52 +08:00
|
|
|
#include <linux/list.h>
|
|
|
|
#include <linux/proc_fs.h>
|
2009-08-28 17:57:21 +08:00
|
|
|
#include <linux/seq_file.h>
|
2007-11-17 07:53:52 +08:00
|
|
|
#include <linux/uio.h>
|
|
|
|
#include <linux/net.h>
|
|
|
|
#include <linux/netdevice.h>
|
|
|
|
#include <linux/socket.h>
|
|
|
|
#include <linux/if_arp.h>
|
|
|
|
#include <linux/skbuff.h>
|
|
|
|
#include <linux/can.h>
|
|
|
|
#include <linux/can/core.h>
|
2013-01-18 01:43:39 +08:00
|
|
|
#include <linux/can/skb.h>
|
2007-11-17 07:53:52 +08:00
|
|
|
#include <linux/can/bcm.h>
|
include cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h
percpu.h is included by sched.h and module.h and thus ends up being
included when building most .c files. percpu.h includes slab.h which
in turn includes gfp.h making everything defined by the two files
universally available and complicating inclusion dependencies.
percpu.h -> slab.h dependency is about to be removed. Prepare for
this change by updating users of gfp and slab facilities include those
headers directly instead of assuming availability. As this conversion
needs to touch large number of source files, the following script is
used as the basis of conversion.
http://userweb.kernel.org/~tj/misc/slabh-sweep.py
The script does the followings.
* Scan files for gfp and slab usages and update includes such that
only the necessary includes are there. ie. if only gfp is used,
gfp.h, if slab is used, slab.h.
* When the script inserts a new include, it looks at the include
blocks and try to put the new include such that its order conforms
to its surrounding. It's put in the include block which contains
core kernel includes, in the same order that the rest are ordered -
alphabetical, Christmas tree, rev-Xmas-tree or at the end if there
doesn't seem to be any matching order.
* If the script can't find a place to put a new include (mostly
because the file doesn't have fitting include block), it prints out
an error message indicating which .h file needs to be added to the
file.
The conversion was done in the following steps.
1. The initial automatic conversion of all .c files updated slightly
over 4000 files, deleting around 700 includes and adding ~480 gfp.h
and ~3000 slab.h inclusions. The script emitted errors for ~400
files.
2. Each error was manually checked. Some didn't need the inclusion,
some needed manual addition while adding it to implementation .h or
embedding .c file was more appropriate for others. This step added
inclusions to around 150 files.
3. The script was run again and the output was compared to the edits
from #2 to make sure no file was left behind.
4. Several build tests were done and a couple of problems were fixed.
e.g. lib/decompress_*.c used malloc/free() wrappers around slab
APIs requiring slab.h to be added manually.
5. The script was run on all .h files but without automatically
editing them as sprinkling gfp.h and slab.h inclusions around .h
files could easily lead to inclusion dependency hell. Most gfp.h
inclusion directives were ignored as stuff from gfp.h was usually
wildly available and often used in preprocessor macros. Each
slab.h inclusion directive was examined and added manually as
necessary.
6. percpu.h was updated not to include slab.h.
7. Build test were done on the following configurations and failures
were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my
distributed build env didn't work with gcov compiles) and a few
more options had to be turned off depending on archs to make things
build (like ipr on powerpc/64 which failed due to missing writeq).
* x86 and x86_64 UP and SMP allmodconfig and a custom test config.
* powerpc and powerpc64 SMP allmodconfig
* sparc and sparc64 SMP allmodconfig
* ia64 SMP allmodconfig
* s390 SMP allmodconfig
* alpha SMP allmodconfig
* um on x86_64 SMP allmodconfig
8. percpu.h modifications were reverted so that it could be applied as
a separate patch and serve as bisection point.
Given the fact that I had only a couple of failures from tests on step
6, I'm fairly confident about the coverage of this conversion patch.
If there is a breakage, it's likely to be something in one of the arch
headers which should be easily discoverable easily on most builds of
the specific arch.
Signed-off-by: Tejun Heo <tj@kernel.org>
Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
2010-03-24 16:04:11 +08:00
|
|
|
#include <linux/slab.h>
|
2007-11-17 07:53:52 +08:00
|
|
|
#include <net/sock.h>
|
|
|
|
#include <net/net_namespace.h>
|
|
|
|
|
2010-08-12 07:12:35 +08:00
|
|
|
/*
|
|
|
|
* To send multiple CAN frame content within TX_SETUP or to filter
|
|
|
|
* CAN messages with multiplex index within RX_SETUP, the number of
|
|
|
|
* different filters is limited to 256 due to the one byte index value.
|
|
|
|
*/
|
|
|
|
#define MAX_NFRAMES 256
|
|
|
|
|
2007-11-17 07:53:52 +08:00
|
|
|
/* use of last_frames[index].can_dlc */
|
|
|
|
#define RX_RECV 0x40 /* received data for this element */
|
|
|
|
#define RX_THR 0x80 /* element not been sent due to throttle feature */
|
|
|
|
#define BCM_CAN_DLC_MASK 0x0F /* clean private flags in can_dlc by masking */
|
|
|
|
|
|
|
|
/* get best masking value for can_rx_register() for a given single can_id */
|
2008-12-04 07:52:35 +08:00
|
|
|
#define REGMASK(id) ((id & CAN_EFF_FLAG) ? \
|
|
|
|
(CAN_EFF_MASK | CAN_EFF_FLAG | CAN_RTR_FLAG) : \
|
|
|
|
(CAN_SFF_MASK | CAN_EFF_FLAG | CAN_RTR_FLAG))
|
2007-11-17 07:53:52 +08:00
|
|
|
|
2008-12-04 07:52:35 +08:00
|
|
|
#define CAN_BCM_VERSION CAN_VERSION
|
2007-11-17 07:53:52 +08:00
|
|
|
|
|
|
|
MODULE_DESCRIPTION("PF_CAN broadcast manager protocol");
|
|
|
|
MODULE_LICENSE("Dual BSD/GPL");
|
|
|
|
MODULE_AUTHOR("Oliver Hartkopp <oliver.hartkopp@volkswagen.de>");
|
2009-07-15 07:12:25 +08:00
|
|
|
MODULE_ALIAS("can-proto-2");
|
2007-11-17 07:53:52 +08:00
|
|
|
|
|
|
|
/* easy access to can_frame payload */
|
|
|
|
static inline u64 GET_U64(const struct can_frame *cp)
|
|
|
|
{
|
|
|
|
return *(u64 *)cp->data;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct bcm_op {
|
|
|
|
struct list_head list;
|
|
|
|
int ifindex;
|
|
|
|
canid_t can_id;
|
2010-08-12 07:12:35 +08:00
|
|
|
u32 flags;
|
2007-11-17 07:53:52 +08:00
|
|
|
unsigned long frames_abs, frames_filtered;
|
|
|
|
struct timeval ival1, ival2;
|
2008-04-16 10:29:14 +08:00
|
|
|
struct hrtimer timer, thrtimer;
|
2009-01-05 09:31:18 +08:00
|
|
|
struct tasklet_struct tsklet, thrtsklet;
|
2008-04-16 10:29:14 +08:00
|
|
|
ktime_t rx_stamp, kt_ival1, kt_ival2, kt_lastmsg;
|
2007-11-17 07:53:52 +08:00
|
|
|
int rx_ifindex;
|
2010-08-12 07:12:35 +08:00
|
|
|
u32 count;
|
|
|
|
u32 nframes;
|
|
|
|
u32 currframe;
|
2007-11-17 07:53:52 +08:00
|
|
|
struct can_frame *frames;
|
|
|
|
struct can_frame *last_frames;
|
|
|
|
struct can_frame sframe;
|
|
|
|
struct can_frame last_sframe;
|
|
|
|
struct sock *sk;
|
|
|
|
struct net_device *rx_reg_dev;
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct proc_dir_entry *proc_dir;
|
|
|
|
|
|
|
|
struct bcm_sock {
|
|
|
|
struct sock sk;
|
|
|
|
int bound;
|
|
|
|
int ifindex;
|
|
|
|
struct notifier_block notifier;
|
|
|
|
struct list_head rx_ops;
|
|
|
|
struct list_head tx_ops;
|
|
|
|
unsigned long dropped_usr_msgs;
|
|
|
|
struct proc_dir_entry *bcm_proc_read;
|
2010-12-26 14:54:53 +08:00
|
|
|
char procname [32]; /* inode number in decimal with \0 */
|
2007-11-17 07:53:52 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
static inline struct bcm_sock *bcm_sk(const struct sock *sk)
|
|
|
|
{
|
|
|
|
return (struct bcm_sock *)sk;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define CFSIZ sizeof(struct can_frame)
|
|
|
|
#define OPSIZ sizeof(struct bcm_op)
|
|
|
|
#define MHSIZ sizeof(struct bcm_msg_head)
|
|
|
|
|
|
|
|
/*
|
|
|
|
* procfs functions
|
|
|
|
*/
|
2009-11-06 08:23:01 +08:00
|
|
|
static char *bcm_proc_getifname(char *result, int ifindex)
|
2007-11-17 07:53:52 +08:00
|
|
|
{
|
|
|
|
struct net_device *dev;
|
|
|
|
|
|
|
|
if (!ifindex)
|
|
|
|
return "any";
|
|
|
|
|
2009-11-10 15:54:56 +08:00
|
|
|
rcu_read_lock();
|
|
|
|
dev = dev_get_by_index_rcu(&init_net, ifindex);
|
2007-11-17 07:53:52 +08:00
|
|
|
if (dev)
|
2009-11-06 08:23:01 +08:00
|
|
|
strcpy(result, dev->name);
|
|
|
|
else
|
|
|
|
strcpy(result, "???");
|
2009-11-10 15:54:56 +08:00
|
|
|
rcu_read_unlock();
|
2007-11-17 07:53:52 +08:00
|
|
|
|
2009-11-06 08:23:01 +08:00
|
|
|
return result;
|
2007-11-17 07:53:52 +08:00
|
|
|
}
|
|
|
|
|
2009-08-28 17:57:21 +08:00
|
|
|
static int bcm_proc_show(struct seq_file *m, void *v)
|
2007-11-17 07:53:52 +08:00
|
|
|
{
|
2009-11-06 08:23:01 +08:00
|
|
|
char ifname[IFNAMSIZ];
|
2009-08-28 17:57:21 +08:00
|
|
|
struct sock *sk = (struct sock *)m->private;
|
2007-11-17 07:53:52 +08:00
|
|
|
struct bcm_sock *bo = bcm_sk(sk);
|
|
|
|
struct bcm_op *op;
|
|
|
|
|
net: convert %p usage to %pK
The %pK format specifier is designed to hide exposed kernel pointers,
specifically via /proc interfaces. Exposing these pointers provides an
easy target for kernel write vulnerabilities, since they reveal the
locations of writable structures containing easily triggerable function
pointers. The behavior of %pK depends on the kptr_restrict sysctl.
If kptr_restrict is set to 0, no deviation from the standard %p behavior
occurs. If kptr_restrict is set to 1, the default, if the current user
(intended to be a reader via seq_printf(), etc.) does not have CAP_SYSLOG
(currently in the LSM tree), kernel pointers using %pK are printed as 0's.
If kptr_restrict is set to 2, kernel pointers using %pK are printed as
0's regardless of privileges. Replacing with 0's was chosen over the
default "(null)", which cannot be parsed by userland %p, which expects
"(nil)".
The supporting code for kptr_restrict and %pK are currently in the -mm
tree. This patch converts users of %p in net/ to %pK. Cases of printing
pointers to the syslog are not covered, since this would eliminate useful
information for postmortem debugging and the reading of the syslog is
already optionally protected by the dmesg_restrict sysctl.
Signed-off-by: Dan Rosenberg <drosenberg@vsecurity.com>
Cc: James Morris <jmorris@namei.org>
Cc: Eric Dumazet <eric.dumazet@gmail.com>
Cc: Thomas Graf <tgraf@infradead.org>
Cc: Eugene Teo <eugeneteo@kernel.org>
Cc: Kees Cook <kees.cook@canonical.com>
Cc: Ingo Molnar <mingo@elte.hu>
Cc: David S. Miller <davem@davemloft.net>
Cc: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Eric Paris <eparis@parisplace.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2011-05-23 20:17:35 +08:00
|
|
|
seq_printf(m, ">>> socket %pK", sk->sk_socket);
|
|
|
|
seq_printf(m, " / sk %pK", sk);
|
|
|
|
seq_printf(m, " / bo %pK", bo);
|
2009-08-28 17:57:21 +08:00
|
|
|
seq_printf(m, " / dropped %lu", bo->dropped_usr_msgs);
|
2009-11-06 08:23:01 +08:00
|
|
|
seq_printf(m, " / bound %s", bcm_proc_getifname(ifname, bo->ifindex));
|
2009-08-28 17:57:21 +08:00
|
|
|
seq_printf(m, " <<<\n");
|
2007-11-17 07:53:52 +08:00
|
|
|
|
|
|
|
list_for_each_entry(op, &bo->rx_ops, list) {
|
|
|
|
|
|
|
|
unsigned long reduction;
|
|
|
|
|
|
|
|
/* print only active entries & prevent division by zero */
|
|
|
|
if (!op->frames_abs)
|
|
|
|
continue;
|
|
|
|
|
2009-08-28 17:57:21 +08:00
|
|
|
seq_printf(m, "rx_op: %03X %-5s ",
|
2009-11-06 08:23:01 +08:00
|
|
|
op->can_id, bcm_proc_getifname(ifname, op->ifindex));
|
2010-08-12 07:12:35 +08:00
|
|
|
seq_printf(m, "[%u]%c ", op->nframes,
|
2007-11-17 07:53:52 +08:00
|
|
|
(op->flags & RX_CHECK_DLC)?'d':' ');
|
2008-04-16 10:29:14 +08:00
|
|
|
if (op->kt_ival1.tv64)
|
2009-08-28 17:57:21 +08:00
|
|
|
seq_printf(m, "timeo=%lld ",
|
2008-04-16 10:29:14 +08:00
|
|
|
(long long)
|
|
|
|
ktime_to_us(op->kt_ival1));
|
2007-11-17 07:53:52 +08:00
|
|
|
|
2008-04-16 10:29:14 +08:00
|
|
|
if (op->kt_ival2.tv64)
|
2009-08-28 17:57:21 +08:00
|
|
|
seq_printf(m, "thr=%lld ",
|
2008-04-16 10:29:14 +08:00
|
|
|
(long long)
|
|
|
|
ktime_to_us(op->kt_ival2));
|
2007-11-17 07:53:52 +08:00
|
|
|
|
2009-08-28 17:57:21 +08:00
|
|
|
seq_printf(m, "# recv %ld (%ld) => reduction: ",
|
2007-11-17 07:53:52 +08:00
|
|
|
op->frames_filtered, op->frames_abs);
|
|
|
|
|
|
|
|
reduction = 100 - (op->frames_filtered * 100) / op->frames_abs;
|
|
|
|
|
2009-08-28 17:57:21 +08:00
|
|
|
seq_printf(m, "%s%ld%%\n",
|
2007-11-17 07:53:52 +08:00
|
|
|
(reduction == 100)?"near ":"", reduction);
|
|
|
|
}
|
|
|
|
|
|
|
|
list_for_each_entry(op, &bo->tx_ops, list) {
|
|
|
|
|
2010-08-12 07:12:35 +08:00
|
|
|
seq_printf(m, "tx_op: %03X %s [%u] ",
|
2009-11-06 08:23:01 +08:00
|
|
|
op->can_id,
|
|
|
|
bcm_proc_getifname(ifname, op->ifindex),
|
2007-11-17 07:53:52 +08:00
|
|
|
op->nframes);
|
|
|
|
|
2008-04-16 10:29:14 +08:00
|
|
|
if (op->kt_ival1.tv64)
|
2009-08-28 17:57:21 +08:00
|
|
|
seq_printf(m, "t1=%lld ",
|
2008-04-16 10:29:14 +08:00
|
|
|
(long long) ktime_to_us(op->kt_ival1));
|
|
|
|
|
|
|
|
if (op->kt_ival2.tv64)
|
2009-08-28 17:57:21 +08:00
|
|
|
seq_printf(m, "t2=%lld ",
|
2008-04-16 10:29:14 +08:00
|
|
|
(long long) ktime_to_us(op->kt_ival2));
|
2007-11-17 07:53:52 +08:00
|
|
|
|
2009-08-28 17:57:21 +08:00
|
|
|
seq_printf(m, "# sent %ld\n", op->frames_abs);
|
2007-11-17 07:53:52 +08:00
|
|
|
}
|
2009-08-28 17:57:21 +08:00
|
|
|
seq_putc(m, '\n');
|
|
|
|
return 0;
|
|
|
|
}
|
2007-11-17 07:53:52 +08:00
|
|
|
|
2009-08-28 17:57:21 +08:00
|
|
|
static int bcm_proc_open(struct inode *inode, struct file *file)
|
|
|
|
{
|
2013-04-01 06:16:14 +08:00
|
|
|
return single_open(file, bcm_proc_show, PDE_DATA(inode));
|
2007-11-17 07:53:52 +08:00
|
|
|
}
|
|
|
|
|
2009-08-28 17:57:21 +08:00
|
|
|
static const struct file_operations bcm_proc_fops = {
|
|
|
|
.owner = THIS_MODULE,
|
|
|
|
.open = bcm_proc_open,
|
|
|
|
.read = seq_read,
|
|
|
|
.llseek = seq_lseek,
|
|
|
|
.release = single_release,
|
|
|
|
};
|
|
|
|
|
2007-11-17 07:53:52 +08:00
|
|
|
/*
|
|
|
|
* bcm_can_tx - send the (next) CAN frame to the appropriate CAN interface
|
|
|
|
* of the given bcm tx op
|
|
|
|
*/
|
|
|
|
static void bcm_can_tx(struct bcm_op *op)
|
|
|
|
{
|
|
|
|
struct sk_buff *skb;
|
|
|
|
struct net_device *dev;
|
|
|
|
struct can_frame *cf = &op->frames[op->currframe];
|
|
|
|
|
|
|
|
/* no target device? => exit */
|
|
|
|
if (!op->ifindex)
|
|
|
|
return;
|
|
|
|
|
|
|
|
dev = dev_get_by_index(&init_net, op->ifindex);
|
|
|
|
if (!dev) {
|
|
|
|
/* RFC: should this bcm_op remove itself here? */
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-01-18 01:43:39 +08:00
|
|
|
skb = alloc_skb(CFSIZ + sizeof(struct can_skb_priv), gfp_any());
|
2007-11-17 07:53:52 +08:00
|
|
|
if (!skb)
|
|
|
|
goto out;
|
|
|
|
|
2013-01-28 16:33:33 +08:00
|
|
|
can_skb_reserve(skb);
|
|
|
|
can_skb_prv(skb)->ifindex = dev->ifindex;
|
2013-01-18 01:43:39 +08:00
|
|
|
|
2007-11-17 07:53:52 +08:00
|
|
|
memcpy(skb_put(skb, CFSIZ), cf, CFSIZ);
|
|
|
|
|
|
|
|
/* send with loopback */
|
|
|
|
skb->dev = dev;
|
2014-01-30 17:11:28 +08:00
|
|
|
can_skb_set_owner(skb, op->sk);
|
2007-11-17 07:53:52 +08:00
|
|
|
can_send(skb, 1);
|
|
|
|
|
|
|
|
/* update statistics */
|
|
|
|
op->currframe++;
|
|
|
|
op->frames_abs++;
|
|
|
|
|
|
|
|
/* reached last frame? */
|
|
|
|
if (op->currframe >= op->nframes)
|
|
|
|
op->currframe = 0;
|
|
|
|
out:
|
|
|
|
dev_put(dev);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* bcm_send_to_user - send a BCM message to the userspace
|
|
|
|
* (consisting of bcm_msg_head + x CAN frames)
|
|
|
|
*/
|
|
|
|
static void bcm_send_to_user(struct bcm_op *op, struct bcm_msg_head *head,
|
|
|
|
struct can_frame *frames, int has_timestamp)
|
|
|
|
{
|
|
|
|
struct sk_buff *skb;
|
|
|
|
struct can_frame *firstframe;
|
|
|
|
struct sockaddr_can *addr;
|
|
|
|
struct sock *sk = op->sk;
|
2010-08-12 07:12:35 +08:00
|
|
|
unsigned int datalen = head->nframes * CFSIZ;
|
2007-11-17 07:53:52 +08:00
|
|
|
int err;
|
|
|
|
|
|
|
|
skb = alloc_skb(sizeof(*head) + datalen, gfp_any());
|
|
|
|
if (!skb)
|
|
|
|
return;
|
|
|
|
|
|
|
|
memcpy(skb_put(skb, sizeof(*head)), head, sizeof(*head));
|
|
|
|
|
|
|
|
if (head->nframes) {
|
|
|
|
/* can_frames starting here */
|
2008-07-06 14:38:43 +08:00
|
|
|
firstframe = (struct can_frame *)skb_tail_pointer(skb);
|
2007-11-17 07:53:52 +08:00
|
|
|
|
|
|
|
memcpy(skb_put(skb, datalen), frames, datalen);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* the BCM uses the can_dlc-element of the can_frame
|
|
|
|
* structure for internal purposes. This is only
|
|
|
|
* relevant for updates that are generated by the
|
|
|
|
* BCM, where nframes is 1
|
|
|
|
*/
|
|
|
|
if (head->nframes == 1)
|
|
|
|
firstframe->can_dlc &= BCM_CAN_DLC_MASK;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (has_timestamp) {
|
|
|
|
/* restore rx timestamp */
|
|
|
|
skb->tstamp = op->rx_stamp;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Put the datagram to the queue so that bcm_recvmsg() can
|
|
|
|
* get it from there. We need to pass the interface index to
|
|
|
|
* bcm_recvmsg(). We pass a whole struct sockaddr_can in skb->cb
|
|
|
|
* containing the interface index.
|
|
|
|
*/
|
|
|
|
|
|
|
|
BUILD_BUG_ON(sizeof(skb->cb) < sizeof(struct sockaddr_can));
|
|
|
|
addr = (struct sockaddr_can *)skb->cb;
|
|
|
|
memset(addr, 0, sizeof(*addr));
|
|
|
|
addr->can_family = AF_CAN;
|
|
|
|
addr->can_ifindex = op->rx_ifindex;
|
|
|
|
|
|
|
|
err = sock_queue_rcv_skb(sk, skb);
|
|
|
|
if (err < 0) {
|
|
|
|
struct bcm_sock *bo = bcm_sk(sk);
|
|
|
|
|
|
|
|
kfree_skb(skb);
|
|
|
|
/* don't care about overflows in this statistic */
|
|
|
|
bo->dropped_usr_msgs++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-09-30 03:33:47 +08:00
|
|
|
static void bcm_tx_start_timer(struct bcm_op *op)
|
|
|
|
{
|
|
|
|
if (op->kt_ival1.tv64 && op->count)
|
|
|
|
hrtimer_start(&op->timer,
|
|
|
|
ktime_add(ktime_get(), op->kt_ival1),
|
|
|
|
HRTIMER_MODE_ABS);
|
|
|
|
else if (op->kt_ival2.tv64)
|
|
|
|
hrtimer_start(&op->timer,
|
|
|
|
ktime_add(ktime_get(), op->kt_ival2),
|
|
|
|
HRTIMER_MODE_ABS);
|
|
|
|
}
|
|
|
|
|
2009-01-05 09:31:18 +08:00
|
|
|
static void bcm_tx_timeout_tsklet(unsigned long data)
|
|
|
|
{
|
|
|
|
struct bcm_op *op = (struct bcm_op *)data;
|
|
|
|
struct bcm_msg_head msg_head;
|
|
|
|
|
2008-04-16 10:29:14 +08:00
|
|
|
if (op->kt_ival1.tv64 && (op->count > 0)) {
|
2007-11-17 07:53:52 +08:00
|
|
|
|
|
|
|
op->count--;
|
2009-01-15 13:06:55 +08:00
|
|
|
if (!op->count && (op->flags & TX_COUNTEVT)) {
|
|
|
|
|
|
|
|
/* create notification to user */
|
|
|
|
msg_head.opcode = TX_EXPIRED;
|
|
|
|
msg_head.flags = op->flags;
|
|
|
|
msg_head.count = op->count;
|
|
|
|
msg_head.ival1 = op->ival1;
|
|
|
|
msg_head.ival2 = op->ival2;
|
|
|
|
msg_head.can_id = op->can_id;
|
|
|
|
msg_head.nframes = 0;
|
|
|
|
|
|
|
|
bcm_send_to_user(op, &msg_head, NULL, 0);
|
|
|
|
}
|
2007-11-17 07:53:52 +08:00
|
|
|
bcm_can_tx(op);
|
|
|
|
|
2011-09-30 03:33:47 +08:00
|
|
|
} else if (op->kt_ival2.tv64)
|
|
|
|
bcm_can_tx(op);
|
2007-11-17 07:53:52 +08:00
|
|
|
|
2011-09-30 03:33:47 +08:00
|
|
|
bcm_tx_start_timer(op);
|
2009-01-15 13:06:55 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2011-03-31 09:57:33 +08:00
|
|
|
* bcm_tx_timeout_handler - performs cyclic CAN frame transmissions
|
2009-01-15 13:06:55 +08:00
|
|
|
*/
|
|
|
|
static enum hrtimer_restart bcm_tx_timeout_handler(struct hrtimer *hrtimer)
|
|
|
|
{
|
|
|
|
struct bcm_op *op = container_of(hrtimer, struct bcm_op, timer);
|
|
|
|
|
|
|
|
tasklet_schedule(&op->tsklet);
|
2007-11-17 07:53:52 +08:00
|
|
|
|
2009-01-15 13:06:55 +08:00
|
|
|
return HRTIMER_NORESTART;
|
2007-11-17 07:53:52 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* bcm_rx_changed - create a RX_CHANGED notification due to changed content
|
|
|
|
*/
|
|
|
|
static void bcm_rx_changed(struct bcm_op *op, struct can_frame *data)
|
|
|
|
{
|
|
|
|
struct bcm_msg_head head;
|
|
|
|
|
|
|
|
/* update statistics */
|
|
|
|
op->frames_filtered++;
|
|
|
|
|
|
|
|
/* prevent statistics overflow */
|
|
|
|
if (op->frames_filtered > ULONG_MAX/100)
|
|
|
|
op->frames_filtered = op->frames_abs = 0;
|
|
|
|
|
2009-01-05 09:31:18 +08:00
|
|
|
/* this element is not throttled anymore */
|
|
|
|
data->can_dlc &= (BCM_CAN_DLC_MASK|RX_RECV);
|
|
|
|
|
2007-11-17 07:53:52 +08:00
|
|
|
head.opcode = RX_CHANGED;
|
|
|
|
head.flags = op->flags;
|
|
|
|
head.count = op->count;
|
|
|
|
head.ival1 = op->ival1;
|
|
|
|
head.ival2 = op->ival2;
|
|
|
|
head.can_id = op->can_id;
|
|
|
|
head.nframes = 1;
|
|
|
|
|
|
|
|
bcm_send_to_user(op, &head, data, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* bcm_rx_update_and_send - process a detected relevant receive content change
|
|
|
|
* 1. update the last received data
|
|
|
|
* 2. send a notification to the user (if possible)
|
|
|
|
*/
|
|
|
|
static void bcm_rx_update_and_send(struct bcm_op *op,
|
|
|
|
struct can_frame *lastdata,
|
2009-01-05 09:31:18 +08:00
|
|
|
const struct can_frame *rxdata)
|
2007-11-17 07:53:52 +08:00
|
|
|
{
|
|
|
|
memcpy(lastdata, rxdata, CFSIZ);
|
|
|
|
|
2009-01-05 09:31:18 +08:00
|
|
|
/* mark as used and throttled by default */
|
|
|
|
lastdata->can_dlc |= (RX_RECV|RX_THR);
|
2007-11-17 07:53:52 +08:00
|
|
|
|
2014-12-06 01:54:38 +08:00
|
|
|
/* throttling mode inactive ? */
|
2009-01-05 09:31:18 +08:00
|
|
|
if (!op->kt_ival2.tv64) {
|
2008-04-16 10:29:14 +08:00
|
|
|
/* send RX_CHANGED to the user immediately */
|
2009-01-05 09:31:18 +08:00
|
|
|
bcm_rx_changed(op, lastdata);
|
2008-04-16 10:29:14 +08:00
|
|
|
return;
|
|
|
|
}
|
2007-11-17 07:53:52 +08:00
|
|
|
|
2009-01-05 09:31:18 +08:00
|
|
|
/* with active throttling timer we are just done here */
|
|
|
|
if (hrtimer_active(&op->thrtimer))
|
2008-04-16 10:29:14 +08:00
|
|
|
return;
|
2007-11-17 07:53:52 +08:00
|
|
|
|
2014-12-06 01:54:38 +08:00
|
|
|
/* first reception with enabled throttling mode */
|
2009-01-05 09:31:18 +08:00
|
|
|
if (!op->kt_lastmsg.tv64)
|
|
|
|
goto rx_changed_settime;
|
2008-04-16 10:29:14 +08:00
|
|
|
|
2009-01-05 09:31:18 +08:00
|
|
|
/* got a second frame inside a potential throttle period? */
|
2008-04-16 10:29:14 +08:00
|
|
|
if (ktime_us_delta(ktime_get(), op->kt_lastmsg) <
|
|
|
|
ktime_to_us(op->kt_ival2)) {
|
2009-01-05 09:31:18 +08:00
|
|
|
/* do not send the saved data - only start throttle timer */
|
2008-04-16 10:29:14 +08:00
|
|
|
hrtimer_start(&op->thrtimer,
|
|
|
|
ktime_add(op->kt_lastmsg, op->kt_ival2),
|
|
|
|
HRTIMER_MODE_ABS);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* the gap was that big, that throttling was not needed here */
|
2009-01-05 09:31:18 +08:00
|
|
|
rx_changed_settime:
|
|
|
|
bcm_rx_changed(op, lastdata);
|
2008-04-16 10:29:14 +08:00
|
|
|
op->kt_lastmsg = ktime_get();
|
2007-11-17 07:53:52 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* bcm_rx_cmp_to_index - (bit)compares the currently received data to formerly
|
|
|
|
* received data stored in op->last_frames[]
|
|
|
|
*/
|
2010-08-12 07:12:35 +08:00
|
|
|
static void bcm_rx_cmp_to_index(struct bcm_op *op, unsigned int index,
|
2009-01-05 09:31:18 +08:00
|
|
|
const struct can_frame *rxdata)
|
2007-11-17 07:53:52 +08:00
|
|
|
{
|
|
|
|
/*
|
2014-12-06 01:54:38 +08:00
|
|
|
* no one uses the MSBs of can_dlc for comparison,
|
2007-11-17 07:53:52 +08:00
|
|
|
* so we use it here to detect the first time of reception
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (!(op->last_frames[index].can_dlc & RX_RECV)) {
|
|
|
|
/* received data for the first time => send update to user */
|
|
|
|
bcm_rx_update_and_send(op, &op->last_frames[index], rxdata);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* do a real check in can_frame data section */
|
|
|
|
|
|
|
|
if ((GET_U64(&op->frames[index]) & GET_U64(rxdata)) !=
|
|
|
|
(GET_U64(&op->frames[index]) & GET_U64(&op->last_frames[index]))) {
|
|
|
|
bcm_rx_update_and_send(op, &op->last_frames[index], rxdata);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (op->flags & RX_CHECK_DLC) {
|
|
|
|
/* do a real check in can_frame dlc */
|
|
|
|
if (rxdata->can_dlc != (op->last_frames[index].can_dlc &
|
|
|
|
BCM_CAN_DLC_MASK)) {
|
|
|
|
bcm_rx_update_and_send(op, &op->last_frames[index],
|
|
|
|
rxdata);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2014-12-06 01:54:38 +08:00
|
|
|
* bcm_rx_starttimer - enable timeout monitoring for CAN frame reception
|
2007-11-17 07:53:52 +08:00
|
|
|
*/
|
|
|
|
static void bcm_rx_starttimer(struct bcm_op *op)
|
|
|
|
{
|
|
|
|
if (op->flags & RX_NO_AUTOTIMER)
|
|
|
|
return;
|
|
|
|
|
2008-04-16 10:29:14 +08:00
|
|
|
if (op->kt_ival1.tv64)
|
|
|
|
hrtimer_start(&op->timer, op->kt_ival1, HRTIMER_MODE_REL);
|
2007-11-17 07:53:52 +08:00
|
|
|
}
|
|
|
|
|
2009-01-05 09:31:18 +08:00
|
|
|
static void bcm_rx_timeout_tsklet(unsigned long data)
|
2007-11-17 07:53:52 +08:00
|
|
|
{
|
2009-01-05 09:31:18 +08:00
|
|
|
struct bcm_op *op = (struct bcm_op *)data;
|
2007-11-17 07:53:52 +08:00
|
|
|
struct bcm_msg_head msg_head;
|
|
|
|
|
2009-01-05 09:31:18 +08:00
|
|
|
/* create notification to user */
|
2007-11-17 07:53:52 +08:00
|
|
|
msg_head.opcode = RX_TIMEOUT;
|
|
|
|
msg_head.flags = op->flags;
|
|
|
|
msg_head.count = op->count;
|
|
|
|
msg_head.ival1 = op->ival1;
|
|
|
|
msg_head.ival2 = op->ival2;
|
|
|
|
msg_head.can_id = op->can_id;
|
|
|
|
msg_head.nframes = 0;
|
|
|
|
|
|
|
|
bcm_send_to_user(op, &msg_head, NULL, 0);
|
2009-01-05 09:31:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2014-12-06 01:54:38 +08:00
|
|
|
* bcm_rx_timeout_handler - when the (cyclic) CAN frame reception timed out
|
2009-01-05 09:31:18 +08:00
|
|
|
*/
|
|
|
|
static enum hrtimer_restart bcm_rx_timeout_handler(struct hrtimer *hrtimer)
|
|
|
|
{
|
|
|
|
struct bcm_op *op = container_of(hrtimer, struct bcm_op, timer);
|
|
|
|
|
|
|
|
/* schedule before NET_RX_SOFTIRQ */
|
|
|
|
tasklet_hi_schedule(&op->tsklet);
|
2007-11-17 07:53:52 +08:00
|
|
|
|
|
|
|
/* no restart of the timer is done here! */
|
|
|
|
|
|
|
|
/* if user wants to be informed, when cyclic CAN-Messages come back */
|
|
|
|
if ((op->flags & RX_ANNOUNCE_RESUME) && op->last_frames) {
|
|
|
|
/* clear received can_frames to indicate 'nothing received' */
|
|
|
|
memset(op->last_frames, 0, op->nframes * CFSIZ);
|
|
|
|
}
|
2008-04-16 10:29:14 +08:00
|
|
|
|
|
|
|
return HRTIMER_NORESTART;
|
2007-11-17 07:53:52 +08:00
|
|
|
}
|
|
|
|
|
2009-01-05 09:31:18 +08:00
|
|
|
/*
|
|
|
|
* bcm_rx_do_flush - helper for bcm_rx_thr_flush
|
|
|
|
*/
|
2010-08-12 07:12:35 +08:00
|
|
|
static inline int bcm_rx_do_flush(struct bcm_op *op, int update,
|
|
|
|
unsigned int index)
|
2009-01-05 09:31:18 +08:00
|
|
|
{
|
|
|
|
if ((op->last_frames) && (op->last_frames[index].can_dlc & RX_THR)) {
|
|
|
|
if (update)
|
|
|
|
bcm_rx_changed(op, &op->last_frames[index]);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-11-17 07:53:52 +08:00
|
|
|
/*
|
2008-04-16 10:29:14 +08:00
|
|
|
* bcm_rx_thr_flush - Check for throttled data and send it to the userspace
|
2009-01-05 09:31:18 +08:00
|
|
|
*
|
|
|
|
* update == 0 : just check if throttled data is available (any irq context)
|
|
|
|
* update == 1 : check and send throttled data to userspace (soft_irq context)
|
2007-11-17 07:53:52 +08:00
|
|
|
*/
|
2009-01-05 09:31:18 +08:00
|
|
|
static int bcm_rx_thr_flush(struct bcm_op *op, int update)
|
2007-11-17 07:53:52 +08:00
|
|
|
{
|
2008-04-16 10:29:14 +08:00
|
|
|
int updated = 0;
|
2007-11-17 07:53:52 +08:00
|
|
|
|
|
|
|
if (op->nframes > 1) {
|
2010-08-12 07:12:35 +08:00
|
|
|
unsigned int i;
|
2008-04-16 10:29:14 +08:00
|
|
|
|
2007-11-17 07:53:52 +08:00
|
|
|
/* for MUX filter we start at index 1 */
|
2009-01-05 09:31:18 +08:00
|
|
|
for (i = 1; i < op->nframes; i++)
|
|
|
|
updated += bcm_rx_do_flush(op, update, i);
|
2007-11-17 07:53:52 +08:00
|
|
|
|
|
|
|
} else {
|
|
|
|
/* for RX_FILTER_ID and simple filter */
|
2009-01-05 09:31:18 +08:00
|
|
|
updated += bcm_rx_do_flush(op, update, 0);
|
2007-11-17 07:53:52 +08:00
|
|
|
}
|
2008-04-16 10:29:14 +08:00
|
|
|
|
|
|
|
return updated;
|
|
|
|
}
|
|
|
|
|
2009-01-05 09:31:18 +08:00
|
|
|
static void bcm_rx_thr_tsklet(unsigned long data)
|
|
|
|
{
|
|
|
|
struct bcm_op *op = (struct bcm_op *)data;
|
|
|
|
|
|
|
|
/* push the changed data to the userspace */
|
|
|
|
bcm_rx_thr_flush(op, 1);
|
|
|
|
}
|
|
|
|
|
2008-04-16 10:29:14 +08:00
|
|
|
/*
|
|
|
|
* bcm_rx_thr_handler - the time for blocked content updates is over now:
|
|
|
|
* Check for throttled data and send it to the userspace
|
|
|
|
*/
|
|
|
|
static enum hrtimer_restart bcm_rx_thr_handler(struct hrtimer *hrtimer)
|
|
|
|
{
|
|
|
|
struct bcm_op *op = container_of(hrtimer, struct bcm_op, thrtimer);
|
|
|
|
|
2009-01-05 09:31:18 +08:00
|
|
|
tasklet_schedule(&op->thrtsklet);
|
|
|
|
|
|
|
|
if (bcm_rx_thr_flush(op, 0)) {
|
2008-04-16 10:29:14 +08:00
|
|
|
hrtimer_forward(hrtimer, ktime_get(), op->kt_ival2);
|
|
|
|
return HRTIMER_RESTART;
|
|
|
|
} else {
|
|
|
|
/* rearm throttle handling */
|
|
|
|
op->kt_lastmsg = ktime_set(0, 0);
|
|
|
|
return HRTIMER_NORESTART;
|
|
|
|
}
|
2007-11-17 07:53:52 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2014-12-06 01:54:38 +08:00
|
|
|
* bcm_rx_handler - handle a CAN frame reception
|
2007-11-17 07:53:52 +08:00
|
|
|
*/
|
|
|
|
static void bcm_rx_handler(struct sk_buff *skb, void *data)
|
|
|
|
{
|
|
|
|
struct bcm_op *op = (struct bcm_op *)data;
|
2009-01-05 09:31:18 +08:00
|
|
|
const struct can_frame *rxframe = (struct can_frame *)skb->data;
|
2010-08-12 07:12:35 +08:00
|
|
|
unsigned int i;
|
2007-11-17 07:53:52 +08:00
|
|
|
|
|
|
|
/* disable timeout */
|
2008-04-16 10:29:14 +08:00
|
|
|
hrtimer_cancel(&op->timer);
|
2007-11-17 07:53:52 +08:00
|
|
|
|
2009-01-05 09:31:18 +08:00
|
|
|
if (op->can_id != rxframe->can_id)
|
2009-01-07 03:07:54 +08:00
|
|
|
return;
|
2007-11-17 07:53:52 +08:00
|
|
|
|
2009-01-05 09:31:18 +08:00
|
|
|
/* save rx timestamp */
|
|
|
|
op->rx_stamp = skb->tstamp;
|
|
|
|
/* save originator for recvfrom() */
|
|
|
|
op->rx_ifindex = skb->dev->ifindex;
|
|
|
|
/* update statistics */
|
|
|
|
op->frames_abs++;
|
2007-11-17 07:53:52 +08:00
|
|
|
|
|
|
|
if (op->flags & RX_RTR_FRAME) {
|
|
|
|
/* send reply for RTR-request (placed in op->frames[0]) */
|
|
|
|
bcm_can_tx(op);
|
2009-01-07 03:07:54 +08:00
|
|
|
return;
|
2007-11-17 07:53:52 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (op->flags & RX_FILTER_ID) {
|
|
|
|
/* the easiest case */
|
2009-01-05 09:31:18 +08:00
|
|
|
bcm_rx_update_and_send(op, &op->last_frames[0], rxframe);
|
2009-01-07 03:07:54 +08:00
|
|
|
goto rx_starttimer;
|
2007-11-17 07:53:52 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (op->nframes == 1) {
|
|
|
|
/* simple compare with index 0 */
|
2009-01-05 09:31:18 +08:00
|
|
|
bcm_rx_cmp_to_index(op, 0, rxframe);
|
2009-01-07 03:07:54 +08:00
|
|
|
goto rx_starttimer;
|
2007-11-17 07:53:52 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (op->nframes > 1) {
|
|
|
|
/*
|
|
|
|
* multiplex compare
|
|
|
|
*
|
|
|
|
* find the first multiplex mask that fits.
|
|
|
|
* Remark: The MUX-mask is stored in index 0
|
|
|
|
*/
|
|
|
|
|
|
|
|
for (i = 1; i < op->nframes; i++) {
|
2009-01-05 09:31:18 +08:00
|
|
|
if ((GET_U64(&op->frames[0]) & GET_U64(rxframe)) ==
|
2007-11-17 07:53:52 +08:00
|
|
|
(GET_U64(&op->frames[0]) &
|
|
|
|
GET_U64(&op->frames[i]))) {
|
2009-01-05 09:31:18 +08:00
|
|
|
bcm_rx_cmp_to_index(op, i, rxframe);
|
2007-11-17 07:53:52 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2009-01-05 09:31:18 +08:00
|
|
|
|
2009-01-07 03:07:54 +08:00
|
|
|
rx_starttimer:
|
2009-01-05 09:31:18 +08:00
|
|
|
bcm_rx_starttimer(op);
|
2007-11-17 07:53:52 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* helpers for bcm_op handling: find & delete bcm [rx|tx] op elements
|
|
|
|
*/
|
|
|
|
static struct bcm_op *bcm_find_op(struct list_head *ops, canid_t can_id,
|
|
|
|
int ifindex)
|
|
|
|
{
|
|
|
|
struct bcm_op *op;
|
|
|
|
|
|
|
|
list_for_each_entry(op, ops, list) {
|
|
|
|
if ((op->can_id == can_id) && (op->ifindex == ifindex))
|
|
|
|
return op;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void bcm_remove_op(struct bcm_op *op)
|
|
|
|
{
|
2008-04-16 10:29:14 +08:00
|
|
|
hrtimer_cancel(&op->timer);
|
|
|
|
hrtimer_cancel(&op->thrtimer);
|
2007-11-17 07:53:52 +08:00
|
|
|
|
2009-01-05 09:31:18 +08:00
|
|
|
if (op->tsklet.func)
|
|
|
|
tasklet_kill(&op->tsklet);
|
|
|
|
|
|
|
|
if (op->thrtsklet.func)
|
|
|
|
tasklet_kill(&op->thrtsklet);
|
|
|
|
|
2007-11-17 07:53:52 +08:00
|
|
|
if ((op->frames) && (op->frames != &op->sframe))
|
|
|
|
kfree(op->frames);
|
|
|
|
|
|
|
|
if ((op->last_frames) && (op->last_frames != &op->last_sframe))
|
|
|
|
kfree(op->last_frames);
|
|
|
|
|
|
|
|
kfree(op);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void bcm_rx_unreg(struct net_device *dev, struct bcm_op *op)
|
|
|
|
{
|
|
|
|
if (op->rx_reg_dev == dev) {
|
|
|
|
can_rx_unregister(dev, op->can_id, REGMASK(op->can_id),
|
|
|
|
bcm_rx_handler, op);
|
|
|
|
|
|
|
|
/* mark as removed subscription */
|
|
|
|
op->rx_reg_dev = NULL;
|
|
|
|
} else
|
|
|
|
printk(KERN_ERR "can-bcm: bcm_rx_unreg: registered device "
|
|
|
|
"mismatch %p %p\n", op->rx_reg_dev, dev);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* bcm_delete_rx_op - find and remove a rx op (returns number of removed ops)
|
|
|
|
*/
|
|
|
|
static int bcm_delete_rx_op(struct list_head *ops, canid_t can_id, int ifindex)
|
|
|
|
{
|
|
|
|
struct bcm_op *op, *n;
|
|
|
|
|
|
|
|
list_for_each_entry_safe(op, n, ops, list) {
|
|
|
|
if ((op->can_id == can_id) && (op->ifindex == ifindex)) {
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Don't care if we're bound or not (due to netdev
|
|
|
|
* problems) can_rx_unregister() is always a save
|
|
|
|
* thing to do here.
|
|
|
|
*/
|
|
|
|
if (op->ifindex) {
|
|
|
|
/*
|
|
|
|
* Only remove subscriptions that had not
|
|
|
|
* been removed due to NETDEV_UNREGISTER
|
|
|
|
* in bcm_notifier()
|
|
|
|
*/
|
|
|
|
if (op->rx_reg_dev) {
|
|
|
|
struct net_device *dev;
|
|
|
|
|
|
|
|
dev = dev_get_by_index(&init_net,
|
|
|
|
op->ifindex);
|
|
|
|
if (dev) {
|
|
|
|
bcm_rx_unreg(dev, op);
|
|
|
|
dev_put(dev);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
can_rx_unregister(NULL, op->can_id,
|
|
|
|
REGMASK(op->can_id),
|
|
|
|
bcm_rx_handler, op);
|
|
|
|
|
|
|
|
list_del(&op->list);
|
|
|
|
bcm_remove_op(op);
|
|
|
|
return 1; /* done */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0; /* not found */
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* bcm_delete_tx_op - find and remove a tx op (returns number of removed ops)
|
|
|
|
*/
|
|
|
|
static int bcm_delete_tx_op(struct list_head *ops, canid_t can_id, int ifindex)
|
|
|
|
{
|
|
|
|
struct bcm_op *op, *n;
|
|
|
|
|
|
|
|
list_for_each_entry_safe(op, n, ops, list) {
|
|
|
|
if ((op->can_id == can_id) && (op->ifindex == ifindex)) {
|
|
|
|
list_del(&op->list);
|
|
|
|
bcm_remove_op(op);
|
|
|
|
return 1; /* done */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0; /* not found */
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* bcm_read_op - read out a bcm_op and send it to the user (for bcm_sendmsg)
|
|
|
|
*/
|
|
|
|
static int bcm_read_op(struct list_head *ops, struct bcm_msg_head *msg_head,
|
|
|
|
int ifindex)
|
|
|
|
{
|
|
|
|
struct bcm_op *op = bcm_find_op(ops, msg_head->can_id, ifindex);
|
|
|
|
|
|
|
|
if (!op)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
/* put current values into msg_head */
|
|
|
|
msg_head->flags = op->flags;
|
|
|
|
msg_head->count = op->count;
|
|
|
|
msg_head->ival1 = op->ival1;
|
|
|
|
msg_head->ival2 = op->ival2;
|
|
|
|
msg_head->nframes = op->nframes;
|
|
|
|
|
|
|
|
bcm_send_to_user(op, msg_head, op->frames, 0);
|
|
|
|
|
|
|
|
return MHSIZ;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* bcm_tx_setup - create or update a bcm tx op (for bcm_sendmsg)
|
|
|
|
*/
|
|
|
|
static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
|
|
|
|
int ifindex, struct sock *sk)
|
|
|
|
{
|
|
|
|
struct bcm_sock *bo = bcm_sk(sk);
|
|
|
|
struct bcm_op *op;
|
2010-08-12 07:12:35 +08:00
|
|
|
unsigned int i;
|
|
|
|
int err;
|
2007-11-17 07:53:52 +08:00
|
|
|
|
|
|
|
/* we need a real device to send frames */
|
|
|
|
if (!ifindex)
|
|
|
|
return -ENODEV;
|
|
|
|
|
2010-08-12 07:12:35 +08:00
|
|
|
/* check nframes boundaries - we need at least one can_frame */
|
|
|
|
if (msg_head->nframes < 1 || msg_head->nframes > MAX_NFRAMES)
|
2007-11-17 07:53:52 +08:00
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
/* check the given can_id */
|
|
|
|
op = bcm_find_op(&bo->tx_ops, msg_head->can_id, ifindex);
|
|
|
|
|
|
|
|
if (op) {
|
|
|
|
/* update existing BCM operation */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Do we need more space for the can_frames than currently
|
|
|
|
* allocated? -> This is a _really_ unusual use-case and
|
|
|
|
* therefore (complexity / locking) it is not supported.
|
|
|
|
*/
|
|
|
|
if (msg_head->nframes > op->nframes)
|
|
|
|
return -E2BIG;
|
|
|
|
|
|
|
|
/* update can_frames content */
|
|
|
|
for (i = 0; i < msg_head->nframes; i++) {
|
2014-04-07 09:25:44 +08:00
|
|
|
err = memcpy_from_msg((u8 *)&op->frames[i], msg, CFSIZ);
|
2008-07-06 14:38:43 +08:00
|
|
|
|
|
|
|
if (op->frames[i].can_dlc > 8)
|
|
|
|
err = -EINVAL;
|
|
|
|
|
2007-11-17 07:53:52 +08:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
if (msg_head->flags & TX_CP_CAN_ID) {
|
|
|
|
/* copy can_id into frame */
|
|
|
|
op->frames[i].can_id = msg_head->can_id;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
/* insert new BCM operation for the given can_id */
|
|
|
|
|
|
|
|
op = kzalloc(OPSIZ, GFP_KERNEL);
|
|
|
|
if (!op)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
op->can_id = msg_head->can_id;
|
|
|
|
|
|
|
|
/* create array for can_frames and copy the data */
|
|
|
|
if (msg_head->nframes > 1) {
|
|
|
|
op->frames = kmalloc(msg_head->nframes * CFSIZ,
|
|
|
|
GFP_KERNEL);
|
|
|
|
if (!op->frames) {
|
|
|
|
kfree(op);
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
op->frames = &op->sframe;
|
|
|
|
|
|
|
|
for (i = 0; i < msg_head->nframes; i++) {
|
2014-04-07 09:25:44 +08:00
|
|
|
err = memcpy_from_msg((u8 *)&op->frames[i], msg, CFSIZ);
|
2008-07-06 14:38:43 +08:00
|
|
|
|
|
|
|
if (op->frames[i].can_dlc > 8)
|
|
|
|
err = -EINVAL;
|
|
|
|
|
2007-11-17 07:53:52 +08:00
|
|
|
if (err < 0) {
|
|
|
|
if (op->frames != &op->sframe)
|
|
|
|
kfree(op->frames);
|
|
|
|
kfree(op);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (msg_head->flags & TX_CP_CAN_ID) {
|
|
|
|
/* copy can_id into frame */
|
|
|
|
op->frames[i].can_id = msg_head->can_id;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* tx_ops never compare with previous received messages */
|
|
|
|
op->last_frames = NULL;
|
|
|
|
|
|
|
|
/* bcm_can_tx / bcm_tx_timeout_handler needs this */
|
|
|
|
op->sk = sk;
|
|
|
|
op->ifindex = ifindex;
|
|
|
|
|
|
|
|
/* initialize uninitialized (kzalloc) structure */
|
2008-04-16 10:29:14 +08:00
|
|
|
hrtimer_init(&op->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
|
|
|
|
op->timer.function = bcm_tx_timeout_handler;
|
2007-11-17 07:53:52 +08:00
|
|
|
|
2009-01-05 09:31:18 +08:00
|
|
|
/* initialize tasklet for tx countevent notification */
|
|
|
|
tasklet_init(&op->tsklet, bcm_tx_timeout_tsklet,
|
|
|
|
(unsigned long) op);
|
|
|
|
|
2007-11-17 07:53:52 +08:00
|
|
|
/* currently unused in tx_ops */
|
2008-04-16 10:29:14 +08:00
|
|
|
hrtimer_init(&op->thrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
|
2007-11-17 07:53:52 +08:00
|
|
|
|
|
|
|
/* add this bcm_op to the list of the tx_ops */
|
|
|
|
list_add(&op->list, &bo->tx_ops);
|
|
|
|
|
|
|
|
} /* if ((op = bcm_find_op(&bo->tx_ops, msg_head->can_id, ifindex))) */
|
|
|
|
|
|
|
|
if (op->nframes != msg_head->nframes) {
|
|
|
|
op->nframes = msg_head->nframes;
|
|
|
|
/* start multiple frame transmission with index 0 */
|
|
|
|
op->currframe = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* check flags */
|
|
|
|
|
|
|
|
op->flags = msg_head->flags;
|
|
|
|
|
|
|
|
if (op->flags & TX_RESET_MULTI_IDX) {
|
|
|
|
/* start multiple frame transmission with index 0 */
|
|
|
|
op->currframe = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (op->flags & SETTIMER) {
|
|
|
|
/* set timer values */
|
|
|
|
op->count = msg_head->count;
|
|
|
|
op->ival1 = msg_head->ival1;
|
|
|
|
op->ival2 = msg_head->ival2;
|
2008-04-16 10:29:14 +08:00
|
|
|
op->kt_ival1 = timeval_to_ktime(msg_head->ival1);
|
|
|
|
op->kt_ival2 = timeval_to_ktime(msg_head->ival2);
|
2007-11-17 07:53:52 +08:00
|
|
|
|
|
|
|
/* disable an active timer due to zero values? */
|
2008-04-16 10:29:14 +08:00
|
|
|
if (!op->kt_ival1.tv64 && !op->kt_ival2.tv64)
|
|
|
|
hrtimer_cancel(&op->timer);
|
2007-11-17 07:53:52 +08:00
|
|
|
}
|
|
|
|
|
2011-09-30 03:33:47 +08:00
|
|
|
if (op->flags & STARTTIMER) {
|
|
|
|
hrtimer_cancel(&op->timer);
|
2007-11-17 07:53:52 +08:00
|
|
|
/* spec: send can_frame when starting timer */
|
|
|
|
op->flags |= TX_ANNOUNCE;
|
|
|
|
}
|
|
|
|
|
2011-09-23 16:23:47 +08:00
|
|
|
if (op->flags & TX_ANNOUNCE) {
|
2007-11-17 07:53:52 +08:00
|
|
|
bcm_can_tx(op);
|
2011-09-30 03:33:47 +08:00
|
|
|
if (op->count)
|
2011-09-23 16:23:47 +08:00
|
|
|
op->count--;
|
|
|
|
}
|
2007-11-17 07:53:52 +08:00
|
|
|
|
2011-09-30 03:33:47 +08:00
|
|
|
if (op->flags & STARTTIMER)
|
|
|
|
bcm_tx_start_timer(op);
|
|
|
|
|
2007-11-17 07:53:52 +08:00
|
|
|
return msg_head->nframes * CFSIZ + MHSIZ;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* bcm_rx_setup - create or update a bcm rx op (for bcm_sendmsg)
|
|
|
|
*/
|
|
|
|
static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
|
|
|
|
int ifindex, struct sock *sk)
|
|
|
|
{
|
|
|
|
struct bcm_sock *bo = bcm_sk(sk);
|
|
|
|
struct bcm_op *op;
|
|
|
|
int do_rx_register;
|
|
|
|
int err = 0;
|
|
|
|
|
|
|
|
if ((msg_head->flags & RX_FILTER_ID) || (!(msg_head->nframes))) {
|
|
|
|
/* be robust against wrong usage ... */
|
|
|
|
msg_head->flags |= RX_FILTER_ID;
|
|
|
|
/* ignore trailing garbage */
|
|
|
|
msg_head->nframes = 0;
|
|
|
|
}
|
|
|
|
|
2010-08-12 07:12:35 +08:00
|
|
|
/* the first element contains the mux-mask => MAX_NFRAMES + 1 */
|
|
|
|
if (msg_head->nframes > MAX_NFRAMES + 1)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2007-11-17 07:53:52 +08:00
|
|
|
if ((msg_head->flags & RX_RTR_FRAME) &&
|
|
|
|
((msg_head->nframes != 1) ||
|
|
|
|
(!(msg_head->can_id & CAN_RTR_FLAG))))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
/* check the given can_id */
|
|
|
|
op = bcm_find_op(&bo->rx_ops, msg_head->can_id, ifindex);
|
|
|
|
if (op) {
|
|
|
|
/* update existing BCM operation */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Do we need more space for the can_frames than currently
|
|
|
|
* allocated? -> This is a _really_ unusual use-case and
|
|
|
|
* therefore (complexity / locking) it is not supported.
|
|
|
|
*/
|
|
|
|
if (msg_head->nframes > op->nframes)
|
|
|
|
return -E2BIG;
|
|
|
|
|
|
|
|
if (msg_head->nframes) {
|
|
|
|
/* update can_frames content */
|
2014-04-07 09:25:44 +08:00
|
|
|
err = memcpy_from_msg((u8 *)op->frames, msg,
|
|
|
|
msg_head->nframes * CFSIZ);
|
2007-11-17 07:53:52 +08:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
/* clear last_frames to indicate 'nothing received' */
|
|
|
|
memset(op->last_frames, 0, msg_head->nframes * CFSIZ);
|
|
|
|
}
|
|
|
|
|
|
|
|
op->nframes = msg_head->nframes;
|
|
|
|
|
|
|
|
/* Only an update -> do not call can_rx_register() */
|
|
|
|
do_rx_register = 0;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
/* insert new BCM operation for the given can_id */
|
|
|
|
op = kzalloc(OPSIZ, GFP_KERNEL);
|
|
|
|
if (!op)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
op->can_id = msg_head->can_id;
|
|
|
|
op->nframes = msg_head->nframes;
|
|
|
|
|
|
|
|
if (msg_head->nframes > 1) {
|
|
|
|
/* create array for can_frames and copy the data */
|
|
|
|
op->frames = kmalloc(msg_head->nframes * CFSIZ,
|
|
|
|
GFP_KERNEL);
|
|
|
|
if (!op->frames) {
|
|
|
|
kfree(op);
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* create and init array for received can_frames */
|
|
|
|
op->last_frames = kzalloc(msg_head->nframes * CFSIZ,
|
|
|
|
GFP_KERNEL);
|
|
|
|
if (!op->last_frames) {
|
|
|
|
kfree(op->frames);
|
|
|
|
kfree(op);
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
op->frames = &op->sframe;
|
|
|
|
op->last_frames = &op->last_sframe;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (msg_head->nframes) {
|
2014-04-07 09:25:44 +08:00
|
|
|
err = memcpy_from_msg((u8 *)op->frames, msg,
|
|
|
|
msg_head->nframes * CFSIZ);
|
2007-11-17 07:53:52 +08:00
|
|
|
if (err < 0) {
|
|
|
|
if (op->frames != &op->sframe)
|
|
|
|
kfree(op->frames);
|
|
|
|
if (op->last_frames != &op->last_sframe)
|
|
|
|
kfree(op->last_frames);
|
|
|
|
kfree(op);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* bcm_can_tx / bcm_tx_timeout_handler needs this */
|
|
|
|
op->sk = sk;
|
|
|
|
op->ifindex = ifindex;
|
|
|
|
|
2012-11-27 05:24:23 +08:00
|
|
|
/* ifindex for timeout events w/o previous frame reception */
|
|
|
|
op->rx_ifindex = ifindex;
|
|
|
|
|
2007-11-17 07:53:52 +08:00
|
|
|
/* initialize uninitialized (kzalloc) structure */
|
2008-04-16 10:29:14 +08:00
|
|
|
hrtimer_init(&op->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
|
|
|
|
op->timer.function = bcm_rx_timeout_handler;
|
2007-11-17 07:53:52 +08:00
|
|
|
|
2009-01-05 09:31:18 +08:00
|
|
|
/* initialize tasklet for rx timeout notification */
|
|
|
|
tasklet_init(&op->tsklet, bcm_rx_timeout_tsklet,
|
|
|
|
(unsigned long) op);
|
|
|
|
|
2008-04-16 10:29:14 +08:00
|
|
|
hrtimer_init(&op->thrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
|
|
|
|
op->thrtimer.function = bcm_rx_thr_handler;
|
2007-11-17 07:53:52 +08:00
|
|
|
|
2009-01-05 09:31:18 +08:00
|
|
|
/* initialize tasklet for rx throttle handling */
|
|
|
|
tasklet_init(&op->thrtsklet, bcm_rx_thr_tsklet,
|
|
|
|
(unsigned long) op);
|
|
|
|
|
2007-11-17 07:53:52 +08:00
|
|
|
/* add this bcm_op to the list of the rx_ops */
|
|
|
|
list_add(&op->list, &bo->rx_ops);
|
|
|
|
|
|
|
|
/* call can_rx_register() */
|
|
|
|
do_rx_register = 1;
|
|
|
|
|
|
|
|
} /* if ((op = bcm_find_op(&bo->rx_ops, msg_head->can_id, ifindex))) */
|
|
|
|
|
|
|
|
/* check flags */
|
|
|
|
op->flags = msg_head->flags;
|
|
|
|
|
|
|
|
if (op->flags & RX_RTR_FRAME) {
|
|
|
|
|
|
|
|
/* no timers in RTR-mode */
|
2008-04-16 10:29:14 +08:00
|
|
|
hrtimer_cancel(&op->thrtimer);
|
|
|
|
hrtimer_cancel(&op->timer);
|
2007-11-17 07:53:52 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* funny feature in RX(!)_SETUP only for RTR-mode:
|
|
|
|
* copy can_id into frame BUT without RTR-flag to
|
|
|
|
* prevent a full-load-loopback-test ... ;-]
|
|
|
|
*/
|
|
|
|
if ((op->flags & TX_CP_CAN_ID) ||
|
|
|
|
(op->frames[0].can_id == op->can_id))
|
|
|
|
op->frames[0].can_id = op->can_id & ~CAN_RTR_FLAG;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
if (op->flags & SETTIMER) {
|
|
|
|
|
|
|
|
/* set timer value */
|
|
|
|
op->ival1 = msg_head->ival1;
|
|
|
|
op->ival2 = msg_head->ival2;
|
2008-04-16 10:29:14 +08:00
|
|
|
op->kt_ival1 = timeval_to_ktime(msg_head->ival1);
|
|
|
|
op->kt_ival2 = timeval_to_ktime(msg_head->ival2);
|
2007-11-17 07:53:52 +08:00
|
|
|
|
|
|
|
/* disable an active timer due to zero value? */
|
2008-04-16 10:29:14 +08:00
|
|
|
if (!op->kt_ival1.tv64)
|
|
|
|
hrtimer_cancel(&op->timer);
|
2007-11-17 07:53:52 +08:00
|
|
|
|
|
|
|
/*
|
2008-04-16 10:29:14 +08:00
|
|
|
* In any case cancel the throttle timer, flush
|
|
|
|
* potentially blocked msgs and reset throttle handling
|
2007-11-17 07:53:52 +08:00
|
|
|
*/
|
2008-04-16 10:29:14 +08:00
|
|
|
op->kt_lastmsg = ktime_set(0, 0);
|
|
|
|
hrtimer_cancel(&op->thrtimer);
|
2009-01-05 09:31:18 +08:00
|
|
|
bcm_rx_thr_flush(op, 1);
|
2007-11-17 07:53:52 +08:00
|
|
|
}
|
|
|
|
|
2008-04-16 10:29:14 +08:00
|
|
|
if ((op->flags & STARTTIMER) && op->kt_ival1.tv64)
|
|
|
|
hrtimer_start(&op->timer, op->kt_ival1,
|
|
|
|
HRTIMER_MODE_REL);
|
2007-11-17 07:53:52 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* now we can register for can_ids, if we added a new bcm_op */
|
|
|
|
if (do_rx_register) {
|
|
|
|
if (ifindex) {
|
|
|
|
struct net_device *dev;
|
|
|
|
|
|
|
|
dev = dev_get_by_index(&init_net, ifindex);
|
|
|
|
if (dev) {
|
|
|
|
err = can_rx_register(dev, op->can_id,
|
|
|
|
REGMASK(op->can_id),
|
|
|
|
bcm_rx_handler, op,
|
|
|
|
"bcm");
|
|
|
|
|
|
|
|
op->rx_reg_dev = dev;
|
|
|
|
dev_put(dev);
|
|
|
|
}
|
|
|
|
|
|
|
|
} else
|
|
|
|
err = can_rx_register(NULL, op->can_id,
|
|
|
|
REGMASK(op->can_id),
|
|
|
|
bcm_rx_handler, op, "bcm");
|
|
|
|
if (err) {
|
|
|
|
/* this bcm rx op is broken -> remove it */
|
|
|
|
list_del(&op->list);
|
|
|
|
bcm_remove_op(op);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return msg_head->nframes * CFSIZ + MHSIZ;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* bcm_tx_send - send a single CAN frame to the CAN interface (for bcm_sendmsg)
|
|
|
|
*/
|
|
|
|
static int bcm_tx_send(struct msghdr *msg, int ifindex, struct sock *sk)
|
|
|
|
{
|
|
|
|
struct sk_buff *skb;
|
|
|
|
struct net_device *dev;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
/* we need a real device to send frames */
|
|
|
|
if (!ifindex)
|
|
|
|
return -ENODEV;
|
|
|
|
|
2013-01-18 01:43:39 +08:00
|
|
|
skb = alloc_skb(CFSIZ + sizeof(struct can_skb_priv), GFP_KERNEL);
|
2007-11-17 07:53:52 +08:00
|
|
|
if (!skb)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2013-01-28 16:33:33 +08:00
|
|
|
can_skb_reserve(skb);
|
2013-01-18 01:43:39 +08:00
|
|
|
|
2014-04-07 09:25:44 +08:00
|
|
|
err = memcpy_from_msg(skb_put(skb, CFSIZ), msg, CFSIZ);
|
2007-11-17 07:53:52 +08:00
|
|
|
if (err < 0) {
|
|
|
|
kfree_skb(skb);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
dev = dev_get_by_index(&init_net, ifindex);
|
|
|
|
if (!dev) {
|
|
|
|
kfree_skb(skb);
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
2013-01-28 16:33:33 +08:00
|
|
|
can_skb_prv(skb)->ifindex = dev->ifindex;
|
2007-11-17 07:53:52 +08:00
|
|
|
skb->dev = dev;
|
2014-01-30 17:11:28 +08:00
|
|
|
can_skb_set_owner(skb, sk);
|
2008-07-06 14:38:43 +08:00
|
|
|
err = can_send(skb, 1); /* send with loopback */
|
2007-11-17 07:53:52 +08:00
|
|
|
dev_put(dev);
|
|
|
|
|
2008-07-06 14:38:43 +08:00
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
|
2007-11-17 07:53:52 +08:00
|
|
|
return CFSIZ + MHSIZ;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* bcm_sendmsg - process BCM commands (opcodes) from the userspace
|
|
|
|
*/
|
|
|
|
static int bcm_sendmsg(struct kiocb *iocb, struct socket *sock,
|
|
|
|
struct msghdr *msg, size_t size)
|
|
|
|
{
|
|
|
|
struct sock *sk = sock->sk;
|
|
|
|
struct bcm_sock *bo = bcm_sk(sk);
|
|
|
|
int ifindex = bo->ifindex; /* default ifindex for this bcm_op */
|
|
|
|
struct bcm_msg_head msg_head;
|
|
|
|
int ret; /* read bytes or error codes as return value */
|
|
|
|
|
|
|
|
if (!bo->bound)
|
|
|
|
return -ENOTCONN;
|
|
|
|
|
2008-07-06 14:38:43 +08:00
|
|
|
/* check for valid message length from userspace */
|
|
|
|
if (size < MHSIZ || (size - MHSIZ) % CFSIZ)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2007-11-17 07:53:52 +08:00
|
|
|
/* check for alternative ifindex for this bcm_op */
|
|
|
|
|
|
|
|
if (!ifindex && msg->msg_name) {
|
|
|
|
/* no bound device as default => check msg_name */
|
2014-01-18 05:53:15 +08:00
|
|
|
DECLARE_SOCKADDR(struct sockaddr_can *, addr, msg->msg_name);
|
2007-11-17 07:53:52 +08:00
|
|
|
|
2011-01-16 12:56:42 +08:00
|
|
|
if (msg->msg_namelen < sizeof(*addr))
|
|
|
|
return -EINVAL;
|
|
|
|
|
2007-11-17 07:53:52 +08:00
|
|
|
if (addr->can_family != AF_CAN)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
/* ifindex from sendto() */
|
|
|
|
ifindex = addr->can_ifindex;
|
|
|
|
|
|
|
|
if (ifindex) {
|
|
|
|
struct net_device *dev;
|
|
|
|
|
|
|
|
dev = dev_get_by_index(&init_net, ifindex);
|
|
|
|
if (!dev)
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
if (dev->type != ARPHRD_CAN) {
|
|
|
|
dev_put(dev);
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
|
|
|
dev_put(dev);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* read message head information */
|
|
|
|
|
2014-04-07 09:25:44 +08:00
|
|
|
ret = memcpy_from_msg((u8 *)&msg_head, msg, MHSIZ);
|
2007-11-17 07:53:52 +08:00
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
lock_sock(sk);
|
|
|
|
|
|
|
|
switch (msg_head.opcode) {
|
|
|
|
|
|
|
|
case TX_SETUP:
|
|
|
|
ret = bcm_tx_setup(&msg_head, msg, ifindex, sk);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case RX_SETUP:
|
|
|
|
ret = bcm_rx_setup(&msg_head, msg, ifindex, sk);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TX_DELETE:
|
|
|
|
if (bcm_delete_tx_op(&bo->tx_ops, msg_head.can_id, ifindex))
|
|
|
|
ret = MHSIZ;
|
|
|
|
else
|
|
|
|
ret = -EINVAL;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case RX_DELETE:
|
|
|
|
if (bcm_delete_rx_op(&bo->rx_ops, msg_head.can_id, ifindex))
|
|
|
|
ret = MHSIZ;
|
|
|
|
else
|
|
|
|
ret = -EINVAL;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TX_READ:
|
|
|
|
/* reuse msg_head for the reply to TX_READ */
|
|
|
|
msg_head.opcode = TX_STATUS;
|
|
|
|
ret = bcm_read_op(&bo->tx_ops, &msg_head, ifindex);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case RX_READ:
|
|
|
|
/* reuse msg_head for the reply to RX_READ */
|
|
|
|
msg_head.opcode = RX_STATUS;
|
|
|
|
ret = bcm_read_op(&bo->rx_ops, &msg_head, ifindex);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case TX_SEND:
|
2008-07-06 14:38:43 +08:00
|
|
|
/* we need exactly one can_frame behind the msg head */
|
|
|
|
if ((msg_head.nframes != 1) || (size != CFSIZ + MHSIZ))
|
2007-11-17 07:53:52 +08:00
|
|
|
ret = -EINVAL;
|
|
|
|
else
|
|
|
|
ret = bcm_tx_send(msg, ifindex, sk);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
ret = -EINVAL;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
release_sock(sk);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* notification handler for netdevice status changes
|
|
|
|
*/
|
|
|
|
static int bcm_notifier(struct notifier_block *nb, unsigned long msg,
|
2013-05-28 09:30:21 +08:00
|
|
|
void *ptr)
|
2007-11-17 07:53:52 +08:00
|
|
|
{
|
2013-05-28 09:30:21 +08:00
|
|
|
struct net_device *dev = netdev_notifier_info_to_dev(ptr);
|
2007-11-17 07:53:52 +08:00
|
|
|
struct bcm_sock *bo = container_of(nb, struct bcm_sock, notifier);
|
|
|
|
struct sock *sk = &bo->sk;
|
|
|
|
struct bcm_op *op;
|
|
|
|
int notify_enodev = 0;
|
|
|
|
|
2008-07-20 13:34:43 +08:00
|
|
|
if (!net_eq(dev_net(dev), &init_net))
|
2007-11-17 07:53:52 +08:00
|
|
|
return NOTIFY_DONE;
|
|
|
|
|
|
|
|
if (dev->type != ARPHRD_CAN)
|
|
|
|
return NOTIFY_DONE;
|
|
|
|
|
|
|
|
switch (msg) {
|
|
|
|
|
|
|
|
case NETDEV_UNREGISTER:
|
|
|
|
lock_sock(sk);
|
|
|
|
|
|
|
|
/* remove device specific receive entries */
|
|
|
|
list_for_each_entry(op, &bo->rx_ops, list)
|
|
|
|
if (op->rx_reg_dev == dev)
|
|
|
|
bcm_rx_unreg(dev, op);
|
|
|
|
|
|
|
|
/* remove device reference, if this is our bound device */
|
|
|
|
if (bo->bound && bo->ifindex == dev->ifindex) {
|
|
|
|
bo->bound = 0;
|
|
|
|
bo->ifindex = 0;
|
|
|
|
notify_enodev = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
release_sock(sk);
|
|
|
|
|
|
|
|
if (notify_enodev) {
|
|
|
|
sk->sk_err = ENODEV;
|
|
|
|
if (!sock_flag(sk, SOCK_DEAD))
|
|
|
|
sk->sk_error_report(sk);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case NETDEV_DOWN:
|
|
|
|
if (bo->bound && bo->ifindex == dev->ifindex) {
|
|
|
|
sk->sk_err = ENETDOWN;
|
|
|
|
if (!sock_flag(sk, SOCK_DEAD))
|
|
|
|
sk->sk_error_report(sk);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return NOTIFY_DONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* initial settings for all BCM sockets to be set at socket creation time
|
|
|
|
*/
|
|
|
|
static int bcm_init(struct sock *sk)
|
|
|
|
{
|
|
|
|
struct bcm_sock *bo = bcm_sk(sk);
|
|
|
|
|
|
|
|
bo->bound = 0;
|
|
|
|
bo->ifindex = 0;
|
|
|
|
bo->dropped_usr_msgs = 0;
|
|
|
|
bo->bcm_proc_read = NULL;
|
|
|
|
|
|
|
|
INIT_LIST_HEAD(&bo->tx_ops);
|
|
|
|
INIT_LIST_HEAD(&bo->rx_ops);
|
|
|
|
|
|
|
|
/* set notifier */
|
|
|
|
bo->notifier.notifier_call = bcm_notifier;
|
|
|
|
|
|
|
|
register_netdevice_notifier(&bo->notifier);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* standard socket functions
|
|
|
|
*/
|
|
|
|
static int bcm_release(struct socket *sock)
|
|
|
|
{
|
|
|
|
struct sock *sk = sock->sk;
|
2011-04-20 11:36:59 +08:00
|
|
|
struct bcm_sock *bo;
|
2007-11-17 07:53:52 +08:00
|
|
|
struct bcm_op *op, *next;
|
|
|
|
|
2011-04-20 11:36:59 +08:00
|
|
|
if (sk == NULL)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
bo = bcm_sk(sk);
|
|
|
|
|
2007-11-17 07:53:52 +08:00
|
|
|
/* remove bcm_ops, timer, rx_unregister(), etc. */
|
|
|
|
|
|
|
|
unregister_netdevice_notifier(&bo->notifier);
|
|
|
|
|
|
|
|
lock_sock(sk);
|
|
|
|
|
|
|
|
list_for_each_entry_safe(op, next, &bo->tx_ops, list)
|
|
|
|
bcm_remove_op(op);
|
|
|
|
|
|
|
|
list_for_each_entry_safe(op, next, &bo->rx_ops, list) {
|
|
|
|
/*
|
|
|
|
* Don't care if we're bound or not (due to netdev problems)
|
|
|
|
* can_rx_unregister() is always a save thing to do here.
|
|
|
|
*/
|
|
|
|
if (op->ifindex) {
|
|
|
|
/*
|
|
|
|
* Only remove subscriptions that had not
|
|
|
|
* been removed due to NETDEV_UNREGISTER
|
|
|
|
* in bcm_notifier()
|
|
|
|
*/
|
|
|
|
if (op->rx_reg_dev) {
|
|
|
|
struct net_device *dev;
|
|
|
|
|
|
|
|
dev = dev_get_by_index(&init_net, op->ifindex);
|
|
|
|
if (dev) {
|
|
|
|
bcm_rx_unreg(dev, op);
|
|
|
|
dev_put(dev);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
can_rx_unregister(NULL, op->can_id,
|
|
|
|
REGMASK(op->can_id),
|
|
|
|
bcm_rx_handler, op);
|
|
|
|
|
|
|
|
bcm_remove_op(op);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* remove procfs entry */
|
|
|
|
if (proc_dir && bo->bcm_proc_read)
|
|
|
|
remove_proc_entry(bo->procname, proc_dir);
|
|
|
|
|
|
|
|
/* remove device reference */
|
|
|
|
if (bo->bound) {
|
|
|
|
bo->bound = 0;
|
|
|
|
bo->ifindex = 0;
|
|
|
|
}
|
|
|
|
|
2009-07-15 07:10:21 +08:00
|
|
|
sock_orphan(sk);
|
|
|
|
sock->sk = NULL;
|
|
|
|
|
2007-11-17 07:53:52 +08:00
|
|
|
release_sock(sk);
|
|
|
|
sock_put(sk);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int bcm_connect(struct socket *sock, struct sockaddr *uaddr, int len,
|
|
|
|
int flags)
|
|
|
|
{
|
|
|
|
struct sockaddr_can *addr = (struct sockaddr_can *)uaddr;
|
|
|
|
struct sock *sk = sock->sk;
|
|
|
|
struct bcm_sock *bo = bcm_sk(sk);
|
|
|
|
|
2010-04-01 06:58:26 +08:00
|
|
|
if (len < sizeof(*addr))
|
|
|
|
return -EINVAL;
|
|
|
|
|
2007-11-17 07:53:52 +08:00
|
|
|
if (bo->bound)
|
|
|
|
return -EISCONN;
|
|
|
|
|
|
|
|
/* bind a device to this socket */
|
|
|
|
if (addr->can_ifindex) {
|
|
|
|
struct net_device *dev;
|
|
|
|
|
|
|
|
dev = dev_get_by_index(&init_net, addr->can_ifindex);
|
|
|
|
if (!dev)
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
if (dev->type != ARPHRD_CAN) {
|
|
|
|
dev_put(dev);
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
|
|
|
bo->ifindex = dev->ifindex;
|
|
|
|
dev_put(dev);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
/* no interface reference for ifindex = 0 ('any' CAN device) */
|
|
|
|
bo->ifindex = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
bo->bound = 1;
|
|
|
|
|
|
|
|
if (proc_dir) {
|
|
|
|
/* unique socket address as filename */
|
2010-12-26 14:54:53 +08:00
|
|
|
sprintf(bo->procname, "%lu", sock_i_ino(sk));
|
2009-08-28 17:57:21 +08:00
|
|
|
bo->bcm_proc_read = proc_create_data(bo->procname, 0644,
|
|
|
|
proc_dir,
|
|
|
|
&bcm_proc_fops, sk);
|
2007-11-17 07:53:52 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int bcm_recvmsg(struct kiocb *iocb, struct socket *sock,
|
|
|
|
struct msghdr *msg, size_t size, int flags)
|
|
|
|
{
|
|
|
|
struct sock *sk = sock->sk;
|
|
|
|
struct sk_buff *skb;
|
|
|
|
int error = 0;
|
|
|
|
int noblock;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
noblock = flags & MSG_DONTWAIT;
|
|
|
|
flags &= ~MSG_DONTWAIT;
|
|
|
|
skb = skb_recv_datagram(sk, flags, noblock, &error);
|
|
|
|
if (!skb)
|
|
|
|
return error;
|
|
|
|
|
|
|
|
if (skb->len < size)
|
|
|
|
size = skb->len;
|
|
|
|
|
2014-04-07 09:51:23 +08:00
|
|
|
err = memcpy_to_msg(msg, skb->data, size);
|
2007-11-17 07:53:52 +08:00
|
|
|
if (err < 0) {
|
|
|
|
skb_free_datagram(sk, skb);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
net: Generalize socket rx gap / receive queue overflow cmsg
Create a new socket level option to report number of queue overflows
Recently I augmented the AF_PACKET protocol to report the number of frames lost
on the socket receive queue between any two enqueued frames. This value was
exported via a SOL_PACKET level cmsg. AFter I completed that work it was
requested that this feature be generalized so that any datagram oriented socket
could make use of this option. As such I've created this patch, It creates a
new SOL_SOCKET level option called SO_RXQ_OVFL, which when enabled exports a
SOL_SOCKET level cmsg that reports the nubmer of times the sk_receive_queue
overflowed between any two given frames. It also augments the AF_PACKET
protocol to take advantage of this new feature (as it previously did not touch
sk->sk_drops, which this patch uses to record the overflow count). Tested
successfully by me.
Notes:
1) Unlike my previous patch, this patch simply records the sk_drops value, which
is not a number of drops between packets, but rather a total number of drops.
Deltas must be computed in user space.
2) While this patch currently works with datagram oriented protocols, it will
also be accepted by non-datagram oriented protocols. I'm not sure if thats
agreeable to everyone, but my argument in favor of doing so is that, for those
protocols which aren't applicable to this option, sk_drops will always be zero,
and reporting no drops on a receive queue that isn't used for those
non-participating protocols seems reasonable to me. This also saves us having
to code in a per-protocol opt in mechanism.
3) This applies cleanly to net-next assuming that commit
977750076d98c7ff6cbda51858bb5a5894a9d9ab (my af packet cmsg patch) is reverted
Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
Signed-off-by: Eric Dumazet <eric.dumazet@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2009-10-13 04:26:31 +08:00
|
|
|
sock_recv_ts_and_drops(msg, sk, skb);
|
2007-11-17 07:53:52 +08:00
|
|
|
|
|
|
|
if (msg->msg_name) {
|
2014-01-18 05:53:15 +08:00
|
|
|
__sockaddr_check_size(sizeof(struct sockaddr_can));
|
2007-11-17 07:53:52 +08:00
|
|
|
msg->msg_namelen = sizeof(struct sockaddr_can);
|
|
|
|
memcpy(msg->msg_name, skb->cb, msg->msg_namelen);
|
|
|
|
}
|
|
|
|
|
|
|
|
skb_free_datagram(sk, skb);
|
|
|
|
|
|
|
|
return size;
|
|
|
|
}
|
|
|
|
|
2011-03-22 16:27:25 +08:00
|
|
|
static const struct proto_ops bcm_ops = {
|
2007-11-17 07:53:52 +08:00
|
|
|
.family = PF_CAN,
|
|
|
|
.release = bcm_release,
|
|
|
|
.bind = sock_no_bind,
|
|
|
|
.connect = bcm_connect,
|
|
|
|
.socketpair = sock_no_socketpair,
|
|
|
|
.accept = sock_no_accept,
|
|
|
|
.getname = sock_no_getname,
|
|
|
|
.poll = datagram_poll,
|
2011-03-22 16:27:25 +08:00
|
|
|
.ioctl = can_ioctl, /* use can_ioctl() from af_can.c */
|
2007-11-17 07:53:52 +08:00
|
|
|
.listen = sock_no_listen,
|
|
|
|
.shutdown = sock_no_shutdown,
|
|
|
|
.setsockopt = sock_no_setsockopt,
|
|
|
|
.getsockopt = sock_no_getsockopt,
|
|
|
|
.sendmsg = bcm_sendmsg,
|
|
|
|
.recvmsg = bcm_recvmsg,
|
|
|
|
.mmap = sock_no_mmap,
|
|
|
|
.sendpage = sock_no_sendpage,
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct proto bcm_proto __read_mostly = {
|
|
|
|
.name = "CAN_BCM",
|
|
|
|
.owner = THIS_MODULE,
|
|
|
|
.obj_size = sizeof(struct bcm_sock),
|
|
|
|
.init = bcm_init,
|
|
|
|
};
|
|
|
|
|
2011-05-04 02:40:57 +08:00
|
|
|
static const struct can_proto bcm_can_proto = {
|
2007-11-17 07:53:52 +08:00
|
|
|
.type = SOCK_DGRAM,
|
|
|
|
.protocol = CAN_BCM,
|
|
|
|
.ops = &bcm_ops,
|
|
|
|
.prot = &bcm_proto,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int __init bcm_module_init(void)
|
|
|
|
{
|
|
|
|
int err;
|
|
|
|
|
2014-11-22 15:42:35 +08:00
|
|
|
pr_info("can: broadcast manager protocol (rev " CAN_BCM_VERSION " t)\n");
|
2007-11-17 07:53:52 +08:00
|
|
|
|
|
|
|
err = can_proto_register(&bcm_can_proto);
|
|
|
|
if (err < 0) {
|
|
|
|
printk(KERN_ERR "can: registration of bcm protocol failed\n");
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* create /proc/net/can-bcm directory */
|
|
|
|
proc_dir = proc_mkdir("can-bcm", init_net.proc_net);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void __exit bcm_module_exit(void)
|
|
|
|
{
|
|
|
|
can_proto_unregister(&bcm_can_proto);
|
|
|
|
|
|
|
|
if (proc_dir)
|
2013-02-18 09:34:56 +08:00
|
|
|
remove_proc_entry("can-bcm", init_net.proc_net);
|
2007-11-17 07:53:52 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
module_init(bcm_module_init);
|
|
|
|
module_exit(bcm_module_exit);
|