1 // SPDX-License-Identifier: GPL-2.0-or-later
3 * Helper functions for indirect PCM data transfer to a simple FIFO in
4 * hardware (small, no possibility to read "hardware io position",
5 * updating position done by interrupt, ...)
7 * Copyright (c) by 2007 Joachim Foerster <JOFT@gmx.de>
9 * Based on "pcm-indirect.h" (alsa-driver-1.0.13) by
11 * Copyright (c) by Takashi Iwai <tiwai@suse.de>
12 * Jaroslav Kysela <perex@suse.cz>
16 #include <sound/core.h>
17 /* struct snd_pcm_substream, struct snd_pcm_runtime, snd_pcm_uframes_t
18 * snd_pcm_period_elapsed() */
19 #include <sound/pcm.h>
21 #include "pcm-indirect2.h"
23 #ifdef SND_PCM_INDIRECT2_STAT
25 #include <linux/jiffies.h>
27 void snd_pcm_indirect2_stat(struct snd_pcm_substream
*substream
,
28 struct snd_pcm_indirect2
*rec
)
30 struct snd_pcm_runtime
*runtime
= substream
->runtime
;
34 int seconds
= (rec
->lastbytetime
- rec
->firstbytetime
) / HZ
;
36 snd_printk(KERN_DEBUG
"STAT: mul_elapsed: %u, mul_elapsed_real: %d, "
38 rec
->mul_elapsed
, rec
->mul_elapsed_real
, rec
->irq_occured
);
39 snd_printk(KERN_DEBUG
"STAT: min_multiple: %d (irqs/period)\n",
41 snd_printk(KERN_DEBUG
"STAT: firstbytetime: %lu, lastbytetime: %lu, "
42 "firstzerotime: %lu\n",
43 rec
->firstbytetime
, rec
->lastbytetime
, rec
->firstzerotime
);
44 snd_printk(KERN_DEBUG
"STAT: bytes2hw: %u Bytes => (by runtime->rate) "
46 rec
->bytes2hw
, rec
->bytes2hw
/ 2 / 2 / runtime
->rate
);
47 snd_printk(KERN_DEBUG
"STAT: (by measurement) length: %d => "
48 "rate: %d Bytes/s = %d Frames/s|Hz\n",
49 seconds
, rec
->bytes2hw
/ seconds
,
50 rec
->bytes2hw
/ 2 / 2 / seconds
);
52 "STAT: zeros2hw: %u = %d ms ~ %d * %d zero copies\n",
53 rec
->zeros2hw
, ((rec
->zeros2hw
/ 2 / 2) * 1000) /
55 rec
->zeros2hw
/ (rec
->hw_buffer_size
/ 2),
56 (rec
->hw_buffer_size
/ 2));
57 snd_printk(KERN_DEBUG
"STAT: pointer_calls: %u, lastdifftime: %u\n",
58 rec
->pointer_calls
, rec
->lastdifftime
);
59 snd_printk(KERN_DEBUG
"STAT: sw_io: %d, sw_data: %d\n", rec
->sw_io
,
61 snd_printk(KERN_DEBUG
"STAT: byte_sizes[]:\n");
63 for (j
= 0; j
< 8; j
++) {
64 for (i
= j
* 8; i
< (j
+ 1) * 8; i
++)
65 if (rec
->byte_sizes
[i
] != 0) {
66 snd_printk(KERN_DEBUG
"%u: %u",
67 i
, rec
->byte_sizes
[i
]);
70 if (((k
% 8) == 0) && (k
!= 0)) {
71 snd_printk(KERN_DEBUG
"\n");
75 snd_printk(KERN_DEBUG
"\n");
76 snd_printk(KERN_DEBUG
"STAT: zero_sizes[]:\n");
77 for (j
= 0; j
< 8; j
++) {
79 for (i
= j
* 8; i
< (j
+ 1) * 8; i
++)
80 if (rec
->zero_sizes
[i
] != 0)
81 snd_printk(KERN_DEBUG
"%u: %u",
82 i
, rec
->zero_sizes
[i
]);
86 snd_printk(KERN_DEBUG
"\n");
88 snd_printk(KERN_DEBUG
"\n");
89 snd_printk(KERN_DEBUG
"STAT: min_adds[]:\n");
90 for (j
= 0; j
< 8; j
++) {
91 if (rec
->min_adds
[j
] != 0)
92 snd_printk(KERN_DEBUG
"%u: %u", j
, rec
->min_adds
[j
]);
94 snd_printk(KERN_DEBUG
"\n");
95 snd_printk(KERN_DEBUG
"STAT: mul_adds[]:\n");
96 for (j
= 0; j
< 8; j
++) {
97 if (rec
->mul_adds
[j
] != 0)
98 snd_printk(KERN_DEBUG
"%u: %u", j
, rec
->mul_adds
[j
]);
100 snd_printk(KERN_DEBUG
"\n");
101 snd_printk(KERN_DEBUG
102 "STAT: zero_times_saved: %d, zero_times_notsaved: %d\n",
103 rec
->zero_times_saved
, rec
->zero_times_notsaved
);
104 /* snd_printk(KERN_DEBUG "STAT: zero_times[]\n");
106 for (j = 0; j < 3750; j++) {
107 if (rec->zero_times[j] != 0) {
108 snd_printk(KERN_DEBUG "%u: %u", j, rec->zero_times[j]);
111 if (((i % 8) == 0) && (i != 0))
112 snd_printk(KERN_DEBUG "\n");
114 snd_printk(KERN_DEBUG "\n"); */
120 * _internal_ helper function for playback/capture transfer function
123 snd_pcm_indirect2_increase_min_periods(struct snd_pcm_substream
*substream
,
124 struct snd_pcm_indirect2
*rec
,
125 int isplay
, int iscopy
,
128 if (rec
->min_periods
>= 0) {
131 if (rec
->sw_io
>= rec
->sw_buffer_size
)
132 rec
->sw_io
-= rec
->sw_buffer_size
;
134 /* If application does not write data in multiples of
135 * a period, move sw_data to the next correctly aligned
136 * position, so that sw_io can converge to it (in the
139 if (!rec
->check_alignment
) {
141 snd_pcm_lib_period_bytes(substream
)) {
142 unsigned bytes2hw_aligned
=
145 snd_pcm_lib_period_bytes
147 snd_pcm_lib_period_bytes
152 #ifdef SND_PCM_INDIRECT2_STAT
153 snd_printk(KERN_DEBUG
154 "STAT: @re-align: aligned "
155 "bytes2hw to next period "
160 snd_printk(KERN_DEBUG
161 "STAT: @re-align: sw_data "
166 rec
->check_alignment
= 1;
168 /* We are at the end and are copying zeros into the
170 * Now, we have to make sure that sw_io is increased
171 * until the position of sw_data: Filling the fifo with
172 * the first zeros means, the last bytes were played.
174 if (rec
->sw_io
!= rec
->sw_data
) {
176 if (rec
->sw_data
> rec
->sw_io
)
177 diff
= rec
->sw_data
- rec
->sw_io
;
179 diff
= (rec
->sw_buffer_size
-
183 rec
->sw_io
= rec
->sw_data
;
186 if (rec
->sw_io
>= rec
->sw_buffer_size
)
192 rec
->min_period_count
+= bytes
;
193 if (rec
->min_period_count
>= (rec
->hw_buffer_size
/ 2)) {
194 rec
->min_periods
+= (rec
->min_period_count
/
195 (rec
->hw_buffer_size
/ 2));
196 #ifdef SND_PCM_INDIRECT2_STAT
197 if ((rec
->min_period_count
/
198 (rec
->hw_buffer_size
/ 2)) > 7)
199 snd_printk(KERN_DEBUG
200 "STAT: more than 7 (%d) min_adds "
201 "at once - too big to save!\n",
202 (rec
->min_period_count
/
203 (rec
->hw_buffer_size
/ 2)));
205 rec
->min_adds
[(rec
->min_period_count
/
206 (rec
->hw_buffer_size
/ 2))]++;
208 rec
->min_period_count
= (rec
->min_period_count
%
209 (rec
->hw_buffer_size
/ 2));
211 } else if (isplay
&& iscopy
)
212 rec
->min_periods
= 0;
216 * helper function for playback/capture pointer callback
219 snd_pcm_indirect2_pointer(struct snd_pcm_substream
*substream
,
220 struct snd_pcm_indirect2
*rec
)
222 #ifdef SND_PCM_INDIRECT2_STAT
223 rec
->pointer_calls
++;
225 return bytes_to_frames(substream
->runtime
, rec
->sw_io
);
229 * _internal_ helper function for playback interrupt callback
232 snd_pcm_indirect2_playback_transfer(struct snd_pcm_substream
*substream
,
233 struct snd_pcm_indirect2
*rec
,
234 snd_pcm_indirect2_copy_t copy
,
235 snd_pcm_indirect2_zero_t zero
)
237 struct snd_pcm_runtime
*runtime
= substream
->runtime
;
238 snd_pcm_uframes_t appl_ptr
= runtime
->control
->appl_ptr
;
240 /* runtime->control->appl_ptr: position where ALSA will write next time
241 * rec->appl_ptr: position where ALSA was last time
242 * diff: obviously ALSA wrote that much bytes into the intermediate
243 * buffer since we checked last time
245 snd_pcm_sframes_t diff
= appl_ptr
- rec
->appl_ptr
;
248 #ifdef SND_PCM_INDIRECT2_STAT
249 rec
->lastdifftime
= jiffies
;
251 if (diff
< -(snd_pcm_sframes_t
) (runtime
->boundary
/ 2))
252 diff
+= runtime
->boundary
;
253 /* number of bytes "added" by ALSA increases the number of
254 * bytes which are ready to "be transferred to HW"/"played"
255 * Then, set rec->appl_ptr to not count bytes twice next time.
257 rec
->sw_ready
+= (int)frames_to_bytes(runtime
, diff
);
258 rec
->appl_ptr
= appl_ptr
;
260 if (rec
->hw_ready
&& (rec
->sw_ready
<= 0)) {
263 #ifdef SND_PCM_INDIRECT2_STAT
264 if (rec
->firstzerotime
== 0) {
265 rec
->firstzerotime
= jiffies
;
266 snd_printk(KERN_DEBUG
267 "STAT: @firstzerotime: mul_elapsed: %d, "
268 "min_period_count: %d\n",
269 rec
->mul_elapsed
, rec
->min_period_count
);
270 snd_printk(KERN_DEBUG
271 "STAT: @firstzerotime: sw_io: %d, "
272 "sw_data: %d, appl_ptr: %u\n",
273 rec
->sw_io
, rec
->sw_data
,
274 (unsigned int)appl_ptr
);
276 if ((jiffies
- rec
->firstzerotime
) < 3750) {
277 rec
->zero_times
[(jiffies
- rec
->firstzerotime
)]++;
278 rec
->zero_times_saved
++;
280 rec
->zero_times_notsaved
++;
282 bytes
= zero(substream
, rec
);
284 #ifdef SND_PCM_INDIRECT2_STAT
285 rec
->zeros2hw
+= bytes
;
287 rec
->zero_sizes
[bytes
]++;
289 snd_printk(KERN_DEBUG
290 "STAT: %d zero Bytes copied to hardware at "
291 "once - too big to save!\n",
294 snd_pcm_indirect2_increase_min_periods(substream
, rec
, 1, 0,
298 while (rec
->hw_ready
&& (rec
->sw_ready
> 0)) {
299 /* sw_to_end: max. number of bytes that can be read/take from
300 * the current position (sw_data) in _one_ step
302 unsigned int sw_to_end
= rec
->sw_buffer_size
- rec
->sw_data
;
304 /* bytes: number of bytes we have available (for reading) */
305 unsigned int bytes
= rec
->sw_ready
;
307 if (sw_to_end
< bytes
)
312 #ifdef SND_PCM_INDIRECT2_STAT
313 if (rec
->firstbytetime
== 0)
314 rec
->firstbytetime
= jiffies
;
315 rec
->lastbytetime
= jiffies
;
317 /* copy bytes from intermediate buffer position sw_data to the
318 * HW and return number of bytes actually written
319 * Furthermore, set hw_ready to 0, if the fifo isn't empty
320 * now => more could be transferred to fifo
322 bytes
= copy(substream
, rec
, bytes
);
323 rec
->bytes2hw
+= bytes
;
325 #ifdef SND_PCM_INDIRECT2_STAT
327 rec
->byte_sizes
[bytes
]++;
329 snd_printk(KERN_DEBUG
330 "STAT: %d Bytes copied to hardware at once "
331 "- too big to save!\n",
334 /* increase sw_data by the number of actually written bytes
335 * (= number of taken bytes from intermediate buffer)
337 rec
->sw_data
+= bytes
;
338 if (rec
->sw_data
== rec
->sw_buffer_size
)
340 /* now sw_data is the position where ALSA is going to write
341 * in the intermediate buffer next time = position we are going
342 * to read from next time
345 snd_pcm_indirect2_increase_min_periods(substream
, rec
, 1, 1,
348 /* we read bytes from intermediate buffer, so we need to say
349 * that the number of bytes ready for transfer are decreased
352 rec
->sw_ready
-= bytes
;
358 * helper function for playback interrupt routine
361 snd_pcm_indirect2_playback_interrupt(struct snd_pcm_substream
*substream
,
362 struct snd_pcm_indirect2
*rec
,
363 snd_pcm_indirect2_copy_t copy
,
364 snd_pcm_indirect2_zero_t zero
)
366 #ifdef SND_PCM_INDIRECT2_STAT
369 /* hardware played some bytes, so there is room again (in fifo) */
372 /* don't call ack() now, instead call transfer() function directly
373 * (normally called by ack() )
375 snd_pcm_indirect2_playback_transfer(substream
, rec
, copy
, zero
);
377 if (rec
->min_periods
>= rec
->min_multiple
) {
378 #ifdef SND_PCM_INDIRECT2_STAT
379 if ((rec
->min_periods
/ rec
->min_multiple
) > 7)
380 snd_printk(KERN_DEBUG
381 "STAT: more than 7 (%d) mul_adds - too big "
383 (rec
->min_periods
/ rec
->min_multiple
));
385 rec
->mul_adds
[(rec
->min_periods
/
386 rec
->min_multiple
)]++;
387 rec
->mul_elapsed_real
+= (rec
->min_periods
/
391 rec
->min_periods
= (rec
->min_periods
% rec
->min_multiple
);
392 snd_pcm_period_elapsed(substream
);
397 * _internal_ helper function for capture interrupt callback
400 snd_pcm_indirect2_capture_transfer(struct snd_pcm_substream
*substream
,
401 struct snd_pcm_indirect2
*rec
,
402 snd_pcm_indirect2_copy_t copy
,
403 snd_pcm_indirect2_zero_t null
)
405 struct snd_pcm_runtime
*runtime
= substream
->runtime
;
406 snd_pcm_uframes_t appl_ptr
= runtime
->control
->appl_ptr
;
407 snd_pcm_sframes_t diff
= appl_ptr
- rec
->appl_ptr
;
410 #ifdef SND_PCM_INDIRECT2_STAT
411 rec
->lastdifftime
= jiffies
;
413 if (diff
< -(snd_pcm_sframes_t
) (runtime
->boundary
/ 2))
414 diff
+= runtime
->boundary
;
415 rec
->sw_ready
-= frames_to_bytes(runtime
, diff
);
416 rec
->appl_ptr
= appl_ptr
;
418 /* if hardware has something, but the intermediate buffer is full
419 * => skip contents of buffer
421 if (rec
->hw_ready
&& (rec
->sw_ready
>= (int)rec
->sw_buffer_size
)) {
424 #ifdef SND_PCM_INDIRECT2_STAT
425 if (rec
->firstzerotime
== 0) {
426 rec
->firstzerotime
= jiffies
;
427 snd_printk(KERN_DEBUG
"STAT: (capture) "
428 "@firstzerotime: mul_elapsed: %d, "
429 "min_period_count: %d\n",
430 rec
->mul_elapsed
, rec
->min_period_count
);
431 snd_printk(KERN_DEBUG
"STAT: (capture) "
432 "@firstzerotime: sw_io: %d, sw_data: %d, "
434 rec
->sw_io
, rec
->sw_data
,
435 (unsigned int)appl_ptr
);
437 if ((jiffies
- rec
->firstzerotime
) < 3750) {
438 rec
->zero_times
[(jiffies
- rec
->firstzerotime
)]++;
439 rec
->zero_times_saved
++;
441 rec
->zero_times_notsaved
++;
443 bytes
= null(substream
, rec
);
445 #ifdef SND_PCM_INDIRECT2_STAT
446 rec
->zeros2hw
+= bytes
;
448 rec
->zero_sizes
[bytes
]++;
450 snd_printk(KERN_DEBUG
451 "STAT: (capture) %d zero Bytes copied to "
452 "hardware at once - too big to save!\n",
455 snd_pcm_indirect2_increase_min_periods(substream
, rec
, 0, 0,
457 /* report an overrun */
458 rec
->sw_io
= SNDRV_PCM_POS_XRUN
;
461 while (rec
->hw_ready
&& (rec
->sw_ready
< (int)rec
->sw_buffer_size
)) {
462 /* sw_to_end: max. number of bytes that we can write to the
463 * intermediate buffer (until it's end)
465 size_t sw_to_end
= rec
->sw_buffer_size
- rec
->sw_data
;
467 /* bytes: max. number of bytes, which may be copied to the
468 * intermediate buffer without overflow (in _one_ step)
470 size_t bytes
= rec
->sw_buffer_size
- rec
->sw_ready
;
472 /* limit number of bytes (for transfer) by available room in
473 * the intermediate buffer
475 if (sw_to_end
< bytes
)
480 #ifdef SND_PCM_INDIRECT2_STAT
481 if (rec
->firstbytetime
== 0)
482 rec
->firstbytetime
= jiffies
;
483 rec
->lastbytetime
= jiffies
;
485 /* copy bytes from the intermediate buffer (position sw_data)
486 * to the HW at most and return number of bytes actually copied
488 * Furthermore, set hw_ready to 0, if the fifo is empty now.
490 bytes
= copy(substream
, rec
, bytes
);
491 rec
->bytes2hw
+= bytes
;
493 #ifdef SND_PCM_INDIRECT2_STAT
495 rec
->byte_sizes
[bytes
]++;
497 snd_printk(KERN_DEBUG
498 "STAT: (capture) %d Bytes copied to "
499 "hardware at once - too big to save!\n",
502 /* increase sw_data by the number of actually copied bytes from
505 rec
->sw_data
+= bytes
;
506 if (rec
->sw_data
== rec
->sw_buffer_size
)
509 snd_pcm_indirect2_increase_min_periods(substream
, rec
, 0, 1,
512 /* number of bytes in the intermediate buffer, which haven't
513 * been fetched by ALSA yet.
515 rec
->sw_ready
+= bytes
;
521 * helper function for capture interrupt routine
524 snd_pcm_indirect2_capture_interrupt(struct snd_pcm_substream
*substream
,
525 struct snd_pcm_indirect2
*rec
,
526 snd_pcm_indirect2_copy_t copy
,
527 snd_pcm_indirect2_zero_t null
)
529 #ifdef SND_PCM_INDIRECT2_STAT
532 /* hardware recorded some bytes, so there is something to read from the
537 /* don't call ack() now, instead call transfer() function directly
538 * (normally called by ack() )
540 snd_pcm_indirect2_capture_transfer(substream
, rec
, copy
, null
);
542 if (rec
->min_periods
>= rec
->min_multiple
) {
544 #ifdef SND_PCM_INDIRECT2_STAT
545 if ((rec
->min_periods
/ rec
->min_multiple
) > 7)
546 snd_printk(KERN_DEBUG
547 "STAT: more than 7 (%d) mul_adds - "
548 "too big to save!\n",
549 (rec
->min_periods
/ rec
->min_multiple
));
551 rec
->mul_adds
[(rec
->min_periods
/
552 rec
->min_multiple
)]++;
553 rec
->mul_elapsed_real
+= (rec
->min_periods
/
557 rec
->min_periods
= (rec
->min_periods
% rec
->min_multiple
);
558 snd_pcm_period_elapsed(substream
);