summaryrefslogtreecommitdiff
path: root/cras/src/server/cras_alert.c
blob: 9950ec1146aceec0ad510c0daa58600b751fa143 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
/* 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);
}