blob: 944016c390c004c8d6d6191c6fed8dc08f6ec566 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2010-2011 EIA Electronics
*
* Authors:
* Kurt Van Dijck <kurt.van.dijck@eia.be>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the version 2 of the GNU General Public License
* as published by the Free Software Foundation
*/
#include <linux/skbuff.h>
#include <linux/hrtimer.h>
#include <linux/version.h>
#include <linux/if_arp.h>
#include <linux/wait.h>
#include <linux/seq_file.h>
#include <linux/can/skb.h>
#include "j1939-priv.h"
#define J1939_REGULAR 0
#define J1939_EXTENDED 1
#define J1939_ETP_PGN_CTL 0xc800
#define J1939_ETP_PGN_DAT 0xc700
#define J1939_TP_PGN_CTL 0xec00
#define J1939_TP_PGN_DAT 0xeb00
#define J1939_TP_CMD_BAM 0x20
#define J1939_TP_CMD_RTS 0x10
#define J1939_TP_CMD_CTS 0x11
#define J1939_TP_CMD_EOF 0x13
#define J1939_TP_CMD_ABORT 0xff
#define J1939_ETP_CMD_RTS 0x14
#define J1939_ETP_CMD_CTS 0x15
#define J1939_ETP_CMD_DPO 0x16
#define J1939_CMD_EOF 0x17
#define J1939_ETP_CMD_ABORT 0xff
#define J1939_ABORT_BUSY 1
#define J1939_ABORT_RESOURCE 2
#define J1939_ABORT_TIMEOUT 3
#define J1939_ABORT_GENERIC 4
#define J1939_ABORT_FAULT 5
#define J1939_MAX_TP_PACKET_SIZE (7 * 0xff)
#define J1939_MAX_ETP_PACKET_SIZE (7 * 0x00ffffff)
static unsigned int block = 255;
static unsigned int max_packet_size = J1939_MAX_ETP_PACKET_SIZE;
static unsigned int retry_ms = 20;
static unsigned int packet_delay;
static unsigned int padding = 1;
/* the limit values for sysctl */
static int block_min = 1;
static int block_max = 255;
static int max_size_max = J1939_MAX_ETP_PACKET_SIZE;
static int max_size_min = 8;
static int packet_delay_max = 1250;
static int packet_delay_min = 0;
static int padding_max = 1;
static int padding_min = 0;
static int retry_max = 1250;
static int retry_min = 1;
struct session {
struct list_head list;
atomic_t refs;
spinlock_t lock;
/* ifindex, src, dst, pgn define the session block
* the are _never_ modified after insertion in the list
* this decreases locking problems a _lot_
*/
struct j1939_sk_buff_cb *cb;
struct sk_buff *skb;
int skb_iif;
/* all tx related stuff (last_txcmd, pkt.tx)
* is protected (modified only) with the txtask tasklet
* 'total' & 'block' are never changed,
* last_cmd, last & block are protected by ->lock
* this means that the tx may run after cts is received that should
* have stopped tx, but this time discrepancy is never avoided anyhow
*/
u8 last_cmd, last_txcmd;
bool transmission;
bool extd;
struct {
/* these do not require 16 bit, they should fit in u8
* but putting in int makes it easier to deal with
*/
unsigned int total, done, last, tx;
unsigned int block; /* for TP */
unsigned int dpo; /* for ETP */
} pkt;
struct hrtimer txtimer, rxtimer;
/* tasklets for execution of tx/rx timer handler in softirq */
struct tasklet_struct txtask, rxtask;
};
/* forward declarations */
static struct session *j1939_session_new(struct sk_buff *skb);
static struct session *j1939_session_fresh_new(int size,
struct sk_buff *rel_skb,
pgn_t pgn);
static void j1939tp_del_work(struct work_struct *work);
/* local variables */
static DEFINE_SPINLOCK(tp_lock);
static struct list_head tp_sessionq = LIST_HEAD_INIT(tp_sessionq);
static struct list_head tp_extsessionq = LIST_HEAD_INIT(tp_extsessionq);
static DEFINE_SPINLOCK(tp_dellock);
static struct list_head tp_delsessionq = LIST_HEAD_INIT(tp_delsessionq);
static DECLARE_WORK(tp_delwork, j1939tp_del_work);
static DECLARE_WAIT_QUEUE_HEAD(tp_wait);
/* helpers */
static inline void fix_cb(struct j1939_sk_buff_cb *cb)
{
cb->msg_flags &= ~MSG_SYN;
}
static inline struct list_head *sessionq(bool extd)
{
return extd ? &tp_extsessionq : &tp_sessionq;
}
static inline void j1939_session_destroy(struct session *session)
{
kfree_skb(session->skb);
hrtimer_cancel(&session->rxtimer);
hrtimer_cancel(&session->txtimer);
tasklet_disable(&session->rxtask);
tasklet_disable(&session->txtask);
kfree(session);
}
/* clean up work queue */
static void j1939tp_del_work(struct work_struct *work)
{
struct session *session;
do {
session = NULL;
spin_lock_bh(&tp_dellock);
if (list_empty(&tp_delsessionq)) {
spin_unlock_bh(&tp_dellock);
break;
}
session = list_first_entry(&tp_delsessionq,
struct session, list);
list_del_init(&session->list);
spin_unlock_bh(&tp_dellock);
j1939_session_destroy(session);
} while (1);
}
/* reference counter */
static inline void j1939_session_get(struct session *session)
{
atomic_inc(&session->refs);
}
static void j1939_session_put(struct session *session)
{
if (atomic_add_return(-1, &session->refs) >= 0)
/* not the last one */
return;
hrtimer_try_to_cancel(&session->rxtimer);
hrtimer_try_to_cancel(&session->txtimer);
tasklet_disable_nosync(&session->rxtask);
tasklet_disable_nosync(&session->txtask);
if (in_interrupt()) {
spin_lock_bh(&tp_dellock);
list_add_tail(&session->list, &tp_delsessionq);
spin_unlock_bh(&tp_dellock);
schedule_work(&tp_delwork);
} else {
/* destroy session right here */
j1939_session_destroy(session);
}
}
/* transport status locking */
static inline void j1939_session_lock(struct session *session)
{
j1939_session_get(session); /* safety measure */
spin_lock_bh(&session->lock);
}
static inline void j1939_session_unlock(struct session *session)
{
spin_unlock_bh(&session->lock);
j1939_session_put(session);
}
static inline void j1939_sessionlist_lock(void)
{
spin_lock_bh(&tp_lock);
}
static inline void j1939_sessionlist_unlock(void)
{
spin_unlock_bh(&tp_lock);
}
/* see if we are receiver
* returns 0 for broadcasts, although we will receive them
*/
static inline int j1939tp_im_receiver(struct sk_buff *skb)
{
struct j1939_sk_buff_cb *cb = j1939_get_cb(skb);
return cb->dst_flags & ECU_LOCAL;
}
/* see if we are sender */
static inline int j1939tp_im_transmitter(struct sk_buff *skb)
{
struct j1939_sk_buff_cb *cb = j1939_get_cb(skb);
return cb->src_flags & ECU_LOCAL;
}
/* see if we are involved as either receiver or transmitter */
static int j1939tp_im_involved(struct sk_buff *skb, bool swap)
{
return swap ? j1939tp_im_receiver(skb) : j1939tp_im_transmitter(skb);
}
static int j1939tp_im_involved_anydir(struct sk_buff *skb)
{
struct j1939_sk_buff_cb *cb = j1939_get_cb(skb);
return (cb->src_flags | cb->dst_flags) & ECU_LOCAL;
}
/* extract pgn from flow-ctl message */
static inline pgn_t j1939xtp_ctl_to_pgn(const u8 *dat)
{
pgn_t pgn;
pgn = (dat[7] << 16) | (dat[6] << 8) | (dat[5] << 0);
if (pgn_is_pdu1(pgn))
pgn &= 0xffff00;
return pgn;
}
static inline unsigned int j1939tp_ctl_to_size(const u8 *dat)
{
return (dat[2] << 8) + (dat[1] << 0);
}
static inline unsigned int j1939etp_ctl_to_packet(const u8 *dat)
{
return (dat[4] << 16) | (dat[3] << 8) | (dat[2] << 0);
}
static inline unsigned int j1939etp_ctl_to_size(const u8 *dat)
{
return (dat[4] << 24) | (dat[3] << 16) |
(dat[2] << 8) | (dat[1] << 0);
}
/* find existing session:
* reverse: swap cb's src & dst
* there is no problem with matching broadcasts, since
* broadcasts (no dst, no da) would never call this
* with reverse == true
*/
static bool j1939tp_match(struct session *session, struct sk_buff *skb,
bool reverse)
{
struct j1939_sk_buff_cb *cb = j1939_get_cb(skb);
if (session->skb_iif != skb->skb_iif)
return false;
if (reverse) {
if (session->cb->addr.src_name) {
if (session->cb->addr.src_name != cb->addr.dst_name)
return false;
} else if (session->cb->addr.sa != cb->addr.da) {
return false;
}
if (session->cb->addr.dst_name) {
if (session->cb->addr.dst_name != cb->addr.src_name)
return false;
} else if (session->cb->addr.da != cb->addr.sa) {
return false;
}
} else {
if (session->cb->addr.src_name) {
if (session->cb->addr.src_name != cb->addr.src_name)
return false;
} else if (session->cb->addr.sa != cb->addr.sa) {
return false;
}
if (session->cb->addr.dst_name) {
if (session->cb->addr.dst_name != cb->addr.dst_name)
return false;
} else if (session->cb->addr.da != cb->addr.da) {
return false;
}
}
return true;
}
static struct session *_j1939tp_find(struct list_head *root,
struct sk_buff *skb, bool reverse)
{
struct session *session;
list_for_each_entry(session, root, list) {
j1939_session_get(session);
if (j1939tp_match(session, skb, reverse))
return session;
j1939_session_put(session);
}
return NULL;
}
static struct session *j1939tp_find(struct list_head *root,
struct sk_buff *skb, bool reverse)
{
struct session *session;
j1939_sessionlist_lock();
session = _j1939tp_find(root, skb, reverse);
j1939_sessionlist_unlock();
return session;
}
static void j1939_skbcb_swap(struct j1939_sk_buff_cb *cb)
{
swap(cb->addr.dst_name, cb->addr.src_name);
swap(cb->addr.da, cb->addr.sa);
swap(cb->dst_flags, cb->src_flags);
}
/* TP transmit packet functions */
static int j1939tp_tx_dat(struct sk_buff *related, bool extd,
const u8 *dat, int len)
{
struct sk_buff *skb;
struct j1939_sk_buff_cb *skb_cb;
u8 *skdat;
skb = alloc_skb(sizeof(struct can_frame) + sizeof(struct can_skb_priv),
GFP_ATOMIC);
if (unlikely(!skb)) {
pr_alert("%s: out of memory?\n", __func__);
return -ENOMEM;
}
can_skb_reserve(skb);
can_skb_prv(skb)->ifindex = can_skb_prv(related)->ifindex;
/* reserve CAN header */
skb_reserve(skb, offsetof(struct can_frame, data));
skb->dev = related->dev;
skb->protocol = related->protocol;
skb->pkt_type = related->pkt_type;
skb->ip_summed = related->ip_summed;
memcpy(skb->cb, related->cb, sizeof(skb->cb));
skb_cb = j1939_get_cb(skb);
fix_cb(skb_cb);
/* fix pgn */
skb_cb->addr.pgn = extd ? J1939_ETP_PGN_DAT : J1939_TP_PGN_DAT;
skdat = skb_put(skb, len);
memcpy(skdat, dat, len);
if (padding && len < 8)
memset(skb_put(skb, 8 - len), 0xff, 8 - len);
return j1939_send(skb);
}
static int j1939xtp_do_tx_ctl(struct sk_buff *related, bool extd,
bool swap_src_dst, pgn_t pgn, const u8 *dat)
{
struct sk_buff *skb;
struct j1939_sk_buff_cb *skb_cb;
u8 *skdat;
if (!j1939tp_im_involved(related, swap_src_dst))
return 0;
skb = alloc_skb(sizeof(struct can_frame) + sizeof(struct can_skb_priv),
GFP_ATOMIC);
if (unlikely(!skb)) {
pr_alert("%s: out of memory?\n", __func__);
return -ENOMEM;
}
skb->dev = related->dev;
can_skb_reserve(skb);
can_skb_prv(skb)->ifindex = can_skb_prv(related)->ifindex;
/* reserve CAN header */
skb_reserve(skb, offsetof(struct can_frame, data));
skb->protocol = related->protocol;
skb->pkt_type = related->pkt_type;
skb->ip_summed = related->ip_summed;
memcpy(skb->cb, related->cb, sizeof(skb->cb));
skb_cb = j1939_get_cb(skb);
fix_cb(skb_cb);
if (swap_src_dst)
j1939_skbcb_swap(skb_cb);
skb_cb->addr.pgn = extd ? J1939_ETP_PGN_CTL : J1939_TP_PGN_CTL;
skdat = skb_put(skb, 8);
memcpy(skdat, dat, 5);
skdat[5] = (pgn >> 0);
skdat[6] = (pgn >> 8);
skdat[7] = (pgn >> 16);
return j1939_send(skb);
}
static inline int j1939tp_tx_ctl(struct session *session,
bool swap_src_dst, const u8 *dat)
{
return j1939xtp_do_tx_ctl(session->skb, session->extd, swap_src_dst,
session->cb->addr.pgn, dat);
}
static int j1939xtp_tx_abort(struct sk_buff *related, bool extd,
bool swap_src_dst, int err, pgn_t pgn)
{
u8 dat[5];
if (!j1939tp_im_involved(related, swap_src_dst))
return 0;
memset(dat, 0xff, sizeof(dat));
dat[0] = J1939_TP_CMD_ABORT;
if (!extd)
dat[1] = err ?: J1939_ABORT_GENERIC;
return j1939xtp_do_tx_ctl(related, extd, swap_src_dst, pgn, dat);
}
/* timer & scheduler functions */
static inline void j1939_session_schedule_txnow(struct session *session)
{
tasklet_schedule(&session->txtask);
}
static enum hrtimer_restart j1939tp_txtimer(struct hrtimer *hrtimer)
{
struct session *session;
session = container_of(hrtimer, struct session, txtimer);
j1939_session_schedule_txnow(session);
return HRTIMER_NORESTART;
}
static inline void j1939tp_schedule_txtimer(struct session *session, int msec)
{
hrtimer_start(&session->txtimer,
ktime_set(msec / 1000, (msec % 1000) * 1000000UL),
HRTIMER_MODE_REL);
}
static inline void j1939tp_set_rxtimeout(struct session *session, int msec)
{
hrtimer_start(&session->rxtimer,
ktime_set(msec / 1000, (msec % 1000) * 1000000UL),
HRTIMER_MODE_REL);
}
/* session completion functions */
/* j1939_session_drop
* removes a session from open session list
*/
static inline void j1939_session_drop(struct session *session)
{
j1939_sessionlist_lock();
list_del_init(&session->list);
j1939_sessionlist_unlock();
if (session->transmission) {
if (session->skb && session->skb->sk)
j1939_sock_pending_del(session->skb->sk);
wake_up_all(&tp_wait);
}
j1939_session_put(session);
}
static inline void j1939_session_completed(struct session *session)
{
/* distribute among j1939 receivers */
j1939_recv(session->skb);
j1939_session_drop(session);
}
static void j1939_session_cancel(struct session *session, int err)
{
if ((err >= 0) && j1939tp_im_involved_anydir(session->skb)) {
if (!j1939cb_is_broadcast(session->cb)) {
/* do not send aborts on incoming broadcasts */
j1939xtp_tx_abort(session->skb, session->extd,
!(session->cb->src_flags & ECU_LOCAL),
err, session->cb->addr.pgn);
}
}
j1939_session_drop(session);
}
static enum hrtimer_restart j1939tp_rxtimer(struct hrtimer *hrtimer)
{
struct session *session = container_of(hrtimer, struct session,
rxtimer);
tasklet_schedule(&session->rxtask);
return HRTIMER_NORESTART;
}
static void j1939tp_rxtask(unsigned long val)
{
struct session *session = (void *)val;
j1939_session_get(session);
pr_alert("%s: timeout on %i\n", __func__, session->skb_iif);
j1939_session_cancel(session, J1939_ABORT_TIMEOUT);
j1939_session_put(session);
}
/* receive packet functions */
static void _j1939xtp_rx_bad_message(struct sk_buff *skb, bool extd, bool reverse)
{
struct session *session;
pgn_t pgn;
pgn = j1939xtp_ctl_to_pgn(skb->data);
session = j1939tp_find(sessionq(extd), skb, reverse);
if (session /*&& (session->cb->addr.pgn == pgn)*/) {
/* do not allow TP control messages on 2 pgn's */
j1939_session_cancel(session, J1939_ABORT_FAULT);
j1939_session_put(session); /* ~j1939tp_find */
return;
}
j1939xtp_tx_abort(skb, extd, 0, J1939_ABORT_FAULT, pgn);
if (!session)
return;
j1939_session_put(session); /* ~j1939tp_find */
}
/* abort packets may come in 2 directions */
static void j1939xtp_rx_bad_message(struct sk_buff *skb, bool extd)
{
pr_info("%s, pgn %05x\n", __func__, j1939xtp_ctl_to_pgn(skb->data));
_j1939xtp_rx_bad_message(skb, extd, 0);
_j1939xtp_rx_bad_message(skb, extd, 1);
}
static void _j1939xtp_rx_abort(struct sk_buff *skb, bool extd, bool reverse)
{
struct session *session;
pgn_t pgn;
pgn = j1939xtp_ctl_to_pgn(skb->data);
session = j1939tp_find(sessionq(extd), skb, reverse);
if (!session)
return;
if (session->transmission && !session->last_txcmd) {
/* empty block:
* do not drop session when a transmit session did not
* start yet
*/
} else if (session->cb->addr.pgn == pgn) {
j1939_session_drop(session);
}
/* TODO: maybe cancel current connection
* as another pgn was communicated
*/
j1939_session_put(session); /* ~j1939tp_find */
}
/* abort packets may come in 2 directions */
static inline void j1939xtp_rx_abort(struct sk_buff *skb, bool extd)
{
pr_info("%s %i, %05x\n", __func__, skb->skb_iif,
j1939xtp_ctl_to_pgn(skb->data));
_j1939xtp_rx_abort(skb, extd, 0);
_j1939xtp_rx_abort(skb, extd, 1);
}
static void j1939xtp_rx_eof(struct sk_buff *skb, bool extd)
{
struct session *session;
pgn_t pgn;
/* end of tx cycle */
pgn = j1939xtp_ctl_to_pgn(skb->data);
session = j1939tp_find(sessionq(extd), skb, 1);
if (!session) {
/* strange, we had EOF on closed connection
* do nothing, as EOF closes the connection anyway
*/
return;
}
if (session->cb->addr.pgn != pgn) {
j1939xtp_tx_abort(skb, extd, 1, J1939_ABORT_BUSY, pgn);
j1939_session_cancel(session, J1939_ABORT_BUSY);
} else {
/* transmitted without problems */
j1939_session_completed(session);
}
j1939_session_put(session); /* ~j1939tp_find */
}
static void j1939xtp_rx_cts(struct sk_buff *skb, bool extd)
{
struct session *session;
pgn_t pgn;
unsigned int pkt;
const u8 *dat;
dat = skb->data;
pgn = j1939xtp_ctl_to_pgn(skb->data);
session = j1939tp_find(sessionq(extd), skb, 1);
if (!session) {
/* 'CTS shall be ignored' */
return;
}
if (session->cb->addr.pgn != pgn) {
/* what to do? */
j1939xtp_tx_abort(skb, extd, 1, J1939_ABORT_BUSY, pgn);
j1939_session_cancel(session, J1939_ABORT_BUSY);
j1939_session_put(session); /* ~j1939tp_find */
return;
}
j1939_session_lock(session);
pkt = extd ? j1939etp_ctl_to_packet(dat) : dat[2];
if (!dat[0]) {
hrtimer_cancel(&session->txtimer);
} else if (!pkt) {
goto bad_fmt;
} else if (dat[1] > session->pkt.block /* 0xff for etp */) {
goto bad_fmt;
} else {
/* set packet counters only when not CTS(0) */
session->pkt.done = pkt - 1;
session->pkt.last = session->pkt.done + dat[1];
if (session->pkt.last > session->pkt.total)
/* safety measure */
session->pkt.last = session->pkt.total;
/* TODO: do not set tx here, do it in txtask */
session->pkt.tx = session->pkt.done;
}
session->last_cmd = dat[0];
j1939_session_unlock(session);
if (dat[1]) {
j1939tp_set_rxtimeout(session, 1250);
if (j1939tp_im_transmitter(session->skb))
j1939_session_schedule_txnow(session);
} else {
/* CTS(0) */
j1939tp_set_rxtimeout(session, 550);
}
j1939_session_put(session); /* ~j1939tp_find */
return;
bad_fmt:
j1939_session_unlock(session);
j1939_session_cancel(session, J1939_ABORT_FAULT);
j1939_session_put(session); /* ~j1939tp_find */
}
static void j1939xtp_rx_rts(struct sk_buff *skb, bool extd)
{
struct j1939_sk_buff_cb *cb = j1939_get_cb(skb);
struct session *session;
int len;
const u8 *dat;
pgn_t pgn;
dat = skb->data;
pgn = j1939xtp_ctl_to_pgn(dat);
if ((J1939_TP_CMD_RTS == dat[0]) && j1939cb_is_broadcast(cb)) {
pr_alert("%s: rts without destination (%i %02x)\n", __func__,
skb->skb_iif, cb->addr.sa);
return;
}
/* TODO: abort RTS when a similar
* TP is pending in the other direction
*/
session = j1939tp_find(sessionq(extd), skb, 0);
if (session && !j1939tp_im_transmitter(skb)) {
/* RTS on pending connection */
j1939_session_cancel(session, J1939_ABORT_BUSY);
if ((pgn != session->cb->addr.pgn) && (J1939_TP_CMD_BAM != dat[0]))
j1939xtp_tx_abort(skb, extd, 1, J1939_ABORT_BUSY, pgn);
j1939_session_put(session); /* ~j1939tp_find */
return;
} else if (!session && j1939tp_im_transmitter(skb)) {
pr_alert("%s: I should tx (%i %02x %02x)\n", __func__,
skb->skb_iif, cb->addr.sa, cb->addr.da);
return;
}
if (session && (session->last_cmd != 0)) {
/* we received a second rts on the same connection */
pr_alert("%s: connection exists (%i %02x %02x)\n", __func__,
skb->skb_iif, cb->addr.sa, cb->addr.da);
j1939_session_cancel(session, J1939_ABORT_BUSY);
j1939_session_put(session); /* ~j1939tp_find */
return;
}
if (session) {
/* make sure 'sa' & 'da' are correct !
* They may be 'not filled in yet' for sending
* skb's, since they did not pass the Address Claim ever.
*/
session->cb->addr.sa = cb->addr.sa;
session->cb->addr.da = cb->addr.da;
} else {
int abort = 0;
if (extd) {
len = j1939etp_ctl_to_size(dat);
if (len > J1939_MAX_ETP_PACKET_SIZE)
abort = J1939_ABORT_FAULT;
else if (max_packet_size && (len > max_packet_size))
abort = J1939_ABORT_RESOURCE;
else if (len <= J1939_MAX_TP_PACKET_SIZE)
abort = J1939_ABORT_FAULT;
} else {
len = j1939tp_ctl_to_size(dat);
if (len > J1939_MAX_TP_PACKET_SIZE)
abort = J1939_ABORT_FAULT;
else if (max_packet_size && (len > max_packet_size))
abort = J1939_ABORT_RESOURCE;
}
if (abort) {
j1939xtp_tx_abort(skb, extd, 1, abort, pgn);
return;
}
session = j1939_session_fresh_new(len, skb, pgn);
if (!session) {
j1939xtp_tx_abort(skb, extd, 1, J1939_ABORT_RESOURCE, pgn);
return;
}
session->extd = extd;
/* initialize the control buffer: plain copy */
session->pkt.total = (len + 6) / 7;
session->pkt.block = 0xff;
if (!extd) {
if (dat[3] != session->pkt.total)
pr_alert("%s: strange total, %u != %u\n",
__func__, session->pkt.total,
dat[3]);
session->pkt.total = dat[3];
session->pkt.block = dat[4];
}
session->pkt.done = 0;
session->pkt.tx = 0;
j1939_session_get(session); /* equivalent to j1939tp_find() */
j1939_sessionlist_lock();
list_add_tail(&session->list, sessionq(extd));
j1939_sessionlist_unlock();
}
session->last_cmd = dat[0];
j1939tp_set_rxtimeout(session, 1250);
if (j1939tp_im_receiver(session->skb)) {
if (extd || (J1939_TP_CMD_BAM != dat[0]))
j1939_session_schedule_txnow(session);
}
/* as soon as it's inserted, things can go fast
* protect against a long delay
* between spin_unlock & next statement
* so, only release here, at the end
*/
j1939_session_put(session); /* ~j1939tp_find */
}
static void j1939xtp_rx_dpo(struct sk_buff *skb, bool extd)
{
struct session *session;
pgn_t pgn;
const u8 *dat = skb->data;
pgn = j1939xtp_ctl_to_pgn(dat);
session = j1939tp_find(sessionq(extd), skb, 0);
if (!session) {
pr_info("%s: %s\n", __func__, "no connection found");
return;
}
if (session->cb->addr.pgn != pgn) {
pr_info("%s: different pgn\n", __func__);
j1939xtp_tx_abort(skb, 1, 1, J1939_ABORT_BUSY, pgn);
j1939_session_cancel(session, J1939_ABORT_BUSY);
j1939_session_put(session); /* ~j1939tp_find */
return;
}
/* transmitted without problems */
session->pkt.dpo = j1939etp_ctl_to_packet(skb->data);
session->last_cmd = dat[0];
j1939tp_set_rxtimeout(session, 750);
j1939_session_put(session); /* ~j1939tp_find */
}
static void j1939xtp_rx_dat(struct sk_buff *skb, bool extd)
{
struct session *session;
const u8 *dat;
u8 *tpdat;
int offset;
int nbytes;
int final;
int do_cts_eof;
int packet;
session = j1939tp_find(sessionq(extd), skb, 0);
if (!session) {
pr_info("%s:%s\n", __func__, "no connection found");
return;
}
dat = skb->data;
if (skb->len <= 1)
/* makes no sense */
goto strange_packet_unlocked;
j1939_session_lock(session);
switch (session->last_cmd) {
case 0xff:
break;
case J1939_ETP_CMD_DPO:
if (extd)
break;
case J1939_TP_CMD_BAM:
case J1939_TP_CMD_CTS:
if (!extd)
break;
default:
pr_info("%s: last %02x\n", __func__,
session->last_cmd);
goto strange_packet;
}
packet = (dat[0] - 1 + session->pkt.dpo);
offset = packet * 7;
if ((packet > session->pkt.total) ||
(session->pkt.done + 1) > session->pkt.total) {
pr_info("%s: should have been completed\n", __func__);
goto strange_packet;
}
nbytes = session->skb->len - offset;
if (nbytes > 7)
nbytes = 7;
if ((nbytes <= 0) || ((nbytes + 1) > skb->len)) {
pr_info("%s: nbytes %i, len %i\n", __func__, nbytes,
skb->len);
goto strange_packet;
}
tpdat = session->skb->data;
memcpy(&tpdat[offset], &dat[1], nbytes);
if (packet == session->pkt.done)
++session->pkt.done;
if (!extd && j1939cb_is_broadcast(session->cb)) {
final = session->pkt.done >= session->pkt.total;
do_cts_eof = 0;
} else {
final = 0; /* never final, an EOF must follow */
do_cts_eof = (session->pkt.done >= session->pkt.last);
}
j1939_session_unlock(session);
if (final) {
j1939_session_completed(session);
} else if (do_cts_eof) {
j1939tp_set_rxtimeout(session, 1250);
if (j1939tp_im_receiver(session->skb))
j1939_session_schedule_txnow(session);
} else {
j1939tp_set_rxtimeout(session, 250);
}
session->last_cmd = 0xff;
j1939_session_put(session); /* ~j1939tp_find */
return;
strange_packet:
/* unlock session (spinlock) before trying to send */
j1939_session_unlock(session);
strange_packet_unlocked:
j1939_session_cancel(session, J1939_ABORT_FAULT);
j1939_session_put(session); /* ~j1939tp_find */
}
/* transmit function */
static int j1939tp_txnext(struct session *session)
{
u8 dat[8];
const u8 *tpdat;
int ret, offset, pkt_done, pkt_end;
unsigned int pkt, len, pdelay;
memset(dat, 0xff, sizeof(dat));
j1939_session_get(session); /* do not loose it */
switch (session->last_cmd) {
case 0:
if (!j1939tp_im_transmitter(session->skb))
break;
dat[1] = (session->skb->len >> 0);
dat[2] = (session->skb->len >> 8);
dat[3] = session->pkt.total;
if (session->extd) {
dat[0] = J1939_ETP_CMD_RTS;
dat[1] = (session->skb->len >> 0);
dat[2] = (session->skb->len >> 8);
dat[3] = (session->skb->len >> 16);
dat[4] = (session->skb->len >> 24);
} else if (j1939cb_is_broadcast(session->cb)) {
dat[0] = J1939_TP_CMD_BAM;
/* fake cts for broadcast */
session->pkt.tx = 0;
} else {
dat[0] = J1939_TP_CMD_RTS;
dat[4] = dat[3];
}
if (dat[0] == session->last_txcmd)
/* done already */
break;
ret = j1939tp_tx_ctl(session, 0, dat);
if (ret < 0)
goto failed;
session->last_txcmd = dat[0];
/* must lock? */
if (J1939_TP_CMD_BAM == dat[0])
j1939tp_schedule_txtimer(session, 50);
j1939tp_set_rxtimeout(session, 1250);
break;
case J1939_TP_CMD_RTS:
case J1939_ETP_CMD_RTS: /* fallthrough */
if (!j1939tp_im_receiver(session->skb))
break;
tx_cts:
ret = 0;
len = session->pkt.total - session->pkt.done;
len = min(max(len, session->pkt.block), block ?: 255);
if (session->extd) {
pkt = session->pkt.done + 1;
dat[0] = J1939_ETP_CMD_CTS;
dat[1] = len;
dat[2] = (pkt >> 0);
dat[3] = (pkt >> 8);
dat[4] = (pkt >> 16);
} else {
dat[0] = J1939_TP_CMD_CTS;
dat[1] = len;
dat[2] = session->pkt.done + 1;
}
if (dat[0] == session->last_txcmd)
/* done already */
break;
ret = j1939tp_tx_ctl(session, 1, dat);
if (ret < 0)
goto failed;
if (len)
/* only mark cts done when len is set */
session->last_txcmd = dat[0];
j1939tp_set_rxtimeout(session, 1250);
break;
case J1939_ETP_CMD_CTS:
if (j1939tp_im_transmitter(session->skb) && session->extd &&
(J1939_ETP_CMD_DPO != session->last_txcmd)) {
/* do dpo */
dat[0] = J1939_ETP_CMD_DPO;
session->pkt.dpo = session->pkt.done;
pkt = session->pkt.dpo;
dat[1] = session->pkt.last - session->pkt.done;
dat[2] = (pkt >> 0);
dat[3] = (pkt >> 8);
dat[4] = (pkt >> 16);
ret = j1939tp_tx_ctl(session, 0, dat);
if (ret < 0)
goto failed;
session->last_txcmd = dat[0];
j1939tp_set_rxtimeout(session, 1250);
session->pkt.tx = session->pkt.done;
}
/* fallthrough */
case J1939_TP_CMD_CTS: /* fallthrough */
case 0xff: /* did some data */ /* FIXME: let David Jander recheck this */
case J1939_ETP_CMD_DPO: /* fallthrough */
if ((session->extd || !j1939cb_is_broadcast(session->cb)) &&
j1939tp_im_receiver(session->skb)) {
if (session->pkt.done >= session->pkt.total) {
if (session->extd) {
dat[0] = J1939_CMD_EOF;
dat[1] = session->skb->len >> 0;
dat[2] = session->skb->len >> 8;
dat[3] = session->skb->len >> 16;
dat[4] = session->skb->len >> 24;
} else {
dat[0] = J1939_TP_CMD_EOF;
dat[1] = session->skb->len;
dat[2] = session->skb->len >> 8;
dat[3] = session->pkt.total;
}
if (dat[0] == session->last_txcmd)
/* done already */
break;
ret = j1939tp_tx_ctl(session, 1, dat);
if (ret < 0)
goto failed;
session->last_txcmd = dat[0];
j1939tp_set_rxtimeout(session, 1250);
/* wait for the EOF packet to come in */
break;
} else if (session->pkt.done >= session->pkt.last) {
session->last_txcmd = 0;
goto tx_cts;
}
}
case J1939_TP_CMD_BAM: /* fallthrough */
if (!j1939tp_im_transmitter(session->skb))
break;
tpdat = session->skb->data;
ret = 0;
pkt_done = 0;
pkt_end = (!session->extd && j1939cb_is_broadcast(session->cb))
? session->pkt.total : session->pkt.last;
while (session->pkt.tx < pkt_end) {
dat[0] = session->pkt.tx - session->pkt.dpo + 1;
offset = session->pkt.tx * 7;
len = session->skb->len - offset;
if (len > 7)
len = 7;
memcpy(&dat[1], &tpdat[offset], len);
ret = j1939tp_tx_dat(session->skb, session->extd,
dat, len + 1);
if (ret < 0)
break;
session->last_txcmd = 0xff;
++pkt_done;
++session->pkt.tx;
pdelay = j1939cb_is_broadcast(session->cb) ? 50 :
packet_delay;
if ((session->pkt.tx < session->pkt.total) && pdelay) {
j1939tp_schedule_txtimer(session, pdelay);
break;
}
}
if (pkt_done)
j1939tp_set_rxtimeout(session, 250);
if (ret)
goto failed;
break;
}
j1939_session_put(session);
return 0;
failed:
j1939_session_put(session);
return ret;
}
static void j1939tp_txtask(unsigned long val)
{
struct session *session = (void *)val;
int ret;
j1939_session_get(session);
ret = j1939tp_txnext(session);
if (ret < 0)
j1939tp_schedule_txtimer(session, retry_ms ?: 20);
j1939_session_put(session);
}
static inline int j1939tp_tx_initial(struct session *session)
{
int ret;
j1939_session_get(session);
ret = j1939tp_txnext(session);
/* set nonblocking for further packets */
session->cb->msg_flags |= MSG_DONTWAIT;
j1939_session_put(session);
return ret;
}
/* this call is to be used as probe within wait_event_xxx() */
static int j1939_session_insert(struct session *session)
{
struct session *pending;
j1939_sessionlist_lock();
pending = _j1939tp_find(sessionq(session->extd), session->skb, 0);
if (pending)
/* revert the effect of find() */
j1939_session_put(pending);
else
list_add_tail(&session->list, sessionq(session->extd));
j1939_sessionlist_unlock();
return pending ? 0 : 1;
}
/* j1939 main intf */
int j1939_send_transport(struct sk_buff *skb)
{
struct j1939_sk_buff_cb *cb = j1939_get_cb(skb);
struct session *session;
int ret;
struct j1939_priv *priv;
if ((J1939_TP_PGN_DAT == cb->addr.pgn) || (J1939_TP_PGN_CTL == cb->addr.pgn) ||
(J1939_ETP_PGN_DAT == cb->addr.pgn) || (J1939_ETP_PGN_CTL == cb->addr.pgn))
/* avoid conflict */
return -EDOM;
else if ((skb->len > J1939_MAX_ETP_PACKET_SIZE) ||
(max_packet_size && (skb->len > max_packet_size)))
return -EMSGSIZE;
if (skb->len > J1939_MAX_TP_PACKET_SIZE) {
if (j1939cb_is_broadcast(cb))
return -EDESTADDRREQ;
}
/* fill in addresses from names */
ret = j1939_fixup_address_claim(skb);
if (unlikely(ret))
return ret;
/* fix dst_flags, it may be used there soon */
priv = j1939_priv_get_by_ifindex(can_skb_prv(skb)->ifindex);
if (!priv)
return -EINVAL;
if (j1939_address_is_unicast(cb->addr.da) &&
priv->ents[cb->addr.da].nusers)
cb->dst_flags |= ECU_LOCAL;
j1939_priv_put(priv);
/* src is always local, I'm sending ... */
cb->src_flags |= ECU_LOCAL;
/* prepare new session */
session = j1939_session_new(skb);
if (!session)
return -ENOMEM;
session->skb_iif = can_skb_prv(skb)->ifindex;
session->extd = (skb->len > J1939_MAX_TP_PACKET_SIZE) ? J1939_EXTENDED : J1939_REGULAR;
session->transmission = true;
session->pkt.total = (skb->len + 6) / 7;
session->pkt.block = session->extd ? 255 :
min(block ?: 255, session->pkt.total);
if (j1939cb_is_broadcast(session->cb))
/* set the end-packet for broadcast */
session->pkt.last = session->pkt.total;
/* insert into queue, but avoid collision with pending session */
if (session->cb->msg_flags & MSG_DONTWAIT)
ret = j1939_session_insert(session) ? 0 : -EAGAIN;
else
ret = wait_event_interruptible(tp_wait,
j1939_session_insert(session));
if (ret < 0)
goto failed;
ret = j1939tp_tx_initial(session);
if (!ret)
/* transmission started */
return ret;
j1939_sessionlist_lock();
list_del_init(&session->list);
j1939_sessionlist_unlock();
failed:
/* hide the skb from j1939_session_drop, as it would
* kfree_skb, but our caller will kfree_skb(skb) too.
*/
session->skb = NULL;
j1939_session_drop(session);
return ret;
}
int j1939_recv_transport(struct sk_buff *skb)
{
struct j1939_sk_buff_cb *cb = j1939_get_cb(skb);
const u8 *dat;
switch (cb->addr.pgn) {
case J1939_ETP_PGN_DAT:
j1939xtp_rx_dat(skb, J1939_EXTENDED);
break;
case J1939_ETP_PGN_CTL:
if (skb->len < 8) {
j1939xtp_rx_bad_message(skb, J1939_EXTENDED);
break;
}
dat = skb->data;
switch (*dat) {
case J1939_ETP_CMD_RTS:
j1939xtp_rx_rts(skb, J1939_EXTENDED);
break;
case J1939_ETP_CMD_CTS:
j1939xtp_rx_cts(skb, J1939_EXTENDED);
break;
case J1939_ETP_CMD_DPO:
j1939xtp_rx_dpo(skb, J1939_EXTENDED);
break;
case J1939_CMD_EOF:
j1939xtp_rx_eof(skb, J1939_EXTENDED);
break;
case J1939_ETP_CMD_ABORT:
j1939xtp_rx_abort(skb, J1939_EXTENDED);
break;
default:
j1939xtp_rx_bad_message(skb, J1939_EXTENDED);
break;
}
break;
case J1939_TP_PGN_DAT:
j1939xtp_rx_dat(skb, J1939_REGULAR);
break;
case J1939_TP_PGN_CTL:
if (skb->len < 8) {
j1939xtp_rx_bad_message(skb, J1939_REGULAR);
break;
}
dat = skb->data;
switch (*dat) {
case J1939_TP_CMD_BAM:
case J1939_TP_CMD_RTS:
j1939xtp_rx_rts(skb, J1939_REGULAR);
break;
case J1939_TP_CMD_CTS:
j1939xtp_rx_cts(skb, J1939_REGULAR);
break;
case J1939_TP_CMD_EOF:
j1939xtp_rx_eof(skb, J1939_REGULAR);
break;
case J1939_TP_CMD_ABORT:
j1939xtp_rx_abort(skb, J1939_REGULAR);
break;
default:
j1939xtp_rx_bad_message(skb, J1939_REGULAR);
break;
}
break;
default:
return 0; /* no problem */
}
return 1; /* "I processed the message" */
}
static struct session *j1939_session_fresh_new(int size,
struct sk_buff *rel_skb,
pgn_t pgn)
{
struct sk_buff *skb;
struct j1939_sk_buff_cb *cb;
struct session *session;
/* this SKB is allocated without headroom for CAN skb's.
* This may not pose a problem, this SKB will never
* enter generic CAN functions
*/
skb = alloc_skb(size, GFP_ATOMIC);
if (!skb)
return NULL;
cb = j1939_get_cb(skb);
memcpy(cb, rel_skb->cb, sizeof(*cb));
fix_cb(cb);
cb->addr.pgn = pgn;
session = j1939_session_new(skb);
if (!session) {
kfree_skb(skb);
return NULL;
}
session->skb_iif = rel_skb->skb_iif;
skb->skb_iif = rel_skb->skb_iif;
skb->dev = rel_skb->dev;
/* alloc data area */
skb_put(skb, size);
return session;
}
static struct session *j1939_session_new(struct sk_buff *skb)
{
struct session *session;
session = kzalloc(sizeof(*session), gfp_any());
if (!session)
return NULL;
INIT_LIST_HEAD(&session->list);
spin_lock_init(&session->lock);
session->skb = skb;
session->cb = j1939_get_cb(session->skb);
hrtimer_init(&session->txtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
session->txtimer.function = j1939tp_txtimer;
hrtimer_init(&session->rxtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
session->rxtimer.function = j1939tp_rxtimer;
tasklet_init(&session->txtask, j1939tp_txtask, (unsigned long)session);
tasklet_init(&session->rxtask, j1939tp_rxtask, (unsigned long)session);
return session;
}
int j1939tp_rmdev_notifier(struct net_device *netdev)
{
struct session *session, *saved;
j1939_sessionlist_lock();
list_for_each_entry_safe(session, saved, &tp_sessionq, list) {
if (session->skb_iif != netdev->ifindex)
continue;
list_del_init(&session->list);
j1939_session_put(session);
}
list_for_each_entry_safe(session, saved, &tp_extsessionq, list) {
if (session->skb_iif != netdev->ifindex)
continue;
list_del_init(&session->list);
j1939_session_put(session);
}
j1939_sessionlist_unlock();
return NOTIFY_DONE;
}
/* PROC */
static int j1939tp_proc_show_session(struct seq_file *sqf,
struct session *session)
{
seq_printf(sqf, "%i", session->skb_iif);
if (session->cb->addr.src_name)
seq_printf(sqf, "\t%016llx", session->cb->addr.src_name);
else
seq_printf(sqf, "\t%02x", session->cb->addr.sa);
if (session->cb->addr.dst_name)
seq_printf(sqf, "\t%016llx", session->cb->addr.dst_name);
else if (j1939_address_is_unicast(session->cb->addr.da))
seq_printf(sqf, "\t%02x", session->cb->addr.da);
else
seq_puts(sqf, "\t-");
seq_printf(sqf, "\t%05x\t%u/%u\n", session->cb->addr.pgn,
session->pkt.done * 7, session->skb->len);
return 0;
}
static int j1939tp_proc_show(struct seq_file *sqf, void *v)
{
struct session *session;
seq_puts(sqf, "iface\tsrc\tdst\tpgn\tdone/total\n");
j1939_sessionlist_lock();
list_for_each_entry(session, &tp_sessionq, list)
j1939tp_proc_show_session(sqf, session);
list_for_each_entry(session, &tp_extsessionq, list)
j1939tp_proc_show_session(sqf, session);
j1939_sessionlist_unlock();
return 0;
}
static int j1939tp_proc_open(struct inode *inode, struct file *file)
{
return single_open(file, j1939tp_proc_show, NULL);
}
static const struct file_operations j1939tp_proc_ops = {
.owner = THIS_MODULE,
.open = j1939tp_proc_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static struct ctl_table canj1939_sysctl_table[] = {
{
.procname = "transport_burst_count",
.data = &block,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = &block_min,
.extra2 = &block_max,
}, {
.procname = "transport_max_size",
.data = &max_packet_size,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = &max_size_min,
.extra2 = &max_size_max,
}, {
.procname = "transport_packet_delay",
.data = &packet_delay,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = &packet_delay_min,
.extra2 = &packet_delay_max,
}, {
.procname = "transport_padding",
.data = &padding,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = &padding_min,
.extra2 = &padding_max,
}, {
.procname = "transport_retry_time",
.data = &retry_ms,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = &retry_min,
.extra2 = &retry_max,
}, {
/* sentinel */
},
};
static struct ctl_table_header *sysctl_hdr;
/* module init */
int __init j1939tp_module_init(void)
{
if (!proc_create("transport", 0444, j1939_procdir, &j1939tp_proc_ops))
return -ENOMEM;
sysctl_hdr = register_net_sysctl(&init_net, "net/can-j1939",
canj1939_sysctl_table);
if (!sysctl_hdr) {
remove_proc_entry("transport", j1939_procdir);
return -ENOMEM;
}
return 0;
}
void j1939tp_module_exit(void)
{
struct session *session, *saved;
wake_up_all(&tp_wait);
unregister_net_sysctl_table(sysctl_hdr);
remove_proc_entry("transport", j1939_procdir);
j1939_sessionlist_lock();
list_for_each_entry_safe(session, saved, &tp_extsessionq, list) {
list_del_init(&session->list);
j1939_session_put(session);
}
list_for_each_entry_safe(session, saved, &tp_sessionq, list) {
list_del_init(&session->list);
j1939_session_put(session);
}
j1939_sessionlist_unlock();
flush_scheduled_work();
}