Changeset View
Changeset View
Standalone View
Standalone View
sys/dev/sound/pcm/sndstat.c
Show All 25 Lines | |||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | ||||
* SUCH DAMAGE. | * SUCH DAMAGE. | ||||
*/ | */ | ||||
#ifdef HAVE_KERNEL_OPTION_HEADERS | #ifdef HAVE_KERNEL_OPTION_HEADERS | ||||
#include "opt_snd.h" | #include "opt_snd.h" | ||||
#endif | #endif | ||||
#include <sys/param.h> | |||||
#include <sys/lock.h> | |||||
#include <sys/malloc.h> | |||||
#include <sys/nv.h> | |||||
#include <sys/dnv.h> | |||||
#include <sys/sx.h> | |||||
#ifdef COMPAT_FREEBSD32 | |||||
#include <sys/sysent.h> | |||||
#endif | |||||
#include <dev/sound/pcm/sound.h> | #include <dev/sound/pcm/sound.h> | ||||
#include <dev/sound/pcm/pcm.h> | #include <dev/sound/pcm/pcm.h> | ||||
#include <dev/sound/version.h> | #include <dev/sound/version.h> | ||||
#include <sys/sx.h> | |||||
SND_DECLARE_FILE("$FreeBSD$"); | SND_DECLARE_FILE("$FreeBSD$"); | ||||
#define SS_TYPE_MODULE 0 | #define SS_TYPE_MODULE 0 | ||||
#define SS_TYPE_PCM 1 | #define SS_TYPE_PCM 1 | ||||
#define SS_TYPE_MIDI 2 | #define SS_TYPE_MIDI 2 | ||||
#define SS_TYPE_SEQUENCER 3 | #define SS_TYPE_SEQUENCER 3 | ||||
static d_open_t sndstat_open; | static d_open_t sndstat_open; | ||||
static void sndstat_close(void *); | static void sndstat_close(void *); | ||||
static d_read_t sndstat_read; | static d_read_t sndstat_read; | ||||
static d_write_t sndstat_write; | static d_write_t sndstat_write; | ||||
static d_ioctl_t sndstat_ioctl; | |||||
static struct cdevsw sndstat_cdevsw = { | static struct cdevsw sndstat_cdevsw = { | ||||
.d_version = D_VERSION, | .d_version = D_VERSION, | ||||
.d_open = sndstat_open, | .d_open = sndstat_open, | ||||
.d_read = sndstat_read, | .d_read = sndstat_read, | ||||
.d_write = sndstat_write, | .d_write = sndstat_write, | ||||
.d_ioctl = sndstat_ioctl, | |||||
.d_name = "sndstat", | .d_name = "sndstat", | ||||
.d_flags = D_TRACKCLOSE, | .d_flags = D_TRACKCLOSE, | ||||
}; | }; | ||||
struct sndstat_entry { | struct sndstat_entry { | ||||
TAILQ_ENTRY(sndstat_entry) link; | TAILQ_ENTRY(sndstat_entry) link; | ||||
device_t dev; | device_t dev; | ||||
char *str; | char *str; | ||||
sndstat_handler handler; | sndstat_handler handler; | ||||
int type, unit; | int type, unit; | ||||
}; | }; | ||||
struct sndstat_userdev { | |||||
TAILQ_ENTRY(sndstat_userdev) link; | |||||
char *nameunit; | |||||
char *devnode; | |||||
char *desc; | |||||
unsigned int pchan; | |||||
unsigned int rchan; | |||||
uint32_t pminrate; | |||||
uint32_t pmaxrate; | |||||
uint32_t rminrate; | |||||
uint32_t rmaxrate; | |||||
uint32_t pfmts; | |||||
uint32_t rfmts; | |||||
}; | |||||
struct sndstat_file { | struct sndstat_file { | ||||
TAILQ_ENTRY(sndstat_file) entry; | TAILQ_ENTRY(sndstat_file) entry; | ||||
struct sbuf sbuf; | struct sbuf sbuf; | ||||
struct sx lock; | |||||
void *devs_nvlbuf; /* (l) */ | |||||
size_t devs_nbytes; /* (l) */ | |||||
TAILQ_HEAD(, sndstat_userdev) userdev_list; /* (l) */ | |||||
int out_offset; | int out_offset; | ||||
int in_offset; | int in_offset; | ||||
int fflags; | |||||
}; | }; | ||||
static struct sx sndstat_lock; | static struct sx sndstat_lock; | ||||
static struct cdev *sndstat_dev; | static struct cdev *sndstat_dev; | ||||
#define SNDSTAT_LOCK() sx_xlock(&sndstat_lock) | #define SNDSTAT_LOCK() sx_xlock(&sndstat_lock) | ||||
#define SNDSTAT_UNLOCK() sx_xunlock(&sndstat_lock) | #define SNDSTAT_UNLOCK() sx_xunlock(&sndstat_lock) | ||||
static TAILQ_HEAD(, sndstat_entry) sndstat_devlist = TAILQ_HEAD_INITIALIZER(sndstat_devlist); | static TAILQ_HEAD(, sndstat_entry) sndstat_devlist = TAILQ_HEAD_INITIALIZER(sndstat_devlist); | ||||
static TAILQ_HEAD(, sndstat_file) sndstat_filelist = TAILQ_HEAD_INITIALIZER(sndstat_filelist); | static TAILQ_HEAD(, sndstat_file) sndstat_filelist = TAILQ_HEAD_INITIALIZER(sndstat_filelist); | ||||
int snd_verbose = 0; | int snd_verbose = 0; | ||||
static int sndstat_prepare(struct sndstat_file *); | static int sndstat_prepare(struct sndstat_file *); | ||||
static struct sndstat_userdev * | |||||
sndstat_line2userdev(struct sndstat_file *, const char *, int); | |||||
static int | static int | ||||
sysctl_hw_sndverbose(SYSCTL_HANDLER_ARGS) | sysctl_hw_sndverbose(SYSCTL_HANDLER_ARGS) | ||||
{ | { | ||||
int error, verbose; | int error, verbose; | ||||
verbose = snd_verbose; | verbose = snd_verbose; | ||||
error = sysctl_handle_int(oidp, &verbose, 0, req); | error = sysctl_handle_int(oidp, &verbose, 0, req); | ||||
Show All 12 Lines | |||||
static int | static int | ||||
sndstat_open(struct cdev *i_dev, int flags, int mode, struct thread *td) | sndstat_open(struct cdev *i_dev, int flags, int mode, struct thread *td) | ||||
{ | { | ||||
struct sndstat_file *pf; | struct sndstat_file *pf; | ||||
pf = malloc(sizeof(*pf), M_DEVBUF, M_WAITOK | M_ZERO); | pf = malloc(sizeof(*pf), M_DEVBUF, M_WAITOK | M_ZERO); | ||||
SNDSTAT_LOCK(); | |||||
if (sbuf_new(&pf->sbuf, NULL, 4096, SBUF_AUTOEXTEND) == NULL) { | if (sbuf_new(&pf->sbuf, NULL, 4096, SBUF_AUTOEXTEND) == NULL) { | ||||
SNDSTAT_UNLOCK(); | |||||
free(pf, M_DEVBUF); | free(pf, M_DEVBUF); | ||||
delphij: SNDSTAT_LOCK above was removed, and SNDSTAT_UNLOCK should be removed too here too. | |||||
return (ENOMEM); | return (ENOMEM); | ||||
} | } | ||||
pf->fflags = flags; | |||||
TAILQ_INIT(&pf->userdev_list); | |||||
Done Inline ActionsMinor nit: zero'ing here is not needed (In line 139, the allocation was M_ZERO so I'd expect these be 0/NULL already). delphij: Minor nit: zero'ing here is not needed (In line 139, the allocation was M_ZERO so I'd expect… | |||||
sx_init(&pf->lock, "sndstat_file"); | |||||
SNDSTAT_LOCK(); | |||||
TAILQ_INSERT_TAIL(&sndstat_filelist, pf, entry); | TAILQ_INSERT_TAIL(&sndstat_filelist, pf, entry); | ||||
SNDSTAT_UNLOCK(); | SNDSTAT_UNLOCK(); | ||||
devfs_set_cdevpriv(pf, &sndstat_close); | devfs_set_cdevpriv(pf, &sndstat_close); | ||||
return (0); | return (0); | ||||
} | } | ||||
/* | |||||
* Should only be called either when: | |||||
* * Closing | |||||
* * pf->lock held | |||||
*/ | |||||
static void | static void | ||||
sndstat_remove_all_userdevs(struct sndstat_file *pf) | |||||
{ | |||||
struct sndstat_userdev *ud, *tud; | |||||
Done Inline ActionsSince the code manipulates userdev_list, I think we would want to assert that pf->lock is xlocked here. delphij: Since the code manipulates userdev_list, I think we would want to assert that pf->lock is… | |||||
KASSERT( | |||||
sx_xlocked(&pf->lock), ("%s: Called without pf->lock", __func__)); | |||||
TAILQ_FOREACH_SAFE(ud, &pf->userdev_list, link, tud) { | |||||
Done Inline ActionsBecause you remove all elements here you can simply do: while ((ud = TAILQ_FIRST(&pf->userdev_list)) != NULL) { } hselasky: Because you remove all elements here you can simply do:
while ((ud = TAILQ_FIRST(&pf… | |||||
TAILQ_REMOVE(&pf->userdev_list, ud, link); | |||||
free(ud->desc, M_DEVBUF); | |||||
free(ud->devnode, M_DEVBUF); | |||||
free(ud->nameunit, M_DEVBUF); | |||||
free(ud, M_DEVBUF); | |||||
} | |||||
} | |||||
static void | |||||
sndstat_close(void *sndstat_file) | sndstat_close(void *sndstat_file) | ||||
{ | { | ||||
struct sndstat_file *pf = (struct sndstat_file *)sndstat_file; | struct sndstat_file *pf = (struct sndstat_file *)sndstat_file; | ||||
SNDSTAT_LOCK(); | SNDSTAT_LOCK(); | ||||
sbuf_delete(&pf->sbuf); | sbuf_delete(&pf->sbuf); | ||||
TAILQ_REMOVE(&sndstat_filelist, pf, entry); | TAILQ_REMOVE(&sndstat_filelist, pf, entry); | ||||
SNDSTAT_UNLOCK(); | SNDSTAT_UNLOCK(); | ||||
free(pf->devs_nvlbuf, M_NVLIST); | |||||
sx_xlock(&pf->lock); | |||||
sndstat_remove_all_userdevs(pf); | |||||
sx_xunlock(&pf->lock); | |||||
sx_destroy(&pf->lock); | |||||
Done Inline ActionsI think pf->lock should be acquired here.. delphij: I think pf->lock should be acquired here.. | |||||
free(pf, M_DEVBUF); | free(pf, M_DEVBUF); | ||||
} | } | ||||
static int | static int | ||||
sndstat_read(struct cdev *i_dev, struct uio *buf, int flag) | sndstat_read(struct cdev *i_dev, struct uio *buf, int flag) | ||||
{ | { | ||||
struct sndstat_file *pf; | struct sndstat_file *pf; | ||||
int err; | int err; | ||||
▲ Show 20 Lines • Show All 51 Lines • ▼ Show 20 Lines | if (buf->uio_resid > 65536) | ||||
return (ENOMEM); | return (ENOMEM); | ||||
SNDSTAT_LOCK(); | SNDSTAT_LOCK(); | ||||
if (pf->in_offset != 0) { | if (pf->in_offset != 0) { | ||||
/* don't allow both reading and writing */ | /* don't allow both reading and writing */ | ||||
err = EINVAL; | err = EINVAL; | ||||
} else { | } else { | ||||
/* only remember the last write - allows for updates */ | /* only remember the last write - allows for updates */ | ||||
sbuf_clear(&pf->sbuf); | sx_xlock(&pf->lock); | ||||
sndstat_remove_all_userdevs(pf); | |||||
sx_xunlock(&pf->lock); | |||||
while (1) { | while (1) { | ||||
len = sizeof(temp); | len = sizeof(temp); | ||||
if (len > buf->uio_resid) | if (len > buf->uio_resid) | ||||
len = buf->uio_resid; | len = buf->uio_resid; | ||||
if (len > 0) { | if (len > 0) { | ||||
err = uiomove(temp, len, buf); | err = uiomove(temp, len, buf); | ||||
if (err) | if (err) | ||||
break; | break; | ||||
} else { | } else { | ||||
break; | break; | ||||
} | } | ||||
if (sbuf_bcat(&pf->sbuf, temp, len) < 0) { | if (sbuf_bcat(&pf->sbuf, temp, len) < 0) { | ||||
err = ENOMEM; | err = ENOMEM; | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
sbuf_finish(&pf->sbuf); | sbuf_finish(&pf->sbuf); | ||||
if (err == 0) | |||||
if (err == 0) { | |||||
char *line, *str; | |||||
str = sbuf_data(&pf->sbuf); | |||||
while ((line = strsep(&str, "\n")) != NULL) { | |||||
struct sndstat_userdev *ud; | |||||
ud = sndstat_line2userdev(pf, line, strlen(line)); | |||||
if (ud == NULL) | |||||
continue; | |||||
sx_xlock(&pf->lock); | |||||
TAILQ_INSERT_TAIL(&pf->userdev_list, ud, link); | |||||
sx_xunlock(&pf->lock); | |||||
} | |||||
pf->out_offset = sbuf_len(&pf->sbuf); | pf->out_offset = sbuf_len(&pf->sbuf); | ||||
else | } else | ||||
pf->out_offset = 0; | pf->out_offset = 0; | ||||
sbuf_clear(&pf->sbuf); | |||||
} | } | ||||
SNDSTAT_UNLOCK(); | SNDSTAT_UNLOCK(); | ||||
return (err); | return (err); | ||||
} | } | ||||
static void | |||||
sndstat_get_caps(struct snddev_info *d, bool play, uint32_t *min_rate, | |||||
uint32_t *max_rate, uint32_t *fmts) | |||||
{ | |||||
struct pcm_channel *c; | |||||
int dir; | |||||
dir = play ? PCMDIR_PLAY : PCMDIR_REC; | |||||
*min_rate = 0; | |||||
*max_rate = 0; | |||||
*fmts = 0; | |||||
if (play && d->pvchancount > 0) { | |||||
*min_rate = *max_rate = d->pvchanrate; | |||||
*fmts = d->pvchanformat; | |||||
return; | |||||
} else if (!play && d->rvchancount > 0) { | |||||
*min_rate = *max_rate = d->rvchanrate; | |||||
*fmts = d->rvchanformat; | |||||
return; | |||||
} | |||||
CHN_FOREACH(c, d, channels.pcm) { | |||||
struct pcmchan_caps *caps; | |||||
if (c->direction != dir || (c->flags & CHN_F_VIRTUAL) != 0) | |||||
continue; | |||||
CHN_LOCK(c); | |||||
caps = chn_getcaps(c); | |||||
*min_rate = caps->minspeed; | |||||
*max_rate = caps->maxspeed; | |||||
*fmts = chn_getformats(c); | |||||
CHN_UNLOCK(c); | |||||
} | |||||
} | |||||
/* | |||||
* Should only be called with the following locks held: | |||||
* * sndstat_lock | |||||
*/ | |||||
static int | |||||
sndstat_create_devs_nvlist(nvlist_t **nvlp) | |||||
{ | |||||
int err; | |||||
nvlist_t *nvl; | |||||
struct sndstat_entry *ent; | |||||
struct sndstat_file *pf; | |||||
nvl = nvlist_create(0); | |||||
if (nvl == NULL) | |||||
return (ENOMEM); | |||||
TAILQ_FOREACH (ent, &sndstat_devlist, link) { | |||||
uint32_t maxrate, minrate, fmts; | |||||
struct snddev_info *d; | |||||
nvlist_t *di; | |||||
if (ent->dev == NULL) | |||||
continue; | |||||
d = device_get_softc(ent->dev); | |||||
if (!PCM_REGISTERED(d)) | |||||
continue; | |||||
di = nvlist_create(0); | |||||
if (di == NULL) { | |||||
err = ENOMEM; | |||||
goto done; | |||||
} | |||||
nvlist_add_bool(di, SNDSTAT_LABEL_FROM_USER, false); | |||||
nvlist_add_number(di, SNDSTAT_LABEL_UNIT, | |||||
device_get_unit( | |||||
ent->dev)); // XXX: I want signed integer here | |||||
nvlist_add_stringf(di, SNDSTAT_LABEL_NAMEUNIT, "%s", | |||||
device_get_nameunit(ent->dev)); | |||||
nvlist_add_stringf(di, SNDSTAT_LABEL_DEVNODE, "dsp%d", | |||||
device_get_unit(ent->dev)); | |||||
nvlist_add_string( | |||||
di, SNDSTAT_LABEL_DESC, device_get_desc(ent->dev)); | |||||
PCM_ACQUIRE_QUICK(d); | |||||
nvlist_add_number(di, SNDSTAT_LABEL_PCHAN, d->playcount); | |||||
nvlist_add_number(di, SNDSTAT_LABEL_RCHAN, d->reccount); | |||||
if (d->playcount > 0) { | |||||
sndstat_get_caps(d, true, &minrate, &maxrate, &fmts); | |||||
nvlist_add_number(di, SNDSTAT_LABEL_PMINRATE, minrate); | |||||
nvlist_add_number(di, SNDSTAT_LABEL_PMAXRATE, maxrate); | |||||
nvlist_add_number(di, SNDSTAT_LABEL_PFMTS, fmts); | |||||
} | |||||
if (d->reccount > 0) { | |||||
sndstat_get_caps(d, false, &minrate, &maxrate, &fmts); | |||||
nvlist_add_number(di, SNDSTAT_LABEL_RMINRATE, minrate); | |||||
nvlist_add_number(di, SNDSTAT_LABEL_RMAXRATE, maxrate); | |||||
nvlist_add_number(di, SNDSTAT_LABEL_RFMTS, fmts); | |||||
} | |||||
nvlist_add_bool( | |||||
di, SNDSTAT_LABEL_BITPERFECT, d->flags & SD_F_BITPERFECT); | |||||
nvlist_add_number(di, SNDSTAT_LABEL_PVCHAN, d->pvchancount); | |||||
nvlist_add_number(di, SNDSTAT_LABEL_RVCHAN, d->rvchancount); | |||||
PCM_RELEASE_QUICK(d); | |||||
nvlist_append_nvlist_array(nvl, SNDSTAT_LABEL_DSPS, di); | |||||
nvlist_destroy(di); | |||||
err = nvlist_error(nvl); | |||||
if (err != 0) | |||||
goto done; | |||||
} | |||||
TAILQ_FOREACH(pf, &sndstat_filelist, entry) { | |||||
nvlist_t *di; | |||||
struct sndstat_userdev *ud; | |||||
sx_xlock(&pf->lock); | |||||
TAILQ_FOREACH(ud, &pf->userdev_list, link) { | |||||
di = nvlist_create(0); | |||||
if (di == NULL) { | |||||
err = ENOMEM; | |||||
sx_xunlock(&pf->lock); | |||||
goto done; | |||||
} | |||||
nvlist_add_bool(di, SNDSTAT_LABEL_FROM_USER, true); | |||||
nvlist_add_number(di, SNDSTAT_LABEL_PCHAN, ud->pchan); | |||||
nvlist_add_number(di, SNDSTAT_LABEL_RCHAN, ud->rchan); | |||||
nvlist_add_string(di, SNDSTAT_LABEL_NAMEUNIT, ud->nameunit); | |||||
nvlist_add_string( | |||||
di, SNDSTAT_LABEL_DEVNODE, ud->devnode); | |||||
nvlist_add_string(di, SNDSTAT_LABEL_DESC, ud->desc); | |||||
if (ud->pchan != 0) { | |||||
nvlist_add_number( | |||||
di, SNDSTAT_LABEL_PMINRATE, ud->pminrate); | |||||
nvlist_add_number( | |||||
di, SNDSTAT_LABEL_PMAXRATE, ud->pmaxrate); | |||||
nvlist_add_number( | |||||
di, SNDSTAT_LABEL_PFMTS, ud->pfmts); | |||||
} | |||||
if (ud->rchan != 0) { | |||||
nvlist_add_number( | |||||
di, SNDSTAT_LABEL_RMINRATE, ud->rminrate); | |||||
nvlist_add_number( | |||||
di, SNDSTAT_LABEL_RMAXRATE, ud->rmaxrate); | |||||
nvlist_add_number( | |||||
di, SNDSTAT_LABEL_RFMTS, ud->rfmts); | |||||
} | |||||
nvlist_append_nvlist_array(nvl, SNDSTAT_LABEL_DSPS, di); | |||||
nvlist_destroy(di); | |||||
err = nvlist_error(nvl); | |||||
if (err != 0) { | |||||
sx_xunlock(&pf->lock); | |||||
goto done; | |||||
} | |||||
} | |||||
sx_xunlock(&pf->lock); | |||||
} | |||||
*nvlp = nvl; | |||||
done: | |||||
if (err != 0) | |||||
nvlist_destroy(nvl); | |||||
return (err); | |||||
} | |||||
static int | |||||
sndstat_refresh_devs(struct sndstat_file *pf) | |||||
{ | |||||
sx_xlock(&pf->lock); | |||||
free(pf->devs_nvlbuf, M_NVLIST); | |||||
pf->devs_nvlbuf = NULL; | |||||
pf->devs_nbytes = 0; | |||||
Done Inline Actionsspace after TAILQ_FOREACH hselasky: space after TAILQ_FOREACH | |||||
sx_unlock(&pf->lock); | |||||
return (0); | |||||
} | |||||
static int | |||||
sndstat_get_devs(struct sndstat_file *pf, caddr_t data) | |||||
{ | |||||
int err; | |||||
struct sndstat_nvlbuf_arg *arg = (struct sndstat_nvlbuf_arg *)data; | |||||
SNDSTAT_LOCK(); | |||||
sx_xlock(&pf->lock); | |||||
if (pf->devs_nvlbuf == NULL) { | |||||
nvlist_t *nvl; | |||||
void *nvlbuf; | |||||
size_t nbytes; | |||||
int err; | |||||
sx_xunlock(&pf->lock); | |||||
err = sndstat_create_devs_nvlist(&nvl); | |||||
if (err) { | |||||
SNDSTAT_UNLOCK(); | |||||
return (err); | |||||
} | |||||
sx_xlock(&pf->lock); | |||||
nvlbuf = nvlist_pack(nvl, &nbytes); | |||||
err = nvlist_error(nvl); | |||||
nvlist_destroy(nvl); | |||||
if (nvlbuf == NULL || err != 0) { | |||||
SNDSTAT_UNLOCK(); | |||||
sx_xunlock(&pf->lock); | |||||
if (err == 0) | |||||
return (ENOMEM); | |||||
return (err); | |||||
} | |||||
free(pf->devs_nvlbuf, M_NVLIST); | |||||
pf->devs_nvlbuf = nvlbuf; | |||||
pf->devs_nbytes = nbytes; | |||||
} | |||||
SNDSTAT_UNLOCK(); | |||||
if (!arg->nbytes) { | |||||
arg->nbytes = pf->devs_nbytes; | |||||
err = 0; | |||||
goto done; | |||||
} | |||||
if (arg->nbytes < pf->devs_nbytes) { | |||||
arg->nbytes = 0; | |||||
err = 0; | |||||
goto done; | |||||
} | |||||
err = copyout(pf->devs_nvlbuf, arg->buf, pf->devs_nbytes); | |||||
if (err) | |||||
goto done; | |||||
arg->nbytes = pf->devs_nbytes; | |||||
free(pf->devs_nvlbuf, M_NVLIST); | |||||
pf->devs_nvlbuf = NULL; | |||||
pf->devs_nbytes = 0; | |||||
done: | |||||
sx_unlock(&pf->lock); | |||||
return (err); | |||||
} | |||||
static int | |||||
sndstat_unpack_user_nvlbuf(const void *unvlbuf, size_t nbytes, nvlist_t **nvl) | |||||
{ | |||||
void *nvlbuf; | |||||
int err; | |||||
nvlbuf = malloc(nbytes, M_DEVBUF, M_WAITOK); | |||||
err = copyin(unvlbuf, nvlbuf, nbytes); | |||||
if (err != 0) { | |||||
free(nvlbuf, M_DEVBUF); | |||||
return (err); | |||||
} | |||||
*nvl = nvlist_unpack(nvlbuf, nbytes, 0); | |||||
free(nvlbuf, M_DEVBUF); | |||||
if (nvl == NULL) { | |||||
return (EINVAL); | |||||
} | |||||
return (0); | |||||
} | |||||
static bool | |||||
sndstat_dsp_nvlist_is_sane(const nvlist_t *nvlist) | |||||
{ | |||||
if (!(nvlist_exists_string(nvlist, SNDSTAT_LABEL_DEVNODE) && | |||||
nvlist_exists_string(nvlist, SNDSTAT_LABEL_DESC) && | |||||
nvlist_exists_number(nvlist, SNDSTAT_LABEL_PCHAN) && | |||||
nvlist_exists_number(nvlist, SNDSTAT_LABEL_RCHAN))) | |||||
return (false); | |||||
return (true); | |||||
} | |||||
static void | |||||
sndstat_dsp_unpack_nvlist(const nvlist_t *nvlist, struct sndstat_userdev *ud) | |||||
{ | |||||
const char *nameunit, *devnode, *desc; | |||||
unsigned int pchan, rchan; | |||||
uint32_t pminrate = 0, pmaxrate = 0; | |||||
uint32_t rminrate = 0, rmaxrate = 0; | |||||
uint32_t pfmts = 0, rfmts = 0; | |||||
devnode = nvlist_get_string(nvlist, SNDSTAT_LABEL_DEVNODE); | |||||
if (nvlist_exists_string(nvlist, SNDSTAT_LABEL_NAMEUNIT)) | |||||
nameunit = nvlist_get_string(nvlist, SNDSTAT_LABEL_NAMEUNIT); | |||||
else | |||||
nameunit = devnode; | |||||
desc = nvlist_get_string(nvlist, SNDSTAT_LABEL_DESC); | |||||
pchan = nvlist_get_number(nvlist, SNDSTAT_LABEL_PCHAN); | |||||
rchan = nvlist_get_number(nvlist, SNDSTAT_LABEL_RCHAN); | |||||
if (pchan != 0) { | |||||
pminrate = dnvlist_get_number( | |||||
nvlist, SNDSTAT_LABEL_PMINRATE, 48000); | |||||
pmaxrate = dnvlist_get_number( | |||||
nvlist, SNDSTAT_LABEL_PMAXRATE, 48000); | |||||
pfmts = dnvlist_get_number(nvlist, SNDSTAT_LABEL_PFMTS, | |||||
AFMT_ENCODING(AFMT_S16_LE)); | |||||
} | |||||
if (rchan != 0) { | |||||
rminrate = dnvlist_get_number( | |||||
nvlist, SNDSTAT_LABEL_RMINRATE, 48000); | |||||
rmaxrate = dnvlist_get_number( | |||||
nvlist, SNDSTAT_LABEL_RMAXRATE, 48000); | |||||
rfmts = dnvlist_get_number(nvlist, SNDSTAT_LABEL_RFMTS, | |||||
AFMT_ENCODING(AFMT_S16_LE)); | |||||
} | |||||
ud->devnode = strdup(devnode, M_DEVBUF); | |||||
ud->nameunit = strdup(nameunit, M_DEVBUF); | |||||
ud->desc = strdup(desc, M_DEVBUF); | |||||
ud->pchan = pchan; | |||||
ud->rchan = rchan; | |||||
ud->pminrate = pminrate; | |||||
ud->pmaxrate = pmaxrate; | |||||
ud->rminrate = rminrate; | |||||
ud->rmaxrate = rmaxrate; | |||||
ud->pfmts = pfmts; | |||||
ud->rfmts = rfmts; | |||||
} | |||||
static int | |||||
sndstat_add_user_devs(struct sndstat_file *pf, caddr_t data) | |||||
{ | |||||
int err; | |||||
nvlist_t *nvl = NULL; | |||||
const nvlist_t * const *dsps; | |||||
size_t i, ndsps; | |||||
struct sndstat_nvlbuf_arg *arg = (struct sndstat_nvlbuf_arg *)data; | |||||
if ((pf->fflags & FWRITE) == 0) { | |||||
err = EPERM; | |||||
goto done; | |||||
} | |||||
err = sndstat_unpack_user_nvlbuf(arg->buf, arg->nbytes, &nvl); | |||||
if (err != 0) | |||||
goto done; | |||||
if (!nvlist_exists_nvlist_array(nvl, SNDSTAT_LABEL_DSPS)) { | |||||
err = EINVAL; | |||||
goto done; | |||||
} | |||||
dsps = nvlist_get_nvlist_array(nvl, SNDSTAT_LABEL_DSPS, &ndsps); | |||||
for (i = 0; i < ndsps; i++) { | |||||
if (!sndstat_dsp_nvlist_is_sane(dsps[i])) { | |||||
err = EINVAL; | |||||
goto done; | |||||
} | |||||
} | |||||
sx_xlock(&pf->lock); | |||||
for (i = 0; i < ndsps; i++) { | |||||
struct sndstat_userdev *ud = | |||||
malloc(sizeof(*ud), M_DEVBUF, M_WAITOK); | |||||
sndstat_dsp_unpack_nvlist(dsps[i], ud); | |||||
TAILQ_INSERT_TAIL(&pf->userdev_list, ud, link); | |||||
} | |||||
sx_unlock(&pf->lock); | |||||
done: | |||||
nvlist_destroy(nvl); | |||||
return (err); | |||||
} | |||||
static int | |||||
sndstat_flush_user_devs(struct sndstat_file *pf) | |||||
{ | |||||
if ((pf->fflags & FWRITE) == 0) | |||||
return (EPERM); | |||||
sx_xlock(&pf->lock); | |||||
sndstat_remove_all_userdevs(pf); | |||||
sx_xunlock(&pf->lock); | |||||
return (0); | |||||
} | |||||
#ifdef COMPAT_FREEBSD32 | |||||
static int | |||||
compat_sndstat_get_devs32(struct sndstat_file *pf, caddr_t data) | |||||
{ | |||||
struct sndstat_nvlbuf_arg32 *arg32 = (struct sndstat_nvlbuf_arg32 *)data; | |||||
struct sndstat_nvlbuf_arg arg; | |||||
int err; | |||||
arg.buf = (void *)(uintptr_t)arg32->buf; | |||||
arg.nbytes = arg32->nbytes; | |||||
err = sndstat_get_devs(pf, (caddr_t)&arg); | |||||
if (err == 0) { | |||||
arg32->buf = (uint32_t)(uintptr_t)arg.buf; | |||||
arg32->nbytes = arg.nbytes; | |||||
} | |||||
return (err); | |||||
} | |||||
static int | |||||
compat_sndstat_add_user_devs32(struct sndstat_file *pf, caddr_t data) | |||||
{ | |||||
struct sndstat_nvlbuf_arg32 *arg32 = (struct sndstat_nvlbuf_arg32 *)data; | |||||
struct sndstat_nvlbuf_arg arg; | |||||
int err; | |||||
arg.buf = (void *)(uintptr_t)arg32->buf; | |||||
arg.nbytes = arg32->nbytes; | |||||
err = sndstat_add_user_devs(pf, (caddr_t)&arg); | |||||
if (err == 0) { | |||||
arg32->buf = (uint32_t)(uintptr_t)arg.buf; | |||||
arg32->nbytes = arg.nbytes; | |||||
} | |||||
return (err); | |||||
} | |||||
#endif | |||||
static int | |||||
sndstat_ioctl( | |||||
struct cdev *dev, u_long cmd, caddr_t data, int fflag, struct thread *td) | |||||
{ | |||||
int err; | |||||
struct sndstat_file *pf; | |||||
err = devfs_get_cdevpriv((void **)&pf); | |||||
if (err != 0) | |||||
return (err); | |||||
switch (cmd) { | |||||
case SNDSTAT_GET_DEVS: | |||||
err = sndstat_get_devs(pf, data); | |||||
break; | |||||
#ifdef COMPAT_FREEBSD32 | |||||
case SNDSTAT_GET_DEVS32: | |||||
if (!SV_CURPROC_FLAG(SV_ILP32)) { | |||||
err = ENODEV; | |||||
break; | |||||
} | |||||
err = compat_sndstat_get_devs32(pf, data); | |||||
break; | |||||
#endif | |||||
case SNDSTAT_ADD_USER_DEVS: | |||||
err = sndstat_add_user_devs(pf, data); | |||||
break; | |||||
#ifdef COMPAT_FREEBSD32 | |||||
case SNDSTAT_ADD_USER_DEVS32: | |||||
if (!SV_CURPROC_FLAG(SV_ILP32)) { | |||||
err = ENODEV; | |||||
break; | |||||
} | |||||
err = compat_sndstat_add_user_devs32(pf, data); | |||||
break; | |||||
#endif | |||||
case SNDSTAT_REFRESH_DEVS: | |||||
err = sndstat_refresh_devs(pf); | |||||
break; | |||||
case SNDSTAT_FLUSH_USER_DEVS: | |||||
err = sndstat_flush_user_devs(pf); | |||||
break; | |||||
default: | |||||
err = ENODEV; | |||||
} | |||||
return (err); | |||||
} | |||||
static struct sndstat_userdev * | |||||
sndstat_line2userdev(struct sndstat_file *pf, const char *line, int n) | |||||
{ | |||||
struct sndstat_userdev *ud; | |||||
const char *e, *m; | |||||
ud = malloc(sizeof(*ud), M_DEVBUF, M_WAITOK|M_ZERO); | |||||
e = strchr(line, ':'); | |||||
if (e == NULL) | |||||
goto fail; | |||||
ud->nameunit = strndup(line, e - line, M_DEVBUF); | |||||
ud->devnode = (char *)malloc(e - line + 1, M_DEVBUF, M_WAITOK | M_ZERO); | |||||
strlcat(ud->devnode, ud->nameunit, e - line + 1); | |||||
line = e + 1; | |||||
e = strchr(line, '<'); | |||||
if (e == NULL) | |||||
goto fail; | |||||
line = e + 1; | |||||
e = strrchr(line, '>'); | |||||
if (e == NULL) | |||||
goto fail; | |||||
ud->desc = strndup(line, e - line, M_DEVBUF); | |||||
line = e + 1; | |||||
e = strchr(line, '('); | |||||
if (e == NULL) | |||||
goto fail; | |||||
line = e + 1; | |||||
e = strrchr(line, ')'); | |||||
if (e == NULL) | |||||
goto fail; | |||||
m = strstr(line, "play"); | |||||
if (m != NULL && m < e) | |||||
ud->pchan = 1; | |||||
m = strstr(line, "rec"); | |||||
if (m != NULL && m < e) | |||||
ud->rchan = 1; | |||||
return (ud); | |||||
fail: | |||||
free(ud->nameunit, M_DEVBUF); | |||||
free(ud->devnode, M_DEVBUF); | |||||
free(ud->desc, M_DEVBUF); | |||||
free(ud, M_DEVBUF); | |||||
return (NULL); | |||||
} | |||||
/************************************************************************/ | /************************************************************************/ | ||||
int | int | ||||
sndstat_register(device_t dev, char *str, sndstat_handler handler) | sndstat_register(device_t dev, char *str, sndstat_handler handler) | ||||
{ | { | ||||
struct sndstat_entry *ent; | struct sndstat_entry *ent; | ||||
struct sndstat_entry *pre; | struct sndstat_entry *pre; | ||||
const char *devtype; | const char *devtype; | ||||
▲ Show 20 Lines • Show All 133 Lines • ▼ Show 20 Lines | TAILQ_FOREACH(ent, &sndstat_devlist, link) { | ||||
sbuf_printf(s, "\n"); | sbuf_printf(s, "\n"); | ||||
} | } | ||||
if (k == 0) | if (k == 0) | ||||
sbuf_printf(s, "No devices installed.\n"); | sbuf_printf(s, "No devices installed.\n"); | ||||
/* append any input from userspace */ | /* append any input from userspace */ | ||||
k = 0; | k = 0; | ||||
TAILQ_FOREACH(pf, &sndstat_filelist, entry) { | TAILQ_FOREACH(pf, &sndstat_filelist, entry) { | ||||
struct sndstat_userdev *ud; | |||||
if (pf == pf_self) | if (pf == pf_self) | ||||
continue; | continue; | ||||
if (pf->out_offset == 0) | sx_xlock(&pf->lock); | ||||
if (TAILQ_EMPTY(&pf->userdev_list)) { | |||||
sx_unlock(&pf->lock); | |||||
continue; | continue; | ||||
} | |||||
if (!k++) | if (!k++) | ||||
sbuf_printf(s, "Installed devices from userspace:\n"); | sbuf_printf(s, "Installed devices from userspace:\n"); | ||||
sbuf_bcat(s, sbuf_data(&pf->sbuf), | TAILQ_FOREACH(ud, &pf->userdev_list, link) { | ||||
sbuf_len(&pf->sbuf)); | const char *caps = (ud->pchan && ud->rchan) ? | ||||
"play/rec" : | |||||
(ud->pchan ? "play" : (ud->rchan ? "rec" : "")); | |||||
sbuf_printf(s, "%s: <%s>", ud->nameunit, ud->desc); | |||||
sbuf_printf(s, " (%s)", caps); | |||||
sbuf_printf(s, "\n"); | |||||
} | |||||
sx_unlock(&pf->lock); | |||||
} | } | ||||
if (k == 0) | if (k == 0) | ||||
sbuf_printf(s, "No devices installed from userspace.\n"); | sbuf_printf(s, "No devices installed from userspace.\n"); | ||||
/* append any file versions */ | /* append any file versions */ | ||||
if (snd_verbose >= 3) { | if (snd_verbose >= 3) { | ||||
k = 0; | k = 0; | ||||
TAILQ_FOREACH(ent, &sndstat_devlist, link) { | TAILQ_FOREACH(ent, &sndstat_devlist, link) { | ||||
Show All 32 Lines |
SNDSTAT_LOCK above was removed, and SNDSTAT_UNLOCK should be removed too here too.