--- /dev/null
+#
+# $Id: Makefile,v 2.0 2006/04/13 10:37:19 ethuerm Exp $
+#
+# Copyright (c) 2002-2005 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 <llcf@volkswagen.de>
+
+
+
+MOD_GPL = can.o vcan.o can-raw.o can-bcm.o
+MOD_VW = can-tpgen.o can-tp20.o can-tp16.o can-mcnet.o
+
+ifeq ($(KERNELRELEASE),)
+
+KERNELDIR = /usr/src/linux
+PWD = $(shell pwd)
+
+default:
+ $(MAKE) -C $(KERNELDIR) SUBDIRS=$(PWD) modules
+
+clean:
+ $(MAKE) -C $(KERNELDIR) SUBDIRS=$(PWD) clean
+
+else
+
+obj-m := $(MOD_GPL)
+
+can-objs := af_can.o proc.o
+can-bcm-objs := bcm.o
+can-raw-objs := raw.o
+
+can-tpgen-objs := tp_gen.o
+can-tp20-objs := tp20.o
+can-tp16-objs := tp16.o
+can-mcnet-objs := mcnet.o
+
+endif
--- /dev/null
+/*
+ * af_can.c
+ *
+ * Copyright (c) 2002-2005 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 <llcf@volkswagen.de>
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/version.h>
+#include <linux/kmod.h>
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <linux/socket.h>
+#include <linux/skbuff.h>
+#include <linux/net.h>
+#include <linux/netdevice.h>
+#include <net/sock.h>
+#include <asm/uaccess.h>
+
+#include "af_can.h"
+#include "version.h"
+
+RCSID("$Id: af_can.c,v 2.0 2006/04/13 10:37:19 ethuerm Exp $");
+
+#define NAME "Volkswagen AG - Low Level CAN Framework (LLCF)"
+#define IDENT "af_can"
+static __initdata const char banner[] = BANNER(NAME);
+
+MODULE_DESCRIPTION(NAME);
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_AUTHOR("Urs Thuermann <urs.thuermann@volkswagen.de>, "
+ "Oliver Hartkopp <oliver.hartkopp@volkswagen.de>");
+
+int stats_timer = 1; /* default: on */
+module_param(stats_timer, int, S_IRUGO);
+
+#ifdef DEBUG
+static int debug = 0;
+module_param(debug, int, S_IRUGO);
+#define DBG(args...) (debug & 1 ? \
+ (printk(KERN_DEBUG "CAN %s: ", __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
+
+static __init int can_init(void);
+static __exit void can_exit(void);
+
+static int can_create(struct socket *sock, int protocol);
+static int can_notifier(struct notifier_block *nb,
+ unsigned long msg, void *data);
+static int can_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg);
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,14)
+static int can_rcv(struct sk_buff *skb, struct net_device *dev,
+ struct packet_type *pt, struct net_device *orig_dev);
+#else
+static int can_rcv(struct sk_buff *skb, struct net_device *dev,
+ struct packet_type *pt);
+#endif
+static int can_rcv_filter(struct rcv_dev_list *q, struct sk_buff *skb);
+static struct rcv_list **find_rcv_list(canid_t *can_id, canid_t *mask, struct net_device *dev);
+
+struct notifier_list {
+ struct notifier_list *next;
+ struct net_device *dev;
+ void (*func)(unsigned long msg, void *data);
+ void *data;
+};
+
+static struct notifier_list *nlist;
+
+struct rcv_dev_list *rx_dev_list;
+struct rcv_dev_list rx_alldev_list;
+rwlock_t rcv_lists_lock = RW_LOCK_UNLOCKED;
+
+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,
+};
+
+static struct notifier_block can_netdev_notifier = {
+ .notifier_call = can_notifier,
+};
+
+static struct can_proto *proto_tab[CAN_MAX];
+
+extern struct timer_list stattimer; /* timer for statistics update */
+extern struct s_stats stats; /* statistics */
+extern struct s_pstats pstats;
+
+module_init(can_init);
+module_exit(can_exit);
+
+/**************************************************/
+/* af_can module init/exit functions */
+/**************************************************/
+
+static __init int can_init(void)
+{
+ printk(banner);
+
+ if (stats_timer) {
+ /* statistics init */
+ init_timer(&stattimer);
+ }
+
+ /* 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)
+{
+ if (stats_timer) {
+ /* stop statistics 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);
+}
+
+/**************************************************/
+/* af_can protocol functions */
+/**************************************************/
+
+void can_proto_register(int proto, struct can_proto *cp)
+{
+ if (proto < 0 || proto >= CAN_MAX) {
+ printk(KERN_ERR "CAN: protocol number %d out of range\n", proto);
+ return;
+ }
+ if (proto_tab[proto]) {
+ printk(KERN_ERR "CAN: protocol %d already registered\n", proto);
+ return;
+ }
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,13)
+ if (proto_register(cp->prot, 0) != 0) {
+ return;
+ }
+#endif
+ proto_tab[proto] = cp;
+
+ /* use our generic ioctl function if the module doesn't bring its own */
+ if (!cp->ops->ioctl)
+ cp->ops->ioctl = can_ioctl;
+}
+
+void can_proto_unregister(int proto)
+{
+ struct can_proto *cp;
+
+ if (!(cp = proto_tab[proto])) {
+ printk(KERN_ERR "CAN: protocol %d is not registered\n", proto);
+ return;
+ }
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,13)
+ proto_unregister(cp->prot);
+#endif
+ proto_tab[proto] = NULL;
+}
+
+void can_dev_register(struct net_device *dev,
+ void (*func)(unsigned long msg, void *), void *data)
+{
+ struct notifier_list *p = kmalloc(GFP_KERNEL, sizeof(*p));
+
+ DBG("called for %s\n", dev->name);
+
+ if (!p)
+ return;
+ p->next = nlist;
+ p->dev = dev;
+ p->func = func;
+ p->data = data;
+ nlist = p;
+}
+
+void can_dev_unregister(struct net_device *dev,
+ void (*func)(unsigned long msg, void *), void *data)
+{
+ struct notifier_list *p, **q;
+
+ DBG("called for %s\n", dev->name);
+
+ for (q = &nlist; p = *q; q = &p->next) {
+ if (p->dev == dev && p->func == func && p->data == data) {
+ *q = p->next;
+ kfree(p);
+ return;
+ }
+ }
+}
+
+/**************************************************/
+/* af_can socket functions */
+/**************************************************/
+
+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;
+
+ DBG("socket %p, type %d, proto %d\n", sock, sock->type, protocol);
+
+ sock->state = SS_UNCONNECTED;
+
+ switch (sock->type) {
+ case SOCK_SEQPACKET:
+ switch (protocol) {
+ case CAN_TP16:
+ break;
+ case CAN_TP20:
+ break;
+ case CAN_MCNET:
+ break;
+ case CAN_ISOTP:
+ break;
+ default:
+ return -EPROTONOSUPPORT;
+ }
+ break;
+ case SOCK_DGRAM:
+ switch (protocol) {
+ case CAN_BCM:
+ break;
+ case CAN_BAP:
+ break;
+ default:
+ return -EPROTONOSUPPORT;
+ }
+ break;
+ case SOCK_RAW:
+ switch (protocol) {
+ case CAN_RAW:
+ break;
+ default:
+ return -EPROTONOSUPPORT;
+ }
+ break;
+ default:
+ return -ESOCKTNOSUPPORT;
+ break;
+ }
+
+ DBG("looking up proto %d in proto_tab[]\n", protocol);
+
+ /* try to load protocol module, when CONFIG_KMOD is defined */
+ if (!proto_tab[protocol]) {
+ char module_name[30];
+ sprintf(module_name, "can-proto-%d", protocol);
+ if (request_module(module_name) == -ENOSYS)
+ printk(KERN_INFO "af_can: request_module(%s) not implemented.\n",
+ module_name);
+ }
+
+ /* check for success */
+ if (!(cp = proto_tab[protocol]))
+ return -EPROTONOSUPPORT;
+
+ sock->ops = cp->ops;
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,13)
+ sk = sk_alloc(PF_CAN, GFP_KERNEL, cp->prot, 1);
+ if (!sk)
+ goto oom;
+#else
+ sk = sk_alloc(PF_CAN, GFP_KERNEL, 1, 0);
+ if (!sk)
+ goto oom;
+ if (cp->obj_size &&
+ !(sk->sk_protinfo = kmalloc(cp->obj_size, GFP_KERNEL))) {
+ sk_free(sk);
+ goto oom;
+ }
+ sk_set_owner(sk, proto_tab[protocol]->owner);
+#endif
+ sock_init_data(sock, sk);
+ sk->sk_destruct = can_sock_destruct;
+
+ DBG("created sock: %p\n", sk);
+
+ return 0;
+
+ oom:
+ return -ENOMEM;
+}
+
+static int can_notifier(struct notifier_block *nb,
+ unsigned long msg, void *data)
+{
+ struct net_device *dev = (struct net_device *)data;
+ struct notifier_list *p;
+
+ DBG("called for %s, msg = %lu\n", dev->name, msg);
+
+ for (p = nlist; p; p = p->next) {
+ if (p->dev == dev)
+ p->func(msg, p->data);
+ }
+ return 0;
+}
+
+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:
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16)
+ return -ENOIOCTLCMD;
+#else
+ return dev_ioctl(cmd, (void *)arg);
+#endif
+ }
+ return 0;
+}
+
+/**************************************************/
+/* af_can tx path */
+/**************************************************/
+
+int can_send(struct sk_buff *skb)
+{
+ struct sk_buff *newskb = skb_clone(skb, GFP_ATOMIC);
+ int err;
+
+ if (!(skb->dev->flags & IFF_UP))
+ err = -ENETDOWN;
+ else if ((err = dev_queue_xmit(skb)) > 0) /* send to netdevice */
+ err = net_xmit_errno(err);
+
+ /* update statistics */
+ stats.tx_frames++;
+ stats.tx_frames_delta++;
+
+ newskb->protocol = htons(ETH_P_CAN);
+ newskb->ip_summed = CHECKSUM_UNNECESSARY;
+ netif_rx(newskb); /* local loopback */
+
+ return err;
+}
+
+/**************************************************/
+/* af_can rx path */
+/**************************************************/
+
+void 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 rcv_list *p, **q;
+ struct rcv_dev_list *d;
+
+ DBG("dev %p, id %03X, mask %03X, callback %p, data %p, ident %s\n",
+ dev, can_id, mask, func, data, ident);
+
+ write_lock(&rcv_lists_lock);
+
+ q = find_rcv_list(&can_id, &mask, dev);
+
+ if (!q) {
+ printk(KERN_ERR "CAN: receive list not found for "
+ "dev %s, id %03X, mask %03X, ident %s\n",
+ dev->name, can_id, mask, ident);
+ goto out;
+ }
+
+ /* insert (dev,canid,mask) -> (func,data) */
+ if (!(p = kmalloc(sizeof(struct rcv_list), GFP_KERNEL)))
+ return;
+
+ p->can_id = can_id;
+ p->mask = mask;
+ p->matches = 0;
+ p->func = func;
+ p->data = data;
+ p->ident = ident;
+ p->next = *q;
+ *q = p;
+
+ if (!dev)
+ d = &rx_alldev_list;
+ else
+ for (d = rx_dev_list; d; d = d->next)
+ if (d->dev == dev)
+ break;
+ d->entries++;
+
+ pstats.rcv_entries++;
+ if (pstats.rcv_entries_max < pstats.rcv_entries)
+ pstats.rcv_entries_max = pstats.rcv_entries;
+
+out:
+ write_unlock(&rcv_lists_lock);
+}
+
+void can_rx_unregister(struct net_device *dev, canid_t can_id, canid_t mask,
+ void (*func)(struct sk_buff *, void *), void *data)
+{
+ struct rcv_list *p, **q;
+ struct rcv_dev_list *d;
+
+ DBG("dev %p, id %03X, mask %03X, callback %p, data %p\n",
+ dev, can_id, mask, func, data);
+
+ write_lock(&rcv_lists_lock);
+
+ q = find_rcv_list(&can_id, &mask, dev);
+
+ if (!q) {
+ printk(KERN_ERR "CAN: receive list not found for "
+ "dev %s, id %03X, mask %03X\n", dev->name, can_id, mask);
+ goto out;
+ }
+
+ for (; p = *q; q = &p->next) {
+ if (p->can_id == can_id && p->mask == mask
+ && p->func == func && p->data == data)
+ break;
+ }
+
+ if (!p) {
+ printk(KERN_ERR "CAN: receive list entry not found for "
+ "dev %s, id %03X, mask %03X\n", dev->name, can_id, mask);
+ goto out;
+ }
+
+ *q = p->next;
+ kfree(p);
+
+ if (pstats.rcv_entries > 0)
+ pstats.rcv_entries--;
+
+ if (!dev)
+ d = &rx_alldev_list;
+ else
+ for (d = rx_dev_list; d; d = d->next)
+ if (d->dev == dev)
+ break;
+ d->entries--;
+
+ if(!d->entries)
+ d->dev = NULL; /* mark unused */
+
+out:
+ write_unlock(&rcv_lists_lock);
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,14)
+static int can_rcv(struct sk_buff *skb, struct net_device *dev,
+ struct packet_type *pt, struct net_device *orig_dev)
+#else
+static int can_rcv(struct sk_buff *skb, struct net_device *dev,
+ struct packet_type *pt)
+#endif
+{
+ struct rcv_dev_list *q;
+ 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);
+
+ /* update statistics */
+ stats.rx_frames++;
+ stats.rx_frames_delta++;
+
+ matches = can_rcv_filter(&rx_alldev_list, skb);
+
+ /* find receive list for this device */
+ for (q = rx_dev_list; q; q = q->next)
+ if (q->dev == dev)
+ break;
+
+ if (q)
+ matches += can_rcv_filter(q, skb);
+
+ DBG("freeing skbuff %p\n", skb);
+ kfree_skb(skb);
+
+ if (matches > 0) {
+ stats.matches++;
+ stats.matches_delta++;
+ }
+
+ return 0;
+}
+
+
+static inline void deliver(struct sk_buff *skb, struct rcv_list *p)
+{
+ struct sk_buff *clone = skb_clone(skb, GFP_ATOMIC);
+ DBG("skbuff %p cloned to %p\n", skb, clone);
+ if (clone) {
+ p->func(clone, p->data);
+ p->matches++; /* update specific statistics */
+ }
+}
+
+static int can_rcv_filter(struct rcv_dev_list *q, struct sk_buff *skb)
+{
+ struct rcv_list *p;
+ int matches = 0;
+ struct can_frame *cf = (struct can_frame*)skb->data;
+ canid_t can_id = cf->can_id;
+
+ if (q->entries == 0)
+ return 0;
+
+ /* check for unfiltered entries */
+ for (p = q->rx_all; p; p = p->next) {
+ DBG("match on rx_all skbuff %p\n", skb);
+ deliver(skb, p);
+ matches++;
+ }
+
+ /* check for can_id/mask entries */
+ for (p = q->rx_fil; p; p = p->next) {
+ if ((can_id & p->mask) == p->can_id) {
+ DBG("match on rx_fil skbuff %p\n", skb);
+ deliver(skb, p);
+ matches++;
+ }
+ }
+
+ /* check for inverted can_id/mask entries */
+ for (p = q->rx_inv; p; p = p->next) {
+ if ((can_id & p->mask) != p->can_id) {
+ DBG("match on rx_inv skbuff %p\n", skb);
+ deliver(skb, p);
+ matches++;
+ }
+ }
+
+ /* check CAN_ID specific entries */
+ if (can_id & CAN_EFF_FLAG) {
+ for (p = q->rx_eff; p; p = p->next) {
+ if (p->can_id == can_id) {
+ DBG("match on rx_eff skbuff %p\n", skb);
+ deliver(skb, p);
+ matches++;
+ }
+ }
+ } else {
+ for (p = q->rx_sff[can_id & CAN_SFF_MASK]; p; p = p->next) {
+ DBG("match on rx_sff skbuff %p\n", skb);
+ deliver(skb, p);
+ matches++;
+ }
+ }
+
+ return matches;
+}
+
+static struct rcv_list **find_rcv_list(canid_t *can_id, canid_t *mask, struct net_device *dev)
+{
+ canid_t inv = *can_id & CAN_INV_FILTER; /* save flag before masking values */
+ canid_t eff = *can_id & *mask & CAN_EFF_FLAG; /* correct EFF check? */
+ canid_t rtr = *can_id & *mask & CAN_RTR_FLAG; /* correct RTR check? */
+ struct rcv_dev_list *p;
+
+ /* make some paranoic operations */
+ if (*can_id & CAN_EFF_FLAG)
+ *mask &= (CAN_EFF_MASK | eff | rtr);
+ else
+ *mask &= (CAN_SFF_MASK | rtr);
+
+ *can_id &= *mask;
+
+ /* find receive list for this device */
+ if (!dev)
+ p = &rx_alldev_list;
+ else
+ for (p = rx_dev_list; p; p = p->next)
+ if (p->dev == dev)
+ break;
+
+ if (!p) {
+ /* arrange new rcv_dev_list for this device */
+
+ /* find deactivated receive list for this device */
+ for (p = rx_dev_list; p; p = p->next)
+ if (p->dev == NULL)
+ break;
+
+ if (p) {
+ DBG("reactivating rcv_dev_list for %s\n", dev->name);
+ p->dev = dev;
+ } else {
+ /* create new rcv_dev_list for this device */
+ DBG("creating new rcv_dev_list for %s\n", dev->name);
+ if (!(p = kmalloc(sizeof(struct rcv_dev_list), GFP_KERNEL))) {
+ printk(KERN_ERR "CAN: allocation of receive list failed\n");
+ return NULL;
+ }
+ memset (p, 0, sizeof(struct rcv_dev_list));
+ p->dev = dev;
+ p->next = rx_dev_list;
+ rx_dev_list = p;
+ }
+ }
+
+ if (inv) /* inverse can_id/can_mask filter and RTR */
+ return &p->rx_inv;
+
+ if (*can_id & CAN_RTR_FLAG) /* positive filter RTR */
+ return &p->rx_fil;
+
+ if (!(*mask)) /* mask == 0 => no filter */
+ return &p->rx_all;
+
+ if (*can_id & CAN_EFF_FLAG) {
+ if (*mask == CAN_EFF_MASK) /* filter exact EFF can_id */
+ return &p->rx_eff;
+ } else {
+ if (*mask == CAN_SFF_MASK) /* filter exact SFF can_id */
+ return &p->rx_sff[*can_id];
+ }
+
+ return &p->rx_fil; /* filter via can_id/can_mask */
+}
+
+/**************************************************/
+/* af_can utility stuff */
+/**************************************************/
+
+unsigned long timeval2jiffies(struct timeval *tv, int round_up)
+{
+ unsigned long jif;
+ unsigned long sec = tv->tv_sec;
+ unsigned long usec = tv->tv_usec;
+
+ if (sec > ULONG_MAX / HZ) /* check for overflow */
+ return ULONG_MAX;
+
+ if (round_up) /* any usec below one HZ? */
+ usec += 1000000 / HZ - 1; /* pump it up */
+
+ jif = usec / (1000000 / HZ);
+
+ if (sec * HZ > ULONG_MAX - jif) /* check for overflow */
+ return ULONG_MAX;
+ else
+ return jif + sec * HZ;
+}
+
+
+/**************************************************/
+/* af_can debugging stuff */
+/**************************************************/
+
+#ifdef DEBUG
+
+void can_debug_cframe(const char *msg, struct can_frame *cf, ...)
+{
+ va_list ap;
+ int len;
+ int dlc, i;
+ char buf[1024];
+
+ len = sprintf(buf, KERN_DEBUG);
+ va_start(ap, cf);
+ len += snprintf(buf + len, sizeof(buf) - 64, msg, ap);
+ buf[len++] = ':';
+ buf[len++] = ' ';
+ va_end(ap);
+
+ if ((dlc = cf->can_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);
+}
+
+void can_debug_skb(struct sk_buff *skb)
+{
+ int len, nbytes, i;
+ char buf[1024];
+
+ 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 < sizeof(buf) - 16) {
+ len += sprintf(buf + len, " %02x", skb->head[i]);
+ } else {
+ len += sprintf(buf + len, "...");
+ break;
+ }
+ }
+ buf[len++] = '\n';
+ buf[len] = '\0';
+ printk(buf);
+}
+
+EXPORT_SYMBOL(can_debug_cframe);
+EXPORT_SYMBOL(can_debug_skb);
+
+#endif
+
+/**************************************************/
+/* Exported symbols */
+/**************************************************/
+EXPORT_SYMBOL(can_proto_register);
+EXPORT_SYMBOL(can_proto_unregister);
+EXPORT_SYMBOL(can_rx_register);
+EXPORT_SYMBOL(can_rx_unregister);
+EXPORT_SYMBOL(can_dev_register);
+EXPORT_SYMBOL(can_dev_unregister);
+EXPORT_SYMBOL(can_send);
+EXPORT_SYMBOL(timeval2jiffies);
--- /dev/null
+/*
+ * af_can.h
+ *
+ * Copyright (c) 2002-2005 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 <llcf@volkswagen.de>
+ *
+ */
+
+#ifndef AF_CAN_H
+#define AF_CAN_H
+
+#ifdef __KERNEL__
+#include "version.h"
+RCSID("$Id: af_can.h,v 2.0 2006/04/13 10:37:19 ethuerm Exp $");
+#endif
+
+#ifdef __KERNEL__
+#include <linux/version.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/proc_fs.h>
+#include <linux/if.h>
+#else
+#include <net/if.h>
+#endif
+
+#include "can.h"
+
+#define PF_CAN 30
+#define AF_CAN PF_CAN
+
+#define CAN_TP16 1
+#define CAN_TP20 2
+#define CAN_RAW 3
+#define CAN_BCM 4
+#define CAN_MCNET 5
+#define CAN_ISOTP 6
+#define CAN_BAP 7
+#define CAN_MAX 8
+
+#define SOL_CAN_BASE 100
+
+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;
+ } can_addr;
+};
+
+struct can_filter {
+ canid_t can_id;
+ canid_t can_mask;
+};
+
+#define CAN_INV_FILTER 0x20000000U /* to be set in can_filter.can_id */
+
+#ifdef __KERNEL__
+
+#define CAN_PROC_DIR "sys/net/can" /* /proc/... */
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,13)
+struct can_proto {
+ struct proto_ops *ops;
+ struct proto *prot;
+};
+#else
+struct can_proto {
+ struct proto_ops *ops;
+ struct module *owner;
+ size_t obj_size;
+};
+#endif
+
+void can_proto_register(int proto, struct can_proto *cp);
+void can_proto_unregister(int proto);
+void can_rx_register(struct net_device *dev, canid_t can_id, canid_t mask,
+ void (*func)(struct sk_buff *, void *), void *data, char *ident);
+void can_rx_unregister(struct net_device *dev, canid_t can_id, canid_t mask,
+ void (*func)(struct sk_buff *, void *), void *data);
+void can_dev_register(struct net_device *dev,
+ void (*func)(unsigned long msg, void *), void *data);
+void can_dev_unregister(struct net_device *dev,
+ void (*func)(unsigned long msg, void *), void *data);
+int can_send(struct sk_buff *skb);
+
+unsigned long timeval2jiffies(struct timeval *tv, int round_up);
+
+void can_debug_skb(struct sk_buff *skb);
+void can_debug_cframe(const char *msg, struct can_frame *cframe, ...);
+
+/* af_can rx dispatcher structures */
+
+struct rcv_list {
+ struct rcv_list *next;
+ canid_t can_id;
+ canid_t mask;
+ unsigned long matches;
+ void (*func)(struct sk_buff *, void *);
+ void *data;
+ char *ident;
+};
+
+struct rcv_dev_list {
+ struct rcv_dev_list *next;
+ struct net_device *dev;
+ struct rcv_list *rx_all;
+ struct rcv_list *rx_fil;
+ struct rcv_list *rx_inv;
+ struct rcv_list *rx_sff[0x800];
+ struct rcv_list *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 rcv_entries;
+ unsigned long rcv_entries_max;
+}; /* persistent statistics */
+
+void can_init_proc(void);
+void can_remove_proc(void);
+
+#endif
+
+#endif /* AF_CAN_H */
--- /dev/null
+/*
+ * bcm.c
+ *
+ * Copyright (c) 2002-2005 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 <llcf@volkswagen.de>
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/net.h>
+#include <linux/netdevice.h>
+#include <linux/proc_fs.h>
+#include <linux/poll.h>
+#include <net/sock.h>
+
+#include "af_can.h"
+#include "version.h"
+#include "bcm.h"
+
+RCSID("$Id: bcm.c,v 2.0 2006/04/13 10:37:19 ethuerm Exp $");
+
+#ifdef DEBUG
+static int debug = 0;
+module_param(debug, int, S_IRUGO);
+#define DBG(args...) (debug & 1 ? \
+ (printk(KERN_DEBUG "BCM %s: ", __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
+
+/* use of last_frames[index].can_dlc */
+#define RX_RECV 0x40 /* received data for this element */
+#define RX_THR 0x80 /* this element has not been sent due to throttle functionality */
+#define BCM_CAN_DLC_MASK 0x0F /* clean flags by masking with BCM_CAN_DLC_MASK */
+
+#define NAME "Broadcast Manager (BCM) for LLCF"
+#define IDENT "bcm"
+static __initdata const char banner[] = BANNER(NAME);
+
+MODULE_DESCRIPTION(NAME);
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_AUTHOR("Oliver Hartkopp <oliver.hartkopp@volkswagen.de>");
+
+
+#define GET_U64(p) (*(unsigned long long*)(p)->data)
+
+struct bcm_op {
+ struct bcm_op *next;
+ canid_t can_id;
+ int flags;
+ unsigned long j_ival1, j_ival2, j_lastmsg;
+ unsigned long frames_abs, frames_filtered;
+ struct timeval ival1, ival2, stamp;
+ struct timer_list timer, thrtimer;
+ int count;
+ int nframes;
+ int currframe;
+ struct can_frame *frames;
+ struct can_frame *last_frames;
+ struct sock *sk;
+};
+
+struct bcm_user_data {
+ struct bcm_op *rx_ops;
+ struct bcm_op *tx_ops;
+ struct proc_dir_entry *bcm_proc_read;
+ char procname [9];
+};
+
+static struct proc_dir_entry *proc_dir = NULL;
+int bcm_read_proc(char *page, char **start, off_t off, int count, int *eof, void *data);
+
+static void bcm_notifier(unsigned long msg, void *data);
+static int bcm_release(struct socket *sock);
+static int bcm_connect(struct socket *sock, struct sockaddr *uaddr, int len,
+ int flags);
+static unsigned int bcm_poll(struct file *file, struct socket *sock,
+ poll_table *wait);
+static int bcm_sendmsg(struct kiocb *iocb, struct socket *sock,
+ struct msghdr *msg, size_t size);
+static int bcm_recvmsg(struct kiocb *iocb, struct socket *sock,
+ struct msghdr *msg, size_t size, int flags);
+
+static void bcm_tx_timeout_handler(unsigned long data);
+static void bcm_rx_handler(struct sk_buff *skb, void *op);
+static void bcm_rx_timeout_handler(unsigned long data);
+static void bcm_rx_thr_handler(unsigned long data);
+static struct bcm_op *bcm_find_op(struct bcm_op *ops, canid_t can_id);
+static void bcm_insert_op(struct bcm_op **ops, struct bcm_op *op);
+static void bcm_delete_tx_op(struct bcm_op **ops, canid_t can_id);
+static void bcm_delete_rx_op(struct bcm_op **ops, canid_t can_id);
+static void bcm_remove_op(struct bcm_op *op);
+static void bcm_can_tx(struct bcm_op *op);
+static void bcm_send_to_user(struct sock *sk, struct bcm_msg_head *head,
+ struct can_frame *frames, struct timeval *tv);
+static void bcm_rx_changed(struct bcm_op *op, struct can_frame *data);
+static void bcm_rx_starttimer(struct bcm_op *op);
+static void bcm_rx_update_and_send(struct bcm_op *op,
+ struct can_frame *lastdata,
+ struct can_frame *rxdata);
+static void bcm_rx_cmp_to_index(struct bcm_op *op, int index,
+ struct can_frame *rxdata);
+
+static struct proto_ops bcm_ops = {
+ .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 = bcm_poll,
+ .ioctl = 0,
+ .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,
+};
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,13)
+
+struct bcm_sock {
+ struct sock sk;
+ struct bcm_user_data opt;
+};
+
+#define bcm_sk(sk) ((struct bcm_user_data *)(sk)->sk_protinfo)
+
+static struct proto bcm_proto = {
+ .name = "CAN_BCM",
+ .owner = THIS_MODULE,
+ .obj_size = sizeof(struct bcm_sock),
+};
+
+static struct can_proto bcm_can_proto = {
+ .ops = &bcm_ops,
+ .prot = &bcm_proto,
+};
+
+#else
+
+#define bcm_sk(sk) ((struct bcm_user_data *)(sk)->sk_protinfo)
+
+static struct can_proto bcm_can_proto = {
+ .ops = &bcm_ops,
+ .owner = THIS_MODULE,
+ .obj_size = 0,
+};
+
+#endif
+
+
+static int __init bcm_init(void)
+{
+ printk(banner);
+
+ can_proto_register(CAN_BCM, &bcm_can_proto);
+
+ /* create /proc/can/bcm directory */
+ proc_dir = proc_mkdir(CAN_PROC_DIR"/"IDENT, NULL);
+
+ if (proc_dir)
+ proc_dir->owner = THIS_MODULE;
+
+ return 0;
+}
+
+static void __exit bcm_exit(void)
+{
+ can_proto_unregister(CAN_BCM);
+
+ if (proc_dir)
+ remove_proc_entry(CAN_PROC_DIR"/"IDENT, NULL);
+
+}
+
+static void bcm_notifier(unsigned long msg, void *data)
+{
+ struct sock *sk = (struct sock *)data;
+
+ DBG("called for sock %p\n", sk);
+
+ switch (msg)
+ {
+ case NETDEV_UNREGISTER:
+ sk->sk_bound_dev_if = 0;
+ case NETDEV_DOWN:
+ sk->sk_err = ENETDOWN;
+ sk->sk_error_report(sk);
+ }
+}
+
+static int bcm_release(struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+ struct bcm_user_data *ud = bcm_sk(sk);
+ struct bcm_op *op,*next;
+
+ /* many things to do here:
+ free all rx_ops and tx_ops, bcm_user_data structure,
+ can_rx_unregister(dev, canid, raw_rcv) and can-data in ?x_ops */
+
+ DBG("socket %p, sk %p\n", sock, sk);
+
+ /* remove userdata, bcm_ops, timer, etc. */
+
+ if (ud) {
+ for (op = ud->tx_ops; op ; op = next) {
+ DBG("removing tx_op (%p) for can_id <%03X>\n", op, op->can_id);
+ next = op->next;
+ bcm_remove_op(op);
+ }
+
+ for (op = ud->rx_ops; op ; op = next) {
+ DBG("removing rx_op (%p) for can_id <%03X>\n", op, op->can_id);
+ next = op->next;
+
+ if (sk->sk_bound_dev_if) {
+ struct net_device *dev = dev_get_by_index(sk->sk_bound_dev_if);
+ if (dev) {
+ can_rx_unregister(dev, op->can_id, 0xFFFFFFFFU, bcm_rx_handler, op);
+ dev_put(dev);
+ }
+ } else
+ DBG("sock %p not bound for can_rx_unregister()\n", sk);
+
+ bcm_remove_op(op);
+ }
+
+ if ((proc_dir) && (ud->bcm_proc_read)) {
+ remove_proc_entry(ud->procname, proc_dir);
+ }
+
+ kfree (ud);
+ sk->sk_protinfo = NULL;
+ }
+
+ if (sk->sk_bound_dev_if) {
+ struct net_device *dev = dev_get_by_index(sk->sk_bound_dev_if);
+ if (dev) {
+ can_dev_unregister(dev, bcm_notifier, sk);
+ dev_put(dev);
+ }
+ } else
+ DBG("sock %p not bound for can_dev_unregister()\n", 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 net_device *dev;
+ struct bcm_user_data *ud;
+
+ /* bind a device to this socket */
+
+ dev = dev_get_by_index(addr->can_ifindex);
+ if (!dev) {
+ DBG("could not find device %d\n", addr->can_ifindex);
+ return -ENODEV;
+ }
+ sk->sk_bound_dev_if = dev->ifindex;
+ can_dev_register(dev, bcm_notifier, sk);
+ dev_put(dev);
+
+ DBG("socket %p to device %s (idx %d)\n", sock, dev->name, dev->ifindex);
+
+ /* create struct for BCM-specific data for this socket */
+
+ if (!(ud = kmalloc(sizeof(struct bcm_user_data), GFP_KERNEL)))
+ return -ENOMEM;
+
+ /* intitial BCM operations */
+ ud->tx_ops = NULL;
+ ud->rx_ops = NULL;
+ ud->bcm_proc_read = NULL;
+
+ sk->sk_protinfo = ud;
+
+ if (proc_dir) {
+ sprintf(ud->procname, "%p", ud);
+ ud->bcm_proc_read = create_proc_read_entry(ud->procname, 0644,
+ proc_dir, bcm_read_proc, ud);
+ }
+
+ return 0;
+}
+
+int bcm_read_proc(char *page, char **start, off_t off, int count, int *eof, void *data)
+{
+ int len = 0;
+ struct bcm_user_data *ud = (struct bcm_user_data *) data;
+ struct bcm_op *op;
+ struct net_device *dev = NULL;
+
+ len += snprintf(page + len, PAGE_SIZE - len,">>> ud %p", ud);
+
+ if (ud->rx_ops) {
+ if (ud->rx_ops->sk->sk_bound_dev_if)
+ dev = dev_get_by_index(ud->rx_ops->sk->sk_bound_dev_if);
+ len += snprintf(page + len, PAGE_SIZE - len,
+ " / sk %p / socket %p", ud->rx_ops->sk, ud->rx_ops->sk->sk_socket);
+ }
+ else
+ if (ud->tx_ops) {
+ if (ud->tx_ops->sk->sk_bound_dev_if)
+ dev = dev_get_by_index(ud->tx_ops->sk->sk_bound_dev_if);
+ len += snprintf(page + len, PAGE_SIZE - len,
+ " / sk %p / socket %p", ud->tx_ops->sk, ud->tx_ops->sk->sk_socket);
+ }
+
+ if (dev) {
+ len += snprintf(page + len, PAGE_SIZE - len, " / %s", dev->name);
+ dev_put(dev);
+ }
+
+ len += snprintf(page + len, PAGE_SIZE - len, " <<<\n");
+
+ for (op = ud->rx_ops; op && (len < PAGE_SIZE - 100); op = op->next) {
+
+ unsigned long reduction;
+
+ /* print only active entries & prevent division by zero */
+ if (!op->frames_abs)
+ continue;
+
+ len += snprintf(page + len, PAGE_SIZE - len, "rx_op: %03X [%d]%c ",
+ op->can_id, op->nframes,(op->flags & RX_CHECK_DLC)?'d':' ');
+ if (op->j_ival1)
+ len += snprintf(page + len, PAGE_SIZE - len, "timeo=%ld ", op->j_ival1);
+
+ if (op->j_ival2)
+ len += snprintf(page + len, PAGE_SIZE - len, "thr=%ld ", op->j_ival2);
+
+ len += snprintf(page + len, PAGE_SIZE - len, "# recv %ld (%ld) => reduction: ",
+ op->frames_filtered, op->frames_abs);
+
+ reduction = 100 - (op->frames_filtered * 100) / op->frames_abs;
+
+ len += snprintf(page + len, PAGE_SIZE - len, "%s%ld%%\n",
+ (reduction == 100)?"near ":"", reduction);
+
+ if (len > PAGE_SIZE - 100) /* 100 Bytes before end of buffer */
+ len += snprintf(page + len, PAGE_SIZE - len, "(..)\n"); /* mark output cutted off */
+ }
+
+ for (op = ud->tx_ops; op && (len < PAGE_SIZE - 100); op = op->next) {
+
+ len += snprintf(page + len, PAGE_SIZE - len, "tx_op: %03X [%d] ",
+ op->can_id, op->nframes);
+ if (op->j_ival1)
+ len += snprintf(page + len, PAGE_SIZE - len, "t1=%ld ", op->j_ival1);
+
+ if (op->j_ival2)
+ len += snprintf(page + len, PAGE_SIZE - len, "t2=%ld ", op->j_ival2);
+
+ len += snprintf(page + len, PAGE_SIZE - len, "# sent %ld\n", op->frames_abs);
+
+ if (len > PAGE_SIZE - 100) /* 100 Bytes before end of buffer */
+ len += snprintf(page + len, PAGE_SIZE - len, "(..)\n"); /* mark output cutted off */
+ }
+
+ len += snprintf(page + len, PAGE_SIZE - len, "\n");
+
+ *eof = 1;
+ return len;
+}
+
+static unsigned int bcm_poll(struct file *file, struct socket *sock,
+ poll_table *wait)
+{
+ unsigned int mask = 0;
+
+ DBG("socket %p\n", sock);
+
+ mask = datagram_poll(file, sock, wait);
+ return mask;
+}
+
+static int bcm_sendmsg(struct kiocb *iocb, struct socket *sock,
+ struct msghdr *msg, size_t size)
+{
+ struct bcm_msg_head msg_head;
+ int i;
+ struct bcm_op *op;
+ int err;
+ struct sock *sk = sock->sk;
+ struct bcm_user_data *ud = bcm_sk(sk);
+ char c;
+ int rbytes = 0; /* read bytes as return value */
+
+ /* read message head information */
+
+ if ((err = memcpy_fromiovec((unsigned char*)&msg_head, msg->msg_iov,
+ sizeof(msg_head))) < 0)
+ return err;
+
+ DBG("opcode %d for can_id <%03X>\n", msg_head.opcode, msg_head.can_id);
+
+ if (!sk->sk_bound_dev_if) {
+ DBG("sock %p not bound\n", sk); /* and therefore ud not initialized */
+ return -ENOTCONN;
+ }
+
+ switch (msg_head.opcode) {
+
+ case TX_SETUP:
+
+ if (msg_head.nframes < 1) /* we need at least one can_frame */
+ return -EINVAL;
+
+ /* check the given can_id */
+
+ if (!(op = bcm_find_op(ud->tx_ops, msg_head.can_id))) {
+
+ /* insert new BCM operation for the given can_id */
+
+ if (!(op = kmalloc(sizeof(struct bcm_op), GFP_KERNEL)))
+ return -ENOMEM;
+
+ memset(op, 0, sizeof(struct bcm_op)); /* init to zero, e.g. for timers */
+
+ DBG("TX_SETUP: creating new tx_op (%p) for can_id <%03X>\n",
+ op, msg_head.can_id);
+
+ op->can_id = msg_head.can_id;
+
+ /* create array for can_frames and copy the data */
+ if (!(op->frames = kmalloc(msg_head.nframes * sizeof(struct can_frame), GFP_KERNEL))) {
+ kfree(op);
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < msg_head.nframes; i++) {
+ memcpy_fromiovec((unsigned char*)&op->frames[i], msg->msg_iov, sizeof(struct can_frame));
+ if (msg_head.flags & TX_CP_CAN_ID)
+ op->frames[i].can_id = msg_head.can_id; /* copy can_id into frame */
+ }
+
+ op->last_frames = NULL; /* tx_ops never compare with previous received messages */
+
+ op->sk = sk; /* bcm_can_tx / bcm_tx_timeout_handler needs this */
+
+ init_timer(&op->timer); /* initialize uninitialized (kmalloc) structure */
+ init_timer(&op->thrtimer); /* currently unused in tx_ops */
+
+ op->timer.function = bcm_tx_timeout_handler; /* handler for tx_ops */
+ op->timer.data = (unsigned long)op; /* timer.data points to this op-structure */
+
+ /* add this bcm_op to the list of the tx_ops */
+ bcm_insert_op(&ud->tx_ops, op);
+
+ }
+ else {
+ /* update existing BCM operation */
+
+ DBG("TX_SETUP: modifying existing tx_op (%p) for can_id <%03X>\n",
+ op, msg_head.can_id);
+
+ /* do we need more space for the can_frames? */
+ if (msg_head.nframes > op->nframes) {
+
+ /* yes => create new array */
+
+ struct can_frame *p;
+ if (!(p = kmalloc(msg_head.nframes * sizeof(struct can_frame), GFP_KERNEL)))
+ return -ENOMEM;
+
+ kfree (op->frames);
+ op->frames = p;
+ }
+
+ /* update can_frames content */
+ for (i = 0; i < msg_head.nframes; i++) {
+ memcpy_fromiovec((unsigned char*)&op->frames[i], msg->msg_iov, sizeof(struct can_frame));
+ if (msg_head.flags & TX_CP_CAN_ID)
+ op->frames[i].can_id = msg_head.can_id; /* copy can_id into frame */
+ }
+
+ }
+
+ if (op->nframes != msg_head.nframes) {
+ op->nframes = msg_head.nframes;
+ op->currframe = 0; /* start multiple frame transmission with index 0 */
+ }
+
+ /* check flags */
+
+ op->flags = msg_head.flags;
+
+ if (op->flags & TX_RESET_MULTI_IDX)
+ op->currframe = 0; /* start multiple frame transmission with index 0 */
+
+ if (op->flags & SETTIMER) {
+
+ /* set timer values */
+
+ op->count = msg_head.count;
+ op->ival1 = msg_head.ival1;
+ op->ival2 = msg_head.ival2;
+ op->j_ival1 = timeval2jiffies(&msg_head.ival1, 1);
+ op->j_ival2 = timeval2jiffies(&msg_head.ival2, 1);
+
+ DBG("TX_SETUP: SETTIMER count=%d j_ival1=%ld j_ival2=%ld\n",
+ op->count, op->j_ival1, op->j_ival2);
+
+ /* disable an active timer due to zero values? */
+ if (!op->j_ival1 && !op->j_ival2) {
+ del_timer(&op->timer);
+ DBG("TX_SETUP: SETTIMER disabled timer.\n");
+ }
+
+ }
+
+ if ((op->flags & STARTTIMER) && ((op->j_ival1 && op->count) || op->j_ival2)) {
+
+ del_timer(&op->timer);
+
+ op->flags |= TX_ANNOUNCE; /* spec: send can_frame when starting timer */
+ if (op->j_ival1 && (op->count > 0)){
+ op->timer.expires = jiffies + op->j_ival1;
+ /* op->count-- is done in bcm_tx_timeout_handler */
+ DBG("TX_SETUP: adding timer ival1. func=%p data=(%p) exp=0x%08X\n",
+ op->timer.function,
+ (char*) op->timer.data,
+ (unsigned int) op->timer.expires);
+ }
+ else{
+ op->timer.expires = jiffies + op->j_ival2;
+ DBG("TX_SETUP: adding timer ival2. func=%p data=(%p) exp=0x%08X\n",
+ op->timer.function,
+ (char*) op->timer.data,
+ (unsigned int) op->timer.expires);
+ }
+
+ add_timer(&op->timer);
+ }
+
+ if (op->flags & TX_ANNOUNCE)
+ bcm_can_tx(op);
+
+ rbytes = msg_head.nframes * sizeof(struct can_frame) + sizeof(struct bcm_msg_head);
+
+ break; /* TX_SETUP */
+
+ case TX_DELETE:
+
+ bcm_delete_tx_op(&ud->tx_ops, msg_head.can_id);
+
+ rbytes = sizeof(struct bcm_msg_head);
+
+ break; /* TX_DELETE */
+
+ case TX_READ:
+
+ /* reuse msg_head for the reply */
+ msg_head.opcode = TX_STATUS; /* reply to TX_READ */
+ op = bcm_find_op(ud->tx_ops, msg_head.can_id);
+ c = 'T'; /* for nice debug output ... */
+
+ goto TRX_READ;
+
+ case RX_READ:
+
+ /* reuse msg_head for the reply */
+ msg_head.opcode = RX_STATUS; /* reply to RX_READ */
+ op = bcm_find_op(ud->rx_ops, msg_head.can_id);
+ c = 'R'; /* for nice debug output ... */
+
+ TRX_READ:
+
+ /* check the given can_id */
+
+ if (!op) {
+ DBG("%cX_READ: did not find op for can_id <%03X>\n",
+ c, msg_head.can_id);
+
+ msg_head.flags |= CMD_ERROR;
+ msg_head.nframes = 0;
+ bcm_send_to_user(sk, &msg_head, NULL, NULL);
+ }
+ else {
+ DBG("%cX_READ: sending status for can_id <%03X>\n",
+ c, msg_head.can_id);
+
+ /* 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(sk, &msg_head, op->frames, NULL);
+ }
+
+ rbytes = sizeof(struct bcm_msg_head);
+
+ break; /* [T|R]X_READ */
+
+ case TX_SEND:
+ {
+ struct sk_buff *skb;
+ struct net_device *dev;
+
+ /* just copy and send one can_frame */
+
+ if (msg_head.nframes < 1) /* we need at least one can_frame */
+ return -EINVAL;
+
+ skb = alloc_skb(sizeof(struct can_frame), GFP_KERNEL);
+
+ if (!skb)
+ break;
+
+ memcpy_fromiovec(skb_put(skb, sizeof(struct can_frame)), msg->msg_iov, sizeof(struct can_frame));
+
+ DBG_FRAME("BCM: TX_SEND: sending frame",
+ (struct can_frame *)skb->data);
+ dev = dev_get_by_index(sk->sk_bound_dev_if);
+
+ if (dev) {
+ skb->dev = dev;
+ can_send(skb);
+ dev_put(dev);
+ }
+
+ rbytes = sizeof(struct can_frame) + sizeof(struct bcm_msg_head);
+ }
+ break;
+
+ case RX_SETUP:
+
+ if ((msg_head.flags & RX_FILTER_ID) || (!(msg_head.nframes))) {
+ /* be robust against wrong usage ... */
+ msg_head.flags |= RX_FILTER_ID;
+ msg_head.nframes = 0; /* ignore trailing garbage */
+ }
+
+ if ((msg_head.flags & RX_RTR_FRAME) &&
+ ((msg_head.nframes != 1) || (!(msg_head.can_id & CAN_RTR_FLAG)))) {
+
+ DBG("RX_SETUP: bad RX_RTR_FRAME setup!\n");
+
+ msg_head.flags |= CMD_ERROR; /* return msg_head back to sender */
+ msg_head.nframes = 0;
+ bcm_send_to_user(sk, &msg_head, NULL, NULL);
+
+ rbytes = sizeof(struct bcm_msg_head);
+
+ break;
+ }
+
+ /* check the given can_id */
+
+ if (!(op = bcm_find_op(ud->rx_ops, msg_head.can_id))) {
+
+ /* insert new BCM operation for the given can_id */
+
+ if (!(op = kmalloc(sizeof(struct bcm_op), GFP_KERNEL)))
+ return -ENOMEM;
+
+ memset(op, 0, sizeof(struct bcm_op)); /* init to zero, e.g. for timers */
+
+ DBG("RX_SETUP: creating new rx_op (%p) for can_id <%03X>\n",
+ op, msg_head.can_id);
+
+ op->can_id = msg_head.can_id;
+ op->nframes = msg_head.nframes;
+
+ if (op->nframes) {
+
+ /* create array for can_frames and copy the data */
+ if (!(op->frames = kmalloc(msg_head.nframes * sizeof(struct can_frame), GFP_KERNEL))) {
+ kfree(op);
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < msg_head.nframes; i++)
+ memcpy_fromiovec((unsigned char*)&op->frames[i], msg->msg_iov, sizeof(struct can_frame));
+
+ /* create array for received can_frames */
+ if (!(op->last_frames = kmalloc(msg_head.nframes * sizeof(struct can_frame), GFP_KERNEL))) {
+ kfree(op->frames);
+ kfree(op);
+ return -ENOMEM;
+ }
+
+ /* clear received can_frames to indicate 'nothing received' */
+ memset(op->last_frames, 0, msg_head.nframes * sizeof(struct can_frame));
+ }
+ else {
+ op->frames = NULL;
+
+ /* even when we have the RX_FILTER_ID case, we need to store the last frame */
+ /* for the throttle functionality */
+
+ /* create array for received can_frames */
+ if (!(op->last_frames = kmalloc(sizeof(struct can_frame), GFP_KERNEL)))
+ return -ENOMEM;
+
+ /* clear received can_frames to indicate 'nothing received' */
+ memset(op->last_frames, 0, sizeof(struct can_frame));
+ }
+
+ op->sk = sk; /* bcm_delete_rx_op() needs this */
+
+ init_timer(&op->timer); /* initialize uninitialized (kmalloc) structure */
+ init_timer(&op->thrtimer); /* init throttle timer for RX_CHANGED */
+
+ op->timer.function = bcm_rx_timeout_handler; /* handler for rx timeouts */
+ op->timer.data = (unsigned long)op; /* timer.data points to this op-structure */
+
+ op->thrtimer.function = bcm_rx_thr_handler; /* handler for RX_CHANGED throttle timeouts */
+ op->thrtimer.data = (unsigned long)op; /* timer.data points to this op-structure */
+ op->thrtimer.expires = 0; /* mark disabled timer */
+
+ /* add this bcm_op to the list of the tx_ops */
+ bcm_insert_op(&ud->rx_ops, op);
+
+ c=1; /* call can_rx_register() at end of RX_SETUP */
+
+ }
+ else {
+ /* update existing BCM operation */
+
+ DBG("RX_SETUP: modifying existing rx_op (%p) for can_id <%03X>\n",
+ op, msg_head.can_id);
+
+ /* do we need more space for the can_frames? */
+ if (msg_head.nframes > op->nframes) {
+
+ /* yes => create new arrays */
+
+ struct can_frame *p;
+
+ if (!(p = kmalloc(msg_head.nframes * sizeof(struct can_frame), GFP_KERNEL)))
+ return -ENOMEM;
+
+ if (op->frames)
+ kfree (op->frames);
+ op->frames = p;
+
+ if (!(p = kmalloc(msg_head.nframes * sizeof(struct can_frame), GFP_KERNEL)))
+ return -ENOMEM;
+ if (op->last_frames)
+ kfree (op->last_frames);
+ op->last_frames = p;
+ }
+
+ if (msg_head.nframes) {
+ /* update can_frames content */
+ for (i = 0; i < msg_head.nframes; i++)
+ memcpy_fromiovec((unsigned char*)&op->frames[i], msg->msg_iov, sizeof(struct can_frame));
+
+ /* clear received can_frames to indicate 'nothing received' */
+ memset(op->last_frames, 0, msg_head.nframes * sizeof(struct can_frame));
+ }
+
+ op->nframes = msg_head.nframes;
+ c=0; /* do not call can_rx_register() at end of RX_SETUP */
+
+ } /* if (!bcm_find_op(ud->tx_ops, msg_head.can_id)) */
+
+
+ /* check flags */
+
+ op->flags = msg_head.flags;
+
+ if (op->flags & RX_RTR_FRAME) {
+
+ /* no timers in RTR-mode */
+ del_timer(&op->thrtimer);
+ del_timer(&op->timer);
+
+ /* 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->j_ival1 = timeval2jiffies(&msg_head.ival1, 1);
+ op->ival2 = msg_head.ival2;
+ op->j_ival2 = timeval2jiffies(&msg_head.ival2, 1);
+
+ DBG("RX_SETUP: SETTIMER j_ival1=%ld j_ival2=%ld\n",
+ op->j_ival1, op->j_ival2);
+
+ /* disable an active timer due to zero value? */
+ if (!op->j_ival1) {
+ del_timer(&op->timer);
+ DBG("RX_SETUP: disabled timer for rx timeouts.\n");
+ }
+
+ /* free currently blocked msgs ? */
+ if (op->thrtimer.expires) { /* running throttle timer? */
+ DBG("RX_SETUP: unblocking throttled msgs.\n");
+ del_timer(&op->thrtimer);
+ op->thrtimer.expires = jiffies + 2; /* send blocked msgs hereafter */
+ add_timer(&op->thrtimer);
+ }
+ /* if (op->j_ival2) is zero, no (new) throttling will happen */
+ /* see bcm_rx_update_and_send() and bcm_rx_thr_handler() */
+ }
+
+ if ((op->flags & STARTTIMER) && op->j_ival1) {
+
+ del_timer(&op->timer);
+
+ op->timer.expires = jiffies + op->j_ival1;
+
+ DBG("RX_SETUP: adding timer ival1. func=%p data=(%p) exp=0x%08X\n",
+ (char *) op->timer.function,
+ (char *) op->timer.data,
+ (unsigned int) op->timer.expires);
+
+ add_timer(&op->timer);
+ }
+ }
+
+ /* now we can register for can_ids, if we added a new bcm_op */
+ if (c) {
+ struct net_device *dev = dev_get_by_index(sk->sk_bound_dev_if);
+
+ DBG("RX_SETUP: can_rx_register() for can_id <%03X>. rx_op is (%p)\n", op->can_id, op);
+
+ if (dev) {
+ can_rx_register(dev, op->can_id, 0xFFFFFFFFU, bcm_rx_handler, op, IDENT);
+ dev_put(dev);
+ }
+ }
+
+ rbytes = msg_head.nframes * sizeof(struct can_frame) + sizeof(struct bcm_msg_head);
+
+ break; /* RX_SETUP */
+
+ case RX_DELETE:
+
+ bcm_delete_rx_op(&ud->rx_ops, msg_head.can_id);
+
+ rbytes = sizeof(struct bcm_msg_head);
+
+ break; /* RX_DELETE */
+
+ default:
+
+ DBG("Unknown opcode %d\n", msg_head.opcode);
+
+ msg_head.flags |= CMD_ERROR; /* return msg_head back to sender */
+ msg_head.nframes = 0;
+ bcm_send_to_user(sk, &msg_head, NULL, NULL);
+
+ rbytes = sizeof(struct bcm_msg_head);
+
+ break;
+ }
+
+ return rbytes;
+}
+
+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;
+
+ DBG("socket %p, sk %p\n", sock, sk);
+
+ noblock = flags & MSG_DONTWAIT;
+ flags &= ~MSG_DONTWAIT;
+ if (!(skb = skb_recv_datagram(sk, flags, noblock, &error))) {
+ return error;
+ }
+
+ DBG("delivering skbuff %p\n", skb);
+ DBG_SKB(skb);
+
+ if (skb->len < size)
+ size = skb->len;
+ if ((err = memcpy_toiovec(msg->msg_iov, skb->data, size)) < 0) {
+ skb_free_datagram(sk, skb);
+ return err;
+ }
+
+ sock_recv_timestamp(msg, sk, skb);
+
+ DBG("freeing sock %p, skbuff %p\n", sk, skb);
+ skb_free_datagram(sk, skb);
+
+ return size;
+}
+
+static void bcm_tx_timeout_handler(unsigned long data)
+{
+ struct bcm_op *op = (struct bcm_op*)data;
+
+ DBG("Called with bcm_op (%p)\n", op);
+
+ if (op->j_ival1 && (op->count > 0)) {
+
+ op->count--;
+
+ if (!op->count && (op->flags & TX_COUNTEVT)) { /* create notification to user? */
+
+ struct bcm_msg_head msg_head;
+
+ DBG("sending TX_EXPIRED for can_id <%03X>\n", op->can_id);
+
+ 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->sk, &msg_head, NULL, NULL);
+ }
+ }
+
+ DBG("count=%d j_ival1=%ld j_ival2=%ld\n",
+ op->count, op->j_ival1, op->j_ival2);
+
+ if (op->j_ival1 && (op->count > 0)) {
+
+ op->timer.expires = jiffies + op->j_ival1;
+ add_timer(&op->timer);
+
+ DBG("adding timer ival1. func=%p data=(%p) exp=0x%08X\n",
+ op->timer.function,
+ (char*) op->timer.data,
+ (unsigned int) op->timer.expires);
+
+ bcm_can_tx(op); /* send (next) frame */
+ }
+ else {
+ if (op->j_ival2) {
+ op->timer.expires = jiffies + op->j_ival2;
+ add_timer(&op->timer);
+
+ DBG("adding timer ival2. func=%p data=(%p) exp=0x%08X\n",
+ op->timer.function,
+ (char*) op->timer.data,
+ (unsigned int) op->timer.expires);
+
+ bcm_can_tx(op); /* send (next) frame */
+ }
+ else
+ DBG("no timer restart\n");
+ }
+
+ return;
+
+}
+
+static void bcm_rx_handler(struct sk_buff *skb, void *data)
+{
+ struct bcm_op *op = (struct bcm_op*)data;
+ struct can_frame rxframe;
+ int i;
+
+ del_timer(&op->timer); /* disable timeout */
+
+ DBG("Called with bcm_op (%p)\n", op);
+
+ if (skb->len == sizeof(rxframe)) {
+ memcpy(&rxframe, skb->data, sizeof(rxframe));
+ skb_get_timestamp(skb, &op->stamp); /* save rx timestamp */
+ op->frames_abs++; /* statistics */
+ kfree_skb(skb);
+ DBG("got can_frame with can_id <%03X>\n", rxframe.can_id);
+ }
+ else {
+ DBG("Wrong skb->len = %d\n", skb->len);
+ kfree_skb(skb);
+ return;
+ }
+
+ DBG_FRAME("BCM: bcm_rx_handler: CAN frame", &rxframe);
+
+ if (op->can_id != rxframe.can_id) {
+ DBG("ERROR! Got wrong can_id <%03X>! Expected <%03X>.\n",
+ rxframe.can_id, op->can_id);
+ return;
+ }
+
+ if (op->flags & RX_RTR_FRAME) { /* send reply for RTR-request */
+ DBG("RTR-request\n");
+ bcm_can_tx(op); /* send op->frames[0] to CAN device */
+ return;
+ }
+
+ if (op->flags & RX_FILTER_ID) { /* the easiest case */
+ DBG("Easy does it with RX_FILTER_ID\n");
+ bcm_rx_update_and_send(op, &op->last_frames[0], &rxframe);
+ bcm_rx_starttimer(op);
+ return;
+ }
+
+ if (op->nframes == 1) { /* simple compare with index 0 */
+ DBG("Simple compare\n");
+ bcm_rx_cmp_to_index(op, 0, &rxframe);
+ bcm_rx_starttimer(op);
+ return;
+ }
+
+ if (op->nframes > 1) { /* multiplex compare */
+
+ DBG("Multiplex compare\n");
+ /* find the first multiplex mask that fits */
+ /* MUX-mask is in index 0 */
+
+ for (i=1; i < op->nframes; i++) {
+
+ if ((GET_U64(&op->frames[0]) & GET_U64(&rxframe)) ==
+ (GET_U64(&op->frames[0]) & GET_U64(&op->frames[i]))) {
+ DBG("found MUX index %d\n", i);
+ bcm_rx_cmp_to_index(op, i, &rxframe);
+ break;
+ }
+ }
+ bcm_rx_starttimer(op);
+ }
+}
+
+static void bcm_rx_cmp_to_index(struct bcm_op *op, int index,
+ struct can_frame *rxdata)
+{
+ /* no one uses the MSBs of can_dlc for comparation, */
+ /* so we use it here to detect the first time of reception */
+
+ if (!(op->last_frames[index].can_dlc & RX_RECV)) { /* first time? */
+ DBG("first time :)\n");
+ bcm_rx_update_and_send(op, &op->last_frames[index], rxdata);
+ return;
+ }
+
+ /* do a real check in can_data */
+
+ DBG("op->frames[index].data = 0x%016llx\n", GET_U64(&op->frames[index]));
+ DBG("op->last_frames[index].data = 0x%016llx\n",
+ GET_U64(&op->last_frames[index]));
+ DBG("rxdata->data = 0x%016llx\n", GET_U64(rxdata));
+
+ if ((GET_U64(&op->frames[index]) & GET_U64(rxdata)) !=
+ (GET_U64(&op->frames[index]) & GET_U64(&op->last_frames[index]))) {
+ DBG("relevant data change :)\n");
+ bcm_rx_update_and_send(op, &op->last_frames[index], rxdata);
+ return;
+ }
+
+
+ if (op->flags & RX_CHECK_DLC) {
+
+ /* do a real check in dlc */
+
+ if (rxdata->can_dlc != (op->last_frames[index].can_dlc & BCM_CAN_DLC_MASK)) {
+ DBG("dlc change :)\n");
+ bcm_rx_update_and_send(op, &op->last_frames[index], rxdata);
+ return;
+ }
+ }
+ DBG("no relevant change :(\n");
+}
+
+static void bcm_rx_update_and_send(struct bcm_op *op,
+ struct can_frame *lastdata,
+ struct can_frame *rxdata)
+{
+ unsigned long nexttx = op->j_lastmsg + op->j_ival2;
+
+ memcpy(lastdata, rxdata, sizeof(struct can_frame));
+ lastdata->can_dlc |= RX_RECV; /* mark as used */
+
+ /* throttle bcm_rx_changed ? */
+ if ((op->thrtimer.expires) || /* somebody else is already waiting OR */
+ ((op->j_ival2) && (nexttx > jiffies))) { /* we have to wait */
+
+ lastdata->can_dlc |= RX_THR; /* mark as 'throttled' */
+
+ if (!(op->thrtimer.expires)) { /* start only the first time */
+ op->thrtimer.expires = nexttx;
+ add_timer(&op->thrtimer);
+
+ DBG("adding thrtimer. func=%p data=(%p) exp=0x%08X\n",
+ op->thrtimer.function,
+ (char*) op->thrtimer.data,
+ (unsigned int) op->thrtimer.expires);
+ }
+ }
+ else
+ bcm_rx_changed(op, rxdata); /* send RX_CHANGED to the user */
+}
+
+static void bcm_rx_starttimer(struct bcm_op *op)
+{
+ if (op->flags & RX_NO_AUTOTIMER)
+ return;
+
+ if (op->j_ival1) {
+
+ op->timer.expires = jiffies + op->j_ival1;
+
+ DBG("adding rx timeout timer ival1. func=%p data=(%p) exp=0x%08X\n",
+ op->timer.function,
+ (char*) op->timer.data,
+ (unsigned int) op->timer.expires);
+
+ add_timer(&op->timer);
+ }
+}
+
+
+static void bcm_rx_changed(struct bcm_op *op, struct can_frame *data)
+{
+ struct bcm_msg_head head;
+
+ op->j_lastmsg = jiffies;
+ op->frames_filtered++; /* statistics */
+
+ if (op->frames_filtered > ULONG_MAX/100)
+ op->frames_filtered = op->frames_abs = 0; /* restart - spinlock ? */
+
+ DBG("setting j_lastmsg to 0x%08X for rx_op(%p)\n",
+ (unsigned int) op->j_lastmsg, op);
+ DBG("sending notification\n");
+
+ 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->sk, &head, data, &op->stamp);
+}
+
+
+static void bcm_rx_timeout_handler(unsigned long data)
+{
+ struct bcm_op *op = (struct bcm_op*)data;
+ struct bcm_msg_head msg_head;
+
+ DBG("sending RX_TIMEOUT for can_id <%03X>. op is (%p)\n", op->can_id, op);
+
+ 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->sk, &msg_head, NULL, NULL);
+
+ /* no restart of the timer is done here! */
+
+ /* if the 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 * sizeof(struct can_frame));
+ DBG("RX_ANNOUNCE_RESTART\n");
+ }
+
+}
+
+static void bcm_rx_thr_handler(unsigned long data)
+{
+ struct bcm_op *op = (struct bcm_op*)data;
+ int i = 0;
+
+ op->thrtimer.expires = 0; /* mark disabled / consumed timer */
+
+ if (op->nframes > 1){
+
+ DBG("sending MUX RX_CHANGED for can_id <%03X>. op is (%p)\n",
+ op->can_id, op);
+ /* for MUX filter we start at index 1 */
+ for (i=1; i<op->nframes; i++){
+ if ((op->last_frames) && (op->last_frames[i].can_dlc & RX_THR)){
+ op->last_frames[i].can_dlc &= ~RX_THR;
+ bcm_rx_changed(op, &op->last_frames[i]);
+ }
+ }
+ }
+ else{
+
+ DBG("sending simple RX_CHANGED for can_id <%03X>. op is (%p)\n",
+ op->can_id, op);
+ /* for RX_FILTER_ID and simple filter */
+ if (op->last_frames && (op->last_frames[0].can_dlc & RX_THR)){
+ op->last_frames[0].can_dlc &= ~RX_THR;
+ bcm_rx_changed(op, &op->last_frames[0]);
+ }
+ }
+}
+
+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];
+
+ DBG_FRAME("BCM: bcm_can_tx: sending frame", cf);
+
+ skb = alloc_skb(sizeof(struct can_frame), in_interrupt() ? GFP_ATOMIC : GFP_KERNEL);
+
+ if (!skb)
+ return;
+
+ memcpy(skb_put(skb, sizeof(struct can_frame)), cf, sizeof(struct can_frame));
+
+ if (op->sk->sk_bound_dev_if) {
+ dev = dev_get_by_index(op->sk->sk_bound_dev_if);
+
+ if (dev) {
+ skb->dev = dev;
+ can_send(skb);
+ dev_put(dev);
+ }
+ }
+
+ op->currframe++;
+ op->frames_abs++; /* statistics */
+
+ /* reached last frame? */
+ if (op->currframe >= op->nframes)
+ op->currframe = 0;
+
+}
+
+static void bcm_send_to_user(struct sock *sk, struct bcm_msg_head *head,
+ struct can_frame *frames, struct timeval *tv)
+{
+ struct sk_buff *skb;
+ struct can_frame *firstframe;
+ int datalen = head->nframes * sizeof(struct can_frame);
+ int err;
+
+ if (!sk) {
+ DBG("no sk available\n");
+ return;
+ }
+
+ skb = alloc_skb(sizeof(*head) + datalen, in_interrupt() ? GFP_ATOMIC : GFP_KERNEL);
+ memcpy(skb_put(skb, sizeof(*head)), head, sizeof(*head));
+ firstframe = (struct can_frame *) skb->tail; /* can_frames starting here */
+
+ if (tv)
+ skb_set_timestamp(skb, tv);
+
+ if (head->nframes){
+ 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 ((err = sock_queue_rcv_skb(sk, skb)) < 0) {
+ DBG("sock_queue_rcv_skb failed: %d\n", err);
+ kfree_skb(skb);
+ }
+}
+
+static struct bcm_op *bcm_find_op(struct bcm_op *ops, canid_t can_id)
+{
+ struct bcm_op *p;
+
+ for (p = ops; p; p = p->next)
+ if (p->can_id == can_id)
+ return p;
+
+ return NULL;
+}
+
+static void bcm_delete_rx_op(struct bcm_op **ops, canid_t can_id)
+{
+ struct bcm_op *p, **q;
+
+ for (q = ops; p = *q; q = &p->next)
+ if (p->can_id == can_id) {
+ *q = p->next;
+ DBG("removing rx_op (%p) for can_id <%03X>\n", p, p->can_id);
+
+ if (p->sk->sk_bound_dev_if) {
+ struct net_device *dev = dev_get_by_index(p->sk->sk_bound_dev_if);
+ if (dev) {
+ can_rx_unregister(dev, p->can_id, 0xFFFFFFFFU, bcm_rx_handler, p);
+ dev_put(dev);
+ }
+ } else
+ DBG("sock %p not bound for can_rx_unregister()\n", p->sk);
+
+ bcm_remove_op(p);
+ return;
+ }
+}
+
+static void bcm_delete_tx_op(struct bcm_op **ops, canid_t can_id)
+{
+ struct bcm_op *p, **q;
+
+ for (q = ops; p = *q; q = &p->next)
+ if (p->can_id == can_id) {
+ *q = p->next;
+ DBG("removing rx_op (%p) for can_id <%03X>\n", p, p->can_id);
+ bcm_remove_op(p);
+ return;
+ }
+}
+
+static void bcm_remove_op(struct bcm_op *op)
+{
+ del_timer(&op->timer);
+ del_timer(&op->thrtimer);
+ if (op->frames)
+ kfree(op->frames);
+ if (op->last_frames)
+ kfree(op->last_frames);
+ kfree(op);
+
+ return;
+}
+
+static void bcm_insert_op(struct bcm_op **ops, struct bcm_op *op)
+{
+ op->next = *ops;
+ *ops = op;
+}
+
+module_init(bcm_init);
+module_exit(bcm_exit);
--- /dev/null
+/*
+ * bcm.h
+ *
+ * Copyright (c) 2002-2005 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 <llcf@volkswagen.de>
+ *
+ */
+
+#ifndef BCM_H
+#define BCM_H
+
+#ifdef __KERNEL__
+#include "version.h"
+RCSID("$Id: bcm.h,v 2.0 2006/04/13 10:37:19 ethuerm Exp $");
+#endif
+
+struct bcm_msg_head {
+ int opcode; /* command */
+ int flags; /* special flags */
+ int count; /* run 'count' times ival1 then ival2 */
+ struct timeval ival1, ival2; /* intervals */
+ canid_t can_id; /* 32 Bit SFF/EFF. MSB set at EFF */
+ int nframes; /* number of following can_frame's */
+ struct can_frame frames[0];
+};
+
+enum {NO_OP,
+ TX_SETUP, TX_DELETE, TX_READ, TX_SEND, RX_SETUP, RX_DELETE, RX_READ,
+ TX_STATUS, TX_EXPIRED, RX_STATUS, RX_TIMEOUT, RX_CHANGED};
+
+#define SETTIMER 0x0001
+#define STARTTIMER 0x0002
+#define TX_COUNTEVT 0x0004
+#define TX_ANNOUNCE 0x0008
+#define TX_CP_CAN_ID 0x0010
+#define RX_FILTER_ID 0x0020
+#define RX_CHECK_DLC 0x0040
+#define RX_NO_AUTOTIMER 0x0080
+#define RX_ANNOUNCE_RESUME 0x0100
+#define TX_RESET_MULTI_IDX 0x0200
+#define RX_RTR_FRAME 0x0400
+
+#define CMD_ERROR 0x8000
+
+#endif /* BCM_H */
--- /dev/null
+/*
+ * can.h
+ *
+ * Copyright (c) 2002-2005 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 <llcf@volkswagen.de>
+ *
+ */
+
+#ifndef CAN_H
+#define CAN_H
+
+#ifdef __KERNEL__
+#include "version.h"
+RCSID("$Id: can.h,v 2.0 2006/04/13 10:37:19 ethuerm Exp $");
+#endif
+
+#include <linux/types.h>
+
+#define ETH_P_CAN 0x000c
+
+/* 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 */
+
+/* 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) */
+
+typedef __u32 canid_t;
+
+struct can_frame {
+ canid_t can_id; /* 32 bit CAN_ID + EFF/RTR flags */
+ __u8 can_dlc; /* data length code: 0 .. 8 */
+ __u8 data[8] __attribute__ ((aligned(8)));
+};
+
+#endif /* CAN_H */
--- /dev/null
+/*
+ * can_ioctl_h
+ *
+ * Copyright (c) 2002-2005 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 <llcf@volkswagen.de>
+ *
+ */
+
+#ifndef CAN_IOCTL_H
+#define CAN_IOCTL_H
+
+#ifdef __KERNEL__
+#include "version.h"
+RCSID("$Id: can_ioctl.h,v 2.0 2006/04/13 10:37:19 ethuerm Exp $");
+#endif
+
+#include <linux/sockios.h>
+
+
+/* max. 16 private ioctls */
+
+#define SIOCSRATE (SIOCDEVPRIVATE+0)
+#define SIOCGRATE (SIOCDEVPRIVATE+1)
+
+#define SIOCSMODE (SIOCDEVPRIVATE+2)
+#define SIOCGMODE (SIOCDEVPRIVATE+3)
+
+#define SIOCSFILTER (SIOCDEVPRIVATE+4)
+#define SIOCGFILTER (SIOCDEVPRIVATE+5)
+
+#define SIOCSTRX (SIOCDEVPRIVATE+6)
+#define SIOCGTRX (SIOCDEVPRIVATE+7)
+
+#define SIOCGSTATUS (SIOCDEVPRIVATE+8)
+#define SIOCGTRXSTATUS (SIOCDEVPRIVATE+9)
+
+#define SIOCGSTATS (SIOCDEVPRIVATE+10)
+
+/* parameters for ioctls */
+
+/* baudrate for CAN-controller */
+#define RATE_SPEED 0 /* parameter is in bits/second (speed 0: autoprobe) */
+#define RATE_BTREG 1 /* parameter is controller specific bit-timing register */
+
+/* operation modes for CAN-controller */
+#define MODE_OFFLINE 0
+#define MODE_RX 1
+#define MODE_TX 2
+#define MODE_TRX (MODE_TX | MODE_RX)
+#define MODE_LISTEN 4 /* no acknowledge on CAN layer */
+
+/* filter modes (may vary due to controller specific capabilities) */
+#define FILTER_CAPAB 0 /* get filter type capabilities (32 Bit value) */
+#define FILTER_MASK_VALUE 1 /* easy bit filter (see struct can_filter) */
+#define FILTER_SFF_BITMASK 2 /* bitfield with 2048 bit SFF filter */
+
+ /* filters 3 - 31 currently undefined */
+
+#define FILTER_MAX 31 /* max. filter type value */
+
+/* operation modes for CAN-transceiver */
+#define TRX_OPERATE 0 /* normal operation */
+#define TRX_STANDBY 1 /* standby */
+#define TRX_SLEEP 2 /* goto sleep */
+
+/* operating status of CAN-controller */
+#define STATUS_OK 0
+#define STATUS_WARNING 1 /* see parameter for additional info */
+#define STATUS_ERROR 2 /* see parameter for additional info */
+#define STATUS_ERROR_PASSIVE 3
+#define STATUS_BUS_OFF 4
+
+/* additional info for STATUS_ERROR */
+#define STATUS_ERR_BIT 0x00
+#define STATUS_ERR_FORM 0x01
+#define STATUS_ERR_STUFF 0x02
+#define STATUS_ERR_CRC 0x04
+#define STATUS_ERR_ACK 0x08
+#define STATUS_ERR_OTHER 0x10
+
+/* operating status of CAN-transceiver */
+#define TRXSTATUS_OK 0x000
+#define TRXSTATUS_SLEEP 0x001
+#define TRXSTATUS_CANH_NO_WIRE 0x002
+#define TRXSTATUS_CANH_SHORT_TO_BAT 0x004
+#define TRXSTATUS_CANH_SHORT_TO_VCC 0x008
+#define TRXSTATUS_CANH_SHORT_TO_GND 0x010
+#define TRXSTATUS_CANL_NO_WIRE 0x020
+#define TRXSTATUS_CANL_SHORT_TO_BAT 0x040
+#define TRXSTATUS_CANL_SHORT_TO_VCC 0x080
+#define TRXSTATUS_CANL_SHORT_TO_GND 0x100
+#define TRXSTATUS_CANL_SHORT_TO_CANH 0x200
+
+struct can_device_stats {
+
+ int error_warning;
+ int data_overrun;
+ int wakeup;
+ int bus_error;
+ int error_passive;
+ int arbitration_lost;
+ int restarts;
+ int bus_error_at_init;
+
+};
+
+#endif /* CAN_IOCTL_H */
--- /dev/null
+/*
+ * af_can_proc.c
+ *
+ * Copyright (c) 2002-2005 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 <llcf@volkswagen.de>
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/proc_fs.h>
+
+#include "af_can.h"
+#include "version.h"
+
+RCSID("$Id: proc.c,v 2.0 2006/04/13 10:37:19 ethuerm Exp $");
+
+/* proc filenames */
+
+#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"
+
+static void can_init_stats(int caller);
+static void can_stat_update(unsigned long data);
+
+static struct proc_dir_entry *can_create_proc_read_entry(const char *name, mode_t mode, read_proc_t* read_proc, void *data);
+static void can_remove_proc_entry(const char *name);
+static unsigned long calc_rate(unsigned long oldjif, unsigned long newjif, unsigned long count);
+
+static int can_proc_read_version(char *page, char **start, off_t off, int count, int *eof, void *data);
+static int can_proc_read_stats(char *page, char **start, off_t off, int count, int *eof, void *data);
+static int can_proc_read_reset_stats(char *page, char **start, off_t off, int count, int *eof, void *data);
+static int can_proc_read_rcvlist_all(char *page, char **start, off_t off, int count, int *eof, void *data);
+static int can_proc_read_rcvlist_fil(char *page, char **start, off_t off, int count, int *eof, void *data);
+static int can_proc_read_rcvlist_inv(char *page, char **start, off_t off, int count, int *eof, void *data);
+static int can_proc_read_rcvlist_sff(char *page, char **start, off_t off, int count, int *eof, void *data);
+static int can_proc_read_rcvlist_eff(char *page, char **start, off_t off, int count, int *eof, void *data);
+
+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;
+
+struct timer_list stattimer; /* timer for statistics update */
+
+struct s_stats stats; /* statistics */
+struct s_pstats pstats;
+
+extern struct rcv_dev_list *rx_dev_list; /* rx dispatcher structures */
+extern int stats_timer; /* module parameter. default: on */
+
+/**************************************************/
+/* procfs init / remove */
+/**************************************************/
+
+void can_init_proc(void)
+{
+
+ /* procfs init */
+
+ /* create /proc/can directory */
+ can_dir = proc_mkdir(CAN_PROC_DIR, NULL);
+
+ if (can_dir) {
+
+ can_dir->owner = THIS_MODULE;
+
+ /* own procfs entries from the AF_CAN core */
+ pde_version = can_create_proc_read_entry(CAN_PROC_VERSION, 0644, can_proc_read_version, NULL);
+ pde_stats = can_create_proc_read_entry(CAN_PROC_STATS, 0644, can_proc_read_stats, NULL);
+ pde_reset_stats = can_create_proc_read_entry(CAN_PROC_RESET_STATS, 0644, can_proc_read_reset_stats, NULL);
+ pde_rcvlist_all = can_create_proc_read_entry(CAN_PROC_RCVLIST_ALL, 0644, can_proc_read_rcvlist_all, NULL);
+ pde_rcvlist_fil = can_create_proc_read_entry(CAN_PROC_RCVLIST_FIL, 0644, can_proc_read_rcvlist_fil, NULL);
+ pde_rcvlist_inv = can_create_proc_read_entry(CAN_PROC_RCVLIST_INV, 0644, can_proc_read_rcvlist_inv, NULL);
+ pde_rcvlist_sff = can_create_proc_read_entry(CAN_PROC_RCVLIST_SFF, 0644, can_proc_read_rcvlist_sff, NULL);
+ pde_rcvlist_eff = can_create_proc_read_entry(CAN_PROC_RCVLIST_EFF, 0644, can_proc_read_rcvlist_eff, NULL);
+
+ if (stats_timer) {
+ /* the statistics are updated every second (timer triggered) */
+ stattimer.function = can_stat_update;
+ stattimer.data = 0;
+ stattimer.expires = jiffies + HZ; /* every second */
+ add_timer(&stattimer); /* start statistics timer */
+ }
+ } else
+ printk(KERN_INFO "af_can: failed to create CAN_PROC_DIR. CONFIG_PROC_FS missing?\n");
+}
+
+void can_remove_proc(void)
+{
+ /* procfs remove */
+ if (pde_version) {
+ can_remove_proc_entry(CAN_PROC_VERSION);
+ }
+
+ if (pde_stats) {
+ can_remove_proc_entry(CAN_PROC_STATS);
+ }
+
+ if (pde_reset_stats) {
+ can_remove_proc_entry(CAN_PROC_RESET_STATS);
+ }
+
+ if (pde_rcvlist_all) {
+ can_remove_proc_entry(CAN_PROC_RCVLIST_ALL);
+ }
+
+ if (pde_rcvlist_fil) {
+ can_remove_proc_entry(CAN_PROC_RCVLIST_FIL);
+ }
+
+ if (pde_rcvlist_inv) {
+ can_remove_proc_entry(CAN_PROC_RCVLIST_INV);
+ }
+
+ if (pde_rcvlist_sff) {
+ can_remove_proc_entry(CAN_PROC_RCVLIST_SFF);
+ }
+
+ if (pde_rcvlist_eff) {
+ can_remove_proc_entry(CAN_PROC_RCVLIST_EFF);
+ }
+
+ if (can_dir) {
+ remove_proc_entry(CAN_PROC_DIR, NULL);
+ }
+}
+
+/**************************************************/
+/* proc read functions */
+/**************************************************/
+
+int can_print_recv_list(char *page, int len, struct rcv_list *rx_list, struct net_device *dev)
+{
+ struct rcv_list *p;
+
+ if (rx_list) {
+ for (p = rx_list; p; p = p->next) {
+
+ /* can1. 00000000 00000000 00000000 .......0 tp20 */
+ if (p->can_id & CAN_EFF_FLAG) /* EFF & CAN_ID_ALL */
+ len += snprintf(page + len, PAGE_SIZE - len, " %-5s %08X %08x %08x %08x %8ld %s\n",
+ dev->name, p->can_id, p->mask, (unsigned int)p->func,
+ (unsigned int)p->data, p->matches, p->ident);
+ else
+ len += snprintf(page + len, PAGE_SIZE - len, " %-5s %03X %08x %08x %08x %8ld %s\n",
+ dev->name, p->can_id, p->mask, (unsigned int)p->func,
+ (unsigned int)p->data, p->matches, p->ident);
+
+ /* does a typical line fit into the current buffer? */
+ if (len > PAGE_SIZE - 100) { /* 100 Bytes before end of buffer */
+ len += snprintf(page + len, PAGE_SIZE - len, " (..)\n"); /* mark output cutted off */
+ return len;
+ }
+ }
+ }
+
+ 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;
+}
+
+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");
+
+ 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);
+
+ len += snprintf(page + len, PAGE_SIZE - len, "\n");
+
+ *eof = 1;
+ return len;
+}
+
+int can_proc_read_reset_stats(char *page, char **start, off_t off, int count, int *eof, void *data)
+{
+ int len = 0;
+
+ can_init_stats(1);
+
+ len += snprintf(page + len, PAGE_SIZE - len, "CAN statistic reset #%ld done.\n", pstats.stats_reset);
+
+ *eof = 1;
+ return len;
+}
+
+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,
+ "%06X [ Volkswagen AG - Low Level CAN Framework (LLCF) v%s ]\n",
+ LLCF_VERSION_CODE, VERSION);
+
+ *eof = 1;
+ return len;
+}
+
+int can_proc_read_rcvlist_all(char *page, char **start, off_t off, int count, int *eof, void *data)
+{
+ int len = 0;
+ struct rcv_dev_list *p;
+
+ /* RX_ALL */
+ len += snprintf(page + len, PAGE_SIZE - len, "\nreceive list 'rx_all':\n");
+
+ /* find receive list for this device */
+ for (p = rx_dev_list; p; p = p->next) {
+
+ if (p->rx_all) {
+ len = can_print_recv_banner(page, len);
+ len = can_print_recv_list(page, len, p->rx_all, p->dev);
+ } else
+ if (p->dev)
+ len += snprintf(page + len, PAGE_SIZE - len, " (%s: no entry)\n", p->dev->name);
+ }
+
+ len += snprintf(page + len, PAGE_SIZE - len, "\n");
+
+ *eof = 1;
+ return len;
+}
+
+int can_proc_read_rcvlist_fil(char *page, char **start, off_t off, int count, int *eof, void *data)
+{
+ int len = 0;
+ struct rcv_dev_list *p;
+
+ /* RX_FIL */
+ len += snprintf(page + len, PAGE_SIZE - len, "\nreceive list 'rx_fil':\n");
+
+ /* find receive list for this device */
+ for (p = rx_dev_list; p; p = p->next) {
+
+ if (p->rx_fil) {
+ len = can_print_recv_banner(page, len);
+ len = can_print_recv_list(page, len, p->rx_fil, p->dev);
+ } else
+ if (p->dev)
+ len += snprintf(page + len, PAGE_SIZE - len, " (%s: no entry)\n", p->dev->name);
+ }
+
+ len += snprintf(page + len, PAGE_SIZE - len, "\n");
+
+ *eof = 1;
+ return len;
+}
+
+int can_proc_read_rcvlist_inv(char *page, char **start, off_t off, int count, int *eof, void *data)
+{
+ int len = 0;
+ struct rcv_dev_list *p;
+
+ /* RX_INV */
+ len += snprintf(page + len, PAGE_SIZE - len, "\nreceive list 'rx_inv':\n");
+
+ /* find receive list for this device */
+ for (p = rx_dev_list; p; p = p->next) {
+
+ if (p->rx_inv) {
+ len = can_print_recv_banner(page, len);
+ len = can_print_recv_list(page, len, p->rx_inv, p->dev);
+ } else
+ if (p->dev)
+ len += snprintf(page + len, PAGE_SIZE - len, " (%s: no entry)\n", p->dev->name);
+ }
+
+ len += snprintf(page + len, PAGE_SIZE - len, "\n");
+
+ *eof = 1;
+ return len;
+}
+
+int can_proc_read_rcvlist_sff(char *page, char **start, off_t off, int count, int *eof, void *data)
+{
+ int len = 0;
+ unsigned long id = 0;
+ int i;
+ struct rcv_dev_list *p;
+
+ /* RX_SFF */
+ len += snprintf(page + len, PAGE_SIZE - len, "\nreceive list 'rx_sff':\n");
+
+ /* find receive list for this device */
+ for (p = rx_dev_list; p; p = p->next) {
+
+ for(i=0; i<0x800; i++)
+ id |= (unsigned long) p->rx_sff[i]; /* check if any entry available */
+
+ if (id) {
+ len = can_print_recv_banner(page, len);
+ for(i=0; i<0x800; i++) {
+ if ((p->rx_sff[i]) && (len < PAGE_SIZE - 100))
+ len = can_print_recv_list(page, len, p->rx_sff[i], p->dev);
+ }
+ } else
+ if (p->dev)
+ len += snprintf(page + len, PAGE_SIZE - len, " (%s: no entry)\n", p->dev->name);
+ }
+
+ len += snprintf(page + len, PAGE_SIZE - len, "\n");
+
+ *eof = 1;
+ return len;
+}
+
+int can_proc_read_rcvlist_eff(char *page, char **start, off_t off, int count, int *eof, void *data)
+{
+ int len = 0;
+ struct rcv_dev_list *p;
+
+ /* RX_EFF */
+ len += snprintf(page + len, PAGE_SIZE - len, "\nreceive list 'rx_eff':\n");
+
+ /* find receive list for this device */
+ for (p = rx_dev_list; p; p = p->next) {
+
+ if (p->rx_eff) {
+ len = can_print_recv_banner(page, len);
+ len = can_print_recv_list(page, len, p->rx_eff, p->dev);
+ } else
+ if (p->dev)
+ len += snprintf(page + len, PAGE_SIZE - len, " (%s: no entry)\n", p->dev->name);
+ }
+
+ len += snprintf(page + len, PAGE_SIZE - len, "\n");
+
+ *eof = 1;
+ return len;
+}
+
+/**************************************************/
+/* proc utility functions */
+/**************************************************/
+
+static struct proc_dir_entry *can_create_proc_read_entry(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_entry(const char *name)
+{
+ if (can_dir)
+ remove_proc_entry(name, can_dir);
+}
+
+static unsigned long calc_rate(unsigned long oldjif, unsigned long newjif, unsigned long count){
+
+ unsigned long ret = 0;
+
+ if (oldjif == newjif)
+ return 0;
+
+ if (count > (ULONG_MAX / HZ)) { /* see can_rcv() - this should NEVER happen! */
+ printk(KERN_ERR "af_can: calc_rate: count exceeded! %ld\n", count);
+ return 99999999;
+ }
+
+ ret = ((count * HZ) / (newjif - oldjif));
+
+ return ret;
+};
+
+/**************************************************/
+/* af_can statistics stuff */
+/**************************************************/
+
+static void can_init_stats(int caller){
+
+ memset(&stats, 0, sizeof(stats));
+ stats.jiffies_init = jiffies;
+ pstats.stats_reset++;
+};
+
+static void can_stat_update(unsigned long data){
+
+ unsigned long j = jiffies; /* snapshot */
+
+ //DBG("af_can: can_stat_update() jiffies = %ld\n", j);
+
+ if (j < stats.jiffies_init) /* jiffies overflow */
+ can_init_stats(2);
+
+ /* stats.rx_frames is the definitively max. statistic value */
+ if (stats.rx_frames > (ULONG_MAX / HZ)) /* prevent overflow in calc_rate() */
+ can_init_stats(3); /* restart */
+
+ if (stats.matches > (ULONG_MAX / 100)) /* matches overflow - very improbable */
+ can_init_stats(4);
+
+ /* 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 */
+ stattimer.expires = jiffies + HZ; /* every second */
+ add_timer(&stattimer);
+};
+
+/**************************************************/
+/* EOF */
+/**************************************************/
--- /dev/null
+/*
+ * raw.c
+ *
+ * Copyright (c) 2002-2005 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 <llcf@volkswagen.de>
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/version.h>
+#include <linux/init.h>
+#include <linux/net.h>
+#include <linux/netdevice.h>
+#include <linux/uio.h>
+#include <linux/poll.h>
+#include <net/sock.h>
+
+#include "af_can.h"
+#include "version.h"
+#include "raw.h"
+
+RCSID("$Id: raw.c,v 2.0 2006/04/13 10:37:19 ethuerm Exp $");
+
+
+#define NAME "RAW sockets for LLCF"
+#define IDENT "raw"
+static __initdata const char banner[] = BANNER(NAME);
+
+MODULE_DESCRIPTION(NAME);
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_AUTHOR("Urs Thuermann <urs.thuermann@volkswagen.de>");
+
+#ifdef DEBUG
+static int debug = 0;
+module_param(debug, int, S_IRUGO);
+#define DBG(args...) (debug & 1 ? \
+ (printk(KERN_DEBUG "RAW %s: ", __func__), \
+ printk(args)) : 0)
+#define DBG_SKB(skb) (debug & 4 ? can_debug_skb(skb) : 0)
+#else
+#define DBG(args...)
+#define DBG_SKB(skb)
+#endif
+
+static int raw_release(struct socket *sock);
+static int raw_bind (struct socket *sock, struct sockaddr *uaddr, int len);
+static int raw_getname(struct socket *sock, struct sockaddr *uaddr,
+ int *len, int peer);
+static unsigned int raw_poll(struct file *file, struct socket *sock,
+ poll_table *wait);
+static int raw_setsockopt(struct socket *sock, int level, int optname,
+ char *optval, int optlen);
+static int raw_getsockopt(struct socket *sock, int level, int optname,
+ char *optval, int *optlen);
+static int raw_sendmsg(struct kiocb *iocb, struct socket *sock,
+ struct msghdr *msg, size_t size);
+static int raw_recvmsg(struct kiocb *iocb, struct socket *sock,
+ struct msghdr *msg, size_t size, int flags);
+static void raw_rcv(struct sk_buff *skb, void *data);
+static void raw_notifier(unsigned long msg, void *data);
+
+static void raw_add_filters(struct net_device *dev, struct sock *sk);
+static void raw_remove_filters(struct net_device *dev, struct sock *sk);
+
+
+static struct proto_ops raw_ops = {
+ .family = PF_CAN,
+ .release = raw_release,
+ .bind = raw_bind,
+ .connect = sock_no_connect,
+ .socketpair = sock_no_socketpair,
+ .accept = sock_no_accept,
+ .getname = raw_getname,
+ .poll = raw_poll,
+ .ioctl = 0,
+ .listen = sock_no_listen,
+ .shutdown = sock_no_shutdown,
+ .setsockopt = raw_setsockopt,
+ .getsockopt = raw_getsockopt,
+ .sendmsg = raw_sendmsg,
+ .recvmsg = raw_recvmsg,
+ .mmap = sock_no_mmap,
+ .sendpage = sock_no_sendpage,
+};
+
+
+struct raw_opt {
+ int bound;
+ int ifindex;
+ int count;
+ struct can_filter *filter;
+};
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,13)
+
+struct raw_sock {
+ struct sock sk;
+ struct raw_opt opt;
+};
+
+#define canraw_sk(sk) (&((struct raw_sock *)(sk))->opt)
+
+static struct proto raw_proto = {
+ .name = "CAN_RAW",
+ .owner = THIS_MODULE,
+ .obj_size = sizeof(struct raw_sock),
+};
+
+static struct can_proto raw_can_proto = {
+ .ops = &raw_ops,
+ .prot = &raw_proto,
+};
+
+#else
+
+#define canraw_sk(sk) ((struct raw_opt *)(sk)->sk_protinfo)
+
+static struct can_proto raw_can_proto = {
+ .ops = &raw_ops,
+ .owner = THIS_MODULE,
+ .obj_size = sizeof(struct raw_opt),
+};
+
+#endif
+
+
+static __init int raw_init(void)
+{
+ printk(banner);
+
+ can_proto_register(CAN_RAW, &raw_can_proto);
+ return 0;
+}
+
+static __exit void raw_exit(void)
+{
+ can_proto_unregister(CAN_RAW);
+}
+
+static int raw_release(struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+ struct net_device *dev = NULL;
+
+ DBG("socket %p, sk %p, refcnt %d\n", sock, sk,
+ atomic_read(&sk->sk_refcnt));
+
+ if (canraw_sk(sk)->bound && canraw_sk(sk)->ifindex)
+ dev = dev_get_by_index(canraw_sk(sk)->ifindex);
+
+ /* remove current filters & unregister */
+ if (canraw_sk(sk)->count > 0) {
+ if (canraw_sk(sk)->bound)
+ raw_remove_filters(dev, sk);
+ kfree(canraw_sk(sk)->filter);
+ } else if (canraw_sk(sk)->bound)
+ can_rx_unregister(dev, 0, 0, raw_rcv, sk);
+
+ if (dev) {
+ can_dev_unregister(dev, raw_notifier, sk);
+ dev_put(dev);
+ }
+
+ sock_put(sk);
+
+ return 0;
+}
+
+static int raw_bind(struct socket *sock, struct sockaddr *uaddr, int len)
+{
+ struct sockaddr_can *addr = (struct sockaddr_can *)uaddr;
+ struct sock *sk = sock->sk;
+ struct net_device *dev;
+
+ DBG("socket %p to device %d\n", sock, addr->can_ifindex);
+
+ if (len < sizeof(*addr))
+ return -EINVAL;
+
+ if (canraw_sk(sk)->bound) {
+#if 1
+ return -EOPNOTSUPP;
+#else
+ /* remove current bindings */
+ if (canraw_sk(sk)->ifindex) {
+ dev = dev_get_by_index(canraw_sk(sk)->ifindex);
+ ; /* remove notifier */
+ } else
+ dev = NULL;
+ if (canraw_sk(sk)->count > 0) {
+ raw_remove_filters(sk);
+ } else {
+ can_rx_unregister(dev, 0, 0, raw_rcv, sk);
+ }
+ if (dev)
+ dev_put(dev);
+ canraw_sk(sk)->bound = 0;
+#endif
+ }
+
+ if (addr->can_ifindex) {
+ dev = dev_get_by_index(addr->can_ifindex);
+ if (!dev) {
+ DBG("could not find device %d\n", addr->can_ifindex);
+ return -ENODEV;
+ }
+ if (!(dev->flags & IFF_UP)) {
+ sk->sk_err = ENETDOWN;
+ sk->sk_error_report(sk);
+ goto out;
+ }
+ can_dev_register(dev, raw_notifier, sk);
+ } else
+ dev = NULL;
+
+ canraw_sk(sk)->ifindex = addr->can_ifindex;
+
+ if (canraw_sk(sk)->count > 0) /* filters set by setsockopt */
+ raw_add_filters(dev, sk);
+ else
+ can_rx_register(dev, 0, 0, raw_rcv, sk, IDENT);
+
+ canraw_sk(sk)->bound = 1;
+
+ out:
+ if (dev)
+ dev_put(dev);
+
+ return 0;
+}
+
+static int raw_getname(struct socket *sock, struct sockaddr *uaddr,
+ int *len, int peer)
+{
+ struct sockaddr_can *addr = (struct sockaddr_can *)uaddr;
+ struct sock *sk = sock->sk;
+
+ if (peer)
+ return -EOPNOTSUPP;
+
+ addr->can_family = AF_CAN;
+ addr->can_ifindex = canraw_sk(sk)->ifindex;
+ *len = sizeof(*addr);
+
+ return 0;
+}
+
+static unsigned int raw_poll(struct file *file, struct socket *sock,
+ poll_table *wait)
+{
+ unsigned int mask = 0;
+
+ DBG("socket %p\n", sock);
+
+ mask = datagram_poll(file, sock, wait);
+ return mask;
+}
+
+static int raw_setsockopt(struct socket *sock, int level, int optname,
+ char *optval, int optlen)
+{
+ struct sock *sk = sock->sk;
+ struct can_filter *filter = NULL;
+ struct net_device *dev = NULL;
+ int count = 0;
+ int err;
+
+ if (level != SOL_CAN_RAW)
+ return -EINVAL;
+
+ switch (optname) {
+ case CAN_RAW_FILTER:
+ if (optlen) {
+ if (optlen % sizeof(struct can_filter) != 0)
+ return -EINVAL;
+ if (!(filter = kmalloc(optlen, GFP_KERNEL)))
+ return -ENOMEM;
+ if (err = copy_from_user(filter, optval, optlen)) {
+ kfree(filter);
+ return err;
+ }
+ count = optlen / sizeof(struct can_filter);
+ }
+
+ if (canraw_sk(sk)->bound && canraw_sk(sk)->ifindex)
+ dev = dev_get_by_index(canraw_sk(sk)->ifindex);
+
+ /* remove current filters & unregister */
+ if (canraw_sk(sk)->count > 0) {
+
+ if (canraw_sk(sk)->bound)
+ raw_remove_filters(dev, sk);
+
+ kfree(canraw_sk(sk)->filter);
+ canraw_sk(sk)->count = 0;
+ canraw_sk(sk)->filter = NULL;
+ } else if (canraw_sk(sk)->bound)
+ can_rx_unregister(dev, 0, 0, raw_rcv, sk);
+
+ /* add new filters & register */
+ if (optlen) {
+ canraw_sk(sk)->filter = filter;
+ canraw_sk(sk)->count = count;
+ if (canraw_sk(sk)->bound)
+ raw_add_filters(dev, sk);
+ } else if (canraw_sk(sk)->bound)
+ can_rx_register(dev, 0, 0, raw_rcv, sk, IDENT);
+
+ if (dev)
+ dev_put(dev);
+
+ break;
+
+ default:
+ return -ENOPROTOOPT;
+ }
+ return 0;
+}
+
+static int raw_getsockopt(struct socket *sock, int level, int optname,
+ char *optval, int *optlen)
+{
+ struct sock *sk = sock->sk;
+ struct can_filter *filter = canraw_sk(sk)->filter;
+ int len;
+
+ if (level != SOL_CAN_RAW)
+ return -EINVAL;
+
+ switch (optname) {
+ case CAN_RAW_FILTER:
+ if (get_user(len, optlen))
+ return -EFAULT;
+
+ if (filter) {
+ int filter_size = canraw_sk(sk)->count * sizeof(struct can_filter);
+ if (len < filter_size)
+ return -EINVAL;
+ if (len > filter_size)
+ len = filter_size;
+ if (copy_to_user(optval, filter, len))
+ return -EFAULT;
+ } else
+ len = 0;
+ if (put_user(len, optlen))
+ return -EFAULT;
+ break;
+
+ default:
+ return -ENOPROTOOPT;
+ }
+ return 0;
+}
+
+static void raw_add_filters(struct net_device *dev, struct sock *sk)
+{
+ struct can_filter *filter = canraw_sk(sk)->filter;
+ int i;
+
+ for (i = 0; i < canraw_sk(sk)->count; i++) {
+ can_rx_register(dev, filter[i].can_id, filter[i].can_mask,
+ raw_rcv, sk, IDENT);
+ DBG("filter can_id %08X, can_mask %08X%s, sk %p\n",
+ filter[i].can_id, filter[i].can_mask,
+ filter[i].can_id & CAN_INV_FILTER ? " (inv)" : "", sk);
+ }
+}
+
+static void raw_remove_filters(struct net_device *dev, struct sock *sk)
+{
+ struct can_filter *filter = canraw_sk(sk)->filter;
+ int i;
+
+ for (i = 0; i < canraw_sk(sk)->count; i++) {
+ can_rx_unregister(dev, filter[i].can_id, filter[i].can_mask,
+ raw_rcv, sk);
+ DBG("filter can_id %08X, can_mask %08X%s, sk %p\n",
+ filter[i].can_id, filter[i].can_mask,
+ filter[i].can_id & CAN_INV_FILTER ? " (inv)" : "", sk);
+ }
+}
+
+static int raw_sendmsg(struct kiocb *iocb, struct socket *sock,
+ struct msghdr *msg, size_t size)
+{
+ struct sock *sk = sock->sk;
+ struct sk_buff *skb;
+ struct net_device *dev;
+ int ifindex;
+ int err;
+
+ DBG("socket %p, sk %p\n", sock, sk);
+
+ if (msg->msg_name) {
+ struct sockaddr_can *addr = (struct sockaddr_can *)msg->msg_name;
+ if (addr->can_family != AF_CAN)
+ return -EINVAL;
+ ifindex = addr->can_ifindex;
+ } else
+ ifindex = canraw_sk(sk)->ifindex;
+
+ if (!(dev = dev_get_by_index(ifindex))) {
+ DBG("device %d not found\n", ifindex);
+ return -ENXIO;
+ }
+
+ skb = alloc_skb(size, GFP_KERNEL);
+ if ((err = memcpy_fromiovec(skb_put(skb, size), msg->msg_iov, size)) < 0) {
+ kfree_skb(skb);
+ return err;
+ }
+ skb->dev = dev;
+
+ DBG("sending skbuff to interface %d\n", ifindex);
+ DBG_SKB(skb);
+
+ err = can_send(skb);
+
+ dev_put(dev);
+
+ if (err)
+ return err;
+
+ return size;
+}
+
+static int raw_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;
+
+ DBG("socket %p, sk %p\n", sock, sk);
+
+ noblock = flags & MSG_DONTWAIT;
+ flags &= ~MSG_DONTWAIT;
+ if (!(skb = skb_recv_datagram(sk, flags, noblock, &error))) {
+ return error;
+ }
+
+ DBG("delivering skbuff %p\n", skb);
+ DBG_SKB(skb);
+
+ if (size < skb->len)
+ msg->msg_flags |= MSG_TRUNC;
+ else
+ size = skb->len;
+ if ((error = memcpy_toiovec(msg->msg_iov, skb->data, size)) < 0) {
+ skb_free_datagram(sk, skb);
+ return error;
+ }
+
+ sock_recv_timestamp(msg, sk, skb);
+
+ if (msg->msg_name) {
+ msg->msg_namelen = sizeof(struct sockaddr_can);
+ memcpy(msg->msg_name, skb->cb, msg->msg_namelen);
+ }
+
+ DBG("freeing sock %p, skbuff %p\n", sk, skb);
+ skb_free_datagram(sk, skb);
+
+ return size;
+}
+
+static void raw_rcv(struct sk_buff *skb, void *data)
+{
+ struct sock *sk = (struct sock*)data;
+ struct sockaddr_can *addr;
+ int error;
+
+ DBG("received skbuff %p, sk %p\n", skb, sk);
+ DBG_SKB(skb);
+
+ addr = (struct sockaddr_can *)skb->cb;
+ memset(addr, 0, sizeof(*addr));
+ addr->can_family = AF_CAN;
+ addr->can_ifindex = skb->dev->ifindex;
+
+ if ((error = sock_queue_rcv_skb(sk, skb)) < 0) {
+ DBG("sock_queue_rcv_skb failed: %d\n", error);
+ DBG("freeing skbuff %p\n", skb);
+ kfree_skb(skb);
+ }
+}
+
+static void raw_notifier(unsigned long msg, void *data)
+{
+ struct sock *sk = (struct sock *)data;
+
+ DBG("called for sock %p\n", sk);
+
+ switch (msg) {
+ case NETDEV_UNREGISTER:
+ canraw_sk(sk)->ifindex = 0;
+ /* fallthrough */
+ case NETDEV_DOWN:
+ sk->sk_err = ENETDOWN;
+ sk->sk_error_report(sk);
+ break;
+ }
+}
+
+
+module_init(raw_init);
+module_exit(raw_exit);
--- /dev/null
+/*
+ * raw.h
+ *
+ * Copyright (c) 2002-2005 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 <llcf@volkswagen.de>
+ *
+ */
+
+#ifndef RAW_H
+#define RAW_H
+
+#ifdef __KERNEL__
+#include "version.h"
+RCSID("$Id: raw.h,v 2.0 2006/04/13 10:37:20 ethuerm Exp $");
+#endif
+
+#include "af_can.h"
+
+
+#define SOL_CAN_RAW (SOL_CAN_BASE + CAN_RAW)
+#define CAN_RAW_FILTER 1
+
+#endif
--- /dev/null
+/*
+ * vcan.c
+ *
+ * Copyright (c) 2002-2005 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 <llcf@volkswagen.de>
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/netdevice.h>
+#include <linux/if_arp.h>
+
+#include "af_can.h"
+#include "version.h"
+
+RCSID("$Id: vcan.c,v 2.0 2006/04/13 10:37:20 ethuerm Exp $");
+
+
+#define NAME "VCAN loopback interface for LLCF"
+static __initdata const char banner[] = BANNER(NAME);
+
+MODULE_DESCRIPTION(NAME);
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_AUTHOR("Urs Thuermann <urs.thuermann@volkswagen.de>");
+
+#ifdef DEBUG
+static int debug = 0;
+module_param(debug, int, S_IRUGO);
+#define DBG(args...) (debug & 1 ? \
+ (printk(KERN_DEBUG "VCAN %s: ", __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
+
+/* This 'undef' makes the vcan a kind of NULL device. Since LLCF v0.6 */
+/* the local loopback is implemented in af_can.c for all interfaces. */
+#undef DO_LOOPBACK
+
+#define NDEVICES 4
+
+static struct net_device *vcan_devs[NDEVICES];
+
+static int vcan_open(struct net_device *dev)
+{
+ DBG("%s: interface up\n", dev->name);
+
+ netif_start_queue(dev);
+ return 0;
+}
+
+static int vcan_stop(struct net_device *dev)
+{
+ DBG("%s: interface down\n", dev->name);
+
+ netif_stop_queue(dev);
+ return 0;
+}
+
+#ifdef DO_LOOPBACK
+
+static void vcan_rx(struct sk_buff *skb, struct net_device *dev)
+{
+ struct net_device_stats *stats = netdev_priv(dev);
+ stats->rx_packets++;
+ stats->rx_bytes += skb->len;
+
+ skb->protocol = htons(ETH_P_CAN);
+ skb->dev = dev;
+ skb->ip_summed = CHECKSUM_UNNECESSARY;
+
+ DBG("received skbuff on interface %d\n", dev->ifindex);
+ DBG_SKB(skb);
+
+ netif_rx(skb);
+}
+
+#endif
+
+
+static int vcan_tx(struct sk_buff *skb, struct net_device *dev)
+{
+ struct net_device_stats *stats = netdev_priv(dev);
+
+ DBG("sending skbuff on interface %s\n", dev->name);
+ DBG_SKB(skb);
+ DBG_FRAME("VCAN: transmit CAN frame", (struct can_frame *)skb->data);
+
+#ifdef DO_LOOPBACK
+ if (atomic_read(&skb->users) != 1) {
+ struct sk_buff *old_skb = skb;
+ skb = skb_clone(old_skb, GFP_ATOMIC);
+ DBG(" freeing old skbuff %p, using new skbuff %p\n", old_skb, skb);
+ kfree_skb(old_skb);
+ if (!skb) {
+ return 0;
+ }
+ } else
+ skb_orphan(skb);
+#endif
+
+ stats->tx_packets++;
+ stats->tx_bytes += skb->len;
+#ifdef DO_LOOPBACK
+ vcan_rx(skb, dev);
+#else
+ stats->rx_packets++;
+ stats->rx_bytes += skb->len;
+ kfree_skb(skb);
+#endif
+ return 0;
+}
+
+static int vcan_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
+{
+ return -EOPNOTSUPP;
+}
+
+static int vcan_rebuild_header(struct sk_buff *skb)
+{
+ DBG("called on skbuff %p\n", skb);
+ DBG_SKB(skb);
+ return 0;
+}
+
+static int vcan_header(struct sk_buff *skb, struct net_device *dev,
+ unsigned short type, void *daddr, void *saddr,
+ unsigned int len)
+{
+ DBG("called skbuff %p device %p\n", skb, dev);
+ DBG_SKB(skb);
+ return 0;
+}
+
+
+static struct net_device_stats *vcan_get_stats(struct net_device *dev)
+{
+ struct net_device_stats *stats = netdev_priv(dev);
+ return stats;
+}
+
+static void vcan_init(struct net_device *dev)
+{
+ DBG("dev %s\n", dev->name);
+
+ ether_setup(dev);
+
+ memset(dev->priv, 0, sizeof(struct net_device_stats));
+
+ dev->open = vcan_open;
+ dev->stop = vcan_stop;
+ dev->set_config = NULL;
+ dev->hard_start_xmit = vcan_tx;
+ dev->do_ioctl = vcan_ioctl;
+ dev->get_stats = vcan_get_stats;
+
+ dev->mtu = sizeof(struct can_frame);
+ dev->flags = IFF_LOOPBACK;
+ dev->hard_header = vcan_header;
+ dev->rebuild_header = vcan_rebuild_header;
+ dev->hard_header_cache = NULL;
+ dev->type = ARPHRD_LOOPBACK;
+
+ SET_MODULE_OWNER(dev);
+}
+
+static __init int vcan_init_module(void)
+{
+ int i, ndev = 0, result;
+
+ printk(banner);
+
+ for (i = 0; i < NDEVICES; i++) {
+ if (!(vcan_devs[i] = alloc_netdev(sizeof(struct net_device_stats),
+ "vcan%d", vcan_init)))
+ printk(KERN_ERR "vcan: error allocating net_device\n");
+ else if (result = register_netdev(vcan_devs[i])) {
+ printk(KERN_ERR "vcan: error %d registering interface %s\n",
+ result, vcan_devs[i]->name);
+ free_netdev(vcan_devs[i]);
+ } else {
+ DBG("successfully registered interface %s\n", vcan_devs[i]->name);
+ ndev++;
+ }
+ }
+ return ndev ? 0 : -ENODEV;
+}
+
+static __exit void vcan_cleanup_module(void)
+{
+ int i;
+ for (i = 0; i < NDEVICES; i++) {
+ unregister_netdev(vcan_devs[i]);
+ free_netdev(vcan_devs[i]);
+ }
+}
+
+module_init(vcan_init_module);
+module_exit(vcan_cleanup_module);
--- /dev/null
+/*
+ * version.h
+ *
+ * Copyright (c) 2002-2005 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 <llcf@volkswagen.de>
+ *
+ */
+
+#ifndef VERSION_H
+#define VERSION_H
+
+#define RCSID(s) asm(".section .rodata.str1.1,\"aMS\",@progbits,1\n\t" \
+ ".string \"" s "\"\n\t.previous\n")
+
+RCSID("$Id: version.h,v 2.0 2006/04/13 10:37:20 ethuerm Exp $");
+
+#define MAJORVERSION 1
+#define MINORVERSION 1
+#define PATCHLEVEL 0
+#define EXTRAVERSION ""
+
+#define LLCF_VERSION_CODE (((MAJORVERSION) << 16) + ((MINORVERSION) << 8) \
+ + (PATCHLEVEL))
+
+/* stringification: these are the usual macros to stringify with macro
+ expansion. The str() macro does the expansion, the xstr() macro is
+ for the actual stringification.
+*/
+#define str(arg) xstr(arg)
+#define xstr(arg) #arg
+
+#define VERSION str(MAJORVERSION) "." str(MINORVERSION) "." str(PATCHLEVEL) \
+ EXTRAVERSION
+#define BANNER(name) KERN_INFO name " v" VERSION "\n"
+
+#endif /* VERSION_H */
--- /dev/null
+#
+# $Id: Makefile,v 2.0 2006/04/13 10:37:21 ethuerm Exp $
+#
+# Copyright (c) 2002-2005 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 <llcf@volkswagen.de>
+
+ifeq ($(KERNELRELEASE),)
+
+KERNELDIR = /usr/src/linux
+PWD = $(shell pwd)
+
+default:
+ $(MAKE) -C $(KERNELDIR) SUBDIRS=$(PWD) modules
+
+clean:
+ $(MAKE) -C $(KERNELDIR) SUBDIRS=$(PWD) clean
+
+else
+
+obj-m := sja1000-isa.o sja1000-gw2.o
+
+sja1000-isa-objs := isa.o sja1000.o proc.o
+sja1000-gw2-objs := trajet-gw2.o sja1000.o proc.o
+
+endif
--- /dev/null
+../../can/can.h
\ No newline at end of file
--- /dev/null
+../../can/can_ioctl.h
\ No newline at end of file
--- /dev/null
+/*
+ * $Id: isa.c,v 2.0 2006/04/13 10:37:21 ethuerm Exp $
+ *
+ * isa.c - Philips SJA1000 network device driver for ISA CAN-Cards
+ *
+ * Copyright (c) 2002-2005 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 <llcf@volkswagen.de>
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/types.h>
+#include <linux/fcntl.h>
+#include <linux/interrupt.h>
+#include <linux/ptrace.h>
+#include <linux/ioport.h>
+#include <linux/in.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <asm/io.h>
+
+#include "can.h"
+#include "can_ioctl.h" /* for struct can_device_stats */
+#include "sja1000.h"
+
+#define MAX_CAN 8
+#define CAN_DEV_NAME "can%d"
+#define DRV_NAME "sja1000-isa"
+
+#define DEFAULT_KBIT_PER_SEC 100
+#define SJA1000_HW_CLOCK 16000000
+
+/* driver and version information */
+static const char *drv_name = DRV_NAME;
+static const char *drv_version = "0.0.10";
+static const char *drv_reldate = "2005-10-11";
+static const char *chip_name = SJA1000_CHIP_NAME;
+
+MODULE_AUTHOR("Oliver Hartkopp <oliver.hartkopp@volkswagen.de>");
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_DESCRIPTION("LLCF SJA1000 network device driver '" DRV_NAME "'");
+
+/* module parameters */
+static uint32_t base_addr[MAX_CAN] = { (uint32_t)0x2C0L, (uint32_t)0x320L, 0};
+
+static int irq[MAX_CAN] = { 10, 5, 0 };
+
+static int speed[MAX_CAN] = { DEFAULT_KBIT_PER_SEC, DEFAULT_KBIT_PER_SEC, 0};
+
+static int btr[MAX_CAN] = { 0 };
+static int rx_probe[MAX_CAN] = { 0 };
+
+static int clk = SJA1000_HW_CLOCK;
+static int debug = 0;
+static int restart_ms = 100;
+
+/* array of all can chips */
+static struct net_device *can_dev[MAX_CAN];
+
+
+/* special functions to access the chips registers */
+static uint8_t reg_read(struct net_device *dev, int reg)
+{
+ return inb(dev->base_addr + reg);
+}
+
+static void reg_write(struct net_device *dev, int reg, uint8_t val)
+{
+ outb(val, dev->base_addr + reg);
+}
+
+MODULE_PARM(base_addr, "1-" __MODULE_STRING(MAX_CAN)"i");
+MODULE_PARM(irq, "1-" __MODULE_STRING(MAX_CAN)"i");
+MODULE_PARM(speed, "1-" __MODULE_STRING(MAX_CAN)"i");
+MODULE_PARM(btr, "1-" __MODULE_STRING(MAX_CAN)"i");
+MODULE_PARM(rx_probe, "1-" __MODULE_STRING(MAX_CAN)"i");
+MODULE_PARM(clk, "i");
+MODULE_PARM(debug, "i");
+MODULE_PARM(restart_ms, "i");
+
+static struct net_device* sja1000_isa_probe(uint32_t base, int irq, int speed,
+ int btr, int rx_probe, int clk,
+ int debug, int restart_ms)
+{
+ struct net_device *dev;
+ struct can_priv *priv;
+
+ if (!(dev = alloc_netdev(sizeof(struct can_priv), CAN_DEV_NAME,
+ sja1000_setup))) {
+ printk(KERN_ERR "%s: out of memory\n", chip_name);
+ return NULL;
+ }
+
+ printk(KERN_INFO "%s: base 0x%X / irq %d / speed %d / btr 0x%X / rx_probe %d\n",
+ chip_name, base, irq, speed, btr, rx_probe);
+
+ /* fill net_device structure */
+
+ priv = netdev_priv(dev);
+
+ dev->irq = irq;
+ dev->base_addr = base;
+
+ priv->reg_read = reg_read;
+ priv->reg_write = reg_write;
+
+ priv->speed = speed;
+ priv->btr = btr;
+ priv->rx_probe = rx_probe;
+ priv->clock = clk;
+ priv->restart_ms = restart_ms;
+ priv->debug = debug;
+
+ if (REG_READ(0) == 0xFF)
+ goto free_dev;
+
+ /* set chip into reset mode */
+ set_reset_mode(dev);
+
+ /* go into Pelican mode, disable clkout, disable comparator */
+ REG_WRITE(REG_CDR, 0xCF);
+
+ /* output control */
+ /* connected to external transceiver */
+ REG_WRITE(REG_OCR, 0x1A);
+
+ printk(KERN_INFO "%s: %s found at 0x%X, irq is %d\n",
+ dev->name, chip_name, (uint32_t)dev->base_addr, dev->irq);
+
+ if (register_netdev(dev) == 0)
+ return dev;
+
+ printk(KERN_INFO "%s: probing failed\n", chip_name);
+ free_dev:
+ free_netdev(dev);
+ return NULL;
+}
+
+static __exit void sja1000_isa_cleanup_module(void)
+{
+ int i;
+
+ for (i = 0; i < MAX_CAN; i++) {
+ if (can_dev[i] != NULL) {
+ struct can_priv *priv = netdev_priv(can_dev[i]);
+ unregister_netdev(can_dev[i]);
+ del_timer(&priv->timer);
+ release_region(base_addr[i], SJA1000_IO_SIZE_ISA);
+ free_netdev(can_dev[i]);
+ }
+ }
+ sja1000_proc_delete(drv_name);
+}
+
+static __init int sja1000_isa_init_module(void)
+{
+ int i;
+ struct net_device *dev;
+
+ if (clk < 1000 ) /* MHz command line value */
+ clk *= 1000000;
+
+ if (clk < 1000000 ) /* kHz command line value */
+ clk *= 1000;
+
+ printk(KERN_INFO "%s - %s driver v%s (%s)\n",
+ chip_name, drv_name, drv_version, drv_reldate);
+ printk(KERN_INFO "%s - options [clk %d.%06d MHz] [restart_ms %dms] [debug %d]\n",
+ chip_name, clk/1000000, clk%1000000, restart_ms, debug);
+
+ for (i = 0; base_addr[i]; i++) {
+ printk(KERN_DEBUG "%s: checking for %s on address 0x%X ...\n",
+ chip_name, chip_name, base_addr[i]);
+ if (!request_region(base_addr[i], SJA1000_IO_SIZE_ISA, chip_name)) {
+ printk(KERN_ERR "%s: memory already in use\n", chip_name);
+ sja1000_isa_cleanup_module();
+ return -EBUSY;
+ }
+ dev = sja1000_isa_probe(base_addr[i], irq[i], speed[i], btr[i], rx_probe[i], clk, debug, restart_ms);
+
+ if (dev != NULL) {
+ can_dev[i] = dev;
+ sja1000_proc_init(drv_name, can_dev, MAX_CAN);
+ } else {
+ can_dev[i] = NULL;
+ release_region(base_addr[i], SJA1000_IO_SIZE_ISA);
+ }
+ }
+ return 0;
+}
+
+module_init(sja1000_isa_init_module);
+module_exit(sja1000_isa_cleanup_module);
--- /dev/null
+/*
+ * $Id: proc.c,v 2.0 2006/04/13 10:37:21 ethuerm Exp $
+ *
+ * proc.c - proc file system functions for SJA1000 CAN driver.
+ *
+ * Copyright (c) 2002-2005 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 <llcf@volkswagen.de>
+ *
+ */
+
+#include <linux/module.h>
+
+#include <linux/slab.h>
+#include <linux/proc_fs.h>
+#include <linux/netdevice.h>
+
+#include "can.h"
+#include "can_ioctl.h"
+#include "sja1000.h"
+
+static struct proc_dir_entry *pde = NULL;
+static struct proc_dir_entry *pde_regs = NULL;
+static struct proc_dir_entry *pde_reset = NULL;
+
+static struct net_device **can_dev;
+static int max_devices;
+
+static int sja1000_proc_read(char *page, char **start, off_t off, int count, int *eof, void *data)
+{
+ int len = 0;
+ struct net_device *dev;
+ int i;
+ struct can_priv *priv;
+ unsigned char stat;
+
+ len += snprintf(page + len, PAGE_SIZE - len, "CAN bus device statistics:\n");
+ len += snprintf(page + len, PAGE_SIZE - len, " errwarn overrun wakeup buserr errpass arbitr restarts clock baud\n");
+ for (i = 0; (i < max_devices) && (len < PAGE_SIZE - 200); i++) {
+ if (can_dev[i]) {
+ dev = can_dev[i];
+ stat = REG_READ(REG_SR);
+ priv = netdev_priv(can_dev[i]);
+ len += snprintf(page + len, PAGE_SIZE - len, "can%d: %8d %8d %8d %8d %8d %8d %8d %10d %8d\n", i,
+ priv->can_stats.error_warning,
+ priv->can_stats.data_overrun,
+ priv->can_stats.wakeup,
+ priv->can_stats.bus_error,
+ priv->can_stats.error_passive,
+ priv->can_stats.arbitration_lost,
+ priv->can_stats.restarts,
+ priv->clock,
+ priv->speed
+ );
+ if (stat & 0x80) {
+ len += snprintf(page + len, PAGE_SIZE - len, "can%d: bus status: BUS OFF, ", i);
+ } else if (stat & 0x40) {
+ len += snprintf(page + len, PAGE_SIZE - len, "can%d: bus status: ERROR PASSIVE, ", i);
+ } else {
+ len += snprintf(page + len, PAGE_SIZE - len, "can%d: bus status: OK, ", i);
+ }
+ len += snprintf(page + len, PAGE_SIZE - len, "RXERR: %d, TXERR: %d\n", REG_READ(REG_RXERR), REG_READ(REG_TXERR));
+ }
+ }
+
+ *eof = 1;
+ return len;
+}
+
+static int sja1000_proc_read_regs(char *page, char **start, off_t off, int count, int *eof, void *data)
+{
+ int len = 0;
+ struct net_device *dev;
+ int i;
+ struct can_priv *priv;
+
+ len = sprintf(page, "SJA1000 registers:\n");
+ for (i = 0; (i < max_devices) && (len < PAGE_SIZE - 200); i++) {
+ if (can_dev[i]) {
+ dev = can_dev[i];
+ len += snprintf(page + len, PAGE_SIZE - len, "can%d SJA1000 registers:\n", i);
+
+ priv = netdev_priv(can_dev[i]);
+ len += snprintf(page + len, PAGE_SIZE - len, "00: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n",
+ REG_READ(0x00),
+ REG_READ(0x01),
+ REG_READ(0x02),
+ REG_READ(0x03),
+ REG_READ(0x04),
+ REG_READ(0x05),
+ REG_READ(0x06),
+ REG_READ(0x07),
+ REG_READ(0x08),
+ REG_READ(0x09),
+ REG_READ(0x0a),
+ REG_READ(0x0b),
+ REG_READ(0x0c),
+ REG_READ(0x0d),
+ REG_READ(0x0e),
+ REG_READ(0x0f)
+ );
+ len += snprintf(page + len, PAGE_SIZE - len, "10: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n",
+ REG_READ(0x10),
+ REG_READ(0x11),
+ REG_READ(0x12),
+ REG_READ(0x13),
+ REG_READ(0x14),
+ REG_READ(0x15),
+ REG_READ(0x16),
+ REG_READ(0x17),
+ REG_READ(0x18),
+ REG_READ(0x19),
+ REG_READ(0x1a),
+ REG_READ(0x1b),
+ REG_READ(0x1c),
+ REG_READ(0x1d),
+ REG_READ(0x1e),
+ REG_READ(0x1f)
+ );
+ }
+ }
+
+ *eof = 1;
+ return len;
+}
+
+static int sja1000_proc_read_reset(char *page, char **start, off_t off, int count, int *eof, void *data)
+{
+ int len = 0;
+ struct net_device *dev;
+ int i;
+ struct can_priv *priv;
+
+ len += snprintf(page + len, PAGE_SIZE - len, "resetting ");
+ for (i = 0; (i < max_devices) && (len < PAGE_SIZE - 200); i++) {
+ if (can_dev[i]) {
+ dev = can_dev[i];
+ priv = netdev_priv(can_dev[i]);
+ if ((priv->state != STATE_UNINITIALIZED) && (priv->state != STATE_RESET_MODE)) {
+ len += snprintf(page + len, PAGE_SIZE - len, "%s ", dev->name);
+ dev->stop(dev);
+ dev->open(dev);
+ /* count number of restarts */
+ priv->can_stats.restarts++;
+
+ } else {
+ len += snprintf(page + len, PAGE_SIZE - len, "(%s|%d) ", dev->name, priv->state);
+ }
+ }
+ }
+
+ len += snprintf(page + len, PAGE_SIZE - len, "done\n");
+
+ *eof = 1;
+ return len;
+}
+
+void sja1000_proc_init(const char *drv_name, struct net_device **dev, int max)
+{
+ char fname[256];
+
+ can_dev = dev;
+ max_devices = max;
+
+ if (pde == NULL) {
+ sprintf(fname, PROCBASE "/%s", drv_name);
+ pde = create_proc_read_entry(fname, 0644, NULL,
+ sja1000_proc_read, NULL);
+ }
+ if (pde_regs == NULL) {
+ sprintf(fname, PROCBASE "/%s_regs", drv_name);
+ pde_regs = create_proc_read_entry(fname, 0644, NULL,
+ sja1000_proc_read_regs, NULL);
+ }
+ if (pde_reset == NULL) {
+ sprintf(fname, PROCBASE "/%s_reset", drv_name);
+ pde_reset = create_proc_read_entry(fname, 0644, NULL,
+ sja1000_proc_read_reset, NULL);
+ }
+}
+
+void sja1000_proc_delete(const char *drv_name)
+{
+ char fname[256];
+
+ if (pde) {
+ sprintf(fname, PROCBASE "/%s", drv_name);
+ remove_proc_entry(fname, NULL);
+ }
+ if (pde_regs) {
+ sprintf(fname, PROCBASE "/%s_regs", drv_name);
+ remove_proc_entry(fname, NULL);
+ }
+ if (pde_reset) {
+ sprintf(fname, PROCBASE "/%s_reset", drv_name);
+ remove_proc_entry(fname, NULL);
+ }
+}
--- /dev/null
+/*
+ * $Id: sja1000.c,v 2.0 2006/04/13 10:37:21 ethuerm Exp $
+ *
+ * sja1000.c - Philips SJA1000 network device driver
+ *
+ * Copyright (c) 2003 Matthias Brukner, Trajet Gmbh, Rebenring 33,
+ * 38106 Braunschweig, GERMANY
+ *
+ * Copyright (c) 2002-2005 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 <llcf@volkswagen.de>
+ *
+ */
+
+#include <linux/module.h>
+
+#include <linux/ioport.h>
+#include <linux/slab.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <asm/io.h>
+
+#include "can.h"
+#include "can_ioctl.h"
+#include "sja1000.h"
+
+#define DEBUG
+
+#ifdef DEBUG
+#define DBG(args...) ((priv->debug > 0) ? printk(args) : 0)
+#define iDBG(args...) ((priv->debug > 1) ? printk(args) : 0) /* logging in interrupt context */
+#define iiDBG(args...) ((priv->debug > 2) ? printk(args) : 0) /* logging in interrupt context */
+#else
+#define DBG(args...)
+#define iDBG(args...)
+#define iiDBG(args...)
+#endif
+
+
+static const char *chip_name = SJA1000_CHIP_NAME;
+
+static const char *ecc_errors[] = {
+ NULL,
+ NULL,
+ "ID.28 to ID.28",
+ "start of frame",
+ "bit SRTR",
+ "bit IDE",
+ "ID.20 to ID.18",
+ "ID.17 to ID.13",
+ "CRC sequence",
+ "reserved bit 0",
+ "data field",
+ "data length code",
+ "bit RTR",
+ "reserved bit 1",
+ "ID.4 to ID.0",
+ "ID.12 to ID.5",
+ NULL,
+ "active error flag",
+ "intermission",
+ "tolerate dominant bits",
+ NULL,
+ NULL,
+ "passive error flag",
+ "error delimiter",
+ "CRC delimiter",
+ "acknowledge slot",
+ "end of frame",
+ "acknowledge delimiter",
+ "overload flag",
+ NULL,
+ NULL,
+ NULL
+};
+
+static const char *ecc_types[] = {
+ "bit error",
+ "form error",
+ "stuff error",
+ "other type of error"
+};
+
+/* declarations */
+
+static void can_restart_dev(unsigned long data);
+static void chipset_init(struct net_device *dev, int wake);
+static void chipset_init_rx(struct net_device *dev);
+static void chipset_init_trx(struct net_device *dev);
+
+
+/*
+ * set baud rate divisor values
+ */
+static void set_btr(struct net_device *dev, int btr0, int btr1)
+{
+ struct can_priv *priv = netdev_priv(dev);
+
+ if (priv->state == STATE_UNINITIALIZED) /* no bla bla when restarting the device */
+ printk(KERN_INFO "%s: setting BTR0=%02X BTR1=%02X\n",
+ dev->name, btr0, btr1);
+
+ REG_WRITE(REG_BTR0, btr0);
+ REG_WRITE(REG_BTR1, btr1);
+}
+
+/*
+ * calculate baud rate divisor values
+ */
+static void set_baud(struct net_device *dev, int baud, int clock)
+{
+ struct can_priv *priv = netdev_priv(dev);
+
+ int error;
+ int brp;
+ int tseg;
+ int tseg1 = 0;
+ int tseg2 = 0;
+
+ int best_error = 1000000000;
+ int best_tseg = 0;
+ int best_brp = 0;
+ int best_baud = 0;
+
+ int SAM = (baud > 100000 ? 0 : 1);
+
+ clock >>= 1;
+
+ for (tseg = (0 + 0 + 2) * 2; tseg <= (MAX_TSEG2 + MAX_TSEG1 + 2) * 2 + 1; tseg++) {
+ brp = clock / ((1 + tseg / 2) * baud) + tseg % 2;
+ if ((brp > 0) && (brp <= 64)) {
+ error = baud - clock / (brp * (1 + tseg / 2));
+ if (error < 0) {
+ error = -error;
+ }
+ if (error <= best_error) {
+ best_error = error;
+ best_tseg = tseg / 2;
+ best_brp = brp - 1;
+ best_baud = clock / (brp * (1 + tseg / 2));
+ }
+ }
+ }
+ if (best_error && (baud / best_error < 10)) {
+ printk("%s: unable to set baud rate %d (ext clock %dHz)\n",
+ dev->name, baud, clock * 2);
+ return;
+// return -EINVAL;
+ }
+ tseg2 = best_tseg - (SAMPLE_POINT * (best_tseg + 1)) / 100;
+ if (tseg2 < 0) {
+ tseg2 = 0;
+ } else if (tseg2 > MAX_TSEG2) {
+ tseg2 = MAX_TSEG2;
+ }
+ tseg1 = best_tseg - tseg2 - 2;
+ if (tseg1 > MAX_TSEG1) {
+ tseg1 = MAX_TSEG1;
+ tseg2 = best_tseg - tseg1 - 2;
+ }
+
+ priv->btr = ((best_brp | JUMPWIDTH)<<8) + ((SAM << 7) | (tseg2 << 4) | tseg1);
+
+ printk(KERN_INFO "%s: calculated best baudrate: %d / btr is 0x%04X\n",
+ dev->name, best_baud, priv->btr);
+
+ set_btr(dev, (priv->btr>>8) & 0xFF, priv->btr & 0xFF);
+// set_btr(dev, best_brp | JUMPWIDTH, (SAM << 7) | (tseg2 << 4) | tseg1);
+}
+
+int set_reset_mode(struct net_device *dev)
+{
+ struct can_priv *priv = netdev_priv(dev);
+ unsigned char status = REG_READ(REG_MOD);
+ int i;
+
+ priv->can_stats.bus_error_at_init = priv->can_stats.bus_error;
+
+ /* disable interrupts */
+ REG_WRITE(REG_IER, IRQ_OFF);
+
+ for (i = 0; i < 10; i++) {
+ /* check reset bit */
+ if (status & MOD_RM) {
+ if (i > 1) {
+ iDBG(KERN_INFO "%s: %s looped %d times\n",
+ dev->name, __FUNCTION__, i);
+ }
+ priv->state = STATE_RESET_MODE;
+ return 0;
+ }
+
+ REG_WRITE(REG_MOD, MOD_RM); /* reset chip */
+ status = REG_READ(REG_MOD);
+
+ }
+
+ printk(KERN_ERR "%s: setting sja1000 into reset mode failed!\n", dev->name);
+ return 1;
+
+}
+
+static int set_normal_mode(struct net_device *dev)
+{
+ struct can_priv *priv = netdev_priv(dev);
+ unsigned char status = REG_READ(REG_MOD);
+ int i;
+
+ for (i = 0; i < 10; i++) {
+ /* check reset bit */
+ if ((status & MOD_RM) == 0) {
+ if (i > 1) {
+ iDBG(KERN_INFO "%s: %s looped %d times\n",
+ dev->name, __FUNCTION__, i);
+ }
+ return 0;
+ }
+
+ REG_WRITE(REG_MOD, 0x00); /* set chip to normal mode */
+ status = REG_READ(REG_MOD);
+ }
+
+ printk(KERN_ERR "%s: setting sja1000 into normal mode failed!\n", dev->name);
+ return 1;
+
+}
+
+static int set_listen_mode(struct net_device *dev)
+{
+ struct can_priv *priv = netdev_priv(dev);
+ unsigned char status = REG_READ(REG_MOD);
+ int i;
+
+ for (i = 0; i < 10; i++) {
+ /* check reset mode bit */
+ if ((status & MOD_RM) == 0) {
+ if (i > 1) {
+ iDBG(KERN_INFO "%s: %s looped %d times\n",
+ dev->name, __FUNCTION__, i);
+ }
+ return 0;
+ }
+
+ /* set listen only mode, clear reset */
+ REG_WRITE(REG_MOD, MOD_LOM);
+ status = REG_READ(REG_MOD);
+ }
+
+ printk(KERN_ERR "%s: setting sja1000 into listen mode failed!\n", dev->name);
+ return 1;
+
+}
+
+/*
+ * initialize SJA1000 chip:
+ * - reset chip
+ * - set output mode
+ * - set baudrate
+ * - enable interrupts
+ * - start operating mode
+ */
+static void chipset_init_regs(struct net_device *dev)
+{
+ struct can_priv *priv = netdev_priv(dev);
+
+ /* go into Pelican mode, disable clkout, disable comparator */
+ REG_WRITE(REG_CDR, 0xCF);
+
+ /* set acceptance filter (accept all) */
+ REG_WRITE(REG_ACCC0, 0x00);
+ REG_WRITE(REG_ACCC1, 0x00);
+ REG_WRITE(REG_ACCC2, 0x00);
+ REG_WRITE(REG_ACCC3, 0x00);
+
+ REG_WRITE(REG_ACCM0, 0xFF);
+ REG_WRITE(REG_ACCM1, 0xFF);
+ REG_WRITE(REG_ACCM2, 0xFF);
+ REG_WRITE(REG_ACCM3, 0xFF);
+
+ /* set baudrate */
+ if (priv->btr) { /* no calculation when btr is provided */
+ set_btr(dev, (priv->btr>>8) & 0xFF, priv->btr & 0xFF);
+ } else {
+ if (priv->speed == 0) {
+ priv->speed = DEFAULT_SPEED;
+ }
+ set_baud(dev, priv->speed * 1000, priv->clock);
+ }
+
+ /* output control */
+ REG_WRITE(REG_OCR, 0x1A); /* connected to external transceiver */
+
+}
+
+static void chipset_init(struct net_device *dev, int wake)
+{
+ struct can_priv *priv = netdev_priv(dev);
+
+ if (priv->rx_probe)
+ chipset_init_rx(dev); /* wait for valid reception first */
+ else
+ chipset_init_trx(dev);
+
+ if ((wake) && netif_queue_stopped(dev))
+ netif_wake_queue(dev);
+}
+
+static void chipset_init_rx(struct net_device *dev)
+{
+ struct can_priv *priv = netdev_priv(dev);
+
+ iDBG(KERN_INFO "%s: %s()\n", dev->name, __FUNCTION__);
+
+ /* set chip into reset mode */
+ set_reset_mode(dev);
+
+ /* set registers */
+ chipset_init_regs(dev);
+
+ /* automatic bit rate detection */
+ set_listen_mode(dev);
+
+ priv->state = STATE_PROBE;
+
+ /* enable receive and error interrupts */
+ REG_WRITE(REG_IER, IRQ_RI | IRQ_EI);
+}
+
+static void chipset_init_trx(struct net_device *dev)
+{
+ struct can_priv *priv = netdev_priv(dev);
+
+ iDBG(KERN_INFO "%s: %s()\n", dev->name, __FUNCTION__);
+
+ /* set chip into reset mode */
+ set_reset_mode(dev);
+
+ /* set registers */
+ chipset_init_regs(dev);
+
+ /* leave reset mode */
+ set_normal_mode(dev);
+
+ priv->state = STATE_ACTIVE;
+
+ /* enable all interrupts */
+ REG_WRITE(REG_IER, IRQ_ALL);
+}
+
+/*
+ * transmit a CAN message
+ * message layout in the sk_buff should be like this:
+ * xx xx xx xx ff ll 00 11 22 33 44 55 66 77
+ * [ can-id ] [flags] [len] [can data (up to 8 bytes]
+ */
+static int can_start_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct can_priv *priv = netdev_priv(dev);
+ struct can_frame *cf = (struct can_frame*)skb->data;
+ uint8_t fi;
+ uint8_t dlc;
+ canid_t id;
+ uint8_t dreg;
+ int i;
+
+ netif_stop_queue(dev);
+
+ fi = dlc = cf->can_dlc;
+ id = cf->can_id;
+
+ if (id & CAN_RTR_FLAG)
+ fi |= FI_RTR;
+
+ if (id & CAN_EFF_FLAG) {
+ fi |= FI_FF;
+ dreg = EFF_BUF;
+ REG_WRITE(REG_FI, fi);
+ REG_WRITE(REG_ID1, (id & 0x1fe00000) >> (5 + 16));
+ REG_WRITE(REG_ID2, (id & 0x001fe000) >> (5 + 8));
+ REG_WRITE(REG_ID3, (id & 0x00001fe0) >> 5);
+ REG_WRITE(REG_ID4, (id & 0x0000001f) << 3);
+ } else {
+ dreg = SFF_BUF;
+ REG_WRITE(REG_FI, fi);
+ REG_WRITE(REG_ID1, (id & 0x000007f8) >> 3);
+ REG_WRITE(REG_ID2, (id & 0x00000007) << 5);
+ }
+
+ for (i = 0; i < dlc; i++) {
+ REG_WRITE(dreg++, cf->data[i]);
+ }
+
+ REG_WRITE(REG_CMR, CMD_TR);
+
+ priv->stats.tx_bytes += dlc;
+
+ dev->trans_start = jiffies;
+
+ dev_kfree_skb(skb);
+
+ return 0;
+}
+
+static void can_tx_timeout(struct net_device *dev)
+{
+ struct can_priv *priv = netdev_priv(dev);
+
+ priv->stats.tx_errors++;
+
+ /* do not conflict with e.g. bus error handling */
+ if (!(priv->timer.expires)){ /* no restart on the run */
+ chipset_init_trx(dev); /* no tx queue wakeup */
+ netif_wake_queue(dev); /* wakeup here */
+ }
+ else
+ DBG(KERN_INFO "%s: %s: can_restart_dev already active.\n",
+ dev->name, __FUNCTION__ );
+
+}
+
+static void can_restart_on(struct net_device *dev)
+{
+ struct can_priv *priv = netdev_priv(dev);
+
+ if (!(priv->timer.expires)){ /* no restart on the run */
+
+ set_reset_mode(dev);
+
+ priv->timer.function = can_restart_dev;
+ priv->timer.data = (unsigned long) dev;
+
+ /* restart chip on persistent error in <xxx> ms */
+ priv->timer.expires = jiffies + (priv->restart_ms * HZ) / 1000;
+ add_timer(&priv->timer);
+
+ iDBG(KERN_INFO "%s: %s start (%ld)\n",
+ dev->name, __FUNCTION__ , jiffies);
+ } else
+ iDBG(KERN_INFO "%s: %s already (%ld)\n",
+ dev->name, __FUNCTION__ , jiffies);
+}
+
+static void can_restart_dev(unsigned long data)
+{
+ struct net_device *dev = (struct net_device*) data;
+ struct can_priv *priv = netdev_priv(dev);
+
+ DBG(KERN_INFO "%s: can_restart_dev (%ld)\n",
+ dev->name, jiffies);
+
+ /* mark inactive timer */
+ priv->timer.expires = 0;
+
+ if (priv->state != STATE_UNINITIALIZED) {
+
+ /* count number of restarts */
+ priv->can_stats.restarts++;
+
+ chipset_init(dev, 1);
+ }
+}
+
+#if 0
+/* the timerless version */
+
+static void can_restart_now(struct net_device *dev)
+{
+ struct can_priv *priv = netdev_priv(dev);
+
+ if (priv->state != STATE_UNINITIALIZED) {
+
+ /* count number of restarts */
+ priv->can_stats.restarts++;
+
+ chipset_init(dev, 1);
+ }
+}
+#endif
+
+static void can_rx(struct net_device *dev)
+{
+ struct can_priv *priv = netdev_priv(dev);
+ struct can_frame *cf;
+ struct sk_buff *skb;
+ uint8_t fi;
+ uint8_t dreg;
+ canid_t id;
+ uint8_t dlc;
+ int i;
+
+ skb = dev_alloc_skb(sizeof(struct can_frame));
+ if (skb == NULL) {
+ return;
+ }
+ skb->dev = dev;
+ skb->protocol = htons(ETH_P_CAN);
+
+ fi = REG_READ(REG_FI);
+ dlc = fi & 0x0F;
+
+ if (fi & FI_FF) {
+ /* extended frame format (EFF) */
+ dreg = EFF_BUF;
+ id = (REG_READ(REG_ID1) << (5+16))
+ | (REG_READ(REG_ID2) << (5+8))
+ | (REG_READ(REG_ID3) << 5)
+ | (REG_READ(REG_ID4) >> 3);
+ id |= CAN_EFF_FLAG;
+ } else {
+ /* standard frame format (SFF) */
+ dreg = SFF_BUF;
+ id = (REG_READ(REG_ID1) << 3) | (REG_READ(REG_ID2) >> 5);
+ }
+
+ if (fi & FI_RTR)
+ id |= CAN_RTR_FLAG;
+
+ cf = (struct can_frame*)skb_put(skb, sizeof(struct can_frame));
+ cf->can_id = id;
+ cf->can_dlc = dlc;
+ for (i = 0; i < dlc; i++) {
+ cf->data[i] = REG_READ(dreg++);
+ }
+ while (i < 8)
+ cf->data[i++] = 0;
+
+ /* release receive buffer */
+ REG_WRITE(REG_CMR, CMD_RRB);
+
+ netif_rx(skb);
+
+ dev->last_rx = jiffies;
+ priv->stats.rx_packets++;
+ priv->stats.rx_bytes += dlc;
+}
+
+static struct net_device_stats *can_get_stats(struct net_device *dev)
+{
+ struct can_priv *priv = netdev_priv(dev);
+
+ /* TODO: read statistics from chip */
+ return &priv->stats;
+}
+
+static int can_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
+{
+ if (!netif_running(dev))
+ return -EINVAL;
+
+ switch (cmd) {
+ case SIOCSRATE:
+ ;
+ return 0;
+ case SIOCGRATE:
+ ;
+ return 0;
+ }
+ return 0;
+}
+
+/*
+ * SJA1000 interrupt handler
+ */
+static irqreturn_t can_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+ struct net_device *dev = (struct net_device*)dev_id;
+ struct can_priv *priv = netdev_priv(dev);
+ uint8_t isrc, status, ecc, alc;
+ int n = 0;
+
+ if (priv->state == STATE_UNINITIALIZED) {
+ printk(KERN_ERR "%s: %s: uninitialized controller!\n", dev->name, __FUNCTION__);
+ chipset_init(dev, 1); /* this should be possible at this stage */
+ return IRQ_NONE;
+ }
+
+ if (priv->state == STATE_RESET_MODE) {
+ iiDBG(KERN_ERR "%s: %s: controller is in reset mode! MOD=0x%02X IER=0x%02X IR=0x%02X SR=0x%02X!\n",
+ dev->name, __FUNCTION__, REG_READ(REG_MOD), REG_READ(REG_IER), REG_READ(REG_IR), REG_READ(REG_SR));
+ return IRQ_NONE;
+ }
+
+ while ((isrc = REG_READ(REG_IR)) && (n < 20)) {
+ n++;
+ status = REG_READ(REG_SR);
+
+ if (isrc & IRQ_WUI) {
+ /* wake-up interrupt */
+ priv->can_stats.wakeup++;
+ }
+ if (isrc & IRQ_TI) {
+ /* transmission complete interrupt */
+ priv->stats.tx_packets++;
+ netif_wake_queue(dev);
+ }
+ if (isrc & IRQ_RI) {
+ /* receive interrupt */
+
+ while (status & SR_RBS) {
+ can_rx(dev);
+ status = REG_READ(REG_SR);
+ }
+ if (priv->state == STATE_PROBE) { /* valid RX -> switch to trx-mode */
+ iDBG(KERN_INFO "%s: RI #%d#\n", dev->name, n);
+ chipset_init_trx(dev); /* no tx queue wakeup */
+ break; /* check again after initializing the controller */
+ }
+ }
+ if (isrc & IRQ_DOI) {
+ /* data overrun interrupt */
+ iiDBG(KERN_INFO "%s: data overrun isrc=0x%02X status=0x%02X\n",
+ dev->name, isrc, status);
+ iDBG(KERN_INFO "%s: DOI #%d#\n", dev->name, n);
+ priv->can_stats.data_overrun++;
+ REG_WRITE(REG_CMR, CMD_CDO); /* clear bit */
+ }
+ if (isrc & IRQ_EI) {
+ /* error warning interrupt */
+ iiDBG(KERN_INFO "%s: error warning isrc=0x%02X status=0x%02X\n",
+ dev->name, isrc, status);
+ iDBG(KERN_INFO "%s: EI #%d#\n", dev->name, n);
+ priv->can_stats.error_warning++;
+ if (status & SR_BS) {
+ printk(KERN_INFO "%s: BUS OFF, restarting device\n", dev->name);
+ can_restart_on(dev);
+ return IRQ_HANDLED; /* controller has been restarted, so we leave here */
+ } else if (status & SR_ES) {
+ iDBG(KERN_INFO "%s: error\n", dev->name);
+ }
+ }
+ if (isrc & IRQ_BEI) {
+ /* bus error interrupt */
+ iiDBG(KERN_INFO "%s: bus error isrc=0x%02X status=0x%02X\n",
+ dev->name, isrc, status);
+ iDBG(KERN_INFO "%s: BEI #%d# [%d]\n", dev->name, n,
+ priv->can_stats.bus_error - priv->can_stats.bus_error_at_init);
+ priv->can_stats.bus_error++;
+ ecc = REG_READ(REG_ECC);
+ iDBG(KERN_INFO "%s: ECC = 0x%02X (%s, %s, %s)\n",
+ dev->name, ecc,
+ (ecc & ECC_DIR) ? "RX" : "TX",
+ ecc_types[ecc >> ECC_ERR],
+ ecc_errors[ecc & ECC_SEG]);
+
+ /* when the bus errors flood the system, restart the controller */
+ if (priv->can_stats.bus_error_at_init + MAX_BUS_ERRORS < priv->can_stats.bus_error) {
+ iDBG(KERN_INFO "%s: heavy bus errors, restarting device\n", dev->name);
+ can_restart_on(dev);
+ return IRQ_HANDLED; /* controller has been restarted, so we leave here */
+ }
+#if 1
+ /* don't know, if this is a good idea, but it works fine ... */
+ if (REG_READ(REG_RXERR) > 128) {
+ iDBG(KERN_INFO "%s: RX_ERR > 128, restarting device\n", dev->name);
+ can_restart_on(dev);
+ return IRQ_HANDLED; /* controller has been restarted, so we leave here */
+ }
+#endif
+ }
+ if (isrc & IRQ_EPI) {
+ /* error passive interrupt */
+ iiDBG(KERN_INFO "%s: error passive isrc=0x%02X status=0x%02X\n",
+ dev->name, isrc, status);
+ iDBG(KERN_INFO "%s: EPI #%d#\n", dev->name, n);
+ priv->can_stats.error_passive++;
+ if (status & SR_ES) {
+ iDBG(KERN_INFO "%s: -> ERROR PASSIVE, restarting device\n", dev->name);
+ can_restart_on(dev);
+ return IRQ_HANDLED; /* controller has been restarted, so we leave here */
+ } else {
+ iDBG(KERN_INFO "%s: -> ERROR ACTIVE\n", dev->name);
+ }
+ }
+ if (isrc & IRQ_ALI) {
+ /* arbitration lost interrupt */
+ iiDBG(KERN_INFO "%s: error arbitration lost isrc=0x%02X status=0x%02X\n",
+ dev->name, isrc, status);
+ iDBG(KERN_INFO "%s: ALI #%d#\n", dev->name, n);
+ priv->can_stats.arbitration_lost++;
+ alc = REG_READ(REG_ALC);
+ iDBG(KERN_INFO "%s: ALC = 0x%02X\n", dev->name, alc);
+ }
+ }
+ if (n > 1) {
+ iDBG(KERN_INFO "%s: handled %d IRQs\n", dev->name, n);
+ }
+
+ return n == 0 ? IRQ_NONE : IRQ_HANDLED;
+}
+
+/*
+ * initialize CAN bus driver
+ */
+static int can_open(struct net_device *dev)
+{
+ struct can_priv *priv = netdev_priv(dev);
+
+ /* set chip into reset mode */
+ set_reset_mode(dev);
+
+ priv->state = STATE_UNINITIALIZED;
+
+ /* register interrupt handler */
+ if (request_irq(dev->irq, &can_interrupt, SA_SHIRQ,
+ dev->name, (void*)dev)) {
+ return -EAGAIN;
+ }
+
+ /* clear statistics */
+ memset(&priv->stats, 0, sizeof(priv->stats));
+
+ /* init chip */
+ chipset_init(dev, 0);
+ priv->open_time = jiffies;
+
+ netif_start_queue(dev);
+
+ return 0;
+}
+
+/*
+ * stop CAN bus activity
+ */
+static int can_close(struct net_device *dev)
+{
+ struct can_priv *priv = netdev_priv(dev);
+
+ /* set chip into reset mode */
+ set_reset_mode(dev);
+
+ priv->open_time = 0;
+
+ if (priv->timer.expires) {
+ del_timer(&priv->timer);
+ priv->timer.expires = 0;
+ }
+
+ free_irq(dev->irq, (void*)dev);
+ priv->state = STATE_UNINITIALIZED;
+
+ netif_stop_queue(dev);
+
+ return 0;
+}
+
+#if 0
+static uint8_t reg_read(struct net_device *dev, int reg)
+{
+ return readb(dev->base_addr + reg);
+}
+
+static void reg_write(struct net_device *dev, int reg, uint8_t val)
+{
+ writeb(val, dev->base_addr + reg);
+}
+
+static void test_if(struct net_device *dev)
+{
+ int i;
+ int j;
+ int x;
+
+ REG_WRITE(REG_CDR, 0xCF);
+ for (i = 0; i < 10000; i++) {
+ for (j = 0; j < 256; j++) {
+ REG_WRITE(REG_EWL, j);
+ x = REG_READ(REG_EWL);
+ if (x != j) {
+ printk(KERN_INFO "%s: is: %02X expected: %02X (%d)\n", dev->name, x, j, i);
+ }
+ }
+ }
+}
+#endif
+
+void sja1000_setup(struct net_device *dev)
+{
+ struct can_priv *priv = netdev_priv(dev);
+
+ /* Fill in the the fields of the device structure
+ with CAN/LLCF generic values */
+
+ dev->change_mtu = NULL;
+ dev->hard_header = NULL;
+ dev->rebuild_header = NULL;
+ dev->set_mac_address = NULL;
+ dev->hard_header_cache = NULL;
+ dev->header_cache_update = NULL;
+ dev->hard_header_parse = NULL;
+
+ // dev->type = ARPHRD_CAN;
+ dev->hard_header_len = 0;
+ dev->mtu = sizeof(struct can_frame);
+ dev->addr_len = 0;
+ dev->tx_queue_len = 10;
+
+ dev->flags = IFF_NOARP;
+
+ dev->open = can_open;
+ dev->stop = can_close;
+ dev->hard_start_xmit = can_start_xmit;
+ dev->get_stats = can_get_stats;
+ dev->do_ioctl = can_ioctl;
+
+ dev->tx_timeout = can_tx_timeout;
+ dev->watchdog_timeo = TX_TIMEOUT;
+
+ init_timer(&priv->timer);
+ priv->timer.expires = 0;
+
+ // SET_MODULE_OWNER(dev);
+}
--- /dev/null
+/*
+ * $Id: sja1000.h,v 2.0 2006/04/13 10:37:21 ethuerm Exp $
+ *
+ * sja1000.h - Philips SJA1000 network device driver
+ *
+ * Copyright (c) 2003 Matthias Brukner, Trajet Gmbh, Rebenring 33,
+ * 38106 Braunschweig, GERMANY
+ *
+ * Copyright (c) 2002-2005 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 <llcf@volkswagen.de>
+ *
+ */
+
+#ifndef __SJA1000_H__
+#define __SJA1000_H__
+
+#define SJA1000_IO_SIZE_BASIC 0x20
+#define SJA1000_IO_SIZE_PELICAN 0x80
+
+#define SJA1000_IO_SIZE_ISA 0x20
+
+#define DEFAULT_SPEED 100 /* kBit/s */
+
+#define TX_TIMEOUT (HZ/20) /* 50ms */
+#define RESTART_MS 100 /* restart chip on persistent errors in 100ms */
+#define MAX_BUS_ERRORS 200 /* prevent from flooding bus error interrupts */
+
+/* SJA1000 registers - manual section 6.4 (Pelican Mode) */
+#define REG_MOD 0x00
+#define REG_CMR 0x01
+#define REG_SR 0x02
+#define REG_IR 0x03
+#define REG_IER 0x04
+#define REG_ALC 0x0B
+#define REG_ECC 0x0C
+#define REG_EWL 0x0D
+#define REG_RXERR 0x0E
+#define REG_TXERR 0x0F
+#define REG_ACCC0 0x10
+#define REG_ACCC1 0x11
+#define REG_ACCC2 0x12
+#define REG_ACCC3 0x13
+#define REG_ACCM0 0x14
+#define REG_ACCM1 0x15
+#define REG_ACCM2 0x16
+#define REG_ACCM3 0x17
+#define REG_RMC 0x1D
+#define REG_RBSA 0x1E
+
+/* Common registers - manual section 6.5 */
+#define REG_BTR0 0x06
+#define REG_BTR1 0x07
+#define REG_OCR 0x08
+#define REG_CDR 0x1F
+
+#define REG_FI 0x10
+#define SFF_BUF 0x13
+#define EFF_BUF 0x15
+
+#define FI_FF 0x80
+#define FI_RTR 0x40
+
+#define REG_ID1 0x11
+#define REG_ID2 0x12
+#define REG_ID3 0x13
+#define REG_ID4 0x14
+
+#define CAN_RAM 0x20
+
+/* mode register */
+#define MOD_RM 0x01
+#define MOD_LOM 0x02
+#define MOD_STM 0x04
+#define MOD_AFM 0x08
+#define MOD_SM 0x10
+
+/* commands */
+#define CMD_SRR 0x10
+#define CMD_CDO 0x08
+#define CMD_RRB 0x04
+#define CMD_AT 0x02
+#define CMD_TR 0x01
+
+/* interrupt sources */
+#define IRQ_BEI 0x80
+#define IRQ_ALI 0x40
+#define IRQ_EPI 0x20
+#define IRQ_WUI 0x10
+#define IRQ_DOI 0x08
+#define IRQ_EI 0x04
+#define IRQ_TI 0x02
+#define IRQ_RI 0x01
+#define IRQ_ALL 0xFF
+#define IRQ_OFF 0x00
+
+/* status register content */
+#define SR_BS 0x80
+#define SR_ES 0x40
+#define SR_TS 0x20
+#define SR_RS 0x10
+#define SR_TCS 0x08
+#define SR_TBS 0x04
+#define SR_DOS 0x02
+#define SR_RBS 0x01
+
+#define SR_CRIT (SR_BS|SR_ES)
+
+/* ECC register */
+#define ECC_DIR 0x20
+#define ECC_SEG 0x1F
+#define ECC_ERR 6
+
+/* bus timing */
+#define MAX_TSEG1 15
+#define MAX_TSEG2 7
+#define SAMPLE_POINT 75
+#define JUMPWIDTH 0x40
+
+
+#define REG_READ(addr) ((struct can_priv*)netdev_priv(dev))->reg_read(dev,addr)
+#define REG_WRITE(addr,data) ((struct can_priv*)netdev_priv(dev))->reg_write(dev,addr,data)
+
+#define SJA1000_CHIP_NAME "sja1000"
+#define PROCBASE "net/drivers" /* /proc/ ... */
+
+/*
+ * private data structure:
+ * reg_read and reg_write are functions to access the sja1000 registers
+ */
+struct can_priv {
+ struct net_device_stats stats;
+ long open_time;
+ int clock;
+ int restart_ms;
+ int debug;
+ int speed;
+ int btr;
+ int rx_probe;
+ struct timer_list timer;
+ uint8_t (*reg_read)(struct net_device *dev, int reg);
+ void (*reg_write)(struct net_device *dev, int reg, uint8_t val);
+ struct can_device_stats can_stats;
+ int state;
+};
+
+#define STATE_UNINITIALIZED 0
+#define STATE_PROBE 1
+#define STATE_ACTIVE 2
+#define STATE_ERROR_ACTIVE 3
+#define STATE_ERROR_PASSIVE 4
+#define STATE_BUS_OFF 5
+#define STATE_RESET_MODE 6
+
+void sja1000_setup(struct net_device *dev);
+void sja1000_proc_init(const char *drv_name, struct net_device **dev, int max);
+void sja1000_proc_delete(const char *drv_name);
+
+#endif /* __SJA1000_H__ */
--- /dev/null
+/*
+ * $Id: trajet-gw2.c,v 2.0 2006/04/13 10:37:22 ethuerm Exp $
+ *
+ * trajet-gw2.c - Philips SJA1000 network device driver for TRAJET.GW2
+ *
+ * Copyright (c) 2003 Matthias Brukner, Trajet Gmbh, Rebenring 33,
+ * 38106 Braunschweig, GERMANY
+ *
+ * Copyright (c) 2002-2005 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 <llcf@volkswagen.de>
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/types.h>
+#include <linux/fcntl.h>
+#include <linux/interrupt.h>
+#include <linux/ptrace.h>
+#include <linux/ioport.h>
+#include <linux/in.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <asm/io.h>
+
+#include "can.h"
+#include "can_ioctl.h" /* for struct can_device_stats */
+#include "sja1000.h"
+
+#define MAX_CAN 8
+#define CAN_DEV_NAME "can%d"
+#define DRV_NAME "sja1000-gw2"
+
+#define DEFAULT_KBIT_PER_SEC 500
+#define SJA1000_HW_CLOCK 20000000
+#define ADDR_GAP 1
+#define RSIZE (SJA1000_IO_SIZE_PELICAN * (ADDR_GAP + 1))
+
+/* driver and version information */
+static const char *drv_name = DRV_NAME;
+static const char *drv_version = "0.0.11";
+static const char *drv_reldate = "2005-10-11";
+static const char *chip_name = SJA1000_CHIP_NAME;
+
+MODULE_AUTHOR("Matthias Brukner <M.Brukner@trajet.de>");
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_DESCRIPTION("LLCF SJA1000 network device driver '" DRV_NAME "'");
+
+/* module parameters */
+static uint32_t base_addr[MAX_CAN] = {
+ (uint32_t)0xf0100200L,
+ (uint32_t)0xf0100300L,
+ (uint32_t)0xf0100400L,
+ (uint32_t)0xf0100500L,
+ 0
+};
+static int irq[MAX_CAN] = { 26, 26, 26, 26, 0 };
+static int speed[MAX_CAN] = {
+ DEFAULT_KBIT_PER_SEC, DEFAULT_KBIT_PER_SEC,
+ DEFAULT_KBIT_PER_SEC, DEFAULT_KBIT_PER_SEC,
+ 0
+};
+static int btr[MAX_CAN] = { 0 };
+static int rx_probe[MAX_CAN] = { 0 };
+
+static int clk = SJA1000_HW_CLOCK;
+static int debug = 0;
+static int restart_ms = 100;
+
+/* array of all can chips */
+static struct net_device *can_dev[MAX_CAN];
+
+
+/* special functions to access the chips registers */
+static uint8_t reg_read(struct net_device *dev, int reg)
+{
+ static uint8_t val;
+
+ val = (uint8_t)readw(dev->base_addr + reg * (ADDR_GAP + 1) + ADDR_GAP);
+ rmb();
+
+ return val;
+}
+
+static void reg_write(struct net_device *dev, int reg, uint8_t val)
+{
+ writew(val, dev->base_addr + reg * 2 + 1);
+ wmb();
+}
+
+MODULE_PARM(base_addr, "1-" __MODULE_STRING(MAX_CAN)"i");
+MODULE_PARM(irq, "1-" __MODULE_STRING(MAX_CAN)"i");
+MODULE_PARM(speed, "1-" __MODULE_STRING(MAX_CAN)"i");
+MODULE_PARM(btr, "1-" __MODULE_STRING(MAX_CAN)"i");
+MODULE_PARM(rx_probe, "1-" __MODULE_STRING(MAX_CAN)"i");
+MODULE_PARM(clk, "i");
+MODULE_PARM(debug, "i");
+MODULE_PARM(restart_ms, "i");
+
+static struct net_device* sja1000_gw2_probe(uint32_t base, int irq, int speed,
+ int btr, int rx_probe, int clk,
+ int debug, int restart_ms)
+{
+ struct net_device *dev;
+ struct can_priv *priv;
+
+ if (!(dev = alloc_netdev(sizeof(struct can_priv), CAN_DEV_NAME,
+ sja1000_setup))) {
+ printk(KERN_ERR "%s: out of memory\n", chip_name);
+ return NULL;
+ }
+
+ printk(KERN_INFO "%s: base 0x%X / irq %d / speed %d / btr 0x%X / rx_probe %d\n",
+ chip_name, base, irq, speed, btr, rx_probe);
+
+ /* fill net_device structure */
+
+ priv = netdev_priv(dev);
+
+ dev->irq = irq;
+ dev->base_addr = base;
+
+ priv->reg_read = reg_read;
+ priv->reg_write = reg_write;
+
+ priv->speed = speed;
+ priv->btr = btr;
+ priv->rx_probe = rx_probe;
+ priv->clock = clk;
+ priv->restart_ms = restart_ms;
+ priv->debug = debug;
+
+ if (REG_READ(0) == 0xFF)
+ goto free_dev;
+
+ /* set chip into reset mode */
+ set_reset_mode(dev);
+
+ /* go into Pelican mode, disable clkout, disable comparator */
+ REG_WRITE(REG_CDR, 0xCF);
+
+ /* output control */
+ /* connected to external transceiver */
+ REG_WRITE(REG_OCR, 0x1A);
+
+ printk(KERN_INFO "%s: %s found at 0x%X, irq is %d\n",
+ dev->name, chip_name, (uint32_t)dev->base_addr, dev->irq);
+
+ if (register_netdev(dev) == 0)
+ return dev;
+
+ printk(KERN_INFO "%s: probing failed\n", chip_name);
+ free_dev:
+ free_netdev(dev);
+ return NULL;
+}
+
+static __exit void sja1000_gw2_cleanup_module(void)
+{
+ int i;
+
+ for (i = 0; i < MAX_CAN; i++) {
+ if (can_dev[i] != NULL) {
+ struct can_priv *priv = netdev_priv(can_dev[i]);
+ unregister_netdev(can_dev[i]);
+ del_timer(&priv->timer);
+ iounmap((void*)can_dev[i]->base_addr);
+ release_mem_region(base_addr[i], RSIZE);
+ free_netdev(can_dev[i]);
+ }
+ }
+ sja1000_proc_delete(drv_name);
+}
+
+static __init int sja1000_gw2_init_module(void)
+{
+ int i;
+ struct net_device *dev;
+ void *base;
+
+ if (clk < 1000 ) /* MHz command line value */
+ clk *= 1000000;
+
+ if (clk < 1000000 ) /* kHz command line value */
+ clk *= 1000;
+
+ printk(KERN_INFO "%s - %s driver v%s (%s)\n",
+ chip_name, drv_name, drv_version, drv_reldate);
+ printk(KERN_INFO "%s - options [clk %d.%06d MHz] [restart_ms %dms] [debug %d]\n",
+ chip_name, clk/1000000, clk%1000000, restart_ms, debug);
+
+ for (i = 0; base_addr[i]; i++) {
+ printk(KERN_DEBUG "%s: checking for %s on address 0x%X ...\n",
+ chip_name, chip_name, base_addr[i]);
+ if (!request_mem_region(base_addr[i], RSIZE, chip_name)) {
+ printk(KERN_ERR "%s: memory already in use\n", chip_name);
+ sja1000_gw2_cleanup_module();
+ return -EBUSY;
+ }
+ base = ioremap(base_addr[i], RSIZE);
+ dev = sja1000_gw2_probe((uint32_t)base, irq[i], speed[i], btr[i], rx_probe[i], clk, debug, restart_ms);
+ if (dev != NULL) {
+ can_dev[i] = dev;
+ sja1000_proc_init(drv_name, can_dev, MAX_CAN);
+ } else {
+ can_dev[i] = NULL;
+ iounmap(base);
+ release_mem_region(base_addr[i], RSIZE);
+ }
+ }
+ return 0;
+}
+
+module_init(sja1000_gw2_init_module);
+module_exit(sja1000_gw2_cleanup_module);
--- /dev/null
+../../can/version.h
\ No newline at end of file