* isotp.c - ISO 15765-2 CAN transport protocol for protocol family CAN
*
* WARNING: This is ALPHA code for discussions and first tests that should
- * not be used in productive environments.
+ * not be used in production environments.
*
* In the discussion the Socket-API to the userspace or the ISO-TP socket
* options or the return values we may change! Current behaviour:
#include <linux/module.h>
#include <linux/version.h>
#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/hrtimer.h>
#include <linux/wait.h>
#include <linux/uio.h>
#include <linux/net.h>
#include <linux/socket.h>
#include <linux/if_arp.h>
#include <linux/skbuff.h>
-#include <linux/can.h>
-#include <linux/can/core.h>
-#include <linux/can/isotp.h>
+#include <socketcan/can.h>
+#include <socketcan/can/core.h>
+#include <socketcan/can/isotp.h>
#include <net/sock.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24)
#include <net/net_namespace.h>
#include "compat.h"
#endif
-#include <linux/can/version.h> /* for RCSID. Removed by mkpatch script */
+#include <socketcan/can/version.h> /* for RCSID. Removed by mkpatch script */
RCSID("$Id$");
-#define CAN_ISOTP_VERSION "20081130-alpha"
+#define CAN_ISOTP_VERSION CAN_VERSION
static __initdata const char banner[] =
- KERN_INFO "can: isotp protocol (rev " CAN_ISOTP_VERSION ")\n";
+ KERN_INFO "can: isotp protocol (rev " CAN_ISOTP_VERSION " alpha)\n";
MODULE_DESCRIPTION("PF_CAN isotp 15765-2 protocol");
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("Oliver Hartkopp <oliver.hartkopp@volkswagen.de>");
+MODULE_ALIAS("can-proto-6");
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,22)
#error This modules needs hrtimers (available since Kernel 2.6.22)
canid_t txid;
canid_t rxid;
ktime_t tx_gap;
+ ktime_t lastrxcf_tstamp;
struct hrtimer rxtimer, txtimer;
+ struct tasklet_struct txtsklet;
struct can_isotp_options opt;
struct can_isotp_fc_options rxfc, txfc;
+ __u32 force_tx_stmin;
+ __u32 force_rx_stmin;
struct tpcon rx, tx;
struct notifier_block notifier;
wait_queue_head_t wait;
/* reset blocksize counter */
so->rx.bs = 0;
+ /* reset last CF frame rx timestamp for rx stmin enforcement */
+ so->lastrxcf_tstamp = ktime_set(0,0);
+
/* start rx timeout watchdog */
hrtimer_start(&so->rxtimer, ktime_set(1,0), HRTIMER_MODE_REL);
return 0;
/* add transmission time for CAN frame N_As */
so->tx_gap = ktime_add_ns(so->tx_gap, so->opt.frame_txtime);
/* add waiting time for consecutive frames N_Cs */
- if (so->txfc.stmin < 0x80)
+ if (so->opt.flags & CAN_ISOTP_FORCE_TXSTMIN)
+ so->tx_gap = ktime_add_ns(so->tx_gap,
+ so->force_tx_stmin);
+ else if (so->txfc.stmin < 0x80)
so->tx_gap = ktime_add_ns(so->tx_gap,
so->txfc.stmin * 1000000);
else
if (so->rx.state != ISOTP_WAIT_DATA)
return 0;
+ /* drop if timestamp gap is less than force_rx_stmin nano secs */
+ if (so->opt.flags & CAN_ISOTP_FORCE_RXSTMIN) {
+
+ if (ktime_to_ns(ktime_sub(skb->tstamp, so->lastrxcf_tstamp)) <
+ so->force_rx_stmin)
+ return 0;
+
+ so->lastrxcf_tstamp = skb->tstamp;
+ }
+
hrtimer_cancel(&so->rxtimer);
if ((cf->data[ae] & 0x0F) != so->rx.sn) {
so->tx.state = ISOTP_WAIT_FIRST_FC;
}
-static enum hrtimer_restart isotp_tx_timer_handler(struct hrtimer *hrtimer)
+static void isotp_tx_timer_tsklet(unsigned long data)
{
- struct isotp_sock *so = container_of(hrtimer, struct isotp_sock,
- txtimer);
+ struct isotp_sock *so = (struct isotp_sock *)data;
struct sock *sk = &so->sk;
struct sk_buff *skb;
struct net_device *dev;
struct can_frame *cf;
- enum hrtimer_restart ret;
int ae = (so->opt.flags & CAN_ISOTP_EXTEND_ADDR)? 1:0;
switch (so->tx.state) {
/* reset tx state */
so->tx.state = ISOTP_IDLE;
wake_up_interruptible(&so->wait);
-
- ret = HRTIMER_NORESTART;
break;
case ISOTP_SENDING:
dev = dev_get_by_index(&init_net, so->ifindex);
if (!dev)
- return HRTIMER_NORESTART;
+ break;
+isotp_tx_burst:
skb = alloc_skb(sizeof(*cf), gfp_any());
if (!skb) {
dev_put(dev);
- return HRTIMER_NORESTART;
+ break;
}
cf = (struct can_frame *)skb->data;
skb->dev = dev;
skb->sk = sk;
can_send(skb, 1);
- dev_put(dev);
if (so->tx.idx >= so->tx.len) {
/* we are done */
DBG("we are done\n");
so->tx.state = ISOTP_IDLE;
+ dev_put(dev);
wake_up_interruptible(&so->wait);
- return HRTIMER_NORESTART;
+ break;
}
if (so->txfc.bs && so->tx.bs >= so->txfc.bs) {
/* stop and wait for FC */
DBG("BS stop and wait for FC\n");
so->tx.state = ISOTP_WAIT_FC;
- hrtimer_forward(hrtimer, ktime_get(), ktime_set(1,0));
- } else
- hrtimer_forward(hrtimer, ktime_get(), so->tx_gap);
+ dev_put(dev);
+ hrtimer_start(&so->txtimer,
+ ktime_add(ktime_get(), ktime_set(1,0)),
+ HRTIMER_MODE_ABS);
+ break;
+ }
+
+ /* no gap between data frames needed => use burst mode */
+ if (!so->tx_gap.tv64)
+ goto isotp_tx_burst;
- ret = HRTIMER_RESTART;
+ /* start timer to send next data frame with correct delay */
+ dev_put(dev);
+ hrtimer_start(&so->txtimer,
+ ktime_add(ktime_get(), so->tx_gap),
+ HRTIMER_MODE_ABS);
break;
default:
BUG_ON(1);
}
+}
- return ret;
+static enum hrtimer_restart isotp_tx_timer_handler(struct hrtimer *hrtimer)
+{
+ struct isotp_sock *so = container_of(hrtimer, struct isotp_sock,
+ txtimer);
+ tasklet_schedule(&so->txtsklet);
+
+ return HRTIMER_NORESTART;
}
static int isotp_sendmsg(struct kiocb *iocb, struct socket *sock,
static int isotp_release(struct socket *sock)
{
struct sock *sk = sock->sk;
- struct isotp_sock *so = isotp_sk(sk);
+ struct isotp_sock *so;
+
+ if (!sk)
+ return 0;
+
+ so = isotp_sk(sk);
/* wait for complete transmission of current pdu */
wait_event_interruptible(so->wait, so->tx.state == ISOTP_IDLE);
hrtimer_cancel(&so->txtimer);
hrtimer_cancel(&so->rxtimer);
+ tasklet_kill(&so->txtsklet);
/* remove current filters & unregister */
if (so->bound) {
so->ifindex = 0;
so->bound = 0;
+ sock_orphan(sk);
+ sock->sk = NULL;
+
release_sock(sk);
sock_put(sk);
return 0;
}
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)
+static int isotp_setsockopt(struct socket *sock, int level, int optname,
+ char __user *optval, unsigned int optlen)
+#else
static int isotp_setsockopt(struct socket *sock, int level, int optname,
char __user *optval, int optlen)
+#endif
{
struct sock *sk = sock->sk;
struct isotp_sock *so = isotp_sk(sk);
return -EFAULT;
break;
+ case CAN_ISOTP_TX_STMIN:
+ if (optlen != sizeof(__u32))
+ return -EINVAL;
+
+ if (copy_from_user(&so->force_tx_stmin, optval, optlen))
+ return -EFAULT;
+ break;
+
+ case CAN_ISOTP_RX_STMIN:
+ if (optlen != sizeof(__u32))
+ return -EINVAL;
+
+ if (copy_from_user(&so->force_rx_stmin, optval, optlen))
+ return -EFAULT;
+ break;
+
default:
ret = -ENOPROTOOPT;
}
val = &so->rxfc;
break;
+ case CAN_ISOTP_TX_STMIN:
+ len = min_t(int, len, sizeof(__u32));
+ val = &so->force_tx_stmin;
+ break;
+
+ case CAN_ISOTP_RX_STMIN:
+ len = min_t(int, len, sizeof(__u32));
+ val = &so->force_rx_stmin;
+ break;
+
default:
return -ENOPROTOOPT;
}
hrtimer_init(&so->txtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
so->txtimer.function = isotp_tx_timer_handler;
+ tasklet_init(&so->txtsklet, isotp_tx_timer_tsklet, (unsigned long)so);
+
init_waitqueue_head(&so->wait);
so->notifier.notifier_call = isotp_notifier;
}
-static struct proto_ops isotp_ops __read_mostly = {
+static const struct proto_ops isotp_ops = {
.family = PF_CAN,
.release = isotp_release,
.bind = isotp_bind,
.accept = sock_no_accept,
.getname = isotp_getname,
.poll = datagram_poll,
- .ioctl = NULL, /* use can_ioctl() from af_can.c */
+ .ioctl = can_ioctl, /* use can_ioctl() from af_can.c */
.listen = sock_no_listen,
.shutdown = sock_no_shutdown,
.setsockopt = isotp_setsockopt,
.init = isotp_init,
};
-static struct can_proto isotp_can_proto __read_mostly = {
+static const struct can_proto isotp_can_proto = {
.type = SOCK_DGRAM,
.protocol = CAN_ISOTP,
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,33)
.capability = -1,
+#endif
.ops = &isotp_ops,
.prot = &isotp_proto,
};