/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include <errno.h> #include <stddef.h> #include <stdlib.h> #include <string.h> #include "cras_alert.h" #include "utlist.h" /* A list of callbacks for an alert */ struct cras_alert_cb_list { cras_alert_cb callback; void *arg; struct cras_alert_cb_list *prev, *next; }; /* A list of data args to callbacks. Variable-length structure. */ struct cras_alert_data { struct cras_alert_data *prev, *next; /* This field must be the last in this structure. */ char buf[]; }; struct cras_alert { int pending; unsigned int flags; cras_alert_prepare prepare; struct cras_alert_cb_list *callbacks; struct cras_alert_data *data; struct cras_alert *prev, *next; }; /* A list of all alerts in the system */ static struct cras_alert *all_alerts; /* If there is any alert pending. */ static int has_alert_pending; struct cras_alert *cras_alert_create(cras_alert_prepare prepare, unsigned int flags) { struct cras_alert *alert; alert = calloc(1, sizeof(*alert)); if (!alert) return NULL; alert->prepare = prepare; alert->flags = flags; DL_APPEND(all_alerts, alert); return alert; } int cras_alert_add_callback(struct cras_alert *alert, cras_alert_cb cb, void *arg) { struct cras_alert_cb_list *alert_cb; if (cb == NULL) return -EINVAL; DL_FOREACH(alert->callbacks, alert_cb) if (alert_cb->callback == cb && alert_cb->arg == arg) return -EEXIST; alert_cb = calloc(1, sizeof(*alert_cb)); if (alert_cb == NULL) return -ENOMEM; alert_cb->callback = cb; alert_cb->arg = arg; DL_APPEND(alert->callbacks, alert_cb); return 0; } int cras_alert_rm_callback(struct cras_alert *alert, cras_alert_cb cb, void *arg) { struct cras_alert_cb_list *alert_cb; DL_FOREACH(alert->callbacks, alert_cb) if (alert_cb->callback == cb && alert_cb->arg == arg) { DL_DELETE(alert->callbacks, alert_cb); free(alert_cb); return 0; } return -ENOENT; } /* Checks if the alert is pending, and invoke the prepare function and callbacks * if so. */ static void cras_alert_process(struct cras_alert *alert) { struct cras_alert_cb_list *cb; struct cras_alert_data *data; if (!alert->pending) return; alert->pending = 0; if (alert->prepare) alert->prepare(alert); if (!alert->data) { DL_FOREACH(alert->callbacks, cb) cb->callback(cb->arg, NULL); } /* Have data arguments, pass each to the callbacks. */ DL_FOREACH(alert->data, data) { DL_FOREACH(alert->callbacks, cb) cb->callback(cb->arg, (void *)data->buf); DL_DELETE(alert->data, data); free(data); } } void cras_alert_pending(struct cras_alert *alert) { alert->pending = 1; has_alert_pending = 1; } void cras_alert_pending_data(struct cras_alert *alert, void *data, size_t data_size) { struct cras_alert_data *d; alert->pending = 1; has_alert_pending = 1; d = calloc(1, offsetof(struct cras_alert_data, buf) + data_size); memcpy(d->buf, data, data_size); if (!(alert->flags & CRAS_ALERT_FLAG_KEEP_ALL_DATA) && alert->data) { /* There will never be more than one item in the list. */ free(alert->data); alert->data = NULL; } /* Even when there is only one item, it is important to use DL_APPEND * here so that d's next and prev pointers are setup correctly. */ DL_APPEND(alert->data, d); } void cras_alert_process_all_pending_alerts() { struct cras_alert *alert; while (has_alert_pending) { has_alert_pending = 0; DL_FOREACH(all_alerts, alert) cras_alert_process(alert); } } void cras_alert_destroy(struct cras_alert *alert) { struct cras_alert_cb_list *cb; struct cras_alert_data *data; if (!alert) return; DL_FOREACH(alert->callbacks, cb) { DL_DELETE(alert->callbacks, cb); free(cb); } DL_FOREACH(alert->data, data) { DL_DELETE(alert->data, data); free(data); } alert->callbacks = NULL; DL_DELETE(all_alerts, alert); free(alert); } void cras_alert_destroy_all() { struct cras_alert *alert; DL_FOREACH(all_alerts, alert) cras_alert_destroy(alert); }