xref: /freebsd/sys/dev/sound/pcm/feeder_matrix.c (revision c824383b269d8abe175ea4751194660716d5600e)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2008-2009 Ariff Abdullah <ariff@FreeBSD.org>
5  * All rights reserved.
6  * Copyright (c) 2024-2025 The FreeBSD Foundation
7  *
8  * Portions of this software were developed by Christos Margiolis
9  * <christos@FreeBSD.org> under sponsorship from the FreeBSD Foundation.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer.
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in the
18  *    documentation and/or other materials provided with the distribution.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  */
32 
33 /*
34  * feeder_matrix: Generic any-to-any channel matrixing. Probably not the
35  *                accurate way of doing things, but it should be fast and
36  *                transparent enough, not to mention capable of handling
37  *                possible non-standard way of multichannel interleaving
38  *                order. In other words, it is tough to break.
39  *
40  * The Good:
41  * + very generic and compact, provided that the supplied matrix map is in a
42  *   sane form.
43  * + should be fast enough.
44  *
45  * The Bad:
46  * + somebody might disagree with it.
47  * + 'matrix' is kind of 0x7a69, due to prolong mental block.
48  */
49 
50 #ifdef _KERNEL
51 #ifdef HAVE_KERNEL_OPTION_HEADERS
52 #include "opt_snd.h"
53 #endif
54 #include <dev/sound/pcm/sound.h>
55 #include <dev/sound/pcm/pcm.h>
56 #include "feeder_if.h"
57 
58 #define SND_USE_FXDIV
59 #include "snd_fxdiv_gen.h"
60 #endif
61 
62 #define FEEDMATRIX_RESERVOIR	(SND_CHN_MAX * PCM_32_BPS)
63 
64 #define SND_CHN_T_EOF		0x00e0fe0f
65 #define SND_CHN_T_NULL		0x0e0e0e0e
66 
67 struct feed_matrix_info {
68 	uint32_t fmt;
69 	uint32_t bps;
70 	uint32_t ialign, oalign;
71 	uint32_t in, out;
72 	struct {
73 		int chn[SND_CHN_T_MAX + 1];
74 		int mul, shift;
75 	} matrix[SND_CHN_T_MAX + 1];
76 	uint8_t reservoir[FEEDMATRIX_RESERVOIR];
77 };
78 
79 static struct pcmchan_matrix feeder_matrix_maps[SND_CHN_MATRIX_MAX] = {
80 	[SND_CHN_MATRIX_1_0] = SND_CHN_MATRIX_MAP_1_0,
81 	[SND_CHN_MATRIX_2_0] = SND_CHN_MATRIX_MAP_2_0,
82 	[SND_CHN_MATRIX_2_1] = SND_CHN_MATRIX_MAP_2_1,
83 	[SND_CHN_MATRIX_3_0] = SND_CHN_MATRIX_MAP_3_0,
84 	[SND_CHN_MATRIX_3_1] = SND_CHN_MATRIX_MAP_3_1,
85 	[SND_CHN_MATRIX_4_0] = SND_CHN_MATRIX_MAP_4_0,
86 	[SND_CHN_MATRIX_4_1] = SND_CHN_MATRIX_MAP_4_1,
87 	[SND_CHN_MATRIX_5_0] = SND_CHN_MATRIX_MAP_5_0,
88 	[SND_CHN_MATRIX_5_1] = SND_CHN_MATRIX_MAP_5_1,
89 	[SND_CHN_MATRIX_6_0] = SND_CHN_MATRIX_MAP_6_0,
90 	[SND_CHN_MATRIX_6_1] = SND_CHN_MATRIX_MAP_6_1,
91 	[SND_CHN_MATRIX_7_0] = SND_CHN_MATRIX_MAP_7_0,
92 	[SND_CHN_MATRIX_7_1] = SND_CHN_MATRIX_MAP_7_1
93 };
94 
95 static int feeder_matrix_default_ids[9] = {
96 	[0] = SND_CHN_MATRIX_UNKNOWN,
97 	[1] = SND_CHN_MATRIX_1,
98 	[2] = SND_CHN_MATRIX_2,
99 	[3] = SND_CHN_MATRIX_3,
100 	[4] = SND_CHN_MATRIX_4,
101 	[5] = SND_CHN_MATRIX_5,
102 	[6] = SND_CHN_MATRIX_6,
103 	[7] = SND_CHN_MATRIX_7,
104 	[8] = SND_CHN_MATRIX_8
105 };
106 
107 #ifdef _KERNEL
108 #define FEEDMATRIX_CLIP_CHECK(...)
109 #else
110 #define FEEDMATRIX_CLIP_CHECK(v, BIT)	do {				\
111 	if ((v) < PCM_S##BIT##_MIN || (v) > PCM_S##BIT##_MAX)		\
112 	    errx(1, "\n\n%s(): Sample clipping: %jd\n",			\
113 		__func__, (intmax_t)(v));				\
114 } while (0)
115 #endif
116 
117 __always_inline static void
feed_matrix_apply(struct feed_matrix_info * info,uint8_t * src,uint8_t * dst,uint32_t count,const uint32_t fmt)118 feed_matrix_apply(struct feed_matrix_info *info, uint8_t *src, uint8_t *dst,
119     uint32_t count, const uint32_t fmt)
120 {
121 	intpcm64_t accum;
122 	intpcm_t v;
123 	int i, j;
124 
125 	do {
126 		for (i = 0; info->matrix[i].chn[0] != SND_CHN_T_EOF; i++) {
127 			if (info->matrix[i].chn[0] == SND_CHN_T_NULL) {
128 				pcm_sample_write(dst, 0, fmt);
129 				dst += info->bps;
130 				continue;
131 			} else if (info->matrix[i].chn[1] == SND_CHN_T_EOF) {
132 				v = pcm_sample_read(src +
133 				    info->matrix[i].chn[0], fmt);
134 				pcm_sample_write(dst, v, fmt);
135 				dst += info->bps;
136 				continue;
137 			}
138 
139 			accum = 0;
140 			for (j = 0; info->matrix[i].chn[j] != SND_CHN_T_EOF;
141 			    j++) {
142 				v = pcm_sample_read(src +
143 				    info->matrix[i].chn[j], fmt);
144 				accum += v;
145 			}
146 
147 			accum = (accum * info->matrix[i].mul) >>
148 			    info->matrix[i].shift;
149 
150 			FEEDMATRIX_CLIP_CHECK(accum, AFMT_BIT(fmt));
151 
152 			v = pcm_clamp(accum, fmt);
153 			pcm_sample_write(dst, v, fmt);
154 			dst += info->bps;
155 		}
156 		src += info->ialign;
157 	} while (--count != 0);
158 }
159 
160 static void
feed_matrix_reset(struct feed_matrix_info * info)161 feed_matrix_reset(struct feed_matrix_info *info)
162 {
163 	uint32_t i, j;
164 
165 	for (i = 0; i < nitems(info->matrix); i++) {
166 		for (j = 0;
167 		    j < (sizeof(info->matrix[i].chn) /
168 		    sizeof(info->matrix[i].chn[0])); j++) {
169 			info->matrix[i].chn[j] = SND_CHN_T_EOF;
170 		}
171 		info->matrix[i].mul   = 1;
172 		info->matrix[i].shift = 0;
173 	}
174 }
175 
176 static int
feed_matrix_setup(struct feed_matrix_info * info,struct pcmchan_matrix * m_in,struct pcmchan_matrix * m_out)177 feed_matrix_setup(struct feed_matrix_info *info, struct pcmchan_matrix *m_in,
178     struct pcmchan_matrix *m_out)
179 {
180 	uint32_t i, j, ch, in_mask, merge_mask;
181 	int mul, shift;
182 
183 	if (info == NULL || m_in == NULL || m_out == NULL ||
184 	    AFMT_CHANNEL(info->in) != m_in->channels ||
185 	    AFMT_CHANNEL(info->out) != m_out->channels ||
186 	    m_in->channels < SND_CHN_MIN || m_in->channels > SND_CHN_MAX ||
187 	    m_out->channels < SND_CHN_MIN || m_out->channels > SND_CHN_MAX)
188 		return (EINVAL);
189 
190 	feed_matrix_reset(info);
191 
192 	/*
193 	 * If both in and out are part of standard matrix and identical, skip
194 	 * everything altogether.
195 	 */
196 	if (m_in->id == m_out->id && !(m_in->id < SND_CHN_MATRIX_BEGIN ||
197 	    m_in->id > SND_CHN_MATRIX_END))
198 		return (0);
199 
200 	/*
201 	 * Special case for mono input matrix. If the output supports
202 	 * possible 'center' channel, route it there. Otherwise, let it be
203 	 * matrixed to left/right.
204 	 */
205 	if (m_in->id == SND_CHN_MATRIX_1_0) {
206 		if (m_out->id == SND_CHN_MATRIX_1_0)
207 			in_mask = SND_CHN_T_MASK_FL;
208 		else if (m_out->mask & SND_CHN_T_MASK_FC)
209 			in_mask = SND_CHN_T_MASK_FC;
210 		else
211 			in_mask = SND_CHN_T_MASK_FL | SND_CHN_T_MASK_FR;
212 	} else
213 		in_mask = m_in->mask;
214 
215 	/* Merge, reduce, expand all possibilites. */
216 	for (ch = SND_CHN_T_BEGIN; ch <= SND_CHN_T_END &&
217 	    m_out->map[ch].type != SND_CHN_T_MAX; ch += SND_CHN_T_STEP) {
218 		merge_mask = m_out->map[ch].members & in_mask;
219 		if (merge_mask == 0) {
220 			info->matrix[ch].chn[0] = SND_CHN_T_NULL;
221 			continue;
222 		}
223 
224 		j = 0;
225 		for (i = SND_CHN_T_BEGIN; i <= SND_CHN_T_END;
226 		    i += SND_CHN_T_STEP) {
227 			if (merge_mask & (1 << i)) {
228 				if (m_in->offset[i] >= 0 &&
229 				    m_in->offset[i] < (int)m_in->channels)
230 					info->matrix[ch].chn[j++] =
231 					    m_in->offset[i] * info->bps;
232 				else {
233 					info->matrix[ch].chn[j++] =
234 					    SND_CHN_T_EOF;
235 					break;
236 				}
237 			}
238 		}
239 
240 #define FEEDMATRIX_ATTN_SHIFT	16
241 
242 		if (j > 1) {
243 			/*
244 			 * XXX For channel that require accumulation from
245 			 * multiple channels, apply a slight attenuation to
246 			 * avoid clipping.
247 			 */
248 			mul   = (1 << (FEEDMATRIX_ATTN_SHIFT - 1)) + 143 - j;
249 			shift = FEEDMATRIX_ATTN_SHIFT;
250 			while ((mul & 1) == 0 && shift > 0) {
251 				mul >>= 1;
252 				shift--;
253 			}
254 			info->matrix[ch].mul   = mul;
255 			info->matrix[ch].shift = shift;
256 		}
257 	}
258 
259 #ifndef _KERNEL
260 	fprintf(stderr, "Total: %d\n", ch);
261 
262 	for (i = 0; info->matrix[i].chn[0] != SND_CHN_T_EOF; i++) {
263 		fprintf(stderr, "%d: [", i);
264 		for (j = 0; info->matrix[i].chn[j] != SND_CHN_T_EOF; j++) {
265 			if (j != 0)
266 				fprintf(stderr, ", ");
267 			fprintf(stderr, "%d",
268 			    (info->matrix[i].chn[j] == SND_CHN_T_NULL) ?
269 			    0xffffffff : info->matrix[i].chn[j] / info->bps);
270 		}
271 		fprintf(stderr, "] attn: (x * %d) >> %d\n",
272 		    info->matrix[i].mul, info->matrix[i].shift);
273 	}
274 #endif
275 
276 	return (0);
277 }
278 
279 static int
feed_matrix_init(struct pcm_feeder * f)280 feed_matrix_init(struct pcm_feeder *f)
281 {
282 	struct feed_matrix_info *info;
283 	struct pcmchan_matrix *m_in, *m_out;
284 	int ret;
285 
286 	if (AFMT_ENCODING(f->desc->in) != AFMT_ENCODING(f->desc->out))
287 		return (EINVAL);
288 
289 	info = malloc(sizeof(*info), M_DEVBUF, M_NOWAIT | M_ZERO);
290 	if (info == NULL)
291 		return (ENOMEM);
292 
293 	info->in = f->desc->in;
294 	info->out = f->desc->out;
295 	info->fmt = AFMT_ENCODING(info->in);
296 	info->bps = AFMT_BPS(info->in);
297 	info->ialign = AFMT_ALIGN(info->in);
298 	info->oalign = AFMT_ALIGN(info->out);
299 
300 	m_in  = feeder_matrix_format_map(info->in);
301 	m_out = feeder_matrix_format_map(info->out);
302 
303 	ret = feed_matrix_setup(info, m_in, m_out);
304 	if (ret != 0) {
305 		free(info, M_DEVBUF);
306 		return (ret);
307 	}
308 
309 	f->data = info;
310 
311 	return (0);
312 }
313 
314 static int
feed_matrix_free(struct pcm_feeder * f)315 feed_matrix_free(struct pcm_feeder *f)
316 {
317 	struct feed_matrix_info *info;
318 
319 	info = f->data;
320 	if (info != NULL)
321 		free(info, M_DEVBUF);
322 
323 	f->data = NULL;
324 
325 	return (0);
326 }
327 
328 static int
feed_matrix_feed(struct pcm_feeder * f,struct pcm_channel * c,uint8_t * b,uint32_t count,void * source)329 feed_matrix_feed(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b,
330     uint32_t count, void *source)
331 {
332 	struct feed_matrix_info *info;
333 	uint32_t j, inmax;
334 	uint8_t *src, *dst;
335 
336 	info = f->data;
337 	if (info->matrix[0].chn[0] == SND_CHN_T_EOF)
338 		return (FEEDER_FEED(f->source, c, b, count, source));
339 
340 	dst = b;
341 	count = SND_FXROUND(count, info->oalign);
342 	inmax = info->ialign + info->oalign;
343 
344 	/*
345 	 * This loop might look simmilar to other feeder_* loops, but be
346 	 * advised: matrixing might involve overlapping (think about
347 	 * swapping end to front or something like that). In this regard it
348 	 * might be simmilar to feeder_format, but feeder_format works on
349 	 * 'sample' domain where it can be fitted into single 32bit integer
350 	 * while matrixing works on 'sample frame' domain.
351 	 */
352 	do {
353 		if (count < info->oalign)
354 			break;
355 
356 		if (count < inmax) {
357 			src = info->reservoir;
358 			j = info->ialign;
359 		} else {
360 			if (info->ialign == info->oalign)
361 				j = count - info->oalign;
362 			else if (info->ialign > info->oalign)
363 				j = SND_FXROUND(count - info->oalign,
364 				    info->ialign);
365 			else
366 				j = (SND_FXDIV(count, info->oalign) - 1) *
367 				    info->ialign;
368 			src = dst + count - j;
369 		}
370 
371 		j = SND_FXDIV(FEEDER_FEED(f->source, c, src, j, source),
372 		    info->ialign);
373 		if (j == 0)
374 			break;
375 
376 		/* Optimize some common formats. */
377 		switch (info->fmt) {
378 		case AFMT_S16_NE:
379 			feed_matrix_apply(info, src, dst, j, AFMT_S16_NE);
380 			break;
381 		case AFMT_S24_NE:
382 			feed_matrix_apply(info, src, dst, j, AFMT_S24_NE);
383 			break;
384 		case AFMT_S32_NE:
385 			feed_matrix_apply(info, src, dst, j, AFMT_S32_NE);
386 			break;
387 		default:
388 			feed_matrix_apply(info, src, dst, j, info->fmt);
389 			break;
390 		}
391 
392 		j *= info->oalign;
393 		dst += j;
394 		count -= j;
395 
396 	} while (count != 0);
397 
398 	return (dst - b);
399 }
400 
401 static struct pcm_feederdesc feeder_matrix_desc[] = {
402 	{ FEEDER_MATRIX, 0, 0, 0, 0 },
403 	{ 0, 0, 0, 0, 0 }
404 };
405 
406 static kobj_method_t feeder_matrix_methods[] = {
407 	KOBJMETHOD(feeder_init,		feed_matrix_init),
408 	KOBJMETHOD(feeder_free,		feed_matrix_free),
409 	KOBJMETHOD(feeder_feed,		feed_matrix_feed),
410 	KOBJMETHOD_END
411 };
412 
413 FEEDER_DECLARE(feeder_matrix, NULL);
414 
415 /* External */
416 int
feeder_matrix_setup(struct pcm_feeder * f,struct pcmchan_matrix * m_in,struct pcmchan_matrix * m_out)417 feeder_matrix_setup(struct pcm_feeder *f, struct pcmchan_matrix *m_in,
418     struct pcmchan_matrix *m_out)
419 {
420 
421 	if (f == NULL || f->desc == NULL || f->desc->type != FEEDER_MATRIX ||
422 	    f->data == NULL)
423 		return (EINVAL);
424 
425 	return (feed_matrix_setup(f->data, m_in, m_out));
426 }
427 
428 /*
429  * feeder_matrix_default_id(): For a given number of channels, return
430  *                             default preferred id (example: both 5.1 and
431  *                             6.0 are simply 6 channels, but 5.1 is more
432  *                             preferable).
433  */
434 int
feeder_matrix_default_id(uint32_t ch)435 feeder_matrix_default_id(uint32_t ch)
436 {
437 
438 	if (ch < feeder_matrix_maps[SND_CHN_MATRIX_BEGIN].channels ||
439 	    ch > feeder_matrix_maps[SND_CHN_MATRIX_END].channels)
440 		return (SND_CHN_MATRIX_UNKNOWN);
441 
442 	return (feeder_matrix_maps[feeder_matrix_default_ids[ch]].id);
443 }
444 
445 /*
446  * feeder_matrix_default_channel_map(): Ditto, but return matrix map
447  *                                      instead.
448  */
449 struct pcmchan_matrix *
feeder_matrix_default_channel_map(uint32_t ch)450 feeder_matrix_default_channel_map(uint32_t ch)
451 {
452 
453 	if (ch < feeder_matrix_maps[SND_CHN_MATRIX_BEGIN].channels ||
454 	    ch > feeder_matrix_maps[SND_CHN_MATRIX_END].channels)
455 		return (NULL);
456 
457 	return (&feeder_matrix_maps[feeder_matrix_default_ids[ch]]);
458 }
459 
460 /*
461  * feeder_matrix_default_format(): For a given audio format, return the
462  *                                 proper audio format based on preferable
463  *                                 matrix.
464  */
465 uint32_t
feeder_matrix_default_format(uint32_t format)466 feeder_matrix_default_format(uint32_t format)
467 {
468 	struct pcmchan_matrix *m;
469 	uint32_t i, ch, ext;
470 
471 	ch = AFMT_CHANNEL(format);
472 	ext = AFMT_EXTCHANNEL(format);
473 
474 	if (ext != 0) {
475 		for (i = SND_CHN_MATRIX_BEGIN; i <= SND_CHN_MATRIX_END; i++) {
476 			if (feeder_matrix_maps[i].channels == ch &&
477 			    feeder_matrix_maps[i].ext == ext)
478 			return (SND_FORMAT(format, ch, ext));
479 		}
480 	}
481 
482 	m = feeder_matrix_default_channel_map(ch);
483 	if (m == NULL)
484 		return (0x00000000);
485 
486 	return (SND_FORMAT(format, ch, m->ext));
487 }
488 
489 /*
490  * feeder_matrix_format_id(): For a given audio format, return its matrix
491  *                            id.
492  */
493 int
feeder_matrix_format_id(uint32_t format)494 feeder_matrix_format_id(uint32_t format)
495 {
496 	uint32_t i, ch, ext;
497 
498 	ch = AFMT_CHANNEL(format);
499 	ext = AFMT_EXTCHANNEL(format);
500 
501 	for (i = SND_CHN_MATRIX_BEGIN; i <= SND_CHN_MATRIX_END; i++) {
502 		if (feeder_matrix_maps[i].channels == ch &&
503 		    feeder_matrix_maps[i].ext == ext)
504 			return (feeder_matrix_maps[i].id);
505 	}
506 
507 	return (SND_CHN_MATRIX_UNKNOWN);
508 }
509 
510 /*
511  * feeder_matrix_format_map(): For a given audio format, return its matrix
512  *                             map.
513  */
514 struct pcmchan_matrix *
feeder_matrix_format_map(uint32_t format)515 feeder_matrix_format_map(uint32_t format)
516 {
517 	uint32_t i, ch, ext;
518 
519 	ch = AFMT_CHANNEL(format);
520 	ext = AFMT_EXTCHANNEL(format);
521 
522 	for (i = SND_CHN_MATRIX_BEGIN; i <= SND_CHN_MATRIX_END; i++) {
523 		if (feeder_matrix_maps[i].channels == ch &&
524 		    feeder_matrix_maps[i].ext == ext)
525 			return (&feeder_matrix_maps[i]);
526 	}
527 
528 	return (NULL);
529 }
530 
531 /*
532  * feeder_matrix_id_map(): For a given matrix id, return its matrix map.
533  */
534 struct pcmchan_matrix *
feeder_matrix_id_map(int id)535 feeder_matrix_id_map(int id)
536 {
537 
538 	if (id < SND_CHN_MATRIX_BEGIN || id > SND_CHN_MATRIX_END)
539 		return (NULL);
540 
541 	return (&feeder_matrix_maps[id]);
542 }
543 
544 /*
545  * feeder_matrix_compare(): Compare the simmilarities of matrices.
546  */
547 int
feeder_matrix_compare(struct pcmchan_matrix * m_in,struct pcmchan_matrix * m_out)548 feeder_matrix_compare(struct pcmchan_matrix *m_in, struct pcmchan_matrix *m_out)
549 {
550 	uint32_t i;
551 
552 	if (m_in == m_out)
553 		return (0);
554 
555 	if (m_in->channels != m_out->channels || m_in->ext != m_out->ext ||
556 	    m_in->mask != m_out->mask)
557 		return (1);
558 
559 	for (i = 0; i < nitems(m_in->map); i++) {
560 		if (m_in->map[i].type != m_out->map[i].type)
561 			return (1);
562 		if (m_in->map[i].type == SND_CHN_T_MAX)
563 			break;
564 		if (m_in->map[i].members != m_out->map[i].members)
565 			return (1);
566 		if (i <= SND_CHN_T_END) {
567 			if (m_in->offset[m_in->map[i].type] !=
568 			    m_out->offset[m_out->map[i].type])
569 				return (1);
570 		}
571 	}
572 
573 	return (0);
574 }
575 
576 /*
577  * XXX 4front interpretation of "surround" is ambigous and sort of
578  *     conflicting with "rear"/"back". Map it to "side". Well..
579  *     who cares?
580  */
581 static int snd_chn_to_oss[SND_CHN_T_MAX] = {
582 	[SND_CHN_T_FL] = CHID_L,
583 	[SND_CHN_T_FR] = CHID_R,
584 	[SND_CHN_T_FC] = CHID_C,
585 	[SND_CHN_T_LF] = CHID_LFE,
586 	[SND_CHN_T_SL] = CHID_LS,
587 	[SND_CHN_T_SR] = CHID_RS,
588 	[SND_CHN_T_BL] = CHID_LR,
589 	[SND_CHN_T_BR] = CHID_RR
590 };
591 
592 #define SND_CHN_OSS_VALIDMASK						\
593 			(SND_CHN_T_MASK_FL | SND_CHN_T_MASK_FR |	\
594 			 SND_CHN_T_MASK_FC | SND_CHN_T_MASK_LF |	\
595 			 SND_CHN_T_MASK_SL | SND_CHN_T_MASK_SR |	\
596 			 SND_CHN_T_MASK_BL | SND_CHN_T_MASK_BR)
597 
598 #define SND_CHN_OSS_MAX		8
599 #define SND_CHN_OSS_BEGIN	CHID_L
600 #define SND_CHN_OSS_END		CHID_RR
601 
602 static int oss_to_snd_chn[SND_CHN_OSS_END + 1] = {
603 	[CHID_L]   = SND_CHN_T_FL,
604 	[CHID_R]   = SND_CHN_T_FR,
605 	[CHID_C]   = SND_CHN_T_FC,
606 	[CHID_LFE] = SND_CHN_T_LF,
607 	[CHID_LS]  = SND_CHN_T_SL,
608 	[CHID_RS]  = SND_CHN_T_SR,
609 	[CHID_LR]  = SND_CHN_T_BL,
610 	[CHID_RR]  = SND_CHN_T_BR
611 };
612 
613 /*
614  * Used by SNDCTL_DSP_GET_CHNORDER.
615  */
616 int
feeder_matrix_oss_get_channel_order(struct pcmchan_matrix * m,unsigned long long * map)617 feeder_matrix_oss_get_channel_order(struct pcmchan_matrix *m,
618     unsigned long long *map)
619 {
620 	unsigned long long tmpmap;
621 	uint32_t i;
622 
623 	if (m == NULL || map == NULL || (m->mask & ~SND_CHN_OSS_VALIDMASK) ||
624 	    m->channels > SND_CHN_OSS_MAX)
625 		return (EINVAL);
626 
627 	tmpmap = 0x0000000000000000ULL;
628 
629 	for (i = 0; i < SND_CHN_OSS_MAX && m->map[i].type != SND_CHN_T_MAX;
630 	    i++) {
631 		if ((1 << m->map[i].type) & ~SND_CHN_OSS_VALIDMASK)
632 			return (EINVAL);
633 		tmpmap |=
634 		    (unsigned long long)snd_chn_to_oss[m->map[i].type] <<
635 		    (i * 4);
636 	}
637 
638 	*map = tmpmap;
639 
640 	return (0);
641 }
642 
643 /*
644  * Used by SNDCTL_DSP_SET_CHNORDER.
645  */
646 int
feeder_matrix_oss_set_channel_order(struct pcmchan_matrix * m,unsigned long long * map)647 feeder_matrix_oss_set_channel_order(struct pcmchan_matrix *m,
648     unsigned long long *map)
649 {
650 	struct pcmchan_matrix tmp;
651 	uint32_t chmask, i;
652 	int ch, cheof;
653 
654 	if (m == NULL || map == NULL || (m->mask & ~SND_CHN_OSS_VALIDMASK) ||
655 	    m->channels > SND_CHN_OSS_MAX || (*map & 0xffffffff00000000ULL))
656 		return (EINVAL);
657 
658 	tmp = *m;
659 	tmp.channels = 0;
660 	tmp.ext = 0;
661 	tmp.mask = 0;
662 	memset(tmp.offset, -1, sizeof(tmp.offset));
663 	cheof = 0;
664 
665 	for (i = 0; i < SND_CHN_OSS_MAX; i++) {
666 		ch = (*map >> (i * 4)) & 0xf;
667 		if (ch < SND_CHN_OSS_BEGIN) {
668 			if (cheof == 0 && m->map[i].type != SND_CHN_T_MAX)
669 				return (EINVAL);
670 			cheof++;
671 			tmp.map[i] = m->map[i];
672 			continue;
673 		} else if (ch > SND_CHN_OSS_END)
674 			return (EINVAL);
675 		else if (cheof != 0)
676 			return (EINVAL);
677 		ch = oss_to_snd_chn[ch];
678 		chmask = 1 << ch;
679 		/* channel not exist in matrix */
680 		if (!(chmask & m->mask))
681 			return (EINVAL);
682 		/* duplicated channel */
683 		if (chmask & tmp.mask)
684 			return (EINVAL);
685 		tmp.map[i] = m->map[m->offset[ch]];
686 		if (tmp.map[i].type != ch)
687 			return (EINVAL);
688 		tmp.offset[ch] = i;
689 		tmp.mask |= chmask;
690 		tmp.channels++;
691 		if (chmask & SND_CHN_T_MASK_LF)
692 			tmp.ext++;
693 	}
694 
695 	if (tmp.channels != m->channels || tmp.ext != m->ext ||
696 	    tmp.mask != m->mask ||
697 	    tmp.map[m->channels].type != SND_CHN_T_MAX)
698 		return (EINVAL);
699 
700 	*m = tmp;
701 
702 	return (0);
703 }
704