]> rtime.felk.cvut.cz Git - socketcan-devel.git/blobdiff - kernel/2.6/net/can/isotp.c
Added new socketoptions to force the isotp protocol to intentionally
[socketcan-devel.git] / kernel / 2.6 / net / can / isotp.c
index d5b58d1938ad265a2438c0fc4492ad2161b66e30..4d7f98b0e6ffae9b2f81c4e1cbb4e230c87198b3 100644 (file)
@@ -2,7 +2,7 @@
  * 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:
@@ -61,9 +61,9 @@
 #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 "20080902-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)
@@ -92,8 +93,9 @@ MODULE_AUTHOR("Oliver Hartkopp <oliver.hartkopp@volkswagen.de>");
 #undef DBG
 #define DBG(fmt, args...) 
 
-#define SINGLE_MASK(id) (id & CAN_EFF_FLAG) ? \
-       (CAN_EFF_MASK|CAN_EFF_FLAG) : CAN_SFF_MASK
+#define SINGLE_MASK(id) ((id & CAN_EFF_FLAG) ? \
+                        (CAN_EFF_MASK | CAN_EFF_FLAG | CAN_RTR_FLAG) : \
+                        (CAN_SFF_MASK | CAN_EFF_FLAG | CAN_RTR_FLAG))
 
 /* N_PCI type values in bits 7-4 of N_PCI bytes */
 #define N_PCI_SF 0x00  /* single frame */
@@ -130,9 +132,13 @@ struct isotp_sock {
        canid_t txid;
        canid_t rxid;
        ktime_t tx_gap;
+       ktime_t last_cf_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;
@@ -273,11 +279,17 @@ static int isotp_rcv_fc(struct isotp_sock *so, struct can_frame *cf, int ae)
                    ((so->txfc.stmin < 0xF1) || (so->txfc.stmin > 0xF9)))
                        so->txfc.stmin = 0x7F;
 
+               /* reset CF frame rx timestamp for rx stmin enforcement */
+               so->last_cf_tstamp = ktime_set(0,0);
+
                so->tx_gap = ktime_set(0,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
@@ -394,6 +406,16 @@ static int isotp_rcv_cf(struct sock *sk, struct can_frame *cf, int ae,
        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->last_cf_tstamp)) <
+                   so->force_rx_stmin)
+                       return 0;
+
+               so->last_cf_tstamp = skb->tstamp; 
+       }
+
        hrtimer_cancel(&so->rxtimer);
 
        if ((cf->data[ae] & 0x0F) != so->rx.sn) {
@@ -465,20 +487,16 @@ static void isotp_rcv(struct sk_buff *skb, void *data)
        cf = (struct can_frame *) skb->data;
 
        /* if enabled: check receiption of my configured extended address */
-       if (ae && cf->data[0] != so->opt.ext_address) {
-               kfree_skb(skb);
+       if (ae && cf->data[0] != so->opt.ext_address)
                return;
-       }
 
        n_pci_type = cf->data[ae] & 0xF0;
 
        if (so->opt.flags & CAN_ISOTP_HALF_DUPLEX) {
                /* check rx/tx path half duplex expectations */
                if ((so->tx.state != ISOTP_IDLE && n_pci_type != N_PCI_FC) ||
-                   (so->rx.state != ISOTP_IDLE && n_pci_type == N_PCI_FC)) {
-                       kfree_skb(skb);
+                   (so->rx.state != ISOTP_IDLE && n_pci_type == N_PCI_FC))
                        return;
-               }
        }
 
        switch (n_pci_type) {
@@ -502,8 +520,6 @@ static void isotp_rcv(struct sk_buff *skb, void *data)
                isotp_rcv_cf(sk, cf, ae, skb);
                break;
        }
-
-       kfree_skb(skb);
 }
 
 static void isotp_fill_dataframe(struct can_frame *cf, struct isotp_sock *so,
@@ -553,15 +569,13 @@ static void isotp_create_fframe(struct can_frame *cf, struct isotp_sock *so,
        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) {
@@ -582,8 +596,6 @@ static enum hrtimer_restart isotp_tx_timer_handler(struct hrtimer *hrtimer)
                /* reset tx state */
                so->tx.state = ISOTP_IDLE;
                wake_up_interruptible(&so->wait);
-
-               ret = HRTIMER_NORESTART;
                break;
 
        case ISOTP_SENDING:
@@ -594,12 +606,13 @@ static enum hrtimer_restart isotp_tx_timer_handler(struct hrtimer *hrtimer)
 
                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;
@@ -616,32 +629,50 @@ static enum hrtimer_restart isotp_tx_timer_handler(struct hrtimer *hrtimer)
                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,
@@ -775,6 +806,7 @@ static int isotp_release(struct socket *sock)
 
        hrtimer_cancel(&so->txtimer);
        hrtimer_cancel(&so->rxtimer);
+       tasklet_kill(&so->txtsklet);
 
        /* remove current filters & unregister */
        if (so->bound) {
@@ -794,6 +826,9 @@ static int isotp_release(struct socket *sock)
        so->ifindex = 0;
        so->bound   = 0;
 
+       sock_orphan(sk);
+       sock->sk = NULL;
+
        release_sock(sk);
        sock_put(sk);
 
@@ -816,6 +851,10 @@ static int isotp_bind(struct socket *sock, struct sockaddr *uaddr, int len)
        if (addr->can_addr.tp.rx_id == addr->can_addr.tp.tx_id)
                return -EADDRNOTAVAIL;
 
+       if ((addr->can_addr.tp.rx_id | addr->can_addr.tp.tx_id) &
+           (CAN_ERR_FLAG | CAN_RTR_FLAG))
+               return -EADDRNOTAVAIL;
+
        if (!addr->can_ifindex)
                return -ENODEV;
 
@@ -895,8 +934,13 @@ static int isotp_getname(struct socket *sock, struct sockaddr *uaddr,
        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);
@@ -925,6 +969,22 @@ static int isotp_setsockopt(struct socket *sock, int level, int optname,
                        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;
        }
@@ -959,6 +1019,16 @@ static int isotp_getsockopt(struct socket *sock, int level, int optname,
                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;
        }
@@ -1045,6 +1115,8 @@ static int isotp_init(struct sock *sk)
        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;