From ad708efd5a31ff1559f7902af68408abf1fca077 Mon Sep 17 00:00:00 2001 From: Mike Gerow Date: Sat, 16 May 2015 19:23:55 -0700 Subject: [PATCH 1/4] Add support for managing mixer knobs to snd interface --- kernel/include/mod/snd.h | 15 +++++++++++++++ modules/snd.c | 34 ++++++++++++++++++++++++++++++++++ userspace/lib/snd.h | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+) create mode 100644 userspace/lib/snd.h diff --git a/kernel/include/mod/snd.h b/kernel/include/mod/snd.h index 10aa177f..cf43c196 100644 --- a/kernel/include/mod/snd.h +++ b/kernel/include/mod/snd.h @@ -4,14 +4,29 @@ /* The format isn't really used for anything right now */ #define SND_FORMAT_L16SLE 0 /* Linear 16-bit signed little endian */ +#include #include #include +#define SND_KNOB_VENDOR 1024 + +typedef uint16_t snd_mixer_enum_t; + +typedef struct snd_knob { + char name[SND_KNOB_NAME_SIZE]; + uint32_t id; +} snd_knob_t; + typedef struct snd_device { char name[256]; /* Name of the device. */ void * device; /* Private data for the device. May be NULL. */ uint32_t playback_speed; /* Playback speed in Hz */ uint32_t playback_format; /* Playback format (SND_FORMAT_*) */ + + snd_knob_t *knobs; + uint32_t num_knobs; + int (*mixer_read)(uint32_t knob_id, uint32_t *val); + int (*mixer_write)(uint32_t knob_id, uint32_t val); } snd_device_t; /* diff --git a/modules/snd.c b/modules/snd.c index d5ff54eb..e0891805 100644 --- a/modules/snd.c +++ b/modules/snd.c @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -140,8 +141,41 @@ int snd_request_buf(snd_device_t * device, uint32_t size, uint8_t *buffer) { return size; } +static snd_device_t * snd_main_device() { + spin_lock(&_devices_lock); + foreach(node, &_devices) { + spin_unlock(&_devices_lock); + return node->value; + } + + spin_unlock(&_devices_lock); + return NULL; +} + +DEFINE_SHELL_FUNCTION(snd_full, "[debug] turn snd master to full") { + snd_main_device()->mixer_write(SND_KNOB_MASTER, UINT32_MAX); + + return 0; +} + +DEFINE_SHELL_FUNCTION(snd_half, "[debug] turn snd master to half") { + snd_main_device()->mixer_write(SND_KNOB_MASTER, UINT32_MAX / 2); + + return 0; +} + +DEFINE_SHELL_FUNCTION(snd_off, "[debug] turn snd master to lowest volume") { + snd_main_device()->mixer_write(SND_KNOB_MASTER, 0); + + return 0; +} + static int init(void) { vfs_mount("/dev/dsp", &_main_fnode); + + BIND_SHELL_FUNCTION(snd_full); + BIND_SHELL_FUNCTION(snd_half); + BIND_SHELL_FUNCTION(snd_off); return 0; } diff --git a/userspace/lib/snd.h b/userspace/lib/snd.h new file mode 100644 index 00000000..c88f7fe5 --- /dev/null +++ b/userspace/lib/snd.h @@ -0,0 +1,37 @@ +#ifndef USERSPACE_LIB_SND_H +#define USERSPACE_LIB_SND_H + +#include + +#define SND_MAX_KNOBS 256 +#define SND_KNOB_NAME_SIZE 256 + +#define SND_KNOB_MASTER 0 +#define SND_DEVICE_MAIN 0 + +typedef struct snd_knob_list { + uint32_t device; + uint32_t num; + uint32_t ids[SND_MAX_KNOBS]; +} snd_knob_list_t; + +typedef struct snd_knob_info { + uint32_t device; + uint32_t id; + char name[SND_KNOB_NAME_SIZE]; +} snd_knob_info_t; + +typedef struct snd_knob_value { + uint32_t device; + uint32_t id; + uint32_t val; +} snd_knob_value_t; + + +/* IOCTLs */ +#define SND_MIXER_GET_KNOBS 0 +#define SND_MIXER_GET_KNOB_INFO 1 +#define SND_MIXER_READ_KNOB 2 +#define SND_MIXER_WRITE_KNOB 3 + +#endif /* USERSPACE_LIB_SND_H */ From f53ea98e054d8f670fe3c7d89fc632aaddca2d90 Mon Sep 17 00:00:00 2001 From: Mike Gerow Date: Sat, 16 May 2015 19:24:36 -0700 Subject: [PATCH 2/4] Implement snd mixer interface in ac97.c --- kernel/include/mod/snd.h | 2 +- kernel/include/mod/sound.h | 1 + modules/ac97.c | 75 ++++++++++++++++++++++++++++++++ userspace/lib/{snd.h => sound.h} | 6 +-- 4 files changed, 80 insertions(+), 4 deletions(-) create mode 120000 kernel/include/mod/sound.h rename userspace/lib/{snd.h => sound.h} (86%) diff --git a/kernel/include/mod/snd.h b/kernel/include/mod/snd.h index cf43c196..5e688292 100644 --- a/kernel/include/mod/snd.h +++ b/kernel/include/mod/snd.h @@ -4,7 +4,7 @@ /* The format isn't really used for anything right now */ #define SND_FORMAT_L16SLE 0 /* Linear 16-bit signed little endian */ -#include +#include #include #include diff --git a/kernel/include/mod/sound.h b/kernel/include/mod/sound.h new file mode 120000 index 00000000..398a6ca9 --- /dev/null +++ b/kernel/include/mod/sound.h @@ -0,0 +1 @@ +../../../userspace/lib/sound.h \ No newline at end of file diff --git a/modules/ac97.c b/modules/ac97.c index 8262a249..0097d5f3 100644 --- a/modules/ac97.c +++ b/modules/ac97.c @@ -86,11 +86,34 @@ typedef struct { } ac97_device_t; static ac97_device_t _device; + +#define AC97_KNOB_PCM_OUT (SND_KNOB_VENDOR + 0) + +static snd_knob_t _knobs[] = { + { + "Master", + SND_KNOB_MASTER + }, + { + "PCM Out", + SND_KNOB_VENDOR + 0 + } +}; + +static int ac97_mixer_read(uint32_t knob_id, uint32_t *val); +static int ac97_mixer_write(uint32_t knob_id, uint32_t val); + static snd_device_t _snd = { AC97_SND_NAME, &_device, AC97_PLAYBACK_SPEED, AC97_PLAYBACK_FORMAT, + + _knobs, + N_ELEMENTS(_knobs), + + ac97_mixer_read, + ac97_mixer_write, }; /* @@ -155,6 +178,58 @@ static void irq_handler(struct regs * regs) { irq_ack(_device.irq); } +/* Currently we just assume right and left are the same */ +static int ac97_mixer_read(uint32_t knob_id, uint32_t *val) { + switch (knob_id) { + case SND_KNOB_MASTER: + /* 6 bit value */ + *val = (inports(_device.nambar + AC97_MASTER_VOLUME) & 0x3f) << (sizeof(*val) * 8 - 6); + *val = ~*val; + *val &= 0x3f << (sizeof(*val) * 8 - 6); + break; + case AC97_KNOB_PCM_OUT: + /* 5 bit value */ + *val = (inports(_device.nambar + AC97_PCM_OUT_VOLUME) & 0x1f) << (sizeof(*val) * 8 - 5); + *val = ~*val; + *val &= 0x1f << (sizeof(*val) * 8 - 5); + break; + + default: + return -1; + } + + return 0; +} + +static int ac97_mixer_write(uint32_t knob_id, uint32_t val) { + switch (knob_id) { + case SND_KNOB_MASTER: { + /* 0 is the highest volume */ + val = ~val; + /* 6 bit value */ + val >>= (sizeof(val) * 8 - 6); + uint16_t encoded = val | (val << 8); + outports(_device.nambar + AC97_MASTER_VOLUME, encoded); + break; + } + + case AC97_KNOB_PCM_OUT: { + /* 0 is the highest volume */ + val = ~val; + /* 5 bit value */ + val >>= (sizeof(val) * 8 - 5); + uint16_t encoded = val | (val << 8); + outports(_device.nambar + AC97_PCM_OUT_VOLUME, encoded); + break; + } + + default: + return -1; + } + + return 0; +} + static int init(void) { debug_print(NOTICE, "Initializing AC97"); pci_scan(&find_ac97, -1, &_device); diff --git a/userspace/lib/snd.h b/userspace/lib/sound.h similarity index 86% rename from userspace/lib/snd.h rename to userspace/lib/sound.h index c88f7fe5..0aecb821 100644 --- a/userspace/lib/snd.h +++ b/userspace/lib/sound.h @@ -1,5 +1,5 @@ -#ifndef USERSPACE_LIB_SND_H -#define USERSPACE_LIB_SND_H +#ifndef USERSPACE_LIB_SOUND_H +#define USERSPACE_LIB_SOUND_H #include @@ -34,4 +34,4 @@ typedef struct snd_knob_value { #define SND_MIXER_READ_KNOB 2 #define SND_MIXER_WRITE_KNOB 3 -#endif /* USERSPACE_LIB_SND_H */ +#endif /* USERSPACE_LIB_SOUND_H */ From 0f35a5bfe309301b661fa969eb670d89de3b8c7a Mon Sep 17 00:00:00 2001 From: Mike Gerow Date: Sun, 17 May 2015 20:29:41 -0700 Subject: [PATCH 3/4] Add ioctl interface for snd mixer --- kernel/include/mod/snd.h | 2 + modules/ac97.c | 16 +++--- modules/snd.c | 117 ++++++++++++++++++++++++++++++++++----- userspace/lib/sound.h | 21 +++---- 4 files changed, 124 insertions(+), 32 deletions(-) diff --git a/kernel/include/mod/snd.h b/kernel/include/mod/snd.h index 5e688292..11c86647 100644 --- a/kernel/include/mod/snd.h +++ b/kernel/include/mod/snd.h @@ -27,6 +27,8 @@ typedef struct snd_device { uint32_t num_knobs; int (*mixer_read)(uint32_t knob_id, uint32_t *val); int (*mixer_write)(uint32_t knob_id, uint32_t val); + + uint32_t id; } snd_device_t; /* diff --git a/modules/ac97.c b/modules/ac97.c index 0097d5f3..079a0045 100644 --- a/modules/ac97.c +++ b/modules/ac97.c @@ -104,16 +104,16 @@ static int ac97_mixer_read(uint32_t knob_id, uint32_t *val); static int ac97_mixer_write(uint32_t knob_id, uint32_t val); static snd_device_t _snd = { - AC97_SND_NAME, - &_device, - AC97_PLAYBACK_SPEED, - AC97_PLAYBACK_FORMAT, + .name = AC97_SND_NAME, + .device = &_device, + .playback_speed = AC97_PLAYBACK_SPEED, + .playback_format = AC97_PLAYBACK_FORMAT, - _knobs, - N_ELEMENTS(_knobs), + .knobs = _knobs, + .num_knobs = N_ELEMENTS(_knobs), - ac97_mixer_read, - ac97_mixer_write, + .mixer_read = ac97_mixer_read, + .mixer_write = ac97_mixer_write, }; /* diff --git a/modules/snd.c b/modules/snd.c index e0891805..aaac2b1f 100644 --- a/modules/snd.c +++ b/modules/snd.c @@ -13,6 +13,7 @@ #include +#include #include #include #include @@ -24,30 +25,43 @@ #define SND_BUF_SIZE 0x1000 -static uint32_t snd_write(fs_node_t * node, uint32_t offset, uint32_t size, uint8_t *buffer); -static int snd_ioctl(fs_node_t * node, int request, void * argp); -static void snd_open(fs_node_t * node, unsigned int flags); -static void snd_close(fs_node_t * node); +static uint32_t snd_dsp_write(fs_node_t * node, uint32_t offset, uint32_t size, uint8_t *buffer); +static int snd_dsp_ioctl(fs_node_t * node, int request, void * argp); +static void snd_dsp_open(fs_node_t * node, unsigned int flags); +static void snd_dsp_close(fs_node_t * node); + +static int snd_mixer_ioctl(fs_node_t * node, int request, void * argp); +static void snd_mixer_open(fs_node_t * node, unsigned int flags); +static void snd_mixer_close(fs_node_t * node); static uint8_t _devices_lock; static list_t _devices; -static fs_node_t _main_fnode = { +static fs_node_t _dsp_fnode = { .name = "dsp", .device = &_devices, .flags = FS_CHARDEVICE, - .ioctl = snd_ioctl, - .write = snd_write, - .open = snd_open, - .close = snd_close, + .ioctl = snd_dsp_ioctl, + .write = snd_dsp_write, + .open = snd_dsp_open, + .close = snd_dsp_close, +}; +static fs_node_t _mixer_fnode = { + .name = "mixer", + .ioctl = snd_mixer_ioctl, + .open = snd_mixer_open, + .close = snd_mixer_close, }; static uint8_t _buffers_lock; static list_t _buffers; +static uint32_t _next_device_id = SND_DEVICE_MAIN; int snd_register(snd_device_t * device) { int rv = 0; debug_print(WARNING, "[snd] _devices lock: %d", _devices_lock); spin_lock(&_devices_lock); + device->id = _next_device_id; + _next_device_id++; if (list_find(&_devices, device)) { debug_print(WARNING, "[snd] attempt to register duplicate %s", device->name); rv = -1; @@ -78,16 +92,16 @@ snd_unregister_cleanup: return rv; } -static uint32_t snd_write(fs_node_t * node, uint32_t offset, uint32_t size, uint8_t *buffer) { +static uint32_t snd_dsp_write(fs_node_t * node, uint32_t offset, uint32_t size, uint8_t *buffer) { return ring_buffer_write(node->device, size, buffer); } -static int snd_ioctl(fs_node_t * node, int request, void * argp) { +static int snd_dsp_ioctl(fs_node_t * node, int request, void * argp) { /* Potentially use this to set sample rates in the future */ return -1; } -static void snd_open(fs_node_t * node, unsigned int flags) { +static void snd_dsp_open(fs_node_t * node, unsigned int flags) { /* * XXX(gerow): A process could take the memory of the entire system by opening * too many of these... @@ -99,12 +113,86 @@ static void snd_open(fs_node_t * node, unsigned int flags) { spin_unlock(&_buffers_lock); } -static void snd_close(fs_node_t * node) { +static void snd_dsp_close(fs_node_t * node) { spin_lock(&_buffers_lock); list_delete(&_buffers, list_find(&_buffers, node->device)); spin_unlock(&_buffers_lock); } +static snd_device_t * snd_device_by_id(uint32_t device_id) { + spin_lock(&_devices_lock); + snd_device_t * out = NULL; + snd_device_t * cur = NULL; + + foreach(node, &_devices) { + cur = node->value; + if (cur->id == device_id) { + out = cur; + } + } + spin_unlock(&_devices_lock); + + return out; +} + +static int snd_mixer_ioctl(fs_node_t * node, int request, void * argp) { + switch (request) { + case SND_MIXER_GET_KNOBS: { + snd_knob_list_t * list = argp; + snd_device_t * device = snd_device_by_id(list->device); + if (!device) { + return -EINVAL; + } + list->num = device->num_knobs; + for (uint32_t i = 0; i < device->num_knobs; i++) { + list->ids[i] = device->knobs[i].id; + } + return 0; + } + case SND_MIXER_GET_KNOB_INFO: { + snd_knob_info_t * info = argp; + snd_device_t * device = snd_device_by_id(info->device); + if (!device) { + return -EINVAL; + } + for (uint32_t i = 0; i < device->num_knobs; i++) { + if (device->knobs[i].id == info->id) { + memcpy(info->name, device->knobs[i].name, sizeof(info->name)); + return 0; + } + } + return -EINVAL; + } + case SND_MIXER_READ_KNOB: { + snd_knob_value_t * value = argp; + snd_device_t * device = snd_device_by_id(value->device); + if (!device) { + return -EINVAL; + } + return device->mixer_read(value->id, &value->val); + } + case SND_MIXER_WRITE_KNOB: { + snd_knob_value_t * value = argp; + snd_device_t * device = snd_device_by_id(value->device); + if (!device) { + return -EINVAL; + } + return device->mixer_write(value->id, value->val); + } + default: { + return -EINVAL; + } + } +} + +static void snd_mixer_open(fs_node_t * node, unsigned int flags) { + return; +} + +static void snd_mixer_close(fs_node_t * node) { + return; +} + int snd_request_buf(snd_device_t * device, uint32_t size, uint8_t *buffer) { static uint8_t tmp_buf[0x100]; @@ -171,7 +259,8 @@ DEFINE_SHELL_FUNCTION(snd_off, "[debug] turn snd master to lowest volume") { } static int init(void) { - vfs_mount("/dev/dsp", &_main_fnode); + vfs_mount("/dev/dsp", &_dsp_fnode); + vfs_mount("/dev/mixer", &_mixer_fnode); BIND_SHELL_FUNCTION(snd_full); BIND_SHELL_FUNCTION(snd_half); diff --git a/userspace/lib/sound.h b/userspace/lib/sound.h index 0aecb821..ba8785ff 100644 --- a/userspace/lib/sound.h +++ b/userspace/lib/sound.h @@ -1,30 +1,31 @@ #ifndef USERSPACE_LIB_SOUND_H #define USERSPACE_LIB_SOUND_H -#include +#include #define SND_MAX_KNOBS 256 #define SND_KNOB_NAME_SIZE 256 +#define SND_KNOB_MAX_VALUE UINT32_MAX #define SND_KNOB_MASTER 0 #define SND_DEVICE_MAIN 0 typedef struct snd_knob_list { - uint32_t device; - uint32_t num; - uint32_t ids[SND_MAX_KNOBS]; + uint32_t device; /* IN */ + uint32_t num; /* OUT */ + uint32_t ids[SND_MAX_KNOBS]; /* OUT */ } snd_knob_list_t; typedef struct snd_knob_info { - uint32_t device; - uint32_t id; - char name[SND_KNOB_NAME_SIZE]; + uint32_t device; /* IN */ + uint32_t id; /* IN */ + char name[SND_KNOB_NAME_SIZE]; /* OUT */ } snd_knob_info_t; typedef struct snd_knob_value { - uint32_t device; - uint32_t id; - uint32_t val; + uint32_t device; /* IN */ + uint32_t id; /* IN */ + uint32_t val; /* OUT for SND_MIXER_READ_KNOB, IN for SND_MIXER_WRITE_KNOB */ } snd_knob_value_t; From 368d2c052594644d8a7e673c38277592a3ef7673 Mon Sep 17 00:00:00 2001 From: Mike Gerow Date: Sun, 17 May 2015 20:30:06 -0700 Subject: [PATCH 4/4] Add a command line utility for interacting with /dev/mixer --- userspace/extra/mixerctl.c | 123 +++++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 userspace/extra/mixerctl.c diff --git a/userspace/extra/mixerctl.c b/userspace/extra/mixerctl.c new file mode 100644 index 00000000..83efd379 --- /dev/null +++ b/userspace/extra/mixerctl.c @@ -0,0 +1,123 @@ +/* vim: tabstop=4 shiftwidth=4 noexpandtab + * This file is part of ToaruOS and is released under the terms + * of the NCSA / University of Illinois License - see LICENSE.md + * Copyright (C) 2015 Mike Gerow + */ + +#include +#include +#include +#include +#include +#include + +static const char usage[] = +"Usage %s [-d device_id] -l\n" +" %s [-d device_id] [-k knob_id] -r\n" +" %s [-d device_id] [-k knob_id] -w knob_value\n" +" %s -h\n" +" -d: Device id to address. Defaults to the main sound device.\n" +" -l: List the knobs on a device.\n" +" -k: Knob id to address. Defaults to the device's master knob.\n" +" -r: Perform a read on the given device's knob. Defaults to the device's\n" +" master knob.\n" +" -w: Perform a write on the given device's knob. The value should be a\n" +" float from 0.0 to 1.0.\n" +" -h: Print this help message and exit.\n"; + +int main(int argc, char * argv[]) { + uint32_t device_id = SND_DEVICE_MAIN; + uint32_t knob_id = SND_KNOB_MASTER; + uint8_t list_flag = 0; + uint8_t read_flag = 0; + uint8_t write_flag = 0; + double write_value = 0.0; + + int c; + + while ((c = getopt(argc, argv, "d:lk:rw:h")) != -1) { + switch (c) { + case 'd': + device_id = atol(optarg); + break; + case 'l': + list_flag = 1; + break; + case 'k': + knob_id = atol(optarg); + break; + case 'r': + read_flag = 1; + break; + case 'w': + write_flag = 1; + write_value = atof(optarg); + if (write_value < 0.0 || write_value > 1.0) { + fprintf(stderr, "argument -w value must be between 0.0 and 1.0\n"); + exit(EXIT_FAILURE); + } + break; + case 'h': + fprintf(stdout, usage, argv[0], argv[0], argv[0], argv[0]); + exit(EXIT_SUCCESS); + default: + fprintf(stderr, usage, argv[0], argv[0], argv[0], argv[0]); + exit(EXIT_FAILURE); + } + } + + int mixer = open("/dev/mixer", O_RDONLY); + if (mixer < 1) { + perror("open"); + exit(EXIT_FAILURE); + } + + if (list_flag) { + snd_knob_list_t list = {0}; + list.device = device_id; + if (ioctl(mixer, SND_MIXER_GET_KNOBS, &list) < 0) { + perror("ioctl"); + exit(EXIT_FAILURE); + } + for (uint32_t i = 0; i < list.num; i++) { + snd_knob_info_t info = {0}; + info.device = device_id; + info.id = list.ids[i]; + if (ioctl(mixer, SND_MIXER_GET_KNOB_INFO, &info) < 0) { + perror("ioctl"); + exit(EXIT_FAILURE); + } + fprintf(stdout, "%d: %s\n", info.id, info.name); + } + + exit(EXIT_SUCCESS); + } + + if (read_flag) { + snd_knob_value_t value = {0}; + value.device = device_id; + value.id = knob_id; + if (ioctl(mixer, SND_MIXER_READ_KNOB, &value) < 0) { + perror("ioctl"); + exit(EXIT_FAILURE); + } + double double_val = (double)value.val / SND_KNOB_MAX_VALUE; + fprintf(stdout, "%f\n", double_val); + exit(EXIT_FAILURE); + } + + if (write_flag) { + snd_knob_value_t value = {0}; + value.device = device_id; + value.id = knob_id; + value.val = (uint32_t)(write_value * SND_KNOB_MAX_VALUE); + if (ioctl(mixer, SND_MIXER_WRITE_KNOB, &value) < 0) { + perror("ioctl"); + exit(EXIT_FAILURE); + } + exit(EXIT_SUCCESS); + } + + fprintf(stderr, "No operation specified.\n"); + exit(EXIT_FAILURE); +}