diff options
Diffstat (limited to 'kernel/drv/oss_userdev')
-rw-r--r-- | kernel/drv/oss_userdev/.config | 2 | ||||
-rw-r--r-- | kernel/drv/oss_userdev/.devices | 1 | ||||
-rw-r--r-- | kernel/drv/oss_userdev/.name | 1 | ||||
-rw-r--r-- | kernel/drv/oss_userdev/.params | 11 | ||||
-rw-r--r-- | kernel/drv/oss_userdev/oss_userdev.c | 352 | ||||
-rw-r--r-- | kernel/drv/oss_userdev/oss_userdev.man | 37 | ||||
-rw-r--r-- | kernel/drv/oss_userdev/oss_userdev_devicepair.c | 1255 | ||||
-rw-r--r-- | kernel/drv/oss_userdev/userdev.h | 81 |
8 files changed, 1740 insertions, 0 deletions
diff --git a/kernel/drv/oss_userdev/.config b/kernel/drv/oss_userdev/.config new file mode 100644 index 0000000..828bd4f --- /dev/null +++ b/kernel/drv/oss_userdev/.config @@ -0,0 +1,2 @@ +bus=VIRTUAL +targetcpu=any diff --git a/kernel/drv/oss_userdev/.devices b/kernel/drv/oss_userdev/.devices new file mode 100644 index 0000000..41dd8b4 --- /dev/null +++ b/kernel/drv/oss_userdev/.devices @@ -0,0 +1 @@ +oss_userdev oss_userdev OSS user space audio driver I/O module diff --git a/kernel/drv/oss_userdev/.name b/kernel/drv/oss_userdev/.name new file mode 100644 index 0000000..4d501dc --- /dev/null +++ b/kernel/drv/oss_userdev/.name @@ -0,0 +1 @@ +OSS loopback audio driver diff --git a/kernel/drv/oss_userdev/.params b/kernel/drv/oss_userdev/.params new file mode 100644 index 0000000..45f9032 --- /dev/null +++ b/kernel/drv/oss_userdev/.params @@ -0,0 +1,11 @@ +int userdev_visible_clientnodes=0; +/* + * By default the oss_userdev driver will not create private device nodes + * for the client side devices. Instead all client devices will share + * the same device node (/dev/oss/oss_userdev/client). + * + * If userdev_visible_clientnodes is set to 1 then each oss_userdev instance + * will have private device node (/dev/oss/oss_userdev0/pcmN) that can be + * opened directly. This mode can be used when the oss_userdev driver is used + * for multiple purposes in the same system. + */ diff --git a/kernel/drv/oss_userdev/oss_userdev.c b/kernel/drv/oss_userdev/oss_userdev.c new file mode 100644 index 0000000..c026ff3 --- /dev/null +++ b/kernel/drv/oss_userdev/oss_userdev.c @@ -0,0 +1,352 @@ +/* + * Purpose: Kernel space support module for user land audio/mixer drivers + * + */ +/* + * + * This file is part of Open Sound System. + * + * Copyright (C) 4Front Technologies 1996-2008. + * + * This this source file is released under GPL v2 license (no other versions). + * See the COPYING file included in the main directory of this source + * distribution for the license terms and conditions. + * + */ + +#include "oss_userdev_cfg.h" +#include <oss_userdev_exports.h> +#include "userdev.h" + +oss_device_t *userdev_osdev = NULL; +static int client_dev = -1, server_dev = -1; /* Control devces */ + +char *userdev_client_devnode = "/dev/oss/oss_userdev0/client"; +char *userdev_server_devnode = "/dev/oss/oss_userdev0/server"; + +/* + * Global device lists and the mutex that protects them. + */ +oss_mutex_t userdev_global_mutex; + +/* + * The oss_userdev driver creates new device pairs on-demand. All device + * pairs that are not in use will be kept in the userdev_free_device_list + * (linked) list. If this list contains any entries then they will be + * reused whenever a new device pair is required. + */ +userdev_devc_t *userdev_free_device_list = NULL; + +/* + * Linked list for device pairs that have a server attached. These device + * pairs are available for the clients. + */ +userdev_devc_t *userdev_active_device_list = NULL; + +/*ARGSUSED*/ +static int +userdev_server_redirect (int dev, int mode, int open_flags) +{ +/* + * Purpose: This entry point is used to create new userdev instances and to redirect clients to them. + */ + int server_engine; + + + if ((server_engine=usrdev_find_free_device_pair()) >= 0) + { + userdev_devc_t *devc = audio_engines[server_engine]->devc; + + userdev_reinit_instance(devc); + return server_engine; + } + + return userdev_create_device_pair(); +} + +/*ARGSUSED*/ +static int +userdev_client_redirect (int dev, int mode, int open_flags) +{ +/* + * Purpose: This entry point is used to create new userdev instances and to redirect clients to them. + */ + + userdev_devc_t *devc; + oss_native_word flags; + + uid_t uid; + + uid = oss_get_procinfo(OSS_GET_PROCINFO_UID); + + MUTEX_ENTER_IRQDISABLE(userdev_global_mutex, flags); + devc=userdev_active_device_list; + + while (devc != NULL) + { + int ok=1; + + switch (devc->match_method) + { + case UD_MATCH_UID: + if (devc->match_key != uid) /* Wrong UID */ + ok=0; + break; + } + + if (ok) + { + MUTEX_EXIT_IRQRESTORE(userdev_global_mutex, flags); + return devc->client_portc.audio_dev; + } + + devc = devc->next_instance; + } + + MUTEX_EXIT_IRQRESTORE(userdev_global_mutex, flags); + return OSS_EIO; +} + +/* + * Dummy audio driver entrypoint functions. + * + * Functionality of the control device is handled by userdev_[client|server]_redirect(). + * The other entry points are not used for any purpose but the audio core + * framework expects to see them. + */ +/*ARGSUSED*/ +static int +userdev_control_set_rate (int dev, int arg) +{ + /* Dumy routine - Not actually used */ + return 48000; +} + +/*ARGSUSED*/ +static short +userdev_control_set_channels (int dev, short arg) +{ + /* Dumy routine - Not actually used */ + return 2; +} + +/*ARGSUSED*/ +static unsigned int +userdev_control_set_format (int dev, unsigned int arg) +{ + /* Dumy routine - Not actually used */ + return AFMT_S16_NE; +} + +static void +userdev_control_reset (int dev) +{ + /* Dumy routine - Not actually used */ +} + +/*ARGSUSED*/ +static int +userdev_control_open (int dev, int mode, int open_flags) +{ + /* Dumy routine - Not actually used */ + return OSS_EIO; +} + +/*ARGSUSED*/ +static void +userdev_control_close (int dev, int mode) +{ + /* Dumy routine - Not actually used */ +} + +/*ARGSUSED*/ +static int +userdev_control_ioctl (int dev, unsigned int cmd, ioctl_arg arg) +{ + /* Dumy routine - Not actually used */ + return OSS_EINVAL; +} + +/*ARGSUSED*/ +static void +userdev_control_output_block (int dev, oss_native_word buf, int count, int fragsize, + int intrflag) +{ + /* Dumy routine - Not actually used */ +} + +/*ARGSUSED*/ +static void +userdev_control_start_input (int dev, oss_native_word buf, int count, int fragsize, + int intrflag) +{ + /* Dumy routine - Not actually used */ +} + +/*ARGSUSED*/ +static int +userdev_control_prepare_for_input (int dev, int bsize, int bcount) +{ + /* Dumy routine - Not actually used */ + return OSS_EIO; +} + +/*ARGSUSED*/ +static int +userdev_control_prepare_for_output (int dev, int bsize, int bcount) +{ + /* Dumy routine - Not actually used */ + return OSS_EIO; +} + +static audiodrv_t userdev_server_control_driver = { + userdev_control_open, + userdev_control_close, + userdev_control_output_block, + userdev_control_start_input, + userdev_control_ioctl, + userdev_control_prepare_for_input, + userdev_control_prepare_for_output, + userdev_control_reset, + NULL, + NULL, + NULL, + NULL, + NULL, /* trigger */ + userdev_control_set_rate, + userdev_control_set_format, + userdev_control_set_channels, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + userdev_server_redirect +}; + +static audiodrv_t userdev_client_control_driver = { + userdev_control_open, + userdev_control_close, + userdev_control_output_block, + userdev_control_start_input, + userdev_control_ioctl, + userdev_control_prepare_for_input, + userdev_control_prepare_for_output, + userdev_control_reset, + NULL, + NULL, + NULL, + NULL, + NULL, /* trigger */ + userdev_control_set_rate, + userdev_control_set_format, + userdev_control_set_channels, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + userdev_client_redirect +}; + +static void +attach_control_device(void) +{ +/* + * Create the control device files that are used to create client/server + * device pairs and to redirect access to them. + */ + userdev_devc_t *devc = (userdev_devc_t*)0xdeadcafe; /* This should never get referenced */ + + if ((client_dev = oss_install_audiodev_with_devname (OSS_AUDIO_DRIVER_VERSION, + userdev_osdev, + userdev_osdev, + "User space audio device client side", + &userdev_client_control_driver, + sizeof (audiodrv_t), + ADEV_AUTOMODE, AFMT_S16_NE, devc, -1, + "client")) < 0) + { + return; + } + userdev_server_devnode = audio_engines[server_dev]->devnode; + audio_engines[client_dev]->vmix_mixer=NULL; + + if ((server_dev = oss_install_audiodev_with_devname (OSS_AUDIO_DRIVER_VERSION, + userdev_osdev, + userdev_osdev, + "User space audio device server side", + &userdev_server_control_driver, + sizeof (audiodrv_t), + ADEV_AUTOMODE, AFMT_S16_NE, devc, -1, + "server")) < 0) + { + return; + } + audio_engines[server_dev]->caps |= PCM_CAP_HIDDEN; + audio_engines[server_dev]->vmix_mixer=NULL; + userdev_client_devnode = audio_engines[client_dev]->devnode; +} + +int +oss_userdev_attach (oss_device_t * osdev) +{ + userdev_osdev = osdev; + + osdev->devc = NULL; + MUTEX_INIT (osdev, userdev_global_mutex, MH_DRV); + + oss_register_device (osdev, "User space audio driver subsystem"); + + attach_control_device(); + + return 1; +} + +int +oss_userdev_detach (oss_device_t * osdev) +{ + userdev_devc_t *devc; + + if (oss_disable_device (osdev) < 0) + return 0; + + devc = userdev_free_device_list; + + while (devc != NULL) + { + userdev_devc_t *next = devc->next_instance; + + userdev_delete_device_pair(devc); + + devc = next; + } + + oss_unregister_device (osdev); + + MUTEX_CLEANUP(userdev_global_mutex); + + return 1; +} diff --git a/kernel/drv/oss_userdev/oss_userdev.man b/kernel/drv/oss_userdev/oss_userdev.man new file mode 100644 index 0000000..cdb0d12 --- /dev/null +++ b/kernel/drv/oss_userdev/oss_userdev.man @@ -0,0 +1,37 @@ +NAME +oss_userdev - OSS client/server audio pseudo device. + +NOTICE +This audio device is not designed to be used as-is by the users. It requires +a specially designed server application that implements the actual service +(please see the OSS programming documentation for more info). The server +application will then create the audio devices that can be used to record +and/or play audio. + +DESCRIPTION +The oss_userdev driver is a special purpose loop back audio device that can be +used when implementing OSS audio devices based on a server running in the +background. + +OPTIONS + +o userdev_visible_clientnodes=0|1 +By default (0) common client device node (/dev/oss/oss_userdev0/client) will +be created for all server instances. The clients will then get directed to the +right instance based on some search criteria (for example UID). This +alternative is best when using single server application that can serve large +number of different sesions. + +If this option +is set to 1 then OSS will create separate client device nodes for each +instance. Applications will have to open the right device nodes. This +alternative is best when oss_userdev is used to create different kind of +services in one system. In this way for example a VoIP link can be accessed +by opening a different device node than when opening some other service. + +FILES +CONFIGFILEPATH/oss_userdev.conf Device configuration file. + +AUTHOR +4Front Technologies + diff --git a/kernel/drv/oss_userdev/oss_userdev_devicepair.c b/kernel/drv/oss_userdev/oss_userdev_devicepair.c new file mode 100644 index 0000000..8119cb9 --- /dev/null +++ b/kernel/drv/oss_userdev/oss_userdev_devicepair.c @@ -0,0 +1,1255 @@ +/* + * Purpose: Client/server audio device pair for oss_userdev + * + * This file implements the actual client/server device pair. There will be + * separate oss_userdev instance for each process that has opened the + * client side. + */ +/* + * + * This file is part of Open Sound System. + * + * Copyright (C) 4Front Technologies 1996-2008. + * + * This this source file is released under GPL v2 license (no other versions). + * See the COPYING file included in the main directory of this source + * distribution for the license terms and conditions. + * + */ + +#include "oss_userdev_cfg.h" +#include <oss_userdev_exports.h> +#include "userdev.h" +static void userdev_free_device_pair (userdev_devc_t *devc); + +extern int userdev_visible_clientnodes; + +static void +transfer_audio (userdev_portc_t * server_portc, dmap_t * dmap_from, + dmap_t * dmap_to) +{ + int l = dmap_from->fragment_size; + unsigned char *fromp, *top; + + if (dmap_to->fragment_size != l) + { + cmn_err (CE_WARN, "Fragment size mismatch (%d != %d)\n", + dmap_to->fragment_size, l); + + /* Perform emergency stop */ + server_portc->input_triggered = 0; + server_portc->output_triggered = 0; + server_portc->peer->input_triggered = 0; + server_portc->peer->output_triggered = 0; + return; + } + + fromp = + dmap_from->dmabuf + (dmap_from->byte_counter % dmap_from->bytes_in_use); + top = dmap_to->dmabuf + (dmap_to->byte_counter % dmap_to->bytes_in_use); + + memcpy (top, fromp, l); + +} + +static void +handle_input (userdev_portc_t * server_portc) +{ + userdev_portc_t *client_portc = server_portc->peer; + + if (client_portc->output_triggered) + { + transfer_audio (server_portc, + audio_engines[client_portc->audio_dev]->dmap_out, + audio_engines[server_portc->audio_dev]->dmap_in); + oss_audio_outputintr (client_portc->audio_dev, 0); + } + + oss_audio_inputintr (server_portc->audio_dev, 0); +} + +static void +handle_output (userdev_portc_t * server_portc) +{ + userdev_portc_t *client_portc = server_portc->peer; + + if (client_portc->input_triggered) + { + transfer_audio (server_portc, + audio_engines[server_portc->audio_dev]->dmap_out, + audio_engines[client_portc->audio_dev]->dmap_in); + oss_audio_inputintr (client_portc->audio_dev, 0); + } + + oss_audio_outputintr (server_portc->audio_dev, 0); +} + +static void +userdev_cb (void *pc) +{ +/* + * This timer callback routine will get called 100 times/second. It handles + * movement of audio data between the client and server sides. + */ + userdev_portc_t *server_portc = pc; + userdev_devc_t *devc = server_portc->devc; + int tmout = devc->poll_ticks; + + if (tmout < 1) + tmout = 1; + + devc->timeout_id = 0; /* No longer valid */ + + if (server_portc->input_triggered) + handle_input (server_portc); + + if (server_portc->output_triggered) + handle_output (server_portc); + + /* Retrigger timer callback */ + if (server_portc->input_triggered || server_portc->output_triggered) + devc->timeout_id = timeout (userdev_cb, server_portc, tmout); +} + +static int +userdev_check_input (int dev) +{ + userdev_portc_t *portc = audio_engines[dev]->portc; + if (!portc->peer->output_triggered) + { + return OSS_ECONNRESET; + } + return 0; +} + +static int +userdev_check_output (int dev) +{ + userdev_portc_t *portc = audio_engines[dev]->portc; + + if (!portc->peer->input_triggered) + { + return OSS_ECONNRESET; + } + + if (portc->peer->open_mode == 0) + return OSS_EIO; + return 0; +} + +static void +setup_sample_format (userdev_portc_t * portc) +{ + adev_t *adev; + userdev_devc_t *devc = portc->devc; + int fragsize, frame_size; + + frame_size = devc->channels * devc->fmt_bytes; + if (frame_size == 0) + frame_size = 4; + + fragsize = (devc->rate * frame_size * devc->poll_ticks) / OSS_HZ; /* Number of bytes/fragment */ + devc->rate = fragsize * 100 / frame_size; + +/* Setup the server side */ + adev = audio_engines[portc->audio_dev]; + adev->min_block = adev->max_block = fragsize; + +/* Setup the client side */ + adev = audio_engines[portc->peer->audio_dev]; + adev->min_block = adev->max_block = fragsize; + + adev->max_rate = adev->min_rate = devc->rate; + adev->iformat_mask = devc->fmt; + adev->oformat_mask = devc->fmt; + adev->xformat_mask = devc->fmt; + adev->min_channels = adev->max_channels = devc->channels; +} + +static int +userdev_server_set_rate (int dev, int arg) +{ + userdev_portc_t *portc = audio_engines[dev]->portc; + userdev_devc_t *devc = audio_engines[dev]->devc; + adev_t *client_adev = audio_engines[portc->peer->audio_dev]; + + if (arg == 0) + return devc->rate; + + if (portc->peer->input_triggered || portc->peer->output_triggered) + return devc->rate; + + if (arg < 5000) + arg = 5000; + if (arg > MAX_RATE) + arg = MAX_RATE; + + /* Force the sample rate to be multiple of 100 */ + arg = (arg / 100) * 100; + + devc->rate = arg; + + client_adev->min_rate = arg; + client_adev->max_rate = arg; + + setup_sample_format (portc); + + return devc->rate = arg; +} + +/*ARGSUSED*/ +static int +userdev_client_set_rate (int dev, int arg) +{ + userdev_devc_t *devc = audio_engines[dev]->devc; + + return devc->rate; +} + +static short +userdev_server_set_channels (int dev, short arg) +{ + userdev_portc_t *portc = audio_engines[dev]->portc; + userdev_devc_t *devc = audio_engines[dev]->devc; + adev_t *client_adev = audio_engines[portc->peer->audio_dev]; + + if (arg == 0) + return devc->channels; + + if (portc->peer->input_triggered || portc->peer->output_triggered) + return devc->channels; + + if (arg < 1) + arg = 1; + if (arg > MAX_CHANNELS) + arg = MAX_CHANNELS; + + devc->channels = arg; + client_adev->min_channels=client_adev->max_channels=arg; + + setup_sample_format (portc); + + return devc->channels; +} + +/*ARGSUSED*/ +static short +userdev_client_set_channels (int dev, short arg) +{ + userdev_devc_t *devc = audio_engines[dev]->devc; + + return devc->channels; /* Server side channels */ +} + +static unsigned int +userdev_server_set_format (int dev, unsigned int arg) +{ + userdev_devc_t *devc = audio_engines[dev]->devc; + userdev_portc_t *portc = audio_engines[dev]->portc; + adev_t *client_adev = audio_engines[portc->peer->audio_dev]; + + if (arg == 0) + return devc->fmt; + + if (portc->peer->input_triggered || portc->peer->output_triggered) + return devc->fmt; + + switch (arg) + { + case AFMT_S16_NE: + devc->fmt_bytes = 2; + break; + + case AFMT_S32_NE: + devc->fmt_bytes = 4; + break; + + default: /* Unsupported format */ + arg = AFMT_S16_NE; + devc->fmt_bytes = 2; + + } + + devc->fmt = arg; + + client_adev->oformat_mask = arg; + client_adev->iformat_mask = arg; + + setup_sample_format (portc); + + return devc->fmt; +} + +/*ARGSUSED*/ +static unsigned int +userdev_client_set_format (int dev, unsigned int arg) +{ + userdev_devc_t *devc = audio_engines[dev]->devc; + + return devc->fmt; /* Server side sample format */ +} + +static void userdev_trigger (int dev, int state); + +static void +userdev_reset (int dev) +{ + userdev_trigger (dev, 0); +} + +/*ARGSUSED*/ +static int +userdev_server_open (int dev, int mode, int open_flags) +{ + userdev_portc_t *portc = audio_engines[dev]->portc; + userdev_devc_t *devc = audio_engines[dev]->devc; + oss_native_word flags; + + if (portc == NULL || portc->peer == NULL) + return OSS_ENXIO; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + + if (portc->open_mode) + { + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + return OSS_EBUSY; + } + + portc->open_mode = mode; + + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + + devc->open_count++; + + return 0; +} + +/*ARGSUSED*/ +static int +userdev_client_open (int dev, int mode, int open_flags) +{ + userdev_portc_t *portc = audio_engines[dev]->portc; + userdev_devc_t *devc = audio_engines[dev]->devc; + oss_native_word flags; + + if (portc == NULL || portc->peer == NULL) + return OSS_ENXIO; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + + if (portc->open_mode) + { + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + return OSS_EBUSY; + } + + portc->open_mode = mode; + devc->open_count++; + + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + return 0; +} + +static void +wipe_audio_buffers(userdev_devc_t *devc) +{ +/* + * Write silence to the audio buffers when only one of the sides + * is open. This prevents the device from looping the last fragments + * written to the device. + */ + dmap_t *dmap; + + dmap = audio_engines[devc->client_portc.audio_dev]->dmap_out; + if (dmap != NULL && dmap->dmabuf != NULL) + memset(dmap->dmabuf, 0, dmap->buffsize); + + dmap = audio_engines[devc->client_portc.audio_dev]->dmap_in; + if (dmap != NULL && dmap->dmabuf != NULL) + memset(dmap->dmabuf, 0, dmap->buffsize); + + dmap = audio_engines[devc->server_portc.audio_dev]->dmap_out; + if (dmap != NULL && dmap->dmabuf != NULL) + memset(dmap->dmabuf, 0, dmap->buffsize); + + dmap = audio_engines[devc->server_portc.audio_dev]->dmap_in; + if (dmap != NULL && dmap->dmabuf != NULL) + memset(dmap->dmabuf, 0, dmap->buffsize); +} + +/*ARGSUSED*/ +static void +userdev_server_close (int dev, int mode) +{ + userdev_portc_t *portc = audio_engines[dev]->portc; + userdev_devc_t *devc = audio_engines[dev]->devc; + oss_native_word flags; + int open_count; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + portc->open_mode = 0; + + /* Stop the client side because there is no server */ + portc->peer->input_triggered = 0; + portc->peer->output_triggered = 0; + open_count = --devc->open_count; + + if (open_count == 0) + userdev_free_device_pair (devc); + else + wipe_audio_buffers(devc); + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); +} + +/*ARGSUSED*/ +static void +userdev_client_close (int dev, int mode) +{ + userdev_portc_t *portc = audio_engines[dev]->portc; + userdev_devc_t *devc = audio_engines[dev]->devc; + oss_native_word flags; + int open_count; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + portc->open_mode = 0; + + open_count = --devc->open_count; + + if (open_count == 0) + userdev_free_device_pair (devc); + else + wipe_audio_buffers(devc); + + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); +} + +/*ARGSUSED*/ +static int +userdev_ioctl (int dev, unsigned int cmd, ioctl_arg arg) +{ + switch (cmd) + { + case SNDCTL_GETLABEL: + { + /* + * Return an empty string so that this feature can be tested. + * Complete functionality is to be implemented later. + */ + oss_label_t *s = (oss_label_t *) arg; + memset (s, 0, sizeof (oss_label_t)); + return 0; + } + break; + + case SNDCTL_GETSONG: + { + /* + * Return an empty string so that this feature can be tested. + * Complete functionality is to be implemented later. + */ + oss_longname_t *s = (oss_longname_t *) arg; + memset (s, 0, sizeof (oss_longname_t)); + return 0; + } + break; + } + + return OSS_EINVAL; +} + +static void +set_adev_name(int dev, const char *name) +{ + adev_t *adev = audio_engines[dev]; + + strcpy(adev->name, name); + +#ifdef CONFIG_OSS_VMIX + if (adev->vmix_mixer != NULL) + vmix_change_devnames(adev->vmix_mixer, name); +#endif + +} + +static int +create_instance(int dev, userdev_create_t *crea) +{ + userdev_devc_t *devc = audio_engines[dev]->devc; + char tmp_name[64]; + + devc->match_method = crea->match_method; + devc->match_key = crea->match_key; + devc->create_flags = crea->flags; + + devc->poll_ticks = (crea->poll_interval * OSS_HZ) / 1000; + + if (devc->poll_ticks < 1) + devc->poll_ticks = 1; + + crea->poll_interval = (1000*devc->poll_ticks) / OSS_HZ; + + if (crea->poll_interval<1) + crea->poll_interval = 1; + + crea->name[sizeof(crea->name)-1]=0; /* Overflow protectgion */ + + sprintf(tmp_name, "%s (server)", crea->name); + tmp_name[49]=0; + set_adev_name (devc->client_portc.audio_dev, crea->name); + set_adev_name (devc->server_portc.audio_dev, tmp_name); + + strcpy(crea->devnode, audio_engines[devc->client_portc.audio_dev]->devnode); + + return 0; +} + +static int +userdev_set_control (int dev, int ctl, unsigned int cmd, int value) +{ + userdev_devc_t *devc = mixer_devs[dev]->devc; + + if (ctl < 0 || ctl >= USERDEV_MAX_MIXERS) + return OSS_EINVAL; + + if (cmd == SNDCTL_MIX_READ) + { + return devc->mixer_values[ctl]; + } + + devc->mixer_values[ctl] = value; + devc->modify_counter++; + + return 0; +} + +/*ARGSUSED*/ +static int +userdev_server_ioctl (int dev, unsigned int cmd, ioctl_arg arg) +{ + switch (cmd) + { + case USERDEV_CREATE_INSTANCE: + { + userdev_create_t *crea = (userdev_create_t *)arg; + + return create_instance(dev, crea); + } + break; + + case USERDEV_GET_CLIENTCOUNT: + { + userdev_devc_t *devc = audio_engines[dev]->devc; + + return *arg = (devc->client_portc.open_mode != 0); + } + break; + + /* + * Mixer related ioctl calls + */ + + case USERDEV_CREATE_MIXGROUP: + { + userdev_devc_t *devc = audio_engines[dev]->devc; + userdev_mixgroup_t *grp=(userdev_mixgroup_t*)arg; + int group; + + grp->name[sizeof(grp->name)-1]=0; /* Buffer overflow protection */ + if ((group=mixer_ext_create_group(devc->mixer_dev, grp->parent, grp->name))<0) + return group; + grp->num = group; + return 0; + } + break; + + case USERDEV_CREATE_MIXCTL: + { + userdev_devc_t *devc = audio_engines[dev]->devc; + userdev_mixctl_t *c=(userdev_mixctl_t*)arg; + oss_mixext *ext; + int ctl; + + c->name[sizeof(c->name)-1]=0; /* Buffer overflow protection */ + + if (c->index < 0 || c->index >= USERDEV_MAX_MIXERS) + return OSS_EINVAL; + + if ((ctl = mixer_ext_create_control (devc->mixer_dev, + c->parent, + c->index, + userdev_set_control, + c->type, + c->name, + c->maxvalue, + c->flags)) < 0) + return ctl; + + c->num = ctl; + ext = mixer_find_ext (devc->mixer_dev, ctl); + + ext->minvalue = c->offset; + ext->control_no= c->control_no; + ext->rgbcolor = c->rgbcolor; + + if (c->type == MIXT_ENUM) + { + memcpy(ext->enum_present, c->enum_present, sizeof(ext->enum_present)); + mixer_ext_set_strings (devc->mixer_dev, ctl, c->enum_choises, 0); + } + + return 0; + } + break; + + case USERDEV_GET_MIX_CHANGECOUNT: + { + userdev_devc_t *devc = audio_engines[dev]->devc; + + return *arg = devc->modify_counter; + } + break; + + case USERDEV_SET_MIXERS: + { + userdev_devc_t *devc = audio_engines[dev]->devc; + + memcpy(devc->mixer_values, arg, sizeof(devc->mixer_values)); + mixer_devs[devc->mixer_dev]->modify_counter++; +//cmn_err(CE_CONT, "Set %08x %08x\n", devc->mixer_values[0], devc->mixer_values[1]); + return 0; + } + break; + + case USERDEV_GET_MIXERS: + { + userdev_devc_t *devc = audio_engines[dev]->devc; + + memcpy(arg, devc->mixer_values, sizeof(devc->mixer_values)); + return 0; + } + break; + + } + + return userdev_ioctl(dev, cmd, arg); +} + +/*ARGSUSED*/ +static void +userdev_output_block (int dev, oss_native_word buf, int count, int fragsize, + int intrflag) +{ +} + +/*ARGSUSED*/ +static void +userdev_start_input (int dev, oss_native_word buf, int count, int fragsize, + int intrflag) +{ +} + +static void +userdev_trigger (int dev, int state) +{ + userdev_portc_t *portc = audio_engines[dev]->portc; + userdev_devc_t *devc = audio_engines[dev]->devc; + + if (portc->open_mode & OPEN_READ) /* Handle input */ + { + portc->input_triggered = !!(state & OPEN_READ); + } + + if (portc->open_mode & OPEN_WRITE) /* Handle output */ + { + portc->output_triggered = !!(state & OPEN_WRITE); + } + + if (portc->output_triggered || portc->input_triggered) /* Something is going on */ + { + int tmout = devc->poll_ticks; + + if (tmout < 1) + tmout = 1; + + if (portc->port_type != PT_SERVER) + portc = portc->peer; /* Switch to the server side */ + + if (portc->output_triggered || portc->input_triggered) /* Something is going on */ + if (devc->timeout_id == 0) + { + devc->timeout_id = timeout (userdev_cb, portc, tmout); + } + } + else + { + if (portc->port_type == PT_SERVER) + if (devc->timeout_id != 0) + { + untimeout (devc->timeout_id); + devc->timeout_id = 0; + } + } +} + +/*ARGSUSED*/ +static int +userdev_server_prepare_for_input (int dev, int bsize, int bcount) +{ + oss_native_word flags; + + userdev_portc_t *portc = audio_engines[dev]->portc; + userdev_devc_t *devc = audio_engines[dev]->devc; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + portc->input_triggered = 0; + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + + return 0; +} + +/*ARGSUSED*/ +static int +userdev_server_prepare_for_output (int dev, int bsize, int bcount) +{ + oss_native_word flags; + + userdev_portc_t *portc = audio_engines[dev]->portc; + userdev_devc_t *devc = audio_engines[dev]->devc; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + portc->output_triggered = 0; + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + + return 0; +} + +/*ARGSUSED*/ +static int +userdev_client_prepare_for_input (int dev, int bsize, int bcount) +{ + oss_native_word flags; + userdev_portc_t *portc = audio_engines[dev]->portc; + userdev_devc_t *devc = audio_engines[dev]->devc; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + portc->input_triggered = 0; + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + + return 0; +} + +/*ARGSUSED*/ +static int +userdev_client_prepare_for_output (int dev, int bsize, int bcount) +{ + oss_native_word flags; + userdev_portc_t *portc = audio_engines[dev]->portc; + userdev_devc_t *devc = audio_engines[dev]->devc; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + portc->output_triggered = 0; + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + + return 0; +} + +/*ARGSUSED*/ +static int +userdev_alloc_buffer (int dev, dmap_t * dmap, int direction) +{ +#define MY_BUFFSIZE (64*1024) + if (dmap->dmabuf != NULL) + return 0; + dmap->dmabuf_phys = 0; /* Not mmap() capable */ + dmap->dmabuf = KERNEL_MALLOC (MY_BUFFSIZE); + if (dmap->dmabuf == NULL) + return OSS_ENOSPC; + dmap->buffsize = MY_BUFFSIZE; + + return 0; +} + +/*ARGSUSED*/ +static int +userdev_free_buffer (int dev, dmap_t * dmap, int direction) +{ + if (dmap->dmabuf == NULL) + return 0; + KERNEL_FREE (dmap->dmabuf); + + dmap->dmabuf = NULL; + return 0; +} + +#if 0 +static int +userdev_get_buffer_pointer (int dev, dmap_t * dmap, int direction) +{ +} +#endif + +/*ARGSUSED*/ +static int +userdev_ioctl_override (int dev, unsigned int cmd, ioctl_arg arg) +{ +/* + * Purpose of this ioctl override function is to intercept mixer + * ioctl calls made on the client side and to hide everything + * outside the userdev instance from the application. + * + * Note that this ioctl is related with the client side audio device. However + * if /dev/mixer points to this (audio) device then all mixer acess will + * be redirected too. Also the vmix driver will redirect mixer/system ioctl + * calls to this function. + */ + int err; + userdev_devc_t *devc = audio_engines[dev]->devc; + adev_t *adev = audio_engines[devc->client_portc.audio_dev]; + + switch (cmd) + { + case SNDCTL_MIX_NRMIX: + return *arg=1; + break; + + case SNDCTL_MIX_NREXT: + *arg = devc->mixer_dev; + return OSS_EAGAIN; /* Continue with the default handler */ + break; + + case SNDCTL_SYSINFO: + { + /* + * Fake SNDCTL_SYSINFO to report just one mixer device which is + * the one associated with the client. + */ + oss_sysinfo *info = (oss_sysinfo *) arg; + int i; + + if ((err=oss_mixer_ext(dev, OSS_DEV_DSP_ENGINE, cmd, arg))<0) + return err; + + /* + * Hide all non-oss_userdev devices + */ + strcpy (info->product, "OSS (userdev)"); + info->nummixers = 1; + info->numcards = 1; + info->numaudios = 1; + for (i = 0; i < 8; i++) + info->openedaudio[i] = 0; + + return 0; + } + break; + + case SNDCTL_MIXERINFO: + { + oss_mixerinfo *info = (oss_mixerinfo *) arg; + + info->dev = devc->mixer_dev; /* Redirect to oss_userdev mixer */ + + if ((err=oss_mixer_ext(dev, OSS_DEV_DSP_ENGINE, cmd, arg))<0) + return err; + + strcpy(info->name, adev->name); + info->card_number = 0; + return 0; + } + + case SNDCTL_AUDIOINFO: + case SNDCTL_AUDIOINFO_EX: + { + oss_audioinfo *info = (oss_audioinfo *) arg; + + info->dev = devc->client_portc.audio_dev; + + cmd = SNDCTL_ENGINEINFO; + + if ((err=oss_mixer_ext(dev, OSS_DEV_DSP_ENGINE, cmd, arg))<0) + return err; + + info->card_number = 0; + return 0; + } + + case SNDCTL_CARDINFO: + { + oss_card_info *info = (oss_card_info *) arg; + + info->card = adev->card_number; /* Redirect to oss_userdev0 */ + + if ((err=oss_mixer_ext(dev, OSS_DEV_DSP_ENGINE, cmd, arg))<0) + return err; + + info->card = 0; + return 0; + } + + case SNDCTL_MIX_EXTINFO: + { + oss_mixext *ext = (oss_mixext*)arg; + + ext->dev = devc->mixer_dev; + + if ((err=oss_mixer_ext(dev, OSS_DEV_DSP_ENGINE, cmd, arg))<0) + return err; + + if (ext->type == MIXT_DEVROOT) + { + oss_mixext_root *root = (oss_mixext_root *) ext->data; + strncpy(root->name, adev->name, 48); + root->name[47]=0; + } + + return 0; + } + break; + + case SNDCTL_MIX_READ: + case SNDCTL_MIX_WRITE: + { + oss_mixer_value *ent = (oss_mixer_value*)arg; + + ent->dev = devc->mixer_dev; + + return OSS_EAGAIN; /* Redirect */ + } + break; + + default: + return OSS_EAGAIN; + } +} + +static audiodrv_t userdev_server_driver = { + userdev_server_open, + userdev_server_close, + userdev_output_block, + userdev_start_input, + userdev_server_ioctl, + userdev_server_prepare_for_input, + userdev_server_prepare_for_output, + userdev_reset, + NULL, + NULL, + NULL, + NULL, + userdev_trigger, + userdev_server_set_rate, + userdev_server_set_format, + userdev_server_set_channels, + NULL, + NULL, + userdev_check_input, + userdev_check_output, + userdev_alloc_buffer, + userdev_free_buffer, + NULL, + NULL, + NULL /* userdev_get_buffer_pointer */ +}; + +static audiodrv_t userdev_client_driver = { + userdev_client_open, + userdev_client_close, + userdev_output_block, + userdev_start_input, + userdev_ioctl, + userdev_client_prepare_for_input, + userdev_client_prepare_for_output, + userdev_reset, + NULL, + NULL, + NULL, + NULL, + userdev_trigger, + userdev_client_set_rate, + userdev_client_set_format, + userdev_client_set_channels, + NULL, + NULL, + userdev_check_input, + userdev_check_output, + userdev_alloc_buffer, + userdev_free_buffer, + NULL, + NULL, + NULL, // userdev_get_buffer_pointer + NULL, // userdev_calibrate_speed + NULL, // userdev_sync_control + NULL, // userdev_prepare_to_stop + NULL, // userdev_get_input_pointer + NULL, // userdev_get_output_pointer + NULL, // userdev_bind + NULL, // userdev_setup_fragments + NULL, // userdev_redirect + userdev_ioctl_override +}; + +static int +userdev_mixer_ioctl (int dev, int audiodev, unsigned int cmd, ioctl_arg arg) +{ + + if (cmd == SOUND_MIXER_READ_CAPS) + return *arg = SOUND_CAP_NOLEGACY; + +#if 0 + if (cmd == SOUND_MIXER_READ_DEVMASK || + cmd == SOUND_MIXER_READ_RECMASK || cmd == SOUND_MIXER_READ_RECSRC) + return *arg = 0; + + if (cmd == SOUND_MIXER_READ_VOLUME || cmd == SOUND_MIXER_READ_PCM) + return *arg = 100 | (100 << 8); + if (cmd == SOUND_MIXER_WRITE_VOLUME || cmd == SOUND_MIXER_WRITE_PCM) + return *arg = 100 | (100 << 8); +#endif + return OSS_EINVAL; +} + +static mixer_driver_t userdev_mixer_driver = { + userdev_mixer_ioctl +}; + +static int +install_server (userdev_devc_t * devc) +{ + userdev_portc_t *portc = &devc->server_portc; + int adev; + + int opts = + ADEV_STEREOONLY | ADEV_16BITONLY | ADEV_VIRTUAL | + ADEV_FIXEDRATE | ADEV_SPECIAL | ADEV_HIDDEN | ADEV_DUPLEX; + + memset (portc, 0, sizeof (*portc)); + + portc->devc = devc; + portc->port_type = PT_SERVER; + + if ((adev = oss_install_audiodev (OSS_AUDIO_DRIVER_VERSION, + devc->osdev, + devc->osdev, + "User space audio device server side", + &userdev_server_driver, + sizeof (audiodrv_t), + opts, SUPPORTED_FORMATS, devc, -1)) < 0) + { + return adev; + } + + audio_engines[adev]->portc = portc; + audio_engines[adev]->min_rate = 5000; + audio_engines[adev]->max_rate = MAX_RATE; + audio_engines[adev]->min_channels = 1; + audio_engines[adev]->max_channels = MAX_CHANNELS; + audio_engines[adev]->vmix_mixer=NULL; + strcpy(audio_engines[adev]->devnode, userdev_server_devnode); + + portc->audio_dev = adev; + + return adev; +} + +static int +null_mixer_init(int de) +{ + return 0; +} + +static void +userdev_create_mixer(userdev_devc_t * devc) +{ + if ((devc->mixer_dev = oss_install_mixer (OSS_MIXER_DRIVER_VERSION, + devc->osdev, + devc->osdev, + "OSS userdev mixer", + &userdev_mixer_driver, + sizeof (mixer_driver_t), devc)) < 0) + { + devc->mixer_dev = -1; + return; + } + mixer_ext_set_init_fn (devc->mixer_dev, null_mixer_init, USERDEV_MAX_MIXERS*2); +} + +static int +install_client (userdev_devc_t * devc) +{ + userdev_portc_t *portc = &devc->client_portc; + int adev; + + int opts = + ADEV_STEREOONLY | ADEV_16BITONLY | ADEV_VIRTUAL | ADEV_DUPLEX | + ADEV_FIXEDRATE | ADEV_SPECIAL | ADEV_LOOP; + + memset (portc, 0, sizeof (*portc)); + + userdev_create_mixer(devc); + + portc->devc = devc; + portc->port_type = PT_CLIENT; + + if (!userdev_visible_clientnodes && !(devc->create_flags & USERDEV_F_VMIX_PRIVATENODE)) + { + opts |= ADEV_HIDDEN; + } + + if ((adev = oss_install_audiodev (OSS_AUDIO_DRIVER_VERSION, + devc->osdev, + devc->osdev, + "User space audio device", + &userdev_client_driver, + sizeof (audiodrv_t), + opts, SUPPORTED_FORMATS, devc, -1)) < 0) + { + return adev; + } + + if (!userdev_visible_clientnodes) /* Invisible client device nodes */ + strcpy(audio_engines[adev]->devnode, userdev_client_devnode); + + audio_engines[adev]->portc = portc; + audio_engines[adev]->mixer_dev = devc->mixer_dev; + audio_engines[adev]->min_rate = 5000; + audio_engines[adev]->max_rate = MAX_RATE; + audio_engines[adev]->min_channels = 1; + audio_engines[adev]->max_channels = MAX_CHANNELS; + + portc->audio_dev = adev; +#ifdef CONFIG_OSS_VMIX + vmix_attach_audiodev(devc->osdev, adev, -1, 0); +#endif + + return adev; +} + +int +userdev_create_device_pair(void) +{ + int client_engine, server_engine; + userdev_devc_t *devc; + oss_native_word flags; + + if ((devc=PMALLOC(userdev_osdev, sizeof (*devc))) == NULL) + return OSS_ENOMEM; + memset(devc, 0, sizeof(*devc)); + + devc->osdev = userdev_osdev; + MUTEX_INIT (devc->osdev, devc->mutex, MH_DRV); + devc->active=1; + + devc->rate = 48000; + devc->fmt = AFMT_S16_NE; + devc->fmt_bytes = 2; + devc->channels = 2; + devc->poll_ticks = 10; + + if ((server_engine=install_server (devc)) < 0) + return server_engine; + + if ((client_engine=install_client (devc)) < 0) + return client_engine; + + devc->client_portc.peer = &devc->server_portc; + devc->server_portc.peer = &devc->client_portc; + + /* + * Insert the device to the list of available devices + */ + MUTEX_ENTER_IRQDISABLE(userdev_global_mutex, flags); + devc->next_instance = userdev_active_device_list; + userdev_active_device_list = devc; + MUTEX_EXIT_IRQRESTORE(userdev_global_mutex, flags); + + return server_engine; +} + +static void +userdev_free_device_pair (userdev_devc_t *devc) +{ + oss_native_word flags; + + set_adev_name(devc->client_portc.audio_dev, "User space audio device"); + set_adev_name(devc->server_portc.audio_dev, "User space audio device server side"); + + MUTEX_ENTER_IRQDISABLE(userdev_global_mutex, flags); + + devc->match_method = 0; + devc->match_key = 0; + + /* + * Add to the free device pair list. + */ + devc->next_instance = userdev_free_device_list; + userdev_free_device_list = devc; + + /* + * Remove the device pair from the active device list. + */ + + if (userdev_active_device_list == devc) /* First device in the list */ + { + userdev_active_device_list = userdev_active_device_list->next_instance; + } + else + { + userdev_devc_t *this = userdev_active_device_list, *prev = NULL; + + while (this != NULL) + { + if (this == devc) + { + prev->next_instance = this->next_instance; /* Remove */ + break; + } + + prev = this; + this = this->next_instance; + } + } + MUTEX_EXIT_IRQRESTORE(userdev_global_mutex, flags); +} + +void +userdev_reinit_instance(userdev_devc_t *devc) +{ + if (devc->mixer_dev < 0) + return; + + mixer_ext_rebuild_all (devc->mixer_dev, null_mixer_init, USERDEV_MAX_MIXERS*2); +} + +void +userdev_delete_device_pair(userdev_devc_t *devc) +{ + if (!devc->active) + return; + + devc->active = 0; + MUTEX_CLEANUP(devc->mutex); +} + +int +usrdev_find_free_device_pair(void) +{ + oss_native_word flags; + userdev_devc_t *devc; + + MUTEX_ENTER_IRQDISABLE(userdev_global_mutex, flags); + + if (userdev_free_device_list != NULL) + { + devc = userdev_free_device_list; + userdev_free_device_list = userdev_free_device_list->next_instance; + + devc->next_instance = userdev_active_device_list; + userdev_active_device_list = devc; + + MUTEX_EXIT_IRQRESTORE(userdev_global_mutex, flags); + return devc->server_portc.audio_dev; + } + MUTEX_EXIT_IRQRESTORE(userdev_global_mutex, flags); + + return OSS_ENXIO; +} diff --git a/kernel/drv/oss_userdev/userdev.h b/kernel/drv/oss_userdev/userdev.h new file mode 100644 index 0000000..1ed1040 --- /dev/null +++ b/kernel/drv/oss_userdev/userdev.h @@ -0,0 +1,81 @@ +/* + * Purpose: Definition file for the oss_userdev driver + * + */ +/* + * + * This file is part of Open Sound System. + * + * Copyright (C) 4Front Technologies 1996-2008. + * + * This this source file is released under GPL v2 license (no other versions). + * See the COPYING file included in the main directory of this source + * distribution for the license terms and conditions. + * + */ +#define MAX_RATE 192000 +#define MAX_CHANNELS 64 +#define SUPPORTED_FORMATS (AFMT_S16_NE|AFMT_S32_NE) + +typedef struct _userdev_devc_t userdev_devc_t; +typedef struct _userdev_portc_t userdev_portc_t; + +struct _userdev_portc_t +{ + userdev_devc_t *devc; + userdev_portc_t *peer; + int audio_dev; + int open_mode; + int port_type; +#define PT_CLIENT 1 +#define PT_SERVER 2 + + /* State variables */ + int input_triggered, output_triggered; +}; + +struct _userdev_devc_t +{ + oss_device_t *osdev; + int active; + oss_mutex_t mutex; + + int open_count; /* 0=not in use, 2=both client and server in use */ + + int create_flags; /* Flags from ioctl(USERDEV_CREATE_INSTANCE) */ + + unsigned int poll_ticks; /* Number of clock tickes (OSS_HZ) between polls. */ + + unsigned int match_method; + unsigned int match_key; + int mixer_dev; + + userdev_devc_t *next_instance; + + int rate; + int channels; + unsigned int fmt, fmt_bytes; + timeout_id_t timeout_id; + + userdev_portc_t client_portc; + userdev_portc_t server_portc; + + /* + * Mixer related fields + */ + int modify_counter; + int mixer_values[USERDEV_MAX_MIXERS]; +}; + +extern oss_device_t *userdev_osdev; +extern oss_mutex_t userdev_global_mutex; +extern userdev_devc_t *userdev_active_device_list; +extern userdev_devc_t *userdev_free_device_list; + +extern int userdev_create_device_pair(void); +extern void userdev_delete_device_pair(userdev_devc_t *devc); +extern int usrdev_find_free_device_pair(void); +extern void userdev_reinit_instance(userdev_devc_t *devc); + +extern char *userdev_client_devnode; +extern char *userdev_server_devnode; |