| /* 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); |
| } |