Commit 73e87e02 authored by Oliver Hartkopp's avatar Oliver Hartkopp Committed by David S. Miller

CAN: use hrtimers in can-bcm protocol

Make use of hrtimers to support high resolution capabilities, when
provided by the system clocksource.

The conversion to hrtimers additionally discovered and solved an
unlikely race condition that has been reproduced under (unrealistic)
massive receive load, which can only be produced on vcan software devices.

[ Fix printf format warnings on 64-bit -DaveM ]
Signed-off-by: default avatarOliver Hartkopp <oliver@hartkopp.net>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 85035568
...@@ -43,6 +43,7 @@ ...@@ -43,6 +43,7 @@
#include <linux/module.h> #include <linux/module.h>
#include <linux/init.h> #include <linux/init.h>
#include <linux/hrtimer.h>
#include <linux/list.h> #include <linux/list.h>
#include <linux/proc_fs.h> #include <linux/proc_fs.h>
#include <linux/uio.h> #include <linux/uio.h>
...@@ -66,7 +67,7 @@ ...@@ -66,7 +67,7 @@
#define REGMASK(id) ((id & CAN_RTR_FLAG) | ((id & CAN_EFF_FLAG) ? \ #define REGMASK(id) ((id & CAN_RTR_FLAG) | ((id & CAN_EFF_FLAG) ? \
(CAN_EFF_MASK | CAN_EFF_FLAG) : CAN_SFF_MASK)) (CAN_EFF_MASK | CAN_EFF_FLAG) : CAN_SFF_MASK))
#define CAN_BCM_VERSION CAN_VERSION #define CAN_BCM_VERSION "20080415"
static __initdata const char banner[] = KERN_INFO static __initdata const char banner[] = KERN_INFO
"can: broadcast manager protocol (rev " CAN_BCM_VERSION ")\n"; "can: broadcast manager protocol (rev " CAN_BCM_VERSION ")\n";
...@@ -85,11 +86,10 @@ struct bcm_op { ...@@ -85,11 +86,10 @@ struct bcm_op {
int ifindex; int ifindex;
canid_t can_id; canid_t can_id;
int flags; int flags;
unsigned long j_ival1, j_ival2, j_lastmsg;
unsigned long frames_abs, frames_filtered; unsigned long frames_abs, frames_filtered;
struct timer_list timer, thrtimer;
struct timeval ival1, ival2; struct timeval ival1, ival2;
ktime_t rx_stamp; struct hrtimer timer, thrtimer;
ktime_t rx_stamp, kt_ival1, kt_ival2, kt_lastmsg;
int rx_ifindex; int rx_ifindex;
int count; int count;
int nframes; int nframes;
...@@ -125,39 +125,6 @@ static inline struct bcm_sock *bcm_sk(const struct sock *sk) ...@@ -125,39 +125,6 @@ static inline struct bcm_sock *bcm_sk(const struct sock *sk)
#define OPSIZ sizeof(struct bcm_op) #define OPSIZ sizeof(struct bcm_op)
#define MHSIZ sizeof(struct bcm_msg_head) #define MHSIZ sizeof(struct bcm_msg_head)
/*
* rounded_tv2jif - calculate jiffies from timeval including optional up
* @tv: pointer to timeval
*
* Description:
* Unlike timeval_to_jiffies() provided in include/linux/jiffies.h, this
* function is intentionally more relaxed on precise timer ticks to get
* exact one jiffy for requested 1000us on a 1000HZ machine.
* This code is to be removed when upgrading to kernel hrtimer.
*
* Return:
* calculated jiffies (max: ULONG_MAX)
*/
static unsigned long rounded_tv2jif(const struct timeval *tv)
{
unsigned long sec = tv->tv_sec;
unsigned long usec = tv->tv_usec;
unsigned long jif;
if (sec > ULONG_MAX / HZ)
return ULONG_MAX;
/* round up to get at least the requested time */
usec += 1000000 / HZ - 1;
jif = usec / (1000000 / HZ);
if (sec * HZ > ULONG_MAX - jif)
return ULONG_MAX;
return jif + sec * HZ;
}
/* /*
* procfs functions * procfs functions
*/ */
...@@ -208,13 +175,17 @@ static int bcm_read_proc(char *page, char **start, off_t off, ...@@ -208,13 +175,17 @@ static int bcm_read_proc(char *page, char **start, off_t off,
len += snprintf(page + len, PAGE_SIZE - len, "[%d]%c ", len += snprintf(page + len, PAGE_SIZE - len, "[%d]%c ",
op->nframes, op->nframes,
(op->flags & RX_CHECK_DLC)?'d':' '); (op->flags & RX_CHECK_DLC)?'d':' ');
if (op->j_ival1) if (op->kt_ival1.tv64)
len += snprintf(page + len, PAGE_SIZE - len, len += snprintf(page + len, PAGE_SIZE - len,
"timeo=%ld ", op->j_ival1); "timeo=%lld ",
(long long)
ktime_to_us(op->kt_ival1));
if (op->j_ival2) if (op->kt_ival2.tv64)
len += snprintf(page + len, PAGE_SIZE - len, len += snprintf(page + len, PAGE_SIZE - len,
"thr=%ld ", op->j_ival2); "thr=%lld ",
(long long)
ktime_to_us(op->kt_ival2));
len += snprintf(page + len, PAGE_SIZE - len, len += snprintf(page + len, PAGE_SIZE - len,
"# recv %ld (%ld) => reduction: ", "# recv %ld (%ld) => reduction: ",
...@@ -238,13 +209,14 @@ static int bcm_read_proc(char *page, char **start, off_t off, ...@@ -238,13 +209,14 @@ static int bcm_read_proc(char *page, char **start, off_t off,
"tx_op: %03X %s [%d] ", "tx_op: %03X %s [%d] ",
op->can_id, bcm_proc_getifname(op->ifindex), op->can_id, bcm_proc_getifname(op->ifindex),
op->nframes); op->nframes);
if (op->j_ival1)
len += snprintf(page + len, PAGE_SIZE - len, "t1=%ld ",
op->j_ival1);
if (op->j_ival2) if (op->kt_ival1.tv64)
len += snprintf(page + len, PAGE_SIZE - len, "t2=%ld ", len += snprintf(page + len, PAGE_SIZE - len, "t1=%lld ",
op->j_ival2); (long long) ktime_to_us(op->kt_ival1));
if (op->kt_ival2.tv64)
len += snprintf(page + len, PAGE_SIZE - len, "t2=%lld ",
(long long) ktime_to_us(op->kt_ival2));
len += snprintf(page + len, PAGE_SIZE - len, "# sent %ld\n", len += snprintf(page + len, PAGE_SIZE - len, "# sent %ld\n",
op->frames_abs); op->frames_abs);
...@@ -371,11 +343,12 @@ static void bcm_send_to_user(struct bcm_op *op, struct bcm_msg_head *head, ...@@ -371,11 +343,12 @@ static void bcm_send_to_user(struct bcm_op *op, struct bcm_msg_head *head,
/* /*
* bcm_tx_timeout_handler - performes cyclic CAN frame transmissions * bcm_tx_timeout_handler - performes cyclic CAN frame transmissions
*/ */
static void bcm_tx_timeout_handler(unsigned long data) static enum hrtimer_restart bcm_tx_timeout_handler(struct hrtimer *hrtimer)
{ {
struct bcm_op *op = (struct bcm_op *)data; struct bcm_op *op = container_of(hrtimer, struct bcm_op, timer);
enum hrtimer_restart ret = HRTIMER_NORESTART;
if (op->j_ival1 && (op->count > 0)) { if (op->kt_ival1.tv64 && (op->count > 0)) {
op->count--; op->count--;
if (!op->count && (op->flags & TX_COUNTEVT)) { if (!op->count && (op->flags & TX_COUNTEVT)) {
...@@ -394,22 +367,24 @@ static void bcm_tx_timeout_handler(unsigned long data) ...@@ -394,22 +367,24 @@ static void bcm_tx_timeout_handler(unsigned long data)
} }
} }
if (op->j_ival1 && (op->count > 0)) { if (op->kt_ival1.tv64 && (op->count > 0)) {
/* send (next) frame */ /* send (next) frame */
bcm_can_tx(op); bcm_can_tx(op);
mod_timer(&op->timer, jiffies + op->j_ival1); hrtimer_forward(hrtimer, ktime_get(), op->kt_ival1);
ret = HRTIMER_RESTART;
} else { } else {
if (op->j_ival2) { if (op->kt_ival2.tv64) {
/* send (next) frame */ /* send (next) frame */
bcm_can_tx(op); bcm_can_tx(op);
mod_timer(&op->timer, jiffies + op->j_ival2); hrtimer_forward(hrtimer, ktime_get(), op->kt_ival2);
ret = HRTIMER_RESTART;
} }
} }
return; return ret;
} }
/* /*
...@@ -419,8 +394,6 @@ static void bcm_rx_changed(struct bcm_op *op, struct can_frame *data) ...@@ -419,8 +394,6 @@ static void bcm_rx_changed(struct bcm_op *op, struct can_frame *data)
{ {
struct bcm_msg_head head; struct bcm_msg_head head;
op->j_lastmsg = jiffies;
/* update statistics */ /* update statistics */
op->frames_filtered++; op->frames_filtered++;
...@@ -439,6 +412,12 @@ static void bcm_rx_changed(struct bcm_op *op, struct can_frame *data) ...@@ -439,6 +412,12 @@ static void bcm_rx_changed(struct bcm_op *op, struct can_frame *data)
bcm_send_to_user(op, &head, data, 1); bcm_send_to_user(op, &head, data, 1);
} }
/* TODO: move to linux/hrtimer.h */
static inline int hrtimer_callback_running(struct hrtimer *timer)
{
return timer->state & HRTIMER_STATE_CALLBACK;
}
/* /*
* bcm_rx_update_and_send - process a detected relevant receive content change * bcm_rx_update_and_send - process a detected relevant receive content change
* 1. update the last received data * 1. update the last received data
...@@ -448,30 +427,44 @@ static void bcm_rx_update_and_send(struct bcm_op *op, ...@@ -448,30 +427,44 @@ static void bcm_rx_update_and_send(struct bcm_op *op,
struct can_frame *lastdata, struct can_frame *lastdata,
struct can_frame *rxdata) struct can_frame *rxdata)
{ {
unsigned long nexttx = op->j_lastmsg + op->j_ival2;
memcpy(lastdata, rxdata, CFSIZ); memcpy(lastdata, rxdata, CFSIZ);
/* mark as used */ /* mark as used */
lastdata->can_dlc |= RX_RECV; lastdata->can_dlc |= RX_RECV;
/* throttle bcm_rx_changed ? */ /* throtteling mode inactive OR data update already on the run ? */
if ((op->thrtimer.expires) || if (!op->kt_ival2.tv64 || hrtimer_callback_running(&op->thrtimer)) {
((op->j_ival2) && (nexttx > jiffies))) { /* send RX_CHANGED to the user immediately */
/* we are already waiting OR we have to start waiting */ bcm_rx_changed(op, rxdata);
return;
}
if (hrtimer_active(&op->thrtimer)) {
/* mark as 'throttled' */ /* mark as 'throttled' */
lastdata->can_dlc |= RX_THR; lastdata->can_dlc |= RX_THR;
return;
if (!(op->thrtimer.expires)) {
/* start the timer only the first time */
mod_timer(&op->thrtimer, nexttx);
} }
} else { if (!op->kt_lastmsg.tv64) {
/* send RX_CHANGED to the user immediately */ /* send first RX_CHANGED to the user immediately */
bcm_rx_changed(op, rxdata); bcm_rx_changed(op, rxdata);
op->kt_lastmsg = ktime_get();
return;
}
if (ktime_us_delta(ktime_get(), op->kt_lastmsg) <
ktime_to_us(op->kt_ival2)) {
/* mark as 'throttled' and start timer */
lastdata->can_dlc |= RX_THR;
hrtimer_start(&op->thrtimer,
ktime_add(op->kt_lastmsg, op->kt_ival2),
HRTIMER_MODE_ABS);
return;
} }
/* the gap was that big, that throttling was not needed here */
bcm_rx_changed(op, rxdata);
op->kt_lastmsg = ktime_get();
} }
/* /*
...@@ -519,16 +512,16 @@ static void bcm_rx_starttimer(struct bcm_op *op) ...@@ -519,16 +512,16 @@ static void bcm_rx_starttimer(struct bcm_op *op)
if (op->flags & RX_NO_AUTOTIMER) if (op->flags & RX_NO_AUTOTIMER)
return; return;
if (op->j_ival1) if (op->kt_ival1.tv64)
mod_timer(&op->timer, jiffies + op->j_ival1); hrtimer_start(&op->timer, op->kt_ival1, HRTIMER_MODE_REL);
} }
/* /*
* bcm_rx_timeout_handler - when the (cyclic) CAN frame receiption timed out * bcm_rx_timeout_handler - when the (cyclic) CAN frame receiption timed out
*/ */
static void bcm_rx_timeout_handler(unsigned long data) static enum hrtimer_restart bcm_rx_timeout_handler(struct hrtimer *hrtimer)
{ {
struct bcm_op *op = (struct bcm_op *)data; struct bcm_op *op = container_of(hrtimer, struct bcm_op, timer);
struct bcm_msg_head msg_head; struct bcm_msg_head msg_head;
msg_head.opcode = RX_TIMEOUT; msg_head.opcode = RX_TIMEOUT;
...@@ -548,27 +541,27 @@ static void bcm_rx_timeout_handler(unsigned long data) ...@@ -548,27 +541,27 @@ static void bcm_rx_timeout_handler(unsigned long data)
/* clear received can_frames to indicate 'nothing received' */ /* clear received can_frames to indicate 'nothing received' */
memset(op->last_frames, 0, op->nframes * CFSIZ); memset(op->last_frames, 0, op->nframes * CFSIZ);
} }
return HRTIMER_NORESTART;
} }
/* /*
* bcm_rx_thr_handler - the time for blocked content updates is over now: * bcm_rx_thr_flush - Check for throttled data and send it to the userspace
* Check for throttled data and send it to the userspace
*/ */
static void bcm_rx_thr_handler(unsigned long data) static int bcm_rx_thr_flush(struct bcm_op *op)
{ {
struct bcm_op *op = (struct bcm_op *)data; int updated = 0;
int i = 0;
/* mark disabled / consumed timer */
op->thrtimer.expires = 0;
if (op->nframes > 1) { if (op->nframes > 1) {
int i;
/* for MUX filter we start at index 1 */ /* for MUX filter we start at index 1 */
for (i = 1; i < op->nframes; i++) { for (i = 1; i < op->nframes; i++) {
if ((op->last_frames) && if ((op->last_frames) &&
(op->last_frames[i].can_dlc & RX_THR)) { (op->last_frames[i].can_dlc & RX_THR)) {
op->last_frames[i].can_dlc &= ~RX_THR; op->last_frames[i].can_dlc &= ~RX_THR;
bcm_rx_changed(op, &op->last_frames[i]); bcm_rx_changed(op, &op->last_frames[i]);
updated++;
} }
} }
...@@ -577,8 +570,29 @@ static void bcm_rx_thr_handler(unsigned long data) ...@@ -577,8 +570,29 @@ static void bcm_rx_thr_handler(unsigned long data)
if (op->last_frames && (op->last_frames[0].can_dlc & RX_THR)) { if (op->last_frames && (op->last_frames[0].can_dlc & RX_THR)) {
op->last_frames[0].can_dlc &= ~RX_THR; op->last_frames[0].can_dlc &= ~RX_THR;
bcm_rx_changed(op, &op->last_frames[0]); bcm_rx_changed(op, &op->last_frames[0]);
updated++;
} }
} }
return updated;
}
/*
* bcm_rx_thr_handler - the time for blocked content updates is over now:
* Check for throttled data and send it to the userspace
*/
static enum hrtimer_restart bcm_rx_thr_handler(struct hrtimer *hrtimer)
{
struct bcm_op *op = container_of(hrtimer, struct bcm_op, thrtimer);
if (bcm_rx_thr_flush(op)) {
hrtimer_forward(hrtimer, ktime_get(), op->kt_ival2);
return HRTIMER_RESTART;
} else {
/* rearm throttle handling */
op->kt_lastmsg = ktime_set(0, 0);
return HRTIMER_NORESTART;
}
} }
/* /*
...@@ -591,7 +605,7 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data) ...@@ -591,7 +605,7 @@ static void bcm_rx_handler(struct sk_buff *skb, void *data)
int i; int i;
/* disable timeout */ /* disable timeout */
del_timer(&op->timer); hrtimer_cancel(&op->timer);
if (skb->len == sizeof(rxframe)) { if (skb->len == sizeof(rxframe)) {
memcpy(&rxframe, skb->data, sizeof(rxframe)); memcpy(&rxframe, skb->data, sizeof(rxframe));
...@@ -669,8 +683,8 @@ static struct bcm_op *bcm_find_op(struct list_head *ops, canid_t can_id, ...@@ -669,8 +683,8 @@ static struct bcm_op *bcm_find_op(struct list_head *ops, canid_t can_id,
static void bcm_remove_op(struct bcm_op *op) static void bcm_remove_op(struct bcm_op *op)
{ {
del_timer(&op->timer); hrtimer_cancel(&op->timer);
del_timer(&op->thrtimer); hrtimer_cancel(&op->thrtimer);
if ((op->frames) && (op->frames != &op->sframe)) if ((op->frames) && (op->frames != &op->sframe))
kfree(op->frames); kfree(op->frames);
...@@ -871,11 +885,11 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, ...@@ -871,11 +885,11 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
op->ifindex = ifindex; op->ifindex = ifindex;
/* initialize uninitialized (kzalloc) structure */ /* initialize uninitialized (kzalloc) structure */
setup_timer(&op->timer, bcm_tx_timeout_handler, hrtimer_init(&op->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
(unsigned long)op); op->timer.function = bcm_tx_timeout_handler;
/* currently unused in tx_ops */ /* currently unused in tx_ops */
init_timer(&op->thrtimer); hrtimer_init(&op->thrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
/* add this bcm_op to the list of the tx_ops */ /* add this bcm_op to the list of the tx_ops */
list_add(&op->list, &bo->tx_ops); list_add(&op->list, &bo->tx_ops);
...@@ -902,25 +916,27 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, ...@@ -902,25 +916,27 @@ static int bcm_tx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
op->count = msg_head->count; op->count = msg_head->count;
op->ival1 = msg_head->ival1; op->ival1 = msg_head->ival1;
op->ival2 = msg_head->ival2; op->ival2 = msg_head->ival2;
op->j_ival1 = rounded_tv2jif(&msg_head->ival1); op->kt_ival1 = timeval_to_ktime(msg_head->ival1);
op->j_ival2 = rounded_tv2jif(&msg_head->ival2); op->kt_ival2 = timeval_to_ktime(msg_head->ival2);
/* disable an active timer due to zero values? */ /* disable an active timer due to zero values? */
if (!op->j_ival1 && !op->j_ival2) if (!op->kt_ival1.tv64 && !op->kt_ival2.tv64)
del_timer(&op->timer); hrtimer_cancel(&op->timer);
} }
if ((op->flags & STARTTIMER) && if ((op->flags & STARTTIMER) &&
((op->j_ival1 && op->count) || op->j_ival2)) { ((op->kt_ival1.tv64 && op->count) || op->kt_ival2.tv64)) {
/* spec: send can_frame when starting timer */ /* spec: send can_frame when starting timer */
op->flags |= TX_ANNOUNCE; op->flags |= TX_ANNOUNCE;
if (op->j_ival1 && (op->count > 0)) { if (op->kt_ival1.tv64 && (op->count > 0)) {
/* op->count-- is done in bcm_tx_timeout_handler */ /* op->count-- is done in bcm_tx_timeout_handler */
mod_timer(&op->timer, jiffies + op->j_ival1); hrtimer_start(&op->timer, op->kt_ival1,
HRTIMER_MODE_REL);
} else } else
mod_timer(&op->timer, jiffies + op->j_ival2); hrtimer_start(&op->timer, op->kt_ival2,
HRTIMER_MODE_REL);
} }
if (op->flags & TX_ANNOUNCE) if (op->flags & TX_ANNOUNCE)
...@@ -1032,15 +1048,11 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, ...@@ -1032,15 +1048,11 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
op->ifindex = ifindex; op->ifindex = ifindex;
/* initialize uninitialized (kzalloc) structure */ /* initialize uninitialized (kzalloc) structure */
setup_timer(&op->timer, bcm_rx_timeout_handler, hrtimer_init(&op->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
(unsigned long)op); op->timer.function = bcm_rx_timeout_handler;
/* init throttle timer for RX_CHANGED */ hrtimer_init(&op->thrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
setup_timer(&op->thrtimer, bcm_rx_thr_handler, op->thrtimer.function = bcm_rx_thr_handler;
(unsigned long)op);
/* mark disabled timer */
op->thrtimer.expires = 0;
/* add this bcm_op to the list of the rx_ops */ /* add this bcm_op to the list of the rx_ops */
list_add(&op->list, &bo->rx_ops); list_add(&op->list, &bo->rx_ops);
...@@ -1056,8 +1068,8 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, ...@@ -1056,8 +1068,8 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
if (op->flags & RX_RTR_FRAME) { if (op->flags & RX_RTR_FRAME) {
/* no timers in RTR-mode */ /* no timers in RTR-mode */
del_timer(&op->thrtimer); hrtimer_cancel(&op->thrtimer);
del_timer(&op->timer); hrtimer_cancel(&op->timer);
/* /*
* funny feature in RX(!)_SETUP only for RTR-mode: * funny feature in RX(!)_SETUP only for RTR-mode:
...@@ -1074,28 +1086,25 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg, ...@@ -1074,28 +1086,25 @@ static int bcm_rx_setup(struct bcm_msg_head *msg_head, struct msghdr *msg,
/* set timer value */ /* set timer value */
op->ival1 = msg_head->ival1; op->ival1 = msg_head->ival1;
op->ival2 = msg_head->ival2; op->ival2 = msg_head->ival2;
op->j_ival1 = rounded_tv2jif(&msg_head->ival1); op->kt_ival1 = timeval_to_ktime(msg_head->ival1);
op->j_ival2 = rounded_tv2jif(&msg_head->ival2); op->kt_ival2 = timeval_to_ktime(msg_head->ival2);
/* disable an active timer due to zero value? */ /* disable an active timer due to zero value? */
if (!op->j_ival1) if (!op->kt_ival1.tv64)
del_timer(&op->timer); hrtimer_cancel(&op->timer);
/* free currently blocked msgs ? */
if (op->thrtimer.expires) {
/* send blocked msgs hereafter */
mod_timer(&op->thrtimer, jiffies + 2);
}
/* /*
* if (op->j_ival2) is zero, no (new) throttling * In any case cancel the throttle timer, flush
* will happen. For details see functions * potentially blocked msgs and reset throttle handling
* bcm_rx_update_and_send() and bcm_rx_thr_handler()
*/ */
op->kt_lastmsg = ktime_set(0, 0);
hrtimer_cancel(&op->thrtimer);
bcm_rx_thr_flush(op);
} }
if ((op->flags & STARTTIMER) && op->j_ival1) if ((op->flags & STARTTIMER) && op->kt_ival1.tv64)
mod_timer(&op->timer, jiffies + op->j_ival1); hrtimer_start(&op->timer, op->kt_ival1,
HRTIMER_MODE_REL);
} }
/* now we can register for can_ids, if we added a new bcm_op */ /* now we can register for can_ids, if we added a new bcm_op */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment