diff options
Diffstat (limited to 'qcom/qrtr/src/waiter.c')
-rw-r--r-- | qcom/qrtr/src/waiter.c | 378 |
1 files changed, 378 insertions, 0 deletions
diff --git a/qcom/qrtr/src/waiter.c b/qcom/qrtr/src/waiter.c new file mode 100644 index 0000000..f21896f --- /dev/null +++ b/qcom/qrtr/src/waiter.c @@ -0,0 +1,378 @@ +/* + * Copyright (c) 2013-2014, Sony Mobile Communications Inc. + * Copyright (c) 2014, Courtney Cavin + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * - Neither the name of the organization nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <poll.h> + +#include "list.h" +#include "waiter.h" +#include "util.h" + +struct pollset { + int nfds; + int cause; +}; + +static struct pollset *pollset_create(int count) +{ + struct pollset *ps; + + ps = calloc(1, sizeof(*ps) + sizeof(struct pollfd) * count); + if (ps == NULL) + return NULL; + + return ps; +} + +static void pollset_destroy(struct pollset *ps) +{ + free(ps); +} + +static void pollset_reset(struct pollset *ps) +{ + ps->nfds = 0; +} + +static void pollset_add_fd(struct pollset *ps, int fd) +{ + struct pollfd *pfd = (struct pollfd *)(ps + 1); + pfd[ps->nfds].fd = fd; + pfd[ps->nfds].events = POLLERR | POLLIN; + ps->nfds++; +} + +static int pollset_wait(struct pollset *ps, int ms) +{ + struct pollfd *pfd = (struct pollfd *)(ps + 1); + int rc; + int i; + + rc = poll(pfd, ps->nfds, ms); + if (rc <= 0) + return rc; + + ps->cause = -1; + for (i = 0; i < ps->nfds; ++i) { + if (pfd[i].revents & (POLLERR | POLLIN)) { + ps->cause = i; + break; + } + } + return rc; + +} + +static int pollset_cause_fd(struct pollset *ps, int fd) +{ + struct pollfd *pfd = (struct pollfd *)(ps + 1); + return (ps->cause >= 0 && pfd[ps->cause].fd == fd); +} + +enum waiter_type { + WATCH_TYPE_NULL, + WATCH_TYPE_FD, + WATCH_TYPE_TIMEOUT, +}; + +struct waiter_ticket { + enum waiter_type type; + union { + int filedes; + unsigned int event; + unsigned int interval; + }; + struct { + void (* fn)(void *data, struct waiter_ticket *); + void *data; + } callback; + + uint64_t start; + int updated; + struct waiter *waiter; + struct list_item list_item; +}; + +struct waiter { + struct list tickets; + struct pollset *pollset; + int count; +}; + +struct waiter *waiter_create(void) +{ + struct waiter *w; + + w = calloc(1, sizeof(*w)); + if (w == NULL) + return NULL; + + list_init(&w->tickets); + return w; +} + +void waiter_destroy(struct waiter *w) +{ + struct waiter_ticket *ticket; + struct list_item *safe; + struct list_item *node; + + list_for_each_safe(&w->tickets, node, safe) { + ticket = list_entry(node, struct waiter_ticket, list_item); + free(ticket); + } + + if (w->pollset) + pollset_destroy(w->pollset); + free(w); +} + +void waiter_synchronize(struct waiter *w) +{ + struct waiter_ticket *oticket; + struct waiter_ticket *ticket; + struct list_item *node; + + list_for_each(&w->tickets, node) { + struct list_item *onode; + ticket = list_entry(node, struct waiter_ticket, list_item); + + if (ticket->type != WATCH_TYPE_TIMEOUT) + continue; + + list_for_each_after(node, onode) { + oticket = list_entry(onode, struct waiter_ticket, list_item); + if (oticket->type != WATCH_TYPE_TIMEOUT) + continue; + + if (oticket->interval == ticket->interval) { + oticket->start = ticket->start; + break; + } + } + } +} + +void waiter_wait(struct waiter *w) +{ + struct pollset *ps = w->pollset; + struct waiter_ticket *ticket; + struct list_item *node; + uint64_t term_time; + uint64_t now; + int rc; + + pollset_reset(ps); + + term_time = (uint64_t)-1; + list_for_each(&w->tickets, node) { + ticket = list_entry(node, struct waiter_ticket, list_item); + switch (ticket->type) { + case WATCH_TYPE_TIMEOUT: + if (ticket->start + ticket->interval < term_time) + term_time = ticket->start + ticket->interval; + break; + case WATCH_TYPE_FD: + pollset_add_fd(ps, ticket->filedes); + break; + case WATCH_TYPE_NULL: + break; + } + } + + if (term_time == (uint64_t)-1) { /* wait forever */ + rc = pollset_wait(ps, -1); + } else { + now = time_ms(); + if (now >= term_time) { /* already past timeout, skip poll */ + rc = 0; + } else { + uint64_t delta; + + delta = term_time - now; + if (delta > ((1u << 31) - 1)) + delta = ((1u << 31) - 1); + rc = pollset_wait(ps, (int)delta); + } + } + + if (rc < 0) + return; + + now = time_ms(); + list_for_each(&w->tickets, node) { + int fresh = 0; + + ticket = list_entry(node, struct waiter_ticket, list_item); + switch (ticket->type) { + case WATCH_TYPE_TIMEOUT: + if (now >= ticket->start + ticket->interval) { + ticket->start = now; + fresh = !ticket->updated; + } + break; + case WATCH_TYPE_FD: + if (rc == 0) /* timed-out */ + break; + if (pollset_cause_fd(ps, ticket->filedes)) + fresh = !ticket->updated; + break; + case WATCH_TYPE_NULL: + break; + } + if (fresh) { + ticket->updated = 1; + if (ticket->callback.fn) + (* ticket->callback.fn)( + ticket->callback.data, + ticket + ); + } + } +} + +int waiter_wait_timeout(struct waiter *w, unsigned int ms) +{ + struct waiter_ticket ticket; + int rc; + + memset(&ticket, 0, sizeof(ticket)); + waiter_ticket_set_timeout(&ticket, ms); + list_append(&w->tickets, &ticket.list_item); + w->count++; + + waiter_wait(w); + rc = waiter_ticket_check(&ticket); + + list_remove(&w->tickets, &ticket.list_item); + w->count--; + + return -!rc; +} + +void waiter_ticket_set_null(struct waiter_ticket *ticket) +{ + ticket->type = WATCH_TYPE_NULL; +} + +void waiter_ticket_set_fd(struct waiter_ticket *ticket, int fd) +{ + ticket->type = WATCH_TYPE_FD; + ticket->filedes = fd; +} + +void waiter_ticket_set_timeout(struct waiter_ticket *ticket, unsigned int ms) +{ + ticket->type = WATCH_TYPE_TIMEOUT; + ticket->interval = ms; + ticket->start = time_ms(); +} + +struct waiter_ticket *waiter_add_null(struct waiter *w) +{ + struct waiter_ticket *ticket; + + ticket = calloc(1, sizeof(*ticket)); + if (ticket == NULL) + return NULL; + ticket->waiter = w; + + list_append(&w->tickets, &ticket->list_item); + if ((w->count % 32) == 0) { + if (w->pollset) + pollset_destroy(w->pollset); + w->pollset = pollset_create(w->count + 33); + if (w->pollset == NULL) + return NULL; + } + w->count++; + + waiter_ticket_set_null(ticket); + + return ticket; +} + +struct waiter_ticket *waiter_add_fd(struct waiter *w, int fd) +{ + struct waiter_ticket *ticket; + + ticket = waiter_add_null(w); + if (ticket == NULL) + return NULL; + + waiter_ticket_set_fd(ticket, fd); + + return ticket; +} + +struct waiter_ticket *waiter_add_timeout(struct waiter *w, unsigned int ms) +{ + struct waiter_ticket *ticket; + + ticket = waiter_add_null(w); + if (ticket == NULL) + return NULL; + + waiter_ticket_set_timeout(ticket, ms); + + return ticket; +} + +void waiter_ticket_delete(struct waiter_ticket *ticket) +{ + struct waiter *w = ticket->waiter; + list_remove(&w->tickets, &ticket->list_item); + w->count--; + free(ticket); +} + +void waiter_ticket_callback(struct waiter_ticket *ticket, waiter_ticket_cb_t cb_fn, void *data) +{ + ticket->callback.fn = cb_fn; + ticket->callback.data = data; +} + +int waiter_ticket_check(const struct waiter_ticket *ticket) +{ + return -(ticket->updated == 0); +} + +int waiter_ticket_clear(struct waiter_ticket *ticket) +{ + int ret; + + ret = waiter_ticket_check(ticket); + ticket->updated = 0; + + return ret; +} |