blob: d27c0f8af9ee4cba2727609f3a234742e7214ef8 [file] [log] [blame]
/*
*
* Embedded Linux library
*
* Copyright (C) 2011-2014 Intel Corporation. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#define _GNU_SOURCE
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/timerfd.h>
#include <time.h>
#include <limits.h>
#include "util.h"
#include "timeout.h"
#include "private.h"
/**
* SECTION:timeout
* @short_description: Timeout support
*
* Timeout support
*/
/**
* l_timeout:
*
* Opague object representing the timeout.
*/
struct l_timeout {
int fd;
l_timeout_notify_cb_t callback;
l_timeout_destroy_cb_t destroy;
void *user_data;
};
static void timeout_destroy(void *user_data)
{
struct l_timeout *timeout = user_data;
close(timeout->fd);
timeout->fd = -1;
if (timeout->destroy)
timeout->destroy(timeout->user_data);
}
static void timeout_callback(int fd, uint32_t events, void *user_data)
{
struct l_timeout *timeout = user_data;
uint64_t expired;
ssize_t result;
result = read(timeout->fd, &expired, sizeof(expired));
if (result != sizeof(expired))
return;
if (timeout->callback)
timeout->callback(timeout, timeout->user_data);
}
static inline int timeout_set(int fd, unsigned int seconds, long nanoseconds)
{
struct itimerspec itimer;
memset(&itimer, 0, sizeof(itimer));
itimer.it_interval.tv_sec = 0;
itimer.it_interval.tv_nsec = 0;
itimer.it_value.tv_sec = seconds;
itimer.it_value.tv_nsec = nanoseconds;
return timerfd_settime(fd, 0, &itimer, NULL);
}
static bool convert_ms(unsigned long milliseconds, unsigned int *seconds,
long *nanoseconds)
{
unsigned long big_seconds = milliseconds / 1000;
if (big_seconds > UINT_MAX)
return false;
*seconds = big_seconds;
*nanoseconds = (milliseconds % 1000) * 1000000L;
return true;
}
/**
* timeout_create_with_nanoseconds:
* @seconds: number of seconds
* @nanoseconds: number of nanoseconds
* @callback: timeout callback function
* @user_data: user data provided to timeout callback function
* @destroy: destroy function for user data
*
* Create new timeout callback handling.
*
* The timeout will only fire once. The timeout handling needs to be rearmed
* with one of the l_timeout_modify functions to trigger again.
*
* Returns: a newly allocated #l_timeout object. On failure, the function
* returns NULL.
**/
static struct l_timeout *timeout_create_with_nanoseconds(unsigned int seconds,
long nanoseconds, l_timeout_notify_cb_t callback,
void *user_data, l_timeout_destroy_cb_t destroy)
{
struct l_timeout *timeout;
int err;
if (unlikely(!callback))
return NULL;
timeout = l_new(struct l_timeout, 1);
timeout->callback = callback;
timeout->destroy = destroy;
timeout->user_data = user_data;
timeout->fd = timerfd_create(CLOCK_MONOTONIC,
TFD_NONBLOCK | TFD_CLOEXEC);
if (timeout->fd < 0) {
l_free(timeout);
return NULL;
}
if (seconds > 0 || nanoseconds > 0) {
if (timeout_set(timeout->fd, seconds, nanoseconds) < 0) {
close(timeout->fd);
l_free(timeout);
return NULL;
}
}
err = watch_add(timeout->fd, EPOLLIN | EPOLLONESHOT, timeout_callback,
timeout, timeout_destroy);
if (err < 0) {
l_free(timeout);
return NULL;
}
return timeout;
}
/**
* l_timeout_create:
* @seconds: timeout in seconds
* @callback: timeout callback function
* @user_data: user data provided to timeout callback function
* @destroy: destroy function for user data
*
* Create new timeout callback handling.
*
* The timeout will only fire once. The timeout handling needs to be rearmed
* with one of the l_timeout_modify functions to trigger again.
*
* Returns: a newly allocated #l_timeout object. On failure, the function
* returns NULL.
**/
LIB_EXPORT struct l_timeout *l_timeout_create(unsigned int seconds,
l_timeout_notify_cb_t callback,
void *user_data, l_timeout_destroy_cb_t destroy)
{
return timeout_create_with_nanoseconds(seconds, 0, callback,
user_data, destroy);
}
/**
* l_timeout_create_ms:
* @milliseconds: timeout in milliseconds
* @callback: timeout callback function
* @user_data: user data provided to timeout callback function
* @destroy: destroy function for user data
*
* Create new timeout callback handling.
*
* The timeout will only fire once. The timeout handling needs to be rearmed
* with one of the l_timeout_modify functions to trigger again.
*
* Returns: a newly allocated #l_timeout object. On failure, the function
* returns NULL.
**/
LIB_EXPORT struct l_timeout *l_timeout_create_ms(unsigned long milliseconds,
l_timeout_notify_cb_t callback,
void *user_data, l_timeout_destroy_cb_t destroy)
{
unsigned int seconds;
long nanoseconds;
if (!convert_ms(milliseconds, &seconds, &nanoseconds))
return NULL;
return timeout_create_with_nanoseconds(seconds, nanoseconds, callback,
user_data, destroy);
}
/**
* l_timeout_modify:
* @timeout: timeout object
* @seconds: timeout in seconds
*
* Modify an existing @timeout and rearm it.
**/
LIB_EXPORT void l_timeout_modify(struct l_timeout *timeout,
unsigned int seconds)
{
if (unlikely(!timeout))
return;
if (unlikely(timeout->fd < 0))
return;
if (seconds > 0) {
if (timeout_set(timeout->fd, seconds, 0) < 0)
return;
}
watch_modify(timeout->fd, EPOLLIN | EPOLLONESHOT, true);
}
/**
* l_timeout_modify_ms:
* @timeout: timeout object
* @milliseconds: number of milliseconds
*
* Modify an existing @timeout and rearm it.
**/
LIB_EXPORT void l_timeout_modify_ms(struct l_timeout *timeout,
unsigned long milliseconds)
{
if (unlikely(!timeout))
return;
if (unlikely(timeout->fd < 0))
return;
if (milliseconds > 0) {
unsigned int sec;
long nanosec;
if (!convert_ms(milliseconds, &sec, &nanosec) ||
timeout_set(timeout->fd, sec, nanosec) < 0)
return;
}
watch_modify(timeout->fd, EPOLLIN | EPOLLONESHOT, true);
}
/**
* l_timeout_remove:
* @timeout: timeout object
*
* Remove timeout handling.
**/
LIB_EXPORT void l_timeout_remove(struct l_timeout *timeout)
{
if (unlikely(!timeout))
return;
watch_remove(timeout->fd);
l_free(timeout);
}