1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2016 Alex Teaca <iateaca@FreeBSD.org>
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 *
28 */
29
30#include <sys/cdefs.h>
31#ifndef WITHOUT_CAPSICUM
32#include <sys/capsicum.h>
33#include <capsicum_helpers.h>
34#endif
35
36#include <stdio.h>
37#include <stdlib.h>
38#include <fcntl.h>
39#include <sys/ioctl.h>
40#include <unistd.h>
41#include <assert.h>
42#include <errno.h>
43#include <err.h>
44#include <sysexits.h>
45
46#include "audio.h"
47#include "pci_hda.h"
48
49/*
50 * Audio Player internal data structures
51 */
52
53struct audio {
54	int fd;
55	uint8_t dir;
56	uint8_t inited;
57	char dev_name[64];
58};
59
60/*
61 * Audio Player module function definitions
62 */
63
64/*
65 * audio_init - initialize an instance of audio player
66 * @dev_name - the backend sound device used to play / capture
67 * @dir - dir = 1 for write mode, dir = 0 for read mode
68 */
69struct audio *
70audio_init(const char *dev_name, uint8_t dir)
71{
72	struct audio *aud = NULL;
73#ifndef WITHOUT_CAPSICUM
74	cap_rights_t rights;
75	cap_ioctl_t cmds[] = {
76	    SNDCTL_DSP_RESET, SNDCTL_DSP_SETFMT, SNDCTL_DSP_CHANNELS,
77	    SNDCTL_DSP_SPEED,
78#ifdef DEBUG_HDA
79	    SNDCTL_DSP_GETOSPACE, SNDCTL_DSP_GETISPACE,
80#endif
81	};
82#endif
83	size_t nlen;
84
85	assert(dev_name);
86
87	aud = calloc(1, sizeof(*aud));
88	if (!aud)
89		return NULL;
90
91	nlen = strlen(dev_name);
92	if (nlen < sizeof(aud->dev_name))
93		memcpy(aud->dev_name, dev_name, nlen + 1);
94	else {
95		DPRINTF("dev_name too big");
96		free(aud);
97		return NULL;
98	}
99
100	aud->dir = dir;
101
102	aud->fd = open(aud->dev_name, aud->dir ? O_WRONLY : O_RDONLY, 0);
103	if (aud->fd == -1) {
104		DPRINTF("Failed to open dev: %s, errno: %d",
105		    aud->dev_name, errno);
106		free(aud);
107		return (NULL);
108	}
109
110#ifndef WITHOUT_CAPSICUM
111	cap_rights_init(&rights, CAP_IOCTL, CAP_READ, CAP_WRITE);
112	if (caph_rights_limit(aud->fd, &rights) == -1)
113		errx(EX_OSERR, "Unable to apply rights for sandbox");
114	if (caph_ioctls_limit(aud->fd, cmds, nitems(cmds)) == -1)
115		errx(EX_OSERR, "Unable to limit ioctl rights for sandbox");
116#endif
117
118	return aud;
119}
120
121/*
122 * audio_set_params - reset the sound device and set the audio params
123 * @aud - the audio player to be configured
124 * @params - the audio parameters to be set
125 */
126int
127audio_set_params(struct audio *aud, struct audio_params *params)
128{
129	int audio_fd;
130	int format, channels, rate;
131	int err;
132#if DEBUG_HDA == 1
133	audio_buf_info info;
134#endif
135
136	assert(aud);
137	assert(params);
138
139	if ((audio_fd = aud->fd) < 0) {
140		DPRINTF("Incorrect audio device descriptor for %s",
141		    aud->dev_name);
142		return (-1);
143	}
144
145	/* Reset the device if it was previously opened */
146	if (aud->inited) {
147		err = ioctl(audio_fd, SNDCTL_DSP_RESET, NULL);
148		if (err == -1) {
149			DPRINTF("Failed to reset fd: %d, errno: %d",
150			    aud->fd, errno);
151			return (-1);
152		}
153	} else
154		aud->inited = 1;
155
156	/* Set the Format (Bits per Sample) */
157	format = params->format;
158	err = ioctl(audio_fd, SNDCTL_DSP_SETFMT, &format);
159	if (err == -1) {
160		DPRINTF("Fail to set fmt: 0x%x errno: %d",
161		    params->format, errno);
162		return -1;
163	}
164
165	/* The device does not support the requested audio format */
166	if (format != params->format) {
167		DPRINTF("Mismatch format: 0x%x params->format: 0x%x",
168		    format, params->format);
169		return -1;
170	}
171
172	/* Set the Number of Channels */
173	channels = params->channels;
174	err = ioctl(audio_fd, SNDCTL_DSP_CHANNELS, &channels);
175	if (err == -1) {
176		DPRINTF("Fail to set channels: %d errno: %d",
177		    params->channels, errno);
178		return -1;
179	}
180
181	/* The device does not support the requested no. of channels */
182	if (channels != params->channels) {
183		DPRINTF("Mismatch channels: %d params->channels: %d",
184		    channels, params->channels);
185		return -1;
186	}
187
188	/* Set the Sample Rate / Speed */
189	rate = params->rate;
190	err = ioctl(audio_fd, SNDCTL_DSP_SPEED, &rate);
191	if (err == -1) {
192		DPRINTF("Fail to set speed: %d errno: %d",
193		    params->rate, errno);
194		return -1;
195	}
196
197	/* The device does not support the requested rate / speed */
198	if (rate != params->rate) {
199		DPRINTF("Mismatch rate: %d params->rate: %d",
200		    rate, params->rate);
201		return -1;
202	}
203
204#if DEBUG_HDA == 1
205	err = ioctl(audio_fd, aud->dir ? SNDCTL_DSP_GETOSPACE :
206	    SNDCTL_DSP_GETISPACE, &info);
207	if (err == -1) {
208		DPRINTF("Fail to get audio buf info errno: %d", errno);
209		return -1;
210	}
211	DPRINTF("fragstotal: 0x%x fragsize: 0x%x",
212	    info.fragstotal, info.fragsize);
213#endif
214	return 0;
215}
216
217/*
218 * audio_playback - plays samples to the sound device using blocking operations
219 * @aud - the audio player used to play the samples
220 * @buf - the buffer containing the samples
221 * @count - the number of bytes in buffer
222 */
223int
224audio_playback(struct audio *aud, const uint8_t *buf, size_t count)
225{
226	ssize_t len;
227	size_t total;
228	int audio_fd;
229
230	assert(aud);
231	assert(aud->dir);
232	assert(buf);
233
234	audio_fd = aud->fd;
235	assert(audio_fd != -1);
236
237	for (total = 0; total < count; total += len) {
238		len = write(audio_fd, buf + total, count - total);
239		if (len < 0) {
240			DPRINTF("Fail to write to fd: %d, errno: %d",
241			    audio_fd, errno);
242			return -1;
243		}
244	}
245
246	return 0;
247}
248
249/*
250 * audio_record - records samples from the sound device using
251 * blocking operations.
252 * @aud - the audio player used to capture the samples
253 * @buf - the buffer to receive the samples
254 * @count - the number of bytes to capture in buffer
255 * Returns -1 on error and 0 on success
256 */
257int
258audio_record(struct audio *aud, uint8_t *buf, size_t count)
259{
260	ssize_t len;
261	size_t total;
262	int audio_fd;
263
264	assert(aud);
265	assert(!aud->dir);
266	assert(buf);
267
268	audio_fd = aud->fd;
269	assert(audio_fd != -1);
270
271	for (total = 0; total < count; total += len) {
272		len = read(audio_fd, buf + total, count - total);
273		if (len < 0) {
274			DPRINTF("Fail to write to fd: %d, errno: %d",
275			    audio_fd, errno);
276			return -1;
277		}
278	}
279
280	return 0;
281}
282