1 /* PipeWire
2 *
3 * Copyright © 2018 Wim Taymans
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a
6 * copy of this software and associated documentation files (the "Software"),
7 * to deal in the Software without restriction, including without limitation
8 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 * and/or sell copies of the Software, and to permit persons to whom the
10 * Software is furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice (including the next
13 * paragraph) shall be included in all copies or substantial portions of the
14 * Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 */
24
25 #include <spa/pod/parser.h>
26 #include <spa/debug/types.h>
27
28 #include <pipewire/control.h>
29 #include <pipewire/private.h>
30
31 #define NAME "control"
32
33 struct impl {
34 struct pw_control this;
35
36 struct pw_memblock *mem;
37 };
38
39 struct pw_control *
pw_control_new(struct pw_context * context,struct pw_impl_port * port,uint32_t id,uint32_t size,size_t user_data_size)40 pw_control_new(struct pw_context *context,
41 struct pw_impl_port *port,
42 uint32_t id, uint32_t size,
43 size_t user_data_size)
44 {
45 struct impl *impl;
46 struct pw_control *this;
47 enum spa_direction direction;
48
49 switch (id) {
50 case SPA_IO_Control:
51 direction = SPA_DIRECTION_INPUT;
52 break;
53 case SPA_IO_Notify:
54 direction = SPA_DIRECTION_OUTPUT;
55 break;
56 default:
57 errno = ENOTSUP;
58 goto error_exit;
59 }
60
61 impl = calloc(1, sizeof(struct impl) + user_data_size);
62 if (impl == NULL)
63 goto error_exit;
64
65 this = &impl->this;
66 this->id = id;
67 this->size = size;
68
69 pw_log_debug(NAME" %p: new %s %d", this,
70 spa_debug_type_find_name(spa_type_io, this->id), direction);
71
72 this->context = context;
73 this->port = port;
74 this->direction = direction;
75
76 spa_list_init(&this->links);
77
78 if (user_data_size > 0)
79 this->user_data = SPA_PTROFF(impl, sizeof(struct impl), void);
80
81 spa_hook_list_init(&this->listener_list);
82
83 spa_list_append(&context->control_list[direction], &this->link);
84 if (port) {
85 spa_list_append(&port->control_list[direction], &this->port_link);
86 pw_impl_port_emit_control_added(port, this);
87 }
88 return this;
89
90 error_exit:
91 return NULL;
92 }
93
pw_control_destroy(struct pw_control * control)94 void pw_control_destroy(struct pw_control *control)
95 {
96 struct impl *impl = SPA_CONTAINER_OF(control, struct impl, this);
97 struct pw_control_link *link;
98
99 pw_log_debug(NAME" %p: destroy", control);
100
101 pw_control_emit_destroy(control);
102
103 if (control->direction == SPA_DIRECTION_OUTPUT) {
104 spa_list_consume(link, &control->links, out_link)
105 pw_control_remove_link(link);
106 }
107 else {
108 spa_list_consume(link, &control->links, in_link)
109 pw_control_remove_link(link);
110 }
111
112 spa_list_remove(&control->link);
113
114 if (control->port) {
115 spa_list_remove(&control->port_link);
116 pw_impl_port_emit_control_removed(control->port, control);
117 }
118
119 pw_log_debug(NAME" %p: free", control);
120 pw_control_emit_free(control);
121
122 spa_hook_list_clean(&control->listener_list);
123
124 if (control->direction == SPA_DIRECTION_OUTPUT) {
125 if (impl->mem)
126 pw_memblock_unref(impl->mem);
127 }
128 free(control);
129 }
130
131 SPA_EXPORT
pw_control_get_port(struct pw_control * control)132 struct pw_impl_port *pw_control_get_port(struct pw_control *control)
133 {
134 return control->port;
135 }
136
137 SPA_EXPORT
pw_control_add_listener(struct pw_control * control,struct spa_hook * listener,const struct pw_control_events * events,void * data)138 void pw_control_add_listener(struct pw_control *control,
139 struct spa_hook *listener,
140 const struct pw_control_events *events,
141 void *data)
142 {
143 spa_hook_list_append(&control->listener_list, listener, events, data);
144 }
145
port_set_io(struct pw_impl_port * port,uint32_t mix,uint32_t id,void * data,uint32_t size)146 static int port_set_io(struct pw_impl_port *port, uint32_t mix, uint32_t id, void *data, uint32_t size)
147 {
148 int res;
149
150 if (port->mix) {
151 res = spa_node_port_set_io(port->mix, port->direction, mix, id, data, size);
152 if (SPA_RESULT_IS_OK(res))
153 return res;
154 }
155
156 if ((res = spa_node_port_set_io(port->node->node,
157 port->direction, port->port_id,
158 id, data, size)) < 0) {
159 pw_log_warn("port %p: set io failed %d %s", port,
160 res, spa_strerror(res));
161 }
162 return res;
163 }
164
165 SPA_EXPORT
pw_control_add_link(struct pw_control * control,uint32_t cmix,struct pw_control * other,uint32_t omix,struct pw_control_link * link)166 int pw_control_add_link(struct pw_control *control, uint32_t cmix,
167 struct pw_control *other, uint32_t omix,
168 struct pw_control_link *link)
169 {
170 int res = 0;
171 struct impl *impl;
172 uint32_t size;
173
174 if (control->direction == SPA_DIRECTION_INPUT) {
175 SPA_SWAP(control, other);
176 SPA_SWAP(cmix, omix);
177 }
178 if (control->direction != SPA_DIRECTION_OUTPUT ||
179 other->direction != SPA_DIRECTION_INPUT)
180 return -EINVAL;
181
182 impl = SPA_CONTAINER_OF(control, struct impl, this);
183
184 pw_log_debug(NAME" %p: link to %p %s", control, other,
185 spa_debug_type_find_name(spa_type_io, control->id));
186
187 size = SPA_MAX(control->size, other->size);
188
189 if (impl->mem == NULL) {
190 impl->mem = pw_mempool_alloc(control->context->pool,
191 PW_MEMBLOCK_FLAG_READWRITE |
192 PW_MEMBLOCK_FLAG_SEAL |
193 PW_MEMBLOCK_FLAG_MAP,
194 SPA_DATA_MemFd, size);
195 if (impl->mem == NULL) {
196 res = -errno;
197 goto exit;
198 }
199 }
200
201 if (spa_list_is_empty(&control->links)) {
202 if (control->port) {
203 if ((res = port_set_io(control->port, cmix,
204 control->id,
205 impl->mem->map->ptr, size)) < 0) {
206 pw_log_warn(NAME" %p: set io failed %d %s", control,
207 res, spa_strerror(res));
208 goto exit;
209 }
210 }
211 }
212
213 if (other->port) {
214 if ((res = port_set_io(other->port, omix,
215 other->id, impl->mem->map->ptr, size)) < 0) {
216 pw_log_warn(NAME" %p: set io failed %d %s", control,
217 res, spa_strerror(res));
218 goto exit;
219 }
220 }
221
222 link->output = control;
223 link->input = other;
224 link->out_port = cmix;
225 link->in_port = omix;
226 link->valid = true;
227 spa_list_append(&control->links, &link->out_link);
228 spa_list_append(&other->links, &link->in_link);
229
230 pw_control_emit_linked(control, other);
231 pw_control_emit_linked(other, control);
232 exit:
233 return res;
234 }
235
236 SPA_EXPORT
pw_control_remove_link(struct pw_control_link * link)237 int pw_control_remove_link(struct pw_control_link *link)
238 {
239 int res = 0;
240 struct pw_control *output = link->output;
241 struct pw_control *input = link->input;
242
243 pw_log_debug(NAME" %p: unlink from %p", output, input);
244
245 spa_list_remove(&link->in_link);
246 spa_list_remove(&link->out_link);
247 link->valid = false;
248
249 if (spa_list_is_empty(&output->links)) {
250 if ((res = port_set_io(output->port, link->out_port,
251 output->id, NULL, 0)) < 0) {
252 pw_log_warn(NAME" %p: can't unset port control io", output);
253 }
254 }
255
256 if (input->port) {
257 if ((res = port_set_io(input->port, link->in_port,
258 input->id, NULL, 0)) < 0) {
259 pw_log_warn(NAME" %p: can't unset port control io", output);
260 }
261 }
262
263 pw_control_emit_unlinked(output, input);
264 pw_control_emit_unlinked(input, output);
265
266 return res;
267 }
268