Use SO_SELECT_ERR_QUEUE when available.

The current implementation fetches a transmit time stamp by polling on the
socket with pollfd.events set to zero, and then checking if POLLERR has
been returned by the kernel in pollfd.revents. This has the unfortunate
side effect of sleeping in poll() for the entire time out duration,
regardless of when the error queue becomes readable.

Linux kernel version 3.10 introduced a new socket option that allows
polling for transmit time stamps explicitly, waking the process as soon as
a time stamp becomes available. This patch enables the socket option,
falling back to the old behavior if necessary.

Suggested-by: Joe Schaack <jschaack@xes-inc.com>
Signed-off-by: Richard Cochran <richardcochran@gmail.com>
master
Richard Cochran 2014-11-01 12:27:14 +01:00
parent 50d5c63e16
commit a5911e69c7
2 changed files with 18 additions and 2 deletions

View File

@ -58,6 +58,10 @@ enum _missing_hwtstamp_tx_types {
#define SIOCGHWTSTAMP 0x89b1 #define SIOCGHWTSTAMP 0x89b1
#endif #endif
#ifndef SO_SELECT_ERR_QUEUE
#define SO_SELECT_ERR_QUEUE 45
#endif
#ifndef HAVE_CLOCK_ADJTIME #ifndef HAVE_CLOCK_ADJTIME
static inline int clock_adjtime(clockid_t id, struct timex *tx) static inline int clock_adjtime(clockid_t id, struct timex *tx)
{ {

16
sk.c
View File

@ -32,6 +32,7 @@
#include "address.h" #include "address.h"
#include "ether.h" #include "ether.h"
#include "missing.h"
#include "print.h" #include "print.h"
#include "sk.h" #include "sk.h"
@ -208,6 +209,9 @@ int sk_interface_addr(const char *name, int family, struct address *addr)
return result; return result;
} }
static short sk_events = POLLPRI;
static short sk_revents = POLLPRI;
int sk_receive(int fd, void *buf, int buflen, int sk_receive(int fd, void *buf, int buflen,
struct address *addr, struct hw_timestamp *hwts, int flags) struct address *addr, struct hw_timestamp *hwts, int flags)
{ {
@ -230,7 +234,7 @@ int sk_receive(int fd, void *buf, int buflen,
msg.msg_controllen = sizeof(control); msg.msg_controllen = sizeof(control);
if (flags == MSG_ERRQUEUE) { if (flags == MSG_ERRQUEUE) {
struct pollfd pfd = { fd, 0, 0 }; struct pollfd pfd = { fd, sk_events, 0 };
res = poll(&pfd, 1, sk_tx_timeout); res = poll(&pfd, 1, sk_tx_timeout);
if (res < 1) { if (res < 1) {
pr_err(res ? "poll for tx timestamp failed: %m" : pr_err(res ? "poll for tx timestamp failed: %m" :
@ -238,7 +242,7 @@ int sk_receive(int fd, void *buf, int buflen,
pr_err("increasing tx_timestamp_timeout may correct " pr_err("increasing tx_timestamp_timeout may correct "
"this issue, but it is likely caused by a driver bug"); "this issue, but it is likely caused by a driver bug");
return res; return res;
} else if (!(pfd.revents & POLLERR)) { } else if (!(pfd.revents & sk_revents)) {
pr_err("poll for tx timestamp woke up on non ERR event"); pr_err("poll for tx timestamp woke up on non ERR event");
return -1; return -1;
} }
@ -352,6 +356,14 @@ int sk_timestamping_init(int fd, const char *device, enum timestamp_type type,
return -1; return -1;
} }
flags = 1;
if (setsockopt(fd, SOL_SOCKET, SO_SELECT_ERR_QUEUE,
&flags, sizeof(flags)) < 0) {
pr_warning("%s: SO_SELECT_ERR_QUEUE: %m", device);
sk_events = 0;
sk_revents = POLLERR;
}
/* Enable the sk_check_fupsync option, perhaps. */ /* Enable the sk_check_fupsync option, perhaps. */
if (sk_general_init(fd)) { if (sk_general_init(fd)) {
return -1; return -1;