1/**
2 * \file pcm/pcm_plugin.c
3 * \ingroup PCM
4 * \brief PCM Interface
5 * \author Jaroslav Kysela <perex@perex.cz>
6 * \author Abramo Bagnara <abramo@alsa-project.org>
7 * \date 2000-2001
8 */
9/*
10 *  PCM - Common plugin code
11 *  Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
12 *
13 *
14 *   This library is free software; you can redistribute it and/or modify
15 *   it under the terms of the GNU Lesser General Public License as
16 *   published by the Free Software Foundation; either version 2.1 of
17 *   the License, or (at your option) any later version.
18 *
19 *   This program is distributed in the hope that it will be useful,
20 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
21 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22 *   GNU Lesser General Public License for more details.
23 *
24 *   You should have received a copy of the GNU Lesser General Public
25 *   License along with this library; if not, write to the Free Software
26 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
27 *
28 */
29
30/*!
31
32\page pcm_plugins PCM (digital audio) plugins
33
34PCM plugins extends functionality and features of PCM devices.
35The plugins take care about various sample conversions, sample
36copying among channels and so on.
37
38\section pcm_plugins_slave Slave definition
39
40The slave plugin can be specified directly with a string or the definition
41can be entered inside a compound configuration node. Some restrictions can
42be also specified (like static rate or count of channels).
43
44\code
45pcm_slave.NAME {
46	pcm STR		# PCM name
47	# or
48	pcm { }		# PCM definition
49	format STR	# Format or "unchanged"
50	channels INT	# Count of channels or "unchanged" string
51	rate INT	# Rate in Hz or "unchanged" string
52	period_time INT	# Period time in us or "unchanged" string
53	buffer_time INT # Buffer time in us or "unchanged" string
54}
55\endcode
56
57Example:
58
59\code
60pcm_slave.slave_rate44100Hz {
61	pcm "hw:0,0"
62	rate 44100
63}
64
65pcm.rate44100Hz {
66	type plug
67	slave slave_rate44100Hz
68}
69\endcode
70
71The equivalent configuration (in one compound):
72
73\code
74pcm.rate44100Hz {
75	type plug
76	slave {
77		pcm "hw:0,0"
78		rate 44100
79	}
80}
81\endcode
82
83*/
84
85#include <sys/shm.h>
86#include <limits.h>
87#include "pcm_local.h"
88#include "pcm_plugin.h"
89
90#ifndef DOC_HIDDEN
91
92static snd_pcm_sframes_t
93snd_pcm_plugin_undo_read(snd_pcm_t *pcm ATTRIBUTE_UNUSED,
94			 const snd_pcm_channel_area_t *res_areas ATTRIBUTE_UNUSED,
95			 snd_pcm_uframes_t res_offset ATTRIBUTE_UNUSED,
96			 snd_pcm_uframes_t res_size ATTRIBUTE_UNUSED,
97			 snd_pcm_uframes_t slave_undo_size ATTRIBUTE_UNUSED)
98{
99	return -EIO;
100}
101
102static snd_pcm_sframes_t
103snd_pcm_plugin_undo_write(snd_pcm_t *pcm ATTRIBUTE_UNUSED,
104			  const snd_pcm_channel_area_t *res_areas ATTRIBUTE_UNUSED,
105			  snd_pcm_uframes_t res_offset ATTRIBUTE_UNUSED,
106			  snd_pcm_uframes_t res_size ATTRIBUTE_UNUSED,
107			  snd_pcm_uframes_t slave_undo_size ATTRIBUTE_UNUSED)
108{
109	return -EIO;
110}
111
112snd_pcm_sframes_t
113snd_pcm_plugin_undo_read_generic(snd_pcm_t *pcm ATTRIBUTE_UNUSED,
114				 const snd_pcm_channel_area_t *res_areas ATTRIBUTE_UNUSED,
115				 snd_pcm_uframes_t res_offset ATTRIBUTE_UNUSED,
116				 snd_pcm_uframes_t res_size ATTRIBUTE_UNUSED,
117				 snd_pcm_uframes_t slave_undo_size)
118{
119	return slave_undo_size;
120}
121
122snd_pcm_sframes_t
123snd_pcm_plugin_undo_write_generic(snd_pcm_t *pcm ATTRIBUTE_UNUSED,
124				  const snd_pcm_channel_area_t *res_areas ATTRIBUTE_UNUSED,
125				  snd_pcm_uframes_t res_offset ATTRIBUTE_UNUSED,
126				  snd_pcm_uframes_t res_size ATTRIBUTE_UNUSED,
127				  snd_pcm_uframes_t slave_undo_size)
128{
129	return slave_undo_size;
130}
131
132void snd_pcm_plugin_init(snd_pcm_plugin_t *plugin)
133{
134	memset(plugin, 0, sizeof(snd_pcm_plugin_t));
135	plugin->undo_read = snd_pcm_plugin_undo_read;
136	plugin->undo_write = snd_pcm_plugin_undo_write;
137	snd_atomic_write_init(&plugin->watom);
138}
139
140static int snd_pcm_plugin_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp)
141{
142	snd_pcm_plugin_t *plugin = pcm->private_data;
143	snd_pcm_sframes_t sd;
144	int err = snd_pcm_delay(plugin->gen.slave, &sd);
145	if (err < 0)
146		return err;
147        if (pcm->stream == SND_PCM_STREAM_CAPTURE &&
148	    pcm->access != SND_PCM_ACCESS_RW_INTERLEAVED &&
149	    pcm->access != SND_PCM_ACCESS_RW_NONINTERLEAVED) {
150                sd += snd_pcm_mmap_capture_avail(pcm);
151        }
152
153	*delayp = sd;
154	return 0;
155}
156
157static int snd_pcm_plugin_prepare(snd_pcm_t *pcm)
158{
159	snd_pcm_plugin_t *plugin = pcm->private_data;
160	int err;
161	snd_atomic_write_begin(&plugin->watom);
162	err = snd_pcm_prepare(plugin->gen.slave);
163	if (err < 0) {
164		snd_atomic_write_end(&plugin->watom);
165		return err;
166	}
167	*pcm->hw.ptr = 0;
168	*pcm->appl.ptr = 0;
169	snd_atomic_write_end(&plugin->watom);
170	if (plugin->init) {
171		err = plugin->init(pcm);
172		if (err < 0)
173			return err;
174	}
175	return 0;
176}
177
178static int snd_pcm_plugin_reset(snd_pcm_t *pcm)
179{
180	snd_pcm_plugin_t *plugin = pcm->private_data;
181	int err;
182	snd_atomic_write_begin(&plugin->watom);
183	err = snd_pcm_reset(plugin->gen.slave);
184	if (err < 0) {
185		snd_atomic_write_end(&plugin->watom);
186		return err;
187	}
188	*pcm->hw.ptr = 0;
189	*pcm->appl.ptr = 0;
190	snd_atomic_write_end(&plugin->watom);
191	if (plugin->init) {
192		err = plugin->init(pcm);
193		if (err < 0)
194			return err;
195	}
196	return 0;
197}
198
199static snd_pcm_sframes_t snd_pcm_plugin_rewindable(snd_pcm_t *pcm)
200{
201	return snd_pcm_mmap_hw_avail(pcm);
202}
203
204static snd_pcm_sframes_t snd_pcm_plugin_rewind(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
205{
206	snd_pcm_plugin_t *plugin = pcm->private_data;
207	snd_pcm_sframes_t n = snd_pcm_mmap_hw_avail(pcm);
208	snd_pcm_sframes_t sframes;
209
210	if ((snd_pcm_uframes_t)n < frames)
211		frames = n;
212	if (frames == 0)
213		return 0;
214
215        sframes = frames;
216	snd_atomic_write_begin(&plugin->watom);
217	sframes = snd_pcm_rewind(plugin->gen.slave, sframes);
218	if (sframes < 0) {
219		snd_atomic_write_end(&plugin->watom);
220		return sframes;
221	}
222	snd_pcm_mmap_appl_backward(pcm, (snd_pcm_uframes_t) frames);
223	snd_atomic_write_end(&plugin->watom);
224	return (snd_pcm_sframes_t) frames;
225}
226
227static snd_pcm_sframes_t snd_pcm_plugin_forwardable(snd_pcm_t *pcm)
228{
229	return snd_pcm_mmap_avail(pcm);
230}
231
232static snd_pcm_sframes_t snd_pcm_plugin_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
233{
234	snd_pcm_plugin_t *plugin = pcm->private_data;
235	snd_pcm_sframes_t n = snd_pcm_mmap_avail(pcm);
236	snd_pcm_sframes_t sframes;
237
238	if ((snd_pcm_uframes_t)n < frames)
239		frames = n;
240	if (frames == 0)
241		return 0;
242
243        sframes = frames;
244	snd_atomic_write_begin(&plugin->watom);
245	sframes = INTERNAL(snd_pcm_forward)(plugin->gen.slave, sframes);
246	if (sframes < 0) {
247		snd_atomic_write_end(&plugin->watom);
248		return sframes;
249	}
250	snd_pcm_mmap_appl_forward(pcm, (snd_pcm_uframes_t) frames);
251	snd_atomic_write_end(&plugin->watom);
252	return (snd_pcm_sframes_t) frames;
253}
254
255static snd_pcm_sframes_t snd_pcm_plugin_write_areas(snd_pcm_t *pcm,
256						    const snd_pcm_channel_area_t *areas,
257						    snd_pcm_uframes_t offset,
258						    snd_pcm_uframes_t size)
259{
260	snd_pcm_plugin_t *plugin = pcm->private_data;
261	snd_pcm_t *slave = plugin->gen.slave;
262	snd_pcm_uframes_t xfer = 0;
263	snd_pcm_sframes_t result;
264	int err;
265
266	while (size > 0) {
267		snd_pcm_uframes_t frames = size;
268		const snd_pcm_channel_area_t *slave_areas;
269		snd_pcm_uframes_t slave_offset;
270		snd_pcm_uframes_t slave_frames = ULONG_MAX;
271
272		err = snd_pcm_mmap_begin(slave, &slave_areas, &slave_offset, &slave_frames);
273		if (err < 0 || slave_frames == 0)
274			break;
275		frames = plugin->write(pcm, areas, offset, frames,
276				       slave_areas, slave_offset, &slave_frames);
277		if (CHECK_SANITY(slave_frames > snd_pcm_mmap_playback_avail(slave))) {
278			SNDMSG("write overflow %ld > %ld", slave_frames,
279			       snd_pcm_mmap_playback_avail(slave));
280			return -EPIPE;
281		}
282		snd_atomic_write_begin(&plugin->watom);
283		snd_pcm_mmap_appl_forward(pcm, frames);
284		result = snd_pcm_mmap_commit(slave, slave_offset, slave_frames);
285		if (result > 0 && (snd_pcm_uframes_t)result != slave_frames) {
286			snd_pcm_sframes_t res;
287			res = plugin->undo_write(pcm, slave_areas, slave_offset + result, slave_frames, slave_frames - result);
288			if (res < 0)
289				return xfer > 0 ? (snd_pcm_sframes_t)xfer : res;
290			frames -= res;
291		}
292		snd_atomic_write_end(&plugin->watom);
293		if (result <= 0)
294			return xfer > 0 ? (snd_pcm_sframes_t)xfer : result;
295		offset += frames;
296		xfer += frames;
297		size -= frames;
298	}
299	return (snd_pcm_sframes_t)xfer;
300}
301
302static snd_pcm_sframes_t snd_pcm_plugin_read_areas(snd_pcm_t *pcm,
303						   const snd_pcm_channel_area_t *areas,
304						   snd_pcm_uframes_t offset,
305						   snd_pcm_uframes_t size)
306{
307	snd_pcm_plugin_t *plugin = pcm->private_data;
308	snd_pcm_t *slave = plugin->gen.slave;
309	snd_pcm_uframes_t xfer = 0;
310	snd_pcm_sframes_t result;
311
312	while (size > 0) {
313		snd_pcm_uframes_t frames = size;
314		const snd_pcm_channel_area_t *slave_areas;
315		snd_pcm_uframes_t slave_offset;
316		snd_pcm_uframes_t slave_frames = ULONG_MAX;
317
318		snd_pcm_mmap_begin(slave, &slave_areas, &slave_offset, &slave_frames);
319		if (slave_frames == 0)
320			break;
321		frames = (plugin->read)(pcm, areas, offset, frames,
322				      slave_areas, slave_offset, &slave_frames);
323		if (CHECK_SANITY(slave_frames > snd_pcm_mmap_capture_avail(slave))) {
324			SNDMSG("read overflow %ld > %ld", slave_frames,
325			       snd_pcm_mmap_playback_avail(slave));
326			return -EPIPE;
327		}
328		snd_atomic_write_begin(&plugin->watom);
329		snd_pcm_mmap_appl_forward(pcm, frames);
330		result = snd_pcm_mmap_commit(slave, slave_offset, slave_frames);
331		if (result > 0 && (snd_pcm_uframes_t)result != slave_frames) {
332			snd_pcm_sframes_t res;
333
334			res = plugin->undo_read(slave, areas, offset, frames, slave_frames - result);
335			if (res < 0)
336				return xfer > 0 ? (snd_pcm_sframes_t)xfer : res;
337			frames -= res;
338		}
339		snd_atomic_write_end(&plugin->watom);
340		if (result <= 0)
341			return xfer > 0 ? (snd_pcm_sframes_t)xfer : result;
342		offset += frames;
343		xfer += frames;
344		size -= frames;
345	}
346	return (snd_pcm_sframes_t)xfer;
347}
348
349
350static snd_pcm_sframes_t
351snd_pcm_plugin_writei(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size)
352{
353	snd_pcm_channel_area_t areas[pcm->channels];
354	snd_pcm_areas_from_buf(pcm, areas, (void*)buffer);
355	return snd_pcm_write_areas(pcm, areas, 0, size,
356				   snd_pcm_plugin_write_areas);
357}
358
359static snd_pcm_sframes_t
360snd_pcm_plugin_writen(snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size)
361{
362	snd_pcm_channel_area_t areas[pcm->channels];
363	snd_pcm_areas_from_bufs(pcm, areas, bufs);
364	return snd_pcm_write_areas(pcm, areas, 0, size,
365				   snd_pcm_plugin_write_areas);
366}
367
368static snd_pcm_sframes_t
369snd_pcm_plugin_readi(snd_pcm_t *pcm, void *buffer, snd_pcm_uframes_t size)
370{
371	snd_pcm_channel_area_t areas[pcm->channels];
372	snd_pcm_areas_from_buf(pcm, areas, buffer);
373	return snd_pcm_read_areas(pcm, areas, 0, size,
374				  snd_pcm_plugin_read_areas);
375}
376
377static snd_pcm_sframes_t
378snd_pcm_plugin_readn(snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size)
379{
380	snd_pcm_channel_area_t areas[pcm->channels];
381	snd_pcm_areas_from_bufs(pcm, areas, bufs);
382	return snd_pcm_read_areas(pcm, areas, 0, size,
383				  snd_pcm_plugin_read_areas);
384}
385
386static snd_pcm_sframes_t
387snd_pcm_plugin_mmap_commit(snd_pcm_t *pcm,
388			   snd_pcm_uframes_t offset ATTRIBUTE_UNUSED,
389			   snd_pcm_uframes_t size)
390{
391	snd_pcm_plugin_t *plugin = pcm->private_data;
392	snd_pcm_t *slave = plugin->gen.slave;
393	const snd_pcm_channel_area_t *areas;
394	snd_pcm_uframes_t appl_offset;
395	snd_pcm_sframes_t slave_size;
396	snd_pcm_sframes_t xfer;
397
398	if (pcm->stream == SND_PCM_STREAM_CAPTURE) {
399		snd_atomic_write_begin(&plugin->watom);
400		snd_pcm_mmap_appl_forward(pcm, size);
401		snd_atomic_write_end(&plugin->watom);
402		return size;
403	}
404	slave_size = snd_pcm_avail_update(slave);
405	if (slave_size < 0)
406		return slave_size;
407	areas = snd_pcm_mmap_areas(pcm);
408	appl_offset = snd_pcm_mmap_offset(pcm);
409	xfer = 0;
410	while (size > 0 && slave_size > 0) {
411		snd_pcm_uframes_t frames = size;
412		snd_pcm_uframes_t cont = pcm->buffer_size - appl_offset;
413		const snd_pcm_channel_area_t *slave_areas;
414		snd_pcm_uframes_t slave_offset;
415		snd_pcm_uframes_t slave_frames = ULONG_MAX;
416		snd_pcm_sframes_t result;
417		int err;
418
419		err = snd_pcm_mmap_begin(slave, &slave_areas, &slave_offset, &slave_frames);
420		if (err < 0)
421			return xfer > 0 ? xfer : err;
422		if (frames > cont)
423			frames = cont;
424		frames = plugin->write(pcm, areas, appl_offset, frames,
425				       slave_areas, slave_offset, &slave_frames);
426		snd_atomic_write_begin(&plugin->watom);
427		snd_pcm_mmap_appl_forward(pcm, frames);
428		result = snd_pcm_mmap_commit(slave, slave_offset, slave_frames);
429		snd_atomic_write_end(&plugin->watom);
430		if (result > 0 && (snd_pcm_uframes_t)result != slave_frames) {
431			snd_pcm_sframes_t res;
432
433			res = plugin->undo_write(pcm, slave_areas, slave_offset + result, slave_frames, slave_frames - result);
434			if (res < 0)
435				return xfer > 0 ? xfer : res;
436			frames -= res;
437		}
438		if (result <= 0)
439			return xfer > 0 ? xfer : result;
440		if (frames == cont)
441			appl_offset = 0;
442		else
443			appl_offset += result;
444		size -= frames;
445		slave_size -= frames;
446		xfer += frames;
447	}
448	if (CHECK_SANITY(size)) {
449		SNDMSG("short commit: %ld", size);
450		return -EPIPE;
451	}
452	return xfer;
453}
454
455static snd_pcm_sframes_t snd_pcm_plugin_avail_update(snd_pcm_t *pcm)
456{
457	snd_pcm_plugin_t *plugin = pcm->private_data;
458	snd_pcm_t *slave = plugin->gen.slave;
459	snd_pcm_sframes_t slave_size;
460
461	slave_size = snd_pcm_avail_update(slave);
462	if (pcm->stream == SND_PCM_STREAM_CAPTURE &&
463	    pcm->access != SND_PCM_ACCESS_RW_INTERLEAVED &&
464	    pcm->access != SND_PCM_ACCESS_RW_NONINTERLEAVED)
465		goto _capture;
466        *pcm->hw.ptr = *slave->hw.ptr;
467        return slave_size;
468 _capture:
469 	{
470		const snd_pcm_channel_area_t *areas;
471		snd_pcm_uframes_t xfer, hw_offset, size;
472
473		xfer = snd_pcm_mmap_capture_avail(pcm);
474		size = pcm->buffer_size - xfer;
475		areas = snd_pcm_mmap_areas(pcm);
476		hw_offset = snd_pcm_mmap_hw_offset(pcm);
477		while (size > 0 && slave_size > 0) {
478			snd_pcm_uframes_t frames = size;
479			snd_pcm_uframes_t cont = pcm->buffer_size - hw_offset;
480			const snd_pcm_channel_area_t *slave_areas;
481			snd_pcm_uframes_t slave_offset;
482			snd_pcm_uframes_t slave_frames = ULONG_MAX;
483			snd_pcm_sframes_t result;
484			int err;
485
486			err = snd_pcm_mmap_begin(slave, &slave_areas, &slave_offset, &slave_frames);
487			if (err < 0)
488				return xfer > 0 ? (snd_pcm_sframes_t)xfer : err;
489			if (frames > cont)
490				frames = cont;
491			frames = (plugin->read)(pcm, areas, hw_offset, frames,
492					      slave_areas, slave_offset, &slave_frames);
493			snd_atomic_write_begin(&plugin->watom);
494			snd_pcm_mmap_hw_forward(pcm, frames);
495			result = snd_pcm_mmap_commit(slave, slave_offset, slave_frames);
496			snd_atomic_write_end(&plugin->watom);
497			if (result > 0 && (snd_pcm_uframes_t)result != slave_frames) {
498				snd_pcm_sframes_t res;
499
500				res = plugin->undo_read(slave, areas, hw_offset, frames, slave_frames - result);
501				if (res < 0)
502					return xfer > 0 ? (snd_pcm_sframes_t)xfer : res;
503				frames -= res;
504			}
505			if (result <= 0)
506				return xfer > 0 ? (snd_pcm_sframes_t)xfer : result;
507			if (frames == cont)
508				hw_offset = 0;
509			else
510				hw_offset += frames;
511			size -= frames;
512			slave_size -= slave_frames;
513			xfer += frames;
514		}
515		return (snd_pcm_sframes_t)xfer;
516	}
517}
518
519static int snd_pcm_plugin_status(snd_pcm_t *pcm, snd_pcm_status_t * status)
520{
521	snd_pcm_plugin_t *plugin = pcm->private_data;
522	snd_pcm_sframes_t err;
523	snd_atomic_read_t ratom;
524	snd_atomic_read_init(&ratom, &plugin->watom);
525 _again:
526	snd_atomic_read_begin(&ratom);
527	/* sync with the latest hw and appl ptrs */
528	snd_pcm_plugin_avail_update(pcm);
529
530	err = snd_pcm_status(plugin->gen.slave, status);
531	if (err < 0) {
532		snd_atomic_read_ok(&ratom);
533		return err;
534	}
535	status->appl_ptr = *pcm->appl.ptr;
536	status->hw_ptr = *pcm->hw.ptr;
537	if (!snd_atomic_read_ok(&ratom)) {
538		snd_atomic_read_wait(&ratom);
539		goto _again;
540	}
541	return 0;
542}
543
544const snd_pcm_fast_ops_t snd_pcm_plugin_fast_ops = {
545	.status = snd_pcm_plugin_status,
546	.state = snd_pcm_generic_state,
547	.hwsync = snd_pcm_generic_hwsync,
548	.delay = snd_pcm_plugin_delay,
549	.prepare = snd_pcm_plugin_prepare,
550	.reset = snd_pcm_plugin_reset,
551	.start = snd_pcm_generic_start,
552	.drop = snd_pcm_generic_drop,
553	.drain = snd_pcm_generic_drain,
554	.pause = snd_pcm_generic_pause,
555	.rewindable = snd_pcm_plugin_rewindable,
556	.rewind = snd_pcm_plugin_rewind,
557	.forwardable = snd_pcm_plugin_forwardable,
558	.forward = snd_pcm_plugin_forward,
559	.resume = snd_pcm_generic_resume,
560	.link = snd_pcm_generic_link,
561	.link_slaves = snd_pcm_generic_link_slaves,
562	.unlink = snd_pcm_generic_unlink,
563	.writei = snd_pcm_plugin_writei,
564	.writen = snd_pcm_plugin_writen,
565	.readi = snd_pcm_plugin_readi,
566	.readn = snd_pcm_plugin_readn,
567	.avail_update = snd_pcm_plugin_avail_update,
568	.mmap_commit = snd_pcm_plugin_mmap_commit,
569	.htimestamp = snd_pcm_generic_htimestamp,
570	.poll_descriptors_count = snd_pcm_generic_poll_descriptors_count,
571	.poll_descriptors = snd_pcm_generic_poll_descriptors,
572	.poll_revents = snd_pcm_generic_poll_revents,
573};
574
575#endif
576