From: Ralf Jung Date: Thu, 13 Sep 2012 16:16:04 +0000 (+0200) Subject: Imported Upstream version 1.3.2 X-Git-Tag: upstream/1.3.2^0 X-Git-Url: https://git.ralfj.de/osspd.git/commitdiff_plain/9a35a4d0e6dff840a50f824a19cb6542046ecbb4 Imported Upstream version 1.3.2 --- 9a35a4d0e6dff840a50f824a19cb6542046ecbb4 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b578114 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*.[oa] +*~ +ossp-alsap +ossp-padsp +osspd +osstest diff --git a/98-osscuse.rules b/98-osscuse.rules new file mode 100644 index 0000000..c1430fd --- /dev/null +++ b/98-osscuse.rules @@ -0,0 +1,7 @@ +# Since these devices are not part of 'sound' subsystem the group is forced +# to audio by name +# /dev/cuse can stay mode 0660 root:root since osspd is run as root +# and drops privileges to user level when opened by user +KERNEL=="dsp", GROUP="audio" +KERNEL=="mixer", GROUP="audio" +KERNEL=="adsp", GROUP="audio" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d511905 --- /dev/null +++ b/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8111c9b --- /dev/null +++ b/Makefile @@ -0,0 +1,68 @@ +# These can be overridden if needed +# DESTDIR is completely respected +CC := gcc +AR := ar +CFLAGS := -Wall $(CFLAGS) +XLDFLAGS := $(LDFLAGS) +LDFLAGS := -L. -lossp $(LDFLAGS) +prefix := /usr/local +DESTDIR := +UDEVDIR := /etc/udev/rules.d + +ifeq "$(origin OSSPD_CFLAGS)" "undefined" +OSSPD_CFLAGS := $(shell pkg-config --cflags fuse) +endif + +ifeq "$(origin OSSPD_LDFLAGS)" "undefined" +OSSPD_LDFLAGS := $(shell pkg-config --libs fuse) +endif + +ifeq "$(origin OSSP_PADSP_CFLAGS)" "undefined" +OSSP_PADSP_CFLAGS := $(shell pkg-config --cflags libpulse) +endif + +ifeq "$(origin OSSP_PADSP_LDFLAGS)" "undefined" +OSSP_PADSP_LDFLAGS := $(shell pkg-config --libs libpulse) +endif + +ifeq "$(origin OSSP_ALSAP_CFLAGS)" "undefined" +OSSP_ALSAP_CFLAGS := $(shell pkg-config --libs alsa) +endif + +ifeq "$(origin OSSP_ALSAP_LDFLAGS)" "undefined" +OSSP_ALSAP_LDFLAGS := $(shell pkg-config --libs alsa) +endif + +headers := ossp.h ossp-util.h ossp-slave.h + +all: osspd ossp-padsp ossp-alsap + +install: + mkdir -p $(DESTDIR)$(prefix)/sbin + install -m755 osspd ossp-padsp ossp-alsap $(DESTDIR)$(prefix)/sbin + mkdir -p $(DESTDIR)$(UDEVDIR) + install -m644 98-osscuse.rules $(DESTDIR)$(UDEVDIR) + +libossp.a: ossp.c ossp.h ossp-util.c ossp-util.h ossp-slave.c ossp-slave.h + $(CC) $(CFLAGS) -c -o ossp.o ossp.c + $(CC) $(CFLAGS) -c -o ossp-util.o ossp-util.c + $(CC) $(CFLAGS) -c -o ossp-slave.o ossp-slave.c + $(AR) rc $@ ossp.o ossp-util.o ossp-slave.o + +osspd: osspd.c libossp.a $(headers) + $(CC) $(CFLAGS) $(OSSPD_CFLAGS) -o $@ $< $(OSSPD_LDFLAGS) $(LDFLAGS) + +ossp-padsp: ossp-padsp.c libossp.a $(headers) + $(CC) $(CFLAGS) $(OSSP_PADSP_CFLAGS) -o $@ $< $(OSSP_PADSP_LDFLAGS) $(LDFLAGS) + +ossp-alsap: ossp-alsap.c libossp.a $(headers) + $(CC) $(CFLAGS) $(OSSP_ALSAP_CFLAGS) -o $@ $< $(OSSP_ALSAP_LDFLAGS) $(LDFLAGS) + +osstest: osstest.c + $(CC) $(CFLAGS) -o $@ $< $(XLDFLAGS) + +test: osstest + @./osstest + +clean: + rm -f *.o *.a osspd ossp-padsp ossp-alsap osstest diff --git a/README b/README new file mode 100644 index 0000000..6b716c7 --- /dev/null +++ b/README @@ -0,0 +1,119 @@ + + OSS Proxy - emulate OSS device using CUSE + + Copyright (C) 2008-2009 SUSE Linux Products GmbH + Copyright (C) 2008-2009 Tejun Heo + +1. What is it? +-------------- + +Well, first, OSS refers to Open Sound System. If it still doesn't +ring a bell, think /dev/dsp, /dev/adsp and /dev/mixer. + +Currently, Linux supports two audio programming interface - ALSA and +OSS. The latter one is deprecated and has been that way for a long +time but there still are applications which still use them including +UML (usermode Linux) host sound support. + +ALSA contains OSS emulation but sadly the emulation is behind +multiplexing layer (which is in userland) which means that if your +sound card doesn't support multiple audio streams, only either one of +ALSA or OSS interface would be usable at any given moment. + +There have been also attempts to emulate OSS in userland using dynamic +library preloading - aoss and more recently padsp. This works for +many applications but it's just not easy to emulate everything using +the technique. Things like polling, signals, forking, privilege +changes make it very difficult to emulate things reliably. + +OSS Proxy uses CUSE (extension of FUSE allowing character devices to +be implemented in userspace) to implement OSS interface - /dev/dsp, +/dev/adsp and /dev/mixer. From the POV of the applications, these +devices are proper character devices and behave exactly the same way +so it can be made quite versatile. + + +2. Hmmm... So, how does the whole thing work? +--------------------------------------------- + +The OSS Proxy daemon - osspd - should be started first. Note that +osspd will fail to start if sound device number regions are already +occupied. You'll need to turn off OSS or its emulation[1]. + +On startup, osspd creates /dev/dsp, /dev/adsp and /dev/mixer using +CUSE. When an application access one of the devices, all IOs are +redirected to osspd via CUSE. Upon receiving a new DSP open request, +osspd creates a slave process which drops the root privilege and +assumes the opening process's credentials. After handshaking, osspd +forwards all relevant IOs to the slave which is responsible for +actually playing the sound. + +Currently there's only one slave implemented - ossp-padsp, which as +the name suggests forwards (again) the sound to pulseaudio. To sum +up, the whole pipe looks like the following. + + App <-> /dev/dsp <-> CUSE <-> osspd <-> ossp-padsp <-> pulseaudio + +Which is a lot of forwarding, but on modern machines, it won't be too +noticeable. + + +3. What works? +-------------- + +Well, MIDI part isn't implemented and I doubt it will be in any near +future but except that everything should work. Playing, recording, +5.1ch, A-V syncing, all should work. If not, it's a bug, so please +report. + +The mixer behaves a bit differently tho. In the original OSS, +/dev/mixer is the hardware mixer, so adjusting volumes there affects +all audio streams. When using ossp, each process group gets its own +mixer and the mixer always contains only two knobs - PCM and IGAIN. +Combined with per-stream volume control of pulseaudio, this scheme +works quite well for applications with embedded volume control +although it makes standalone OSS mixer programs virtually useless[2]. + + +4. How do I use it? +------------------- + +First you need CUSE support in kernel which might land on 2.6.28 with +sufficient luck[3] and then you also need libfuse which supports +CUSE[4]. Once you have both, it should be easy. First build it by +running `make'. You can set OSSPD_CFLAGS, OSSPD_LDFLAGS, +OSSP_PADSP_CFLAGS and OSSP_PADSP_LDFLAGS if you have stuff at +non-default locations. + +After build completes, there will be two executables - `osspd' and +`ossp-padsp'. Just copy them to where other system executables live. +Specific location doesn't matter as long as both files end up in the +same directory. + +Execute `osspd'. It will create the device files and you're all set. +`osspd' uses syslog with LOG_DAEMON facility, so if something doesn't +work take a look at what osspd complains about. + + +[1] As of this writing, turning on any sound support makes the + soundcore module claim OSS device regions. Patch to make it claim + OSS device regions only when OSS support or emulation is enabled + is scheduled for 2.6.28. Even with the patch, soundcore will + claim OSS device regions if OSS support or ALSA OSS emulation is + enabled. Make sure they're turned off. + +[2] If you have a strong reason to use standalone OSS mixer program, + you can play some shell tricks to put it into the same process + group as the target audio application. e.g. To use aumix with + mpg123 - `(mpg123 asdf.mp3 > /dev/null 2>&1 & aumix)', but + seriously, just use PA or ALSA one. + +[3] For the time being, here's the git tree with all the necessary + changes. This tree is base on top of 2.6.27-rc3. + + http://git.kernel.org/?p=linux/kernel/git/tj/misc.git;a=shortlog;h=cuse + git://git.kernel.org/pub/scm/linux/kernel/git/tj/misc.git cuse + +[4] And libfuse with the modifications can be found at... + + http://userweb.kernel.org/~tj/ossp/fuse-cuse.tar.gz diff --git a/ossp-alsap.c b/ossp-alsap.c new file mode 100644 index 0000000..72f3bd5 --- /dev/null +++ b/ossp-alsap.c @@ -0,0 +1,592 @@ +/* + * ossp-alsap - ossp DSP slave which forwards to alsa + * + * Copyright (C) 2009 Maarten Lankhorst + * + * This file is released under the GPLv2. + * + * Why an alsa plugin as well? Just to show how much + * the alsa userspace api sucks ;-) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "ossp-slave.h" + +enum { + AFMT_FLOAT = 0x00004000, + AFMT_S32_LE = 0x00001000, + AFMT_S32_BE = 0x00002000, +}; + +static size_t page_size; + +/* alsa structures */ +static snd_pcm_t *pcm[2]; +static snd_pcm_hw_params_t *hw_params; +static snd_pcm_sw_params_t *sw_params; +static int block; + +static unsigned int byte_counter[2]; +static snd_pcm_uframes_t mmap_pos[2]; +static int stream_corked[2]; +static int stream_notify; + +static struct format { + snd_pcm_format_t format; + snd_pcm_sframes_t rate; + int channels; +} hw_format = { SND_PCM_FORMAT_U8, 8000, 1 }; + +#if 0 +/* future mmap stuff */ +static size_t mmap_raw_size, mmap_size; +static int mmap_fd[2] = { -1, -1 }; +static void *mmap_map[2]; +static uint64_t mmap_idx[2]; /* mmap pointer */ +static uint64_t mmap_last_idx[2]; /* last idx for get_ptr */ +static struct ring_buf mmap_stg[2]; /* staging ring buffer */ +static size_t mmap_lead[2]; /* lead bytes */ +static int mmap_sync[2]; /* sync with backend stream */ +#endif + +static snd_pcm_format_t fmt_oss_to_alsa(int fmt) +{ + switch (fmt) { + case AFMT_U8: return SND_PCM_FORMAT_U8; + case AFMT_A_LAW: return SND_PCM_FORMAT_A_LAW; + case AFMT_MU_LAW: return SND_PCM_FORMAT_MU_LAW; + case AFMT_S16_LE: return SND_PCM_FORMAT_S16_LE; + case AFMT_S16_BE: return SND_PCM_FORMAT_S16_BE; + case AFMT_FLOAT: return SND_PCM_FORMAT_FLOAT; + case AFMT_S32_LE: return SND_PCM_FORMAT_S32_LE; + case AFMT_S32_BE: return SND_PCM_FORMAT_S32_BE; + default: return SND_PCM_FORMAT_U8; + } +} + +static int fmt_alsa_to_oss(snd_pcm_format_t fmt) +{ + switch (fmt) { + case SND_PCM_FORMAT_U8: return AFMT_U8; + case SND_PCM_FORMAT_A_LAW: return AFMT_A_LAW; + case SND_PCM_FORMAT_MU_LAW: return AFMT_MU_LAW; + case SND_PCM_FORMAT_S16_LE: return AFMT_S16_LE; + case SND_PCM_FORMAT_S16_BE: return AFMT_S16_BE; + case SND_PCM_FORMAT_FLOAT: return AFMT_FLOAT; + case SND_PCM_FORMAT_S32_LE: return AFMT_S32_LE; + case SND_PCM_FORMAT_S32_BE: return AFMT_S32_BE; + default: return AFMT_U8; + } +} + +static void flush_streams(int drain) +{ + /* FIXME: snd_pcm_drain appears to be able to deadlock, + * always drop or check state? */ + if (drain) { + if (pcm[PLAY]) + snd_pcm_drain(pcm[PLAY]); + if (pcm[REC]) + snd_pcm_drain(pcm[REC]); + } else { + if (pcm[PLAY]) + snd_pcm_drop(pcm[PLAY]); + if (pcm[REC]) + snd_pcm_drop(pcm[REC]); + } + + /* XXX: Really needed? */ +#if 0 + if (pcm[PLAY]) { + snd_pcm_close(pcm[PLAY]); + snd_pcm_open(&pcm[PLAY], "default", + SND_PCM_STREAM_PLAYBACK, block); + } + if (pcm[REC]) { + snd_pcm_close(pcm[REC]); + snd_pcm_open(&pcm[REC], "default", + SND_PCM_STREAM_CAPTURE, block); + } +#endif +} + +static void kill_streams(void) +{ + flush_streams(0); +} + +static int trigger_streams(int play, int rec) +{ + int ret = 0; + + if (pcm[PLAY] && play >= 0) { + ret = snd_pcm_sw_params_set_start_threshold(pcm[PLAY], sw_params, + play ? 1 : -1); + if (ret >= 0) + snd_pcm_sw_params(pcm[PLAY], sw_params); + } + if (ret >= 0 && pcm[REC] && rec >= 0) { + ret = snd_pcm_sw_params_set_start_threshold(pcm[REC], sw_params, + rec ? 1 : -1); + if (ret >= 0) + snd_pcm_sw_params(pcm[REC], sw_params); + } + + return ret; +} + +static ssize_t alsap_mixer(enum ossp_opcode opcode, + void *carg, void *din, size_t din_sz, + void *rarg, void *dout, size_t *dout_szp, int tfd) +{ +return -EBUSY; +} + +static int set_hw_params(snd_pcm_t *pcm) +{ + int ret; + unsigned rate; + + ret = snd_pcm_hw_params_any(pcm, hw_params); + if (ret >= 0) + ret = snd_pcm_hw_params_set_access(pcm, hw_params, + SND_PCM_ACCESS_RW_INTERLEAVED); + rate = hw_format.rate; + if (ret >= 0) + ret = snd_pcm_hw_params_set_rate_minmax(pcm, hw_params, + &rate, NULL, + &rate, NULL); + if (ret >= 0) + ret = snd_pcm_hw_params_set_format(pcm, hw_params, hw_format.format); + if (ret >= 0) + ret = snd_pcm_hw_params_set_channels(pcm, hw_params, + hw_format.channels); + if (ret >= 0) + ret = snd_pcm_hw_params(pcm, hw_params); + if (ret >= 0) + ret = snd_pcm_sw_params_current(pcm, sw_params); + if (ret >= 0) + ret = snd_pcm_sw_params(pcm, sw_params); + return ret; +} + +static ssize_t alsap_open(enum ossp_opcode opcode, + void *carg, void *din, size_t din_sz, + void *rarg, void *dout, size_t *dout_szp, int tfd) +{ + struct ossp_dsp_open_arg *arg = carg; + int ret; + block = arg->flags & O_NONBLOCK ? SND_PCM_NONBLOCK : 0; + int access; +// block |= SND_PCM_ASYNC; + /* Woop dee dooo.. I love handling things in SIGIO (PAIN!!) + * Probably needed for MMAP + */ + + if (!hw_params) + ret = snd_pcm_hw_params_malloc(&hw_params); + if (ret < 0) + return ret; + + if (!sw_params) + ret = snd_pcm_sw_params_malloc(&sw_params); + if (ret < 0) + return ret; + + if (pcm[PLAY]) + snd_pcm_close(pcm[PLAY]); + if (pcm[REC]) + snd_pcm_close(pcm[REC]); + pcm[REC] = pcm[PLAY] = NULL; + + access = arg->flags & O_ACCMODE; + if (access == O_WRONLY || access == O_RDWR) { + ret = snd_pcm_open(&pcm[PLAY], "default", + SND_PCM_STREAM_PLAYBACK, block); + if (ret >= 0) + ret = set_hw_params(pcm[PLAY]); + } + + if (ret >= 0 && (access == O_RDONLY || access == O_RDWR)) { + ret = snd_pcm_open(&pcm[REC], "default", + SND_PCM_STREAM_CAPTURE, block); + if (ret >= 0) + ret = set_hw_params(pcm[REC]); + } + + if (ret < 0) { + if (pcm[PLAY]) + snd_pcm_close(pcm[PLAY]); + if (pcm[REC]) + snd_pcm_close(pcm[REC]); + pcm[REC] = pcm[PLAY] = NULL; + return ret; + } + return 0; +} + +static ssize_t alsap_write(enum ossp_opcode opcode, + void *carg, void *din, size_t din_sz, + void *rarg, void *dout, size_t *dout_szp, int tfd) +{ +// struct ossp_dsp_rw_arg *arg = carg; + int ret, insize; + + insize = snd_pcm_bytes_to_frames(pcm[PLAY], din_sz); + + if (snd_pcm_state(pcm[PLAY]) == SND_PCM_STATE_SETUP) + snd_pcm_prepare(pcm[PLAY]); + +// snd_pcm_start(pcm[PLAY]); + ret = snd_pcm_writei(pcm[PLAY], din, insize); + if (ret < 0) + ret = snd_pcm_recover(pcm[PLAY], ret, 1); + + if (ret >= 0) + return snd_pcm_frames_to_bytes(pcm[PLAY], ret); + else + return ret; +} + +static ssize_t alsap_read(enum ossp_opcode opcode, + void *carg, void *din, size_t din_sz, + void *rarg, void *dout, size_t *dout_szp, int tfd) +{ +// struct ossp_dsp_rw_arg *arg = carg; + int ret, outsize; + + outsize = snd_pcm_bytes_to_frames(pcm[REC], *dout_szp); + + if (snd_pcm_state(pcm[REC]) == SND_PCM_STATE_SETUP) + snd_pcm_prepare(pcm[REC]); + + ret = snd_pcm_readi(pcm[REC], dout, outsize); + if (ret < 0) + ret = snd_pcm_recover(pcm[REC], ret, 1); + if (ret >= 0) + *dout_szp = ret = snd_pcm_frames_to_bytes(pcm[REC], ret); + else + *dout_szp = 0; + + return ret; +} + +static ssize_t alsap_poll(enum ossp_opcode opcode, + void *carg, void *din, size_t din_sz, + void *rarg, void *dout, size_t *dout_szp, int tfd) +{ + unsigned revents = 0; + + stream_notify |= *(int *)carg; + + if (pcm[PLAY]) + revents |= POLLOUT; + if (pcm[REC]) + revents |= POLLIN; + + *(unsigned *)rarg = revents; + return 0; +} + + +static ssize_t alsap_flush(enum ossp_opcode opcode, + void *carg, void *din, size_t din_sz, + void *rarg, void *dout, size_t *dout_szp, int tfd) +{ + flush_streams(opcode == OSSP_DSP_SYNC); + return 0; +} + +static ssize_t alsap_post(enum ossp_opcode opcode, + void *carg, void *din, size_t din_sz, + void *rarg, void *dout, size_t *dout_szp, int tfd) +{ + int ret; + + ret = trigger_streams(1, 1); + if (ret >= 0 && pcm[PLAY]) + ret = snd_pcm_start(pcm[PLAY]); + if (pcm[REC]) + ret = snd_pcm_start(pcm[REC]); + return ret; +} + +static ssize_t alsap_get_param(enum ossp_opcode opcode, + void *carg, void *din, size_t din_sz, + void *rarg, void *dout, size_t *dout_szp, + int tfd) +{ + int v = 0; + + switch (opcode) { + case OSSP_DSP_GET_RATE: + return hw_format.rate; + + case OSSP_DSP_GET_CHANNELS: + return hw_format.channels; + + case OSSP_DSP_GET_FORMAT: { + v = fmt_alsa_to_oss(hw_format.format); + break; + } + + case OSSP_DSP_GET_BLKSIZE: { + snd_pcm_uframes_t psize; + snd_pcm_hw_params_get_period_size(hw_params, &psize, NULL); + v = psize; + break; + } + + case OSSP_DSP_GET_FORMATS: + v = AFMT_U8 | AFMT_A_LAW | AFMT_MU_LAW | AFMT_S16_LE | + AFMT_S16_BE | AFMT_FLOAT | AFMT_S32_LE | AFMT_S32_BE; + break; + + case OSSP_DSP_GET_TRIGGER: + if (!stream_corked[PLAY]) + v |= PCM_ENABLE_OUTPUT; + if (!stream_corked[REC]) + v |= PCM_ENABLE_INPUT; + break; + + default: + assert(0); + } + + *(int *)rarg = v; + + return 0; +} + +static ssize_t alsap_set_param(enum ossp_opcode opcode, + void *carg, void *din, size_t din_sz, + void *rarg, void *dout, size_t *dout_szp, + int tfd) +{ + int v = *(int *)carg; + int ret = 0; + + /* kill the streams before changing parameters */ + kill_streams(); + + switch (opcode) { + case OSSP_DSP_SET_RATE: { + hw_format.rate = v; + break; + } + + case OSSP_DSP_SET_CHANNELS: { + hw_format.channels = v; + break; + } + + case OSSP_DSP_SET_FORMAT: { + snd_pcm_format_t format = fmt_oss_to_alsa(v); + hw_format.format = format; + break; + } + + case OSSP_DSP_SET_SUBDIVISION: + if (!v) + v = 1; +#if 0 + if (!v) { + v = user_subdivision ?: 1; + break; + } + user_frag_size = 0; + user_subdivision = v; + break; + + case OSSP_DSP_SET_FRAGMENT: + user_subdivision = 0; + user_frag_size = 1 << (v & 0xffff); + user_max_frags = (v >> 16) & 0xffff; + if (user_frag_size < 4) + user_frag_size = 4; + if (user_max_frags < 2) + user_max_frags = 2; +#else + case OSSP_DSP_SET_FRAGMENT: +#endif + break; + default: + assert(0); + } + + if (pcm[PLAY]) + ret = set_hw_params(pcm[PLAY]); + if (ret >= 0 && pcm[REC]) + ret = set_hw_params(pcm[REC]); + + if (rarg) + *(int *)rarg = v; + return 0; +} + +static ssize_t alsap_set_trigger(enum ossp_opcode opcode, + void *carg, void *din, size_t din_sz, + void *rarg, void *dout, size_t *dout_szp, + int fd) +{ + int enable = *(int *)carg; + + stream_corked[PLAY] = !!(enable & PCM_ENABLE_OUTPUT); + stream_corked[REC] = !!(enable & PCM_ENABLE_INPUT); + + return trigger_streams(enable & PCM_ENABLE_OUTPUT, + enable & PCM_ENABLE_INPUT); +} + +static ssize_t alsap_get_space(enum ossp_opcode opcode, + void *carg, void *din, size_t din_sz, + void *rarg, void *dout, size_t *dout_szp, int tfd) +{ + int dir = (opcode == OSSP_DSP_GET_OSPACE) ? PLAY : REC; + int underrun = 0; + struct audio_buf_info info = { }; + unsigned long bufsize; + snd_pcm_uframes_t avail, fragsize; + snd_pcm_state_t state; + + if (!pcm[dir]) + return -EINVAL; + + state = snd_pcm_state(pcm[dir]); + if (state == SND_PCM_STATE_XRUN) { + snd_pcm_recover(pcm[dir], -EPIPE, 0); + underrun = 1; + } else if (state == SND_PCM_STATE_SUSPENDED) { + snd_pcm_recover(pcm[dir], -ESTRPIPE, 0); + underrun = 1; + } + + snd_pcm_hw_params_current(pcm[dir], hw_params); + snd_pcm_hw_params_get_period_size(hw_params, &fragsize, NULL); + snd_pcm_hw_params_get_buffer_size(hw_params, &bufsize); + info.fragsize = snd_pcm_frames_to_bytes(pcm[dir], fragsize); + info.fragstotal = bufsize / fragsize; + if (!underrun) { + avail = snd_pcm_avail_update(pcm[dir]); + info.fragments = avail / fragsize; + } else + info.fragments = info.fragstotal; + + info.bytes = info.fragsize * info.fragments; + + *(struct audio_buf_info *)rarg = info; + return 0; +} + +static ssize_t alsap_get_ptr(enum ossp_opcode opcode, + void *carg, void *din, size_t din_sz, + void *rarg, void *dout, size_t *dout_szp, int tfd) +{ + int dir = (opcode == OSSP_DSP_GET_OPTR) ? PLAY : REC; + struct count_info info = { }; + + if (!pcm[dir]) + return -EIO; + + snd_pcm_hw_params_current(pcm[dir], hw_params); + info.bytes = byte_counter[dir]; + snd_pcm_hw_params_get_periods(hw_params, (unsigned int *)&info.blocks, NULL); + info.ptr = mmap_pos[dir]; + + *(struct count_info *)rarg = info; + return 0; +} + +static ssize_t alsap_get_odelay(enum ossp_opcode opcode, + void *carg, void *din, size_t din_sz, + void *rarg, void *dout, size_t *dout_szp, + int fd) +{ + snd_pcm_sframes_t delay; + + if (!pcm[PLAY]) + return -EIO; + + if (snd_pcm_delay(pcm[PLAY], &delay) < 0) + return -EIO; + + *(int *)rarg = snd_pcm_frames_to_bytes(pcm[PLAY], delay); + return 0; +} + +static ossp_action_fn_t action_fn_tbl[OSSP_NR_OPCODES] = { + [OSSP_MIXER] = alsap_mixer, + [OSSP_DSP_OPEN] = alsap_open, + [OSSP_DSP_READ] = alsap_read, + [OSSP_DSP_WRITE] = alsap_write, + [OSSP_DSP_POLL] = alsap_poll, +#if 0 + [OSSP_DSP_MMAP] = alsap_mmap, + [OSSP_DSP_MUNMAP] = alsap_munmap, +#endif + [OSSP_DSP_RESET] = alsap_flush, + [OSSP_DSP_SYNC] = alsap_flush, + [OSSP_DSP_POST] = alsap_post, + [OSSP_DSP_GET_RATE] = alsap_get_param, + [OSSP_DSP_GET_CHANNELS] = alsap_get_param, + [OSSP_DSP_GET_FORMAT] = alsap_get_param, + [OSSP_DSP_GET_BLKSIZE] = alsap_get_param, + [OSSP_DSP_GET_FORMATS] = alsap_get_param, + [OSSP_DSP_SET_RATE] = alsap_set_param, + [OSSP_DSP_SET_CHANNELS] = alsap_set_param, + [OSSP_DSP_SET_FORMAT] = alsap_set_param, + [OSSP_DSP_SET_SUBDIVISION] = alsap_set_param, + [OSSP_DSP_SET_FRAGMENT] = alsap_set_param, + [OSSP_DSP_GET_TRIGGER] = alsap_get_param, + [OSSP_DSP_SET_TRIGGER] = alsap_set_trigger, + [OSSP_DSP_GET_OSPACE] = alsap_get_space, + [OSSP_DSP_GET_ISPACE] = alsap_get_space, + [OSSP_DSP_GET_OPTR] = alsap_get_ptr, + [OSSP_DSP_GET_IPTR] = alsap_get_ptr, + [OSSP_DSP_GET_ODELAY] = alsap_get_odelay, +}; + +static int action_pre(void) +{ + return 0; +} + +static void action_post(void) +{ +} + +int main(int argc, char **argv) +{ + int rc; + + ossp_slave_init(argc, argv); + + page_size = sysconf(_SC_PAGE_SIZE); + + /* Okay, now we're open for business */ + rc = 0; + do { + rc = ossp_slave_process_command(ossp_cmd_fd, action_fn_tbl, + action_pre, action_post); + } while (rc > 0); + + return rc ? 1 : 0; +} diff --git a/ossp-padsp.c b/ossp-padsp.c new file mode 100644 index 0000000..1871f5b --- /dev/null +++ b/ossp-padsp.c @@ -0,0 +1,1524 @@ +/* + * ossp-padsp - ossp DSP slave which forwards to pulseaduio + * + * Copyright (C) 2008-2010 SUSE Linux Products GmbH + * Copyright (C) 2008-2010 Tejun Heo + * + * This file is released under the GPLv2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "ossp-slave.h" + +enum { + AFMT_FLOAT = 0x00004000, + AFMT_S32_LE = 0x00001000, + AFMT_S32_BE = 0x00002000, +}; + +/* everything is in millisecs */ +struct stream_params { + size_t min_process; + size_t min_latency; + size_t dfl_process; + size_t dfl_latency; + size_t mmap_process; + size_t mmap_latency; + size_t mmap_lead; + size_t mmap_staging; +}; + +/* TODO: make this configurable */ +static struct stream_params stream_params[] = { + [ PLAY ] = { .min_process = 25, .min_latency = 100, + .dfl_process = 50, .dfl_latency = 200, + .mmap_process = 25, .mmap_latency = 50, + .mmap_lead = 25, .mmap_staging = 100 }, + [ REC ] = { .min_process = 25, .min_latency = 200, + .dfl_process = 50, .dfl_latency = 400, + .mmap_process = 25, .mmap_latency = 50, + .mmap_lead = 25, .mmap_staging = 1000 }, +}; + +static size_t page_size; +static pa_context *context; +static pa_threaded_mainloop *mainloop; +static pa_mainloop_api *mainloop_api; +static char stream_name[128]; +static int stream_enabled[2]; +static int stream_corked[2]; +static int stream_waiting; +static int stream_notify; +static pa_channel_map channel_map_stor; +static pa_channel_map *channel_map; +static pa_stream *stream[2]; +static pa_usec_t stream_ptr_timestamp[2]; +static struct ring_buf rec_buf; +static int stored_oss_vol[2][2] = { { -1, -1 }, { -1, -1 } }; +static int fail_code; + +static pa_sample_spec sample_spec = { + .format = PA_SAMPLE_U8, + .rate = 8000, + .channels = 1, +}; +static size_t sample_bps = 8000; +static size_t frame_size = 1; + +/* user visible stream parameters */ +static size_t user_frag_size; +static size_t user_subdivision; /* alternative way to determine frag_size */ +static size_t user_max_frags; /* maximum number of fragments */ +static size_t user_max_length; + +/* actual stream parameters */ +static size_t frag_size; +static size_t target_length; +static size_t max_length; +static size_t prebuf_size; + +/* mmap stuff */ +static size_t mmap_raw_size, mmap_size; +static void *mmap_map[2]; +static uint64_t mmap_idx[2]; /* mmap pointer */ +static uint64_t mmap_last_idx[2]; /* last idx for get_ptr */ +static struct ring_buf mmap_stg[2]; /* staging ring buffer */ +static size_t mmap_lead[2]; /* lead bytes */ +static int mmap_sync[2]; /* sync with backend stream */ + +static const char *dir_str[] = { + [PLAY] = "PLAY", + [REC] = "REC", +}; + +static void stream_rw_callback(pa_stream *s, size_t length, void *userdata); + +#define __pa_err pa_strerror(pa_context_errno(context)) +#define dbg1_pa(fmt, args...) dbg1(fmt" (%s)" , ##args, __pa_err) +#define dbg0_pa(fmt, args...) dbg0(fmt" (%s)" , ##args, __pa_err) +#define info_pa(fmt, args...) info(fmt" (%s)" , ##args, __pa_err) +#define warn_pa(fmt, args...) warn(fmt" (%s)" , ##args, __pa_err) +#define err_pa(fmt, args...) err(fmt" (%s)" , ##args, __pa_err) + +#define round_down(v, t) ((v) / (t) * (t)) +#define round_up(v, t) (((v) + (t) - 1) / (t) * (t)) +#define is_power2(v) !((v) & ((v) - 1)) + +static int do_mixer(int dir, int *vol); + +static int padsp_done(void) +{ + fail_code = -EIO; + mainloop_api->quit(mainloop_api, 1); + return fail_code; +} + +static int fmt_oss_to_pa(int fmt) +{ + switch (fmt) { + case AFMT_U8: return PA_SAMPLE_U8; + case AFMT_A_LAW: return PA_SAMPLE_ALAW; + case AFMT_MU_LAW: return PA_SAMPLE_ULAW; + case AFMT_S16_LE: return PA_SAMPLE_S16LE; + case AFMT_S16_BE: return PA_SAMPLE_S16BE; + case AFMT_FLOAT: return PA_SAMPLE_FLOAT32NE; + case AFMT_S32_LE: return PA_SAMPLE_S32LE; + case AFMT_S32_BE: return PA_SAMPLE_S32BE; + default: return PA_SAMPLE_U8; + } +} + +static int fmt_pa_to_oss(int fmt) +{ + switch (fmt) { + case PA_SAMPLE_U8: return AFMT_U8; + case PA_SAMPLE_ALAW: return AFMT_A_LAW; + case PA_SAMPLE_ULAW: return AFMT_MU_LAW; + case PA_SAMPLE_S16LE: return AFMT_S16_LE; + case PA_SAMPLE_S16BE: return AFMT_S16_BE; + case PA_SAMPLE_FLOAT32NE: return AFMT_FLOAT; + case PA_SAMPLE_S32LE: return AFMT_S32_LE; + case PA_SAMPLE_S32BE: return AFMT_S32_BE; + default: return AFMT_U8; + } +} + +#define EXEC_OP(op, args...) do { \ + pa_operation *_o; \ + _o = op(args); \ + if (_o) { \ + while (pa_operation_get_state(_o) != PA_OPERATION_DONE) \ + pa_threaded_mainloop_wait(mainloop); \ + pa_operation_unref(_o); \ + } } while (0) + +static void context_op_callback(pa_context *s, int success, void *userdata) +{ + *(int *)userdata = success; + pa_threaded_mainloop_signal(mainloop, 0); +} + +static void stream_op_callback(pa_stream *s, int success, void *userdata) +{ + *(int *)userdata = success; + pa_threaded_mainloop_signal(mainloop, 0); +} + +#define EXEC_CONTEXT_OP(op, args...) ({ \ + int _success; \ + EXEC_OP(op , ##args, context_op_callback, &_success); \ + if (!_success) \ + warn_pa("%s() failed", #op); \ + _success ? 0 : -EIO; }) + +#define EXEC_STREAM_OP(op, args...) ({ \ + int _success; \ + EXEC_OP(op , ##args, stream_op_callback, &_success); \ + if (!_success) \ + warn_pa("%s() failed", #op); \ + _success ? 0 : -EIO; }) + +static int mmapped(void) +{ + return mmap_map[PLAY] || mmap_map[REC]; +} + +static uint64_t get_mmap_idx(int dir) +{ + uint64_t idx; + pa_usec_t time; + + if (!stream[dir]) + return mmap_idx[dir]; + + if (pa_stream_get_time(stream[dir], &time) < 0) { + dbg1_pa("pa_stream_get_time() failed"); + return mmap_idx[dir]; + } + + /* calculate the current index from time elapsed */ + idx = ((uint64_t)time * sample_bps / 1000000); + /* round down to the nearest frame boundary */ + idx = idx / frame_size * frame_size; + + return idx; +} + +static void flush_streams(int drain) +{ + int i; + + if (!(stream[PLAY] || stream[REC])) + return; + + dbg0("FLUSH drain=%d", drain); + + /* mmapped streams run forever, can't drain */ + if (drain && !mmapped() && stream[PLAY]) + EXEC_STREAM_OP(pa_stream_drain, stream[PLAY]); + + for (i = 0; i < 2; i++) + if (stream[i]) + EXEC_STREAM_OP(pa_stream_flush, stream[i]); + + ring_consume(&rec_buf, ring_bytes(&rec_buf)); +} + +static void kill_streams(void) +{ + int dir; + + if (!(stream[PLAY] || stream[REC])) + return; + + flush_streams(1); + + dbg0("KILL"); + + for (dir = 0; dir < 2; dir++) { + if (!stream[dir]) + continue; + pa_stream_disconnect(stream[dir]); + pa_stream_unref(stream[dir]); + stream[dir] = NULL; + stream_ptr_timestamp[dir] = 0; + + ring_consume(&mmap_stg[dir], ring_bytes(&mmap_stg[dir])); + ring_resize(&mmap_stg[dir], 0); + } +} + +static int trigger_streams(int play, int rec) +{ + int ret = 0, dir, rc; + + if (play >= 0) + stream_corked[PLAY] = !play; + if (rec >= 0) + stream_corked[REC] = !rec; + + for (dir = 0; dir < 2; dir++) { + if (!stream[dir]) + continue; + + rc = EXEC_STREAM_OP(pa_stream_cork, stream[dir], + stream_corked[dir]); + if (!rc && dir == PLAY && !mmap_map[dir] && !stream_corked[dir]) + rc = EXEC_STREAM_OP(pa_stream_trigger, stream[dir]); + if (!ret) + ret = rc; + } + + return ret; +} + +static void stream_state_callback(pa_stream *s, void *userdata) +{ + pa_threaded_mainloop_signal(mainloop, 0); +} + +static void stream_underflow_callback(pa_stream *s, void *userdata) +{ + int dir = (s == stream[PLAY]) ? PLAY : REC; + + dbg0("%s stream underrun", dir_str[dir]); +} + +static void stream_overflow_callback(pa_stream *s, void *userdata) +{ + int dir = (s == stream[PLAY]) ? PLAY : REC; + + dbg0("%s stream overrun", dir_str[dir]); +} + +static size_t duration_to_bytes(size_t dur) +{ + return round_up(dur * sample_bps / 1000, frame_size); +} + +static int prepare_streams(void) +{ + const struct stream_params *sp; + size_t min_frag_size, min_target_length, tmp; + int dir, rc; + + /* nothing to do? */ + if ((!stream_enabled[PLAY] || stream[PLAY]) && + (!stream_enabled[REC] || stream[REC])) + return 0; + + /* determine sample parameters */ + sample_bps = pa_bytes_per_second(&sample_spec); + frame_size = pa_frame_size(&sample_spec); + + sp = &stream_params[PLAY]; + if (stream_enabled[REC]) + sp = &stream_params[REC]; + + min_frag_size = duration_to_bytes(sp->min_process); + min_target_length = duration_to_bytes(sp->min_latency); + + /* determine frag_size */ + if (user_frag_size % frame_size) { + warn("requested frag_size (%zu) isn't multiple of frame (%zu)", + user_frag_size, frame_size); + user_frag_size = round_up(user_frag_size, frame_size); + } + + if (user_subdivision) + user_frag_size = round_up(sample_bps / user_subdivision, + frame_size); + + if (user_frag_size) { + frag_size = user_frag_size; + if (frag_size < min_frag_size) { + dbg0("requested frag_size (%zu) is smaller than " + "minimum (%zu)", frag_size, min_frag_size); + frag_size = min_frag_size; + } + } else { + tmp = round_up(sp->dfl_process * sample_bps / 1000, frame_size); + frag_size = tmp; + /* if frame_size is power of two, make frag_size so too */ + if (is_power2(frame_size)) { + frag_size = frame_size; + while (frag_size < tmp) + frag_size <<= 1; + } + user_frag_size = frag_size; + } + + /* determine target and max length */ + if (user_max_frags) { + target_length = user_max_frags * user_frag_size; + if (target_length < min_target_length) { + dbg0("requested target_length (%zu) is smaller than " + "minimum (%zu)", target_length, min_target_length); + target_length = min_target_length; + } + } else { + tmp = round_up(sp->dfl_latency * sample_bps / 1000, frag_size); + target_length = tmp; + /* if frag_size is power of two, make target_length so + * too and align it to page_size. + */ + if (is_power2(frag_size)) { + target_length = frag_size; + while (target_length < max(tmp, page_size)) + target_length <<= 1; + } + user_max_frags = target_length / frag_size; + } + + user_max_length = user_frag_size * user_max_frags; + max_length = target_length + 2 * frag_size; + + /* If mmapped, create backend stream with fixed parameters to + * create illusion of hardware buffer with acceptable latency. + */ + if (mmapped()) { + /* set parameters for backend streams */ + frag_size = duration_to_bytes(sp->mmap_process); + target_length = duration_to_bytes(sp->mmap_latency); + max_length = target_length + frag_size; + prebuf_size = 0; + + mmap_size = round_down(mmap_raw_size, frame_size); + if (mmap_size != mmap_raw_size) + warn("mmap_raw_size (%zu) unaligned to frame_size " + "(%zu), mmap_size adjusted to %zu", + mmap_raw_size, frame_size, mmap_size); + } else { + prebuf_size = min(user_frag_size * 2, user_max_length / 2); + prebuf_size = round_down(prebuf_size, frame_size); + } + + for (dir = 0; dir < 2; dir++) { + pa_buffer_attr new_ba = { }; + char buf[128]; + pa_stream *s; + pa_stream_flags_t flags; + int vol[2]; + size_t size; + + if (!stream_enabled[dir] || stream[dir]) + continue; + + dbg0("CREATE %s %s fsz=%zu:%zu", dir_str[dir], + pa_sample_spec_snprint(buf, sizeof(buf), &sample_spec), + frag_size, frag_size * 1000 / sample_bps); + dbg0(" tlen=%zu:%zu max=%zu:%zu pre=%zu:%zu", + target_length, target_length * 1000 / sample_bps, + max_length, max_length * 1000 / sample_bps, + prebuf_size, prebuf_size * 1000 / sample_bps); + dbg0(" u_sd=%zu u_fsz=%zu:%zu u_maxf=%zu", + user_subdivision, user_frag_size, + user_frag_size * 1000 / sample_bps, user_max_frags); + + channel_map = pa_channel_map_init_auto(&channel_map_stor, + sample_spec.channels, + PA_CHANNEL_MAP_OSS); + + s = pa_stream_new(context, stream_name, &sample_spec, + channel_map); + if (!s) { + err_pa("can't create streams"); + goto fail; + } + stream[dir] = s; + + pa_stream_set_state_callback(s, stream_state_callback, NULL); + if (dir == PLAY) { + pa_stream_set_write_callback(s, + stream_rw_callback, NULL); + pa_stream_set_underflow_callback(s, + stream_underflow_callback, NULL); + } else { + pa_stream_set_read_callback(s, + stream_rw_callback, NULL); + pa_stream_set_overflow_callback(s, + stream_overflow_callback, NULL); + } + + flags = PA_STREAM_AUTO_TIMING_UPDATE | + PA_STREAM_INTERPOLATE_TIMING; + if (stream_corked[dir]) + flags |= PA_STREAM_START_CORKED; + + new_ba.maxlength = max_length; + new_ba.tlength = target_length; + new_ba.prebuf = prebuf_size; + new_ba.minreq = frag_size; + new_ba.fragsize = frag_size; + + if (dir == PLAY) { + if (pa_stream_connect_playback(s, NULL, &new_ba, flags, + NULL, NULL)) { + err_pa("failed to connect playback stream"); + goto fail; + } + } else { + if (pa_stream_connect_record(s, NULL, &new_ba, flags)) { + err_pa("failed to connect record stream"); + goto fail; + } + } + + while (pa_stream_get_state(s) == PA_STREAM_CREATING) + pa_threaded_mainloop_wait(mainloop); + if (pa_stream_get_state(s) != PA_STREAM_READY) { + err_pa("failed to connect stream (state=%d)", + pa_stream_get_state(s)); + goto fail; + } + + /* apply stored OSS volume */ + memcpy(vol, stored_oss_vol[dir], sizeof(vol)); + if (do_mixer(dir, vol)) + warn_pa("initial volume control failed"); + + /* stream is ready setup mmap stuff */ + if (!mmap_map[dir]) + continue; + + /* prep mmap staging buffer */ + size = round_up(sp->mmap_staging * sample_bps / 1000, + frag_size); + rc = ring_resize(&mmap_stg[dir], size); + if (rc) + return rc; + + mmap_idx[dir] = mmap_last_idx[dir] = get_mmap_idx(dir); + mmap_lead[dir] = round_up(sp->mmap_lead * sample_bps / 1000, + frame_size); + mmap_sync[dir] = 1; + + /* apply the current trigger settings */ + trigger_streams(-1, -1); + } + + return 0; + fail: + return padsp_done(); +} + +struct volume_ret { + int success; + pa_cvolume *cv; +}; + +static void play_volume_callback(pa_context *c, const pa_sink_input_info *i, + int eol, void *userdata) +{ + struct volume_ret *vr = userdata; + + if (i) { + *vr->cv = i->volume; + vr->success = 1; + } + pa_threaded_mainloop_signal(mainloop, 0); +} + +static void rec_volume_callback(pa_context *c, const pa_source_info *i, + int eol, void *userdata) +{ + struct volume_ret *vr = userdata; + + if (i) { + *vr->cv = i->volume; + vr->success = 1; + } + pa_threaded_mainloop_signal(mainloop, 0); +} + +static int get_volume(int dir, pa_cvolume *cv) +{ + struct volume_ret vr = { .cv = cv }; + uint32_t idx; + + if (dir == PLAY) { + idx = pa_stream_get_index(stream[PLAY]); + EXEC_OP(pa_context_get_sink_input_info, + context, idx, play_volume_callback, &vr); + } else { + idx = pa_stream_get_device_index(stream[REC]); + EXEC_OP(pa_context_get_source_info_by_index, + context, idx, rec_volume_callback, &vr); + } + if (!vr.success) { + warn_pa("failed to get %s volume", dir_str[dir]); + return -EIO; + } + return 0; +} + +static int set_volume(int dir, pa_cvolume *cv) +{ + uint32_t idx; + int rc; + + if (dir == PLAY) { + idx = pa_stream_get_index(stream[PLAY]); + rc = EXEC_CONTEXT_OP(pa_context_set_sink_input_volume, + context, idx, cv); + } else { + idx = pa_stream_get_device_index(stream[REC]); + rc = EXEC_CONTEXT_OP(pa_context_set_source_volume_by_index, + context, idx, cv); + } + return rc; +} + +static int chan_left_right(int ch) +{ + if (!channel_map || channel_map->channels <= ch) { + switch (ch) { + case 0: + return LEFT; + case 1: + return RIGHT; + default: + return -1; + } + } + + switch (channel_map->map[ch]) { + /*case PA_CHANNEL_POSITION_LEFT:*/ /* same as FRONT_LEFT */ + case PA_CHANNEL_POSITION_FRONT_LEFT: + case PA_CHANNEL_POSITION_REAR_LEFT: + case PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER: + case PA_CHANNEL_POSITION_SIDE_LEFT: + case PA_CHANNEL_POSITION_TOP_FRONT_LEFT: + case PA_CHANNEL_POSITION_TOP_REAR_LEFT: + return LEFT; + /*case PA_CHANNEL_POSITION_RIGHT:*/ /* same as FRONT_RIGHT */ + case PA_CHANNEL_POSITION_FRONT_RIGHT: + case PA_CHANNEL_POSITION_REAR_RIGHT: + case PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER: + case PA_CHANNEL_POSITION_SIDE_RIGHT: + case PA_CHANNEL_POSITION_TOP_FRONT_RIGHT: + case PA_CHANNEL_POSITION_TOP_REAR_RIGHT: + return RIGHT; + default: + return -1; + } +} + +static int do_mixer(int dir, int *vol) +{ + pa_cvolume cv; + unsigned lv, rv; + int i, rc; + + if (vol[0] >= 0) { + int avg; + + stored_oss_vol[dir][LEFT] = vol[LEFT]; + stored_oss_vol[dir][RIGHT] = vol[RIGHT]; + vol[LEFT] = vol[LEFT] * PA_VOLUME_NORM / 100; + vol[RIGHT] = vol[RIGHT] * PA_VOLUME_NORM / 100; + avg = (vol[LEFT] + vol[RIGHT]) / 2; + + pa_cvolume_mute(&cv, sample_spec.channels); + + for (i = 0; i < cv.channels; i++) + switch (chan_left_right(i)) { + case LEFT: cv.values[i] = vol[LEFT]; break; + case RIGHT: cv.values[i] = vol[RIGHT]; break; + default: cv.values[i] = avg; break; + } + + rc = set_volume(dir, &cv); + if (rc) + return rc; + } + + rc = get_volume(dir, &cv); + if (rc) + return rc; + + if (cv.channels == 1) + lv = rv = pa_cvolume_avg(&cv); + else { + unsigned lcnt = 0, rcnt = 0; + + for (i = 0, lv = 0, rv = 0; i < cv.channels; i++) + switch (chan_left_right(i)) { + case LEFT: lv += cv.values[i]; lcnt++; break; + case RIGHT: rv += cv.values[i]; rcnt++; break; + } + + if (lcnt) + lv /= lcnt; + if (rcnt) + rv /= rcnt; + } + + vol[LEFT] = lv * 100 / PA_VOLUME_NORM; + vol[RIGHT] = rv * 100 / PA_VOLUME_NORM; + + return 0; +} + +static ssize_t padsp_mixer(enum ossp_opcode opcode, + void *carg, void *din, size_t din_sz, + void *rarg, void *dout, size_t *dout_szp, int tfd) +{ + struct ossp_mixer_arg *arg = carg; + int i, rc[2] = { }; + + if (prepare_streams()) + return -EIO; + + for (i = 0; i < 2; i++) + if (stream[i]) + rc[i] = do_mixer(i, arg->vol[i]); + else + memset(arg->vol[i], -1, sizeof(arg->vol[i])); + + *(struct ossp_mixer_arg *)rarg = *arg; + return rc[0] ?: rc[1]; +} + +static void context_state_callback(pa_context *cxt, void *userdata) +{ + pa_threaded_mainloop_signal(mainloop, 0); +} + +static void context_subscribe_callback(pa_context *context, + pa_subscription_event_type_t type, + uint32_t idx, void *userdata) +{ + struct ossp_notify event = { .magic = OSSP_NOTIFY_MAGIC, + .opcode = OSSP_NOTIFY_VOLCHG }; + ssize_t ret; + + if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != + PA_SUBSCRIPTION_EVENT_CHANGE) + return; + + ret = write(ossp_notify_fd, &event, sizeof(event)); + if (ret != sizeof(event) && errno != EPIPE) + warn_e(-errno, "write to notify_fd failed"); +} + +static ssize_t padsp_open(enum ossp_opcode opcode, + void *carg, void *din, size_t din_sz, + void *rarg, void *dout, size_t *dout_szp, int tfd) +{ + struct ossp_dsp_open_arg *arg = carg; + char host_name[128] = "(unknown)", opener[128] = "(unknown)"; + int state; + + switch (arg->flags & O_ACCMODE) { + case O_WRONLY: + stream_enabled[PLAY] = 1; + break; + case O_RDONLY: + stream_enabled[REC] = 1; + break; + case O_RDWR: + stream_enabled[PLAY] = 1; + stream_enabled[REC] = 1; + break; + default: + assert(0); + } + + /* determine stream name */ + gethostname(host_name, sizeof(host_name) - 1); + snprintf(stream_name, sizeof(stream_name), "OSS Proxy %s/%s:%ld", + host_name, ossp_user_name, (long)arg->opener_pid); + + /* create and connect PA context */ + get_proc_self_info(arg->opener_pid, NULL, opener, sizeof(opener)); + context = pa_context_new(mainloop_api, opener); + if (!context) { + err("pa_context_new() failed"); + return -EIO; + } + + pa_context_set_state_callback(context, context_state_callback, NULL); + pa_context_set_subscribe_callback(context, context_subscribe_callback, + NULL); + + pa_context_connect(context, NULL, 0, NULL); + while (1) { + state = pa_context_get_state(context); + if (state != PA_CONTEXT_CONNECTING && + state != PA_CONTEXT_AUTHORIZING && + state != PA_CONTEXT_SETTING_NAME) + break; + + pa_threaded_mainloop_wait(mainloop); + } + + if (EXEC_CONTEXT_OP(pa_context_subscribe, context, + PA_SUBSCRIPTION_MASK_SINK_INPUT | + PA_SUBSCRIPTION_MASK_SOURCE)) + warn_pa("failed to subscribe to context events"); + + if (state != PA_CONTEXT_READY) { + err_pa("failed to connect context, state=%d", state); + return -EIO; + } + + return 0; +} + +static void mmap_fill_pstg(void) +{ + struct ring_buf *stg = &mmap_stg[PLAY]; + struct ring_buf mmap; + uint64_t new_idx = get_mmap_idx(PLAY); + size_t bytes, space, size; + void *data; + + if (new_idx <= mmap_idx[PLAY]) + return; + + bytes = new_idx - mmap_idx[PLAY]; + space = ring_space(stg); + + if (bytes > mmap_size) { + dbg0("mmap playback transfer chunk bigger than " + "mmap size (bytes=%zu mmap_size=%zu)", bytes, mmap_size); + mmap_sync[PLAY] = 1; + bytes = mmap_size; + } + + if (bytes > space) { + dbg0("mmap playback staging buffer overflow " + "(bytes=%zu space=%zu)", bytes, space); + mmap_sync[PLAY] = 1; + bytes = space; + } + + ring_manual_init(&mmap, mmap_map[PLAY], mmap_size, + new_idx % mmap_size, bytes); + + while ((data = ring_data(&mmap, &size))) { + ring_fill(stg, data, size); + ring_consume(&mmap, size); + } + + mmap_idx[PLAY] = new_idx; +} + +static void mmap_consume_rstg(void) +{ + struct ring_buf *stg = &mmap_stg[REC]; + struct ring_buf mmap; + uint64_t new_idx = get_mmap_idx(REC); + uint64_t fill_idx = mmap_idx[REC]; + size_t bytes, space; + + if (new_idx <= mmap_idx[REC]) + return; + + space = new_idx - mmap_idx[REC]; /* mmapped space to fill in */ + bytes = ring_bytes(stg); /* recorded bytes in staging */ + + if (space > bytes) { + if (!mmap_sync[REC]) + dbg0("mmap recording staging buffer underflow " + "(space=%zu bytes=%zu)", space, bytes); + mmap_sync[REC] = 1; + } + + if (space > mmap_size) { + if (!mmap_sync[REC]) + dbg0("mmap recording transfer chunk bigger than " + "mmap size (space=%zu mmap_size=%zu)", + bytes, mmap_size); + mmap_sync[REC] = 1; + space = mmap_size; + } + + /* If resync is requested, leave lead bytes in the staging + * buffer and copy everything else such that data is filled + * upto the new_idx. If there are more bytes in staging than + * available space, those will be dropped. + */ + if (mmap_sync[REC]) { + ssize_t avail = bytes - mmap_lead[REC]; + + /* make sure we always have lead bytes in staging */ + if (avail < 0) + goto skip; + + if (avail > space) { + dbg0("dropping %zu bytes from record staging buffer", + avail - space); + ring_consume(&mmap_stg[REC], avail - space); + avail = space; + } else { + dbg0("skippping %zu bytes in record mmap map", + space - avail); + space = avail; + } + + assert(new_idx >= avail); + fill_idx = new_idx - avail; + mmap_sync[REC] = 0; + } + + ring_manual_init(&mmap, mmap_map[REC], mmap_size, + fill_idx % mmap_size, 0); + + while (space) { + void *data; + size_t size, todo; + + data = ring_data(stg, &size); + assert(data); + + todo = min(size, space); + ring_fill(&mmap, data, todo); + + ring_consume(stg, todo); + space -= todo; + } + + skip: + mmap_idx[REC] = new_idx; +} + +static void do_mmap_write(size_t space) +{ + struct ring_buf *stg = &mmap_stg[PLAY]; + size_t todo; + void *data; + + space = round_down(space, frame_size); + mmap_fill_pstg(); + + while (space && (data = ring_data(stg, &todo))) { + pa_seek_mode_t mode = PA_SEEK_RELATIVE_END; + int64_t offset = 0; + + todo = min(todo, space); + + if (mmap_sync[PLAY]) { + mode = PA_SEEK_RELATIVE_ON_READ; + offset = (int64_t)mmap_lead[PLAY] - ring_bytes(stg); + dbg0("mmap resync, offset=%ld", (long)offset); + } + + if (pa_stream_write(stream[PLAY], data, todo, NULL, + offset, mode) < 0) { + err_pa("pa_stream_write() failed"); + padsp_done(); + return; + } + + mmap_sync[PLAY] = 0; + ring_consume(stg, todo); + space -= todo; + } +} + +static void do_mmap_read(size_t bytes) +{ + struct ring_buf *stg = &mmap_stg[REC]; + + bytes = round_down(bytes, frame_size); + mmap_consume_rstg(); + + while (bytes) { + const void *peek_data; + size_t size; + + if (pa_stream_peek(stream[REC], &peek_data, &size)) { + err_pa("pa_stream_peek() failed"); + padsp_done(); + return; + } + + if (!peek_data) + break; + + if (size <= ring_space(stg)) + ring_fill(stg, peek_data, size); + else { + if (!mmap_sync[REC]) + dbg0("recording staging buffer overflow, " + "requesting resync"); + mmap_sync[REC] = 1; + } + + pa_stream_drop(stream[REC]); + bytes -= size; + } +} + +static void stream_rw_callback(pa_stream *s, size_t length, void *userdata) +{ + int dir; + size_t size; + + if (s == stream[PLAY]) { + dir = PLAY; + size = pa_stream_writable_size(s); + if (mmap_map[PLAY]) + do_mmap_write(size); + } else if (s == stream[REC]) { + dir = REC; + size = pa_stream_readable_size(s); + if (mmap_map[REC]) + do_mmap_read(size); + } else { + dbg0("stream_rw_callback(): unknown stream %p PLAY/REC=%p/%p\n", + s, stream[PLAY], stream[REC]); + return; + } + + if (size < user_frag_size) + return; + if (stream_waiting) + pa_threaded_mainloop_signal(mainloop, 0); + if (stream_notify) { + struct ossp_notify event = { .magic = OSSP_NOTIFY_MAGIC, + .opcode = OSSP_NOTIFY_POLL }; + ssize_t ret; + + ret = write(ossp_notify_fd, &event, sizeof(event)); + if (ret != sizeof(event)) { + if (errno != EPIPE) + err_e(-errno, "write to notify_fd failed"); + + /* This function is run from PA mainloop and + * thus the following padsp_done() won't be + * noticed before the mainthread tries to run + * the next command. Well, that's good enough. + */ + padsp_done(); + } + stream_notify = 0; + } +} + +static ssize_t padsp_write(enum ossp_opcode opcode, + void *carg, void *din, size_t din_sz, + void *rarg, void *dout, size_t *dout_szp, int tfd) +{ + struct ossp_dsp_rw_arg *arg = carg; + size_t size; + + if (prepare_streams() || !stream[PLAY]) + return -EIO; + + stream_waiting++; + while (1) { + size = pa_stream_writable_size(stream[PLAY]); + if (arg->nonblock || size >= user_frag_size) + break; + pa_threaded_mainloop_wait(mainloop); + } + stream_waiting--; + + size = round_down(size, user_frag_size); + if (!size) + return -EAGAIN; + + size = min(size, din_sz); + + if (pa_stream_write(stream[PLAY], din, size, NULL, + 0, PA_SEEK_RELATIVE) < 0) { + err_pa("pa_stream_write() failed"); + return padsp_done(); + } + + return size; +} + +static ssize_t padsp_read(enum ossp_opcode opcode, + void *carg, void *din, size_t din_sz, + void *rarg, void *dout, size_t *dout_szp, int tfd) +{ + struct ossp_dsp_rw_arg *arg = carg; + size_t size; + void *data; + + if (prepare_streams() || !stream[REC]) + return -EIO; + again: + if (!arg->nonblock) { + stream_waiting++; + while (1) { + size = pa_stream_readable_size(stream[REC]); + if (size + ring_bytes(&rec_buf) >= user_frag_size) + break; + pa_threaded_mainloop_wait(mainloop); + } + stream_waiting--; + } + + while (ring_bytes(&rec_buf) < max(user_frag_size, *dout_szp)) { + const void *peek_data; + + if (pa_stream_peek(stream[REC], &peek_data, &size) < 0) { + err_pa("pa_stream_peek() failed"); + return padsp_done(); + } + + if (!peek_data) + break; + + if (ring_space(&rec_buf) < size) { + size_t bufsz; + + bufsz = ring_size(&rec_buf); + bufsz = max(2 * bufsz, bufsz + 2 * size); + + if (ring_resize(&rec_buf, bufsz)) { + err("failed to allocate recording buffer"); + return padsp_done(); + } + } + + ring_fill(&rec_buf, peek_data, size); + pa_stream_drop(stream[REC]); + } + + size = round_down(ring_bytes(&rec_buf), user_frag_size); + if (!size) { + if (arg->nonblock) + return -EAGAIN; + else + goto again; + } + + *dout_szp = size = min(size, *dout_szp); + + while (size) { + size_t cnt; + + data = ring_data(&rec_buf, &cnt); + assert(data); + + cnt = min(size, cnt); + memcpy(dout, data, cnt); + ring_consume(&rec_buf, cnt); + dout += cnt; + size -= cnt; + } + + return *dout_szp; +} + +static ssize_t padsp_poll(enum ossp_opcode opcode, + void *carg, void *din, size_t din_sz, + void *rarg, void *dout, size_t *dout_szp, int tfd) +{ + unsigned revents = 0; + + if (prepare_streams() < 0) + return -EIO; + + stream_notify |= *(int *)carg; + + if (stream[PLAY] && + pa_stream_writable_size(stream[PLAY]) >= user_frag_size) + revents |= POLLOUT; + if (stream[REC] && + pa_stream_readable_size(stream[REC]) >= user_frag_size) + revents |= POLLIN; + + *(unsigned *)rarg = revents; + return 0; +} + +static ssize_t padsp_mmap(enum ossp_opcode opcode, + void *carg, void *din, size_t din_sz, + void *rarg, void *dout, size_t *dout_szp, int tfd) +{ + struct ossp_dsp_mmap_arg *arg = carg; + int dir = arg->dir; + + assert(!mmap_map[dir]); + + kill_streams(); + + /* arg->size is rounded up to the nearest page boundary. + * There is no way to tell what the actual requested value is + * but assume that it was the reported buffer space if it + * falls into the same page aligned range. + */ + mmap_raw_size = arg->size; + if (user_max_length && user_max_length < mmap_raw_size && + round_up(mmap_raw_size, page_size) == + round_up(user_max_length, page_size)) { + info("MMAP adjusting raw_size %zu -> %zu", + mmap_raw_size, user_max_length); + mmap_raw_size = user_max_length; + } + + dbg0("MMAP server-addr=%p sz=%zu", ossp_mmap_addr[dir], mmap_raw_size); + + mmap_map[dir] = ossp_mmap_addr[dir]; + + /* if mmapped, only mmapped streams are enabled */ + stream_enabled[PLAY] = !!mmap_map[PLAY]; + stream_enabled[REC] = !!mmap_map[REC]; + + return 0; +} + +static ssize_t padsp_munmap(enum ossp_opcode opcode, + void *carg, void *din, size_t din_sz, + void *rarg, void *dout, size_t *dout_szp, int tfd) +{ + int dir = *(int *)carg; + + assert(mmap_map[dir]); + kill_streams(); + mmap_map[dir] = NULL; + return 0; +} + +static ssize_t padsp_flush(enum ossp_opcode opcode, + void *carg, void *din, size_t din_sz, + void *rarg, void *dout, size_t *dout_szp, int tfd) +{ + flush_streams(opcode == OSSP_DSP_SYNC); + return 0; +} + +static ssize_t padsp_post(enum ossp_opcode opcode, + void *carg, void *din, size_t din_sz, + void *rarg, void *dout, size_t *dout_szp, int tfd) +{ + return trigger_streams(1, -1); +} + +static ssize_t padsp_get_param(enum ossp_opcode opcode, + void *carg, void *din, size_t din_sz, + void *rarg, void *dout, size_t *dout_szp, + int tfd) +{ + int v = 0; + + switch (opcode) { + case OSSP_DSP_GET_RATE: + v = sample_spec.rate; + break; + + case OSSP_DSP_GET_CHANNELS: + v = sample_spec.channels; + break; + + case OSSP_DSP_GET_FORMAT: + v = fmt_pa_to_oss(sample_spec.format); + break; + + case OSSP_DSP_GET_BLKSIZE: + if (prepare_streams() < 0) + return -EIO; + v = user_frag_size; + break; + + case OSSP_DSP_GET_FORMATS: + v = AFMT_U8 | AFMT_A_LAW | AFMT_MU_LAW | AFMT_S16_LE | + AFMT_S16_BE | AFMT_FLOAT | AFMT_S32_LE | AFMT_S32_BE; + break; + + case OSSP_DSP_GET_TRIGGER: + if (!stream_corked[PLAY]) + v |= PCM_ENABLE_OUTPUT; + if (!stream_corked[REC]) + v |= PCM_ENABLE_INPUT; + break; + + default: + assert(0); + } + + *(int *)rarg = v; + + return 0; +} + +static ssize_t padsp_set_param(enum ossp_opcode opcode, + void *carg, void *din, size_t din_sz, + void *rarg, void *dout, size_t *dout_szp, + int tfd) +{ + pa_sample_spec new_spec = sample_spec; + int v = *(int *)carg; + + /* kill the streams before changing parameters */ + kill_streams(); + + switch (opcode) { + case OSSP_DSP_SET_RATE: + new_spec.rate = v; + if (pa_sample_spec_valid(&new_spec)) + sample_spec = new_spec; + v = sample_spec.rate; + break; + + case OSSP_DSP_SET_CHANNELS: + new_spec.channels = v; + if (pa_sample_spec_valid(&new_spec)) + sample_spec = new_spec; + v = sample_spec.channels; + break; + + case OSSP_DSP_SET_FORMAT: + new_spec.format = fmt_oss_to_pa(v); + if (pa_sample_spec_valid(&new_spec)) + sample_spec = new_spec; + v = fmt_pa_to_oss(sample_spec.format); + break; + + case OSSP_DSP_SET_SUBDIVISION: + if (!v) { + v = user_subdivision ?: 1; + break; + } + user_frag_size= 0; + user_subdivision = v; + break; + + case OSSP_DSP_SET_FRAGMENT: + user_subdivision = 0; + user_frag_size = 1 << (v & 0xffff); + user_max_frags = (v >> 16) & 0xffff; + if (user_frag_size < 4) + user_frag_size = 4; + if (user_max_frags < 2) + user_max_frags = 2; + break; + default: + assert(0); + } + + if (rarg) + *(int *)rarg = v; + return 0; +} + +static ssize_t padsp_set_trigger(enum ossp_opcode opcode, + void *carg, void *din, size_t din_sz, + void *rarg, void *dout, size_t *dout_szp, + int fd) +{ + int enable = *(int *)carg; + + return trigger_streams(enable & PCM_ENABLE_OUTPUT, + enable & PCM_ENABLE_INPUT); +} + +static ssize_t padsp_get_space(enum ossp_opcode opcode, + void *carg, void *din, size_t din_sz, + void *rarg, void *dout, size_t *dout_szp, int tfd) +{ + int dir = (opcode == OSSP_DSP_GET_OSPACE) ? PLAY : REC; + struct audio_buf_info info = { }; + int rc; + + rc = prepare_streams(); + if (rc) + return -EIO; + + if (mmapped()) { + info.fragments = mmap_raw_size / user_frag_size; + info.fragstotal = info.fragments; + info.fragsize = user_frag_size; + info.bytes = mmap_raw_size; + } else { + size_t space; + + if (dir == PLAY) + space = pa_stream_writable_size(stream[PLAY]); + else + space = pa_stream_readable_size(stream[REC]); + + space = round_down(space, user_frag_size); + space = min(space, user_frag_size * user_max_frags); + + info.fragments = space / user_frag_size; + info.fragstotal = user_max_frags; + info.fragsize = user_frag_size; + info.bytes = space; + } + + *(struct audio_buf_info *)rarg = info; + return 0; +} + +static ssize_t padsp_get_ptr(enum ossp_opcode opcode, + void *carg, void *din, size_t din_sz, + void *rarg, void *dout, size_t *dout_szp, int tfd) +{ + int dir = (opcode == OSSP_DSP_GET_OPTR) ? PLAY : REC; + struct count_info info = { }; + + if (prepare_streams() < 0 || !stream[dir]) + return -EIO; + + if (mmap_map[dir]) { + /* mmap operation in progress, report mmap buffer parameters */ + if (dir == PLAY) + mmap_fill_pstg(); + else + mmap_consume_rstg(); + + info.bytes = mmap_idx[dir]; + info.blocks = (mmap_idx[dir] - mmap_last_idx[dir]) / frame_size; + info.ptr = mmap_idx[dir] % mmap_size; + + mmap_last_idx[dir] = mmap_idx[dir]; + } else { + /* simulate pointers using timestamps */ + double bpus = (double)sample_bps / 1000000; + size_t bytes, delta_bytes; + pa_usec_t usec, delta; + + if (pa_stream_get_time(stream[dir], &usec) < 0) { + warn_pa("pa_stream_get_time() failed"); + return -EIO; + } + + delta = usec - stream_ptr_timestamp[dir]; + stream_ptr_timestamp[dir] = usec; + bytes = bpus * usec; + delta_bytes = bpus * delta; + + info.bytes = bytes & INT_MAX; + info.blocks = (delta_bytes + frame_size - 1) / frame_size; + info.ptr = bytes % user_max_length; + } + + *(struct count_info *)rarg = info; + return 0; +} + +static ssize_t padsp_get_odelay(enum ossp_opcode opcode, + void *carg, void *din, size_t din_sz, + void *rarg, void *dout, size_t *dout_szp, + int fd) +{ + double bpus = (double)sample_bps / 1000000; + pa_usec_t usec; + + if (prepare_streams() < 0 || !stream[PLAY]) + return -EIO; + + if (pa_stream_get_latency(stream[PLAY], &usec, NULL) < 0) { + warn_pa("pa_stream_get_latency() failed"); + return -EIO; + } + + *(int *)rarg = bpus * usec; + return 0; +} + +static ossp_action_fn_t action_fn_tbl[OSSP_NR_OPCODES] = { + [OSSP_MIXER] = padsp_mixer, + [OSSP_DSP_OPEN] = padsp_open, + [OSSP_DSP_READ] = padsp_read, + [OSSP_DSP_WRITE] = padsp_write, + [OSSP_DSP_POLL] = padsp_poll, + [OSSP_DSP_MMAP] = padsp_mmap, + [OSSP_DSP_MUNMAP] = padsp_munmap, + [OSSP_DSP_RESET] = padsp_flush, + [OSSP_DSP_SYNC] = padsp_flush, + [OSSP_DSP_POST] = padsp_post, + [OSSP_DSP_GET_RATE] = padsp_get_param, + [OSSP_DSP_GET_CHANNELS] = padsp_get_param, + [OSSP_DSP_GET_FORMAT] = padsp_get_param, + [OSSP_DSP_GET_BLKSIZE] = padsp_get_param, + [OSSP_DSP_GET_FORMATS] = padsp_get_param, + [OSSP_DSP_SET_RATE] = padsp_set_param, + [OSSP_DSP_SET_CHANNELS] = padsp_set_param, + [OSSP_DSP_SET_FORMAT] = padsp_set_param, + [OSSP_DSP_SET_SUBDIVISION] = padsp_set_param, + [OSSP_DSP_SET_FRAGMENT] = padsp_set_param, + [OSSP_DSP_GET_TRIGGER] = padsp_get_param, + [OSSP_DSP_SET_TRIGGER] = padsp_set_trigger, + [OSSP_DSP_GET_OSPACE] = padsp_get_space, + [OSSP_DSP_GET_ISPACE] = padsp_get_space, + [OSSP_DSP_GET_OPTR] = padsp_get_ptr, + [OSSP_DSP_GET_IPTR] = padsp_get_ptr, + [OSSP_DSP_GET_ODELAY] = padsp_get_odelay, +}; + +static int action_pre(void) +{ + pa_threaded_mainloop_lock(mainloop); + if (fail_code) { + pa_threaded_mainloop_unlock(mainloop); + return fail_code; + } + return 0; +} + +static void action_post(void) +{ + pa_threaded_mainloop_unlock(mainloop); +} + +int main(int argc, char **argv) +{ + int rc; + + ossp_slave_init(argc, argv); + + page_size = sysconf(_SC_PAGE_SIZE); + + mainloop = pa_threaded_mainloop_new(); + if (!mainloop) { + err("failed to allocate mainloop"); + return 1; + } + mainloop_api = pa_threaded_mainloop_get_api(mainloop); + + if (pa_threaded_mainloop_start(mainloop)) { + err("pa_mainloop_start() failed"); + return 1; + } + + /* Okay, now we're open for business */ + rc = 0; + do { + rc = ossp_slave_process_command(ossp_cmd_fd, action_fn_tbl, + action_pre, action_post); + } while (rc > 0 && !fail_code); + if (rc) + fail_code = rc; + + pa_threaded_mainloop_lock(mainloop); + + kill_streams(); + if (context) { + pa_context_disconnect(context); + pa_context_unref(context); + } + + pa_threaded_mainloop_unlock(mainloop); + + pa_threaded_mainloop_stop(mainloop); + pa_threaded_mainloop_free(mainloop); + + return fail_code ? 1 : 0; +} diff --git a/ossp-slave.c b/ossp-slave.c new file mode 100644 index 0000000..4c5cb2d --- /dev/null +++ b/ossp-slave.c @@ -0,0 +1,250 @@ +/* + * ossp-slave - OSS Proxy: Common codes for slaves + * + * Copyright (C) 2008-2010 SUSE Linux Products GmbH + * Copyright (C) 2008-2010 Tejun Heo + * + * This file is released under the GPLv2. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ossp-slave.h" + +static const char *usage = +"usage: ossp-SLAVE [options]\n" +"\n" +"proxies commands from osspd to pulseaudio\n" +"\n" +"options:\n" +" -u UID uid to use\n" +" -g GID gid to use\n" +" -c CMD_FD fd to receive commands from osspd\n" +" -n NOTIFY_FD fd to send async notifications to osspd\n" +" -m MMAP_FD fd to use for mmap\n" +" -o MMAP_OFFSET mmap offset\n" +" -s MMAP_SIZE mmap size\n" +" -l LOG_LEVEL set log level\n" +" -t enable log timestamps\n"; + +char ossp_user_name[OSSP_USER_NAME_LEN]; +int ossp_cmd_fd = -1, ossp_notify_fd = -1; +void *ossp_mmap_addr[2]; + +void ossp_slave_init(int argc, char **argv) +{ + int have_uid = 0, have_gid = 0; + uid_t uid; + gid_t gid; + int mmap_fd = -1; + off_t mmap_off = 0; + size_t mmap_size = 0; + int opt; + struct passwd *pw, pw_buf; + struct sigaction sa; + char pw_sbuf[sysconf(_SC_GETPW_R_SIZE_MAX)]; + + while ((opt = getopt(argc, argv, "u:g:c:n:m:o:s:l:t")) != -1) { + switch (opt) { + case 'u': + have_uid = 1; + uid = strtol(optarg, NULL, 0); + break; + case 'g': + have_gid = 1; + gid = strtol(optarg, NULL, 0); + break; + case 'c': + ossp_cmd_fd = strtol(optarg, NULL, 0); + break; + case 'n': + ossp_notify_fd = strtol(optarg, NULL, 0); + break; + case 'm': + mmap_fd = strtol(optarg, NULL, 0); + break; + case 'o': + mmap_off = strtoull(optarg, NULL, 0); + break; + case 's': + mmap_size = strtoul(optarg, NULL, 0); + break; + case 'l': + ossp_log_level = strtol(optarg, NULL, 0); + break; + case 't': + ossp_log_timestamp = 1; + break; + } + } + + if (!have_uid || !have_gid || ossp_cmd_fd < 0 || ossp_notify_fd < 0) { + fprintf(stderr, usage); + _exit(1); + } + + snprintf(ossp_user_name, sizeof(ossp_user_name), "uid%d", uid); + if (getpwuid_r(uid, &pw_buf, pw_sbuf, sizeof(pw_sbuf), &pw) == 0) + snprintf(ossp_user_name, sizeof(ossp_user_name), "%s", + pw->pw_name); + + snprintf(ossp_log_name, sizeof(ossp_log_name), "ossp-padsp[%s:%d]", + ossp_user_name, getpid()); + + if (mmap_fd >= 0) { + void *p; + + if (!mmap_off || !mmap_size) { + fprintf(stderr, usage); + _exit(1); + } + + p = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED, + mmap_fd, mmap_off); + if (p == MAP_FAILED) + fatal_e(-errno, "mmap failed"); + + ossp_mmap_addr[PLAY] = p; + ossp_mmap_addr[REC] = p + mmap_size / 2; + close(mmap_fd); + } + + /* mmap done, drop privileges */ + if (setresgid(gid, gid, gid) || setresuid(uid, uid, uid)) + fatal_e(-errno, "failed to drop privileges"); + + /* block SIGPIPE */ + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = SIG_IGN; + if (sigaction(SIGPIPE, &sa, NULL)) + fatal_e(-errno, "failed to ignore SIGPIPE"); +} + +int ossp_slave_process_command(int cmd_fd, + ossp_action_fn_t const *action_fn_tbl, + int (*action_pre_fn)(void), + void (*action_post_fn)(void)) +{ + static struct sized_buf carg_sbuf = { }, rarg_sbuf = { }; + static struct sized_buf din_sbuf = { }, dout_sbuf = { }; + struct ossp_cmd cmd; + int fd = -1; + char cmsg_buf[CMSG_SPACE(sizeof(fd))]; + struct iovec iov = { &cmd, sizeof(cmd) }; + struct msghdr msg = { .msg_iov = &iov, .msg_iovlen = 1, + .msg_control = cmsg_buf, + .msg_controllen = sizeof(cmsg_buf) }; + struct cmsghdr *cmsg; + size_t carg_size, din_size, rarg_size, dout_size; + char *carg = NULL, *din = NULL, *rarg = NULL, *dout = NULL; + struct ossp_reply reply = { .magic = OSSP_REPLY_MAGIC }; + ssize_t ret; + + ret = recvmsg(cmd_fd, &msg, 0); + if (ret == 0) + return 0; + if (ret < 0) { + ret = -errno; + err_e(ret, "failed to read command channel"); + return ret; + } + + if (ret != sizeof(cmd)) { + err("command struct size mismatch (%zu, should be %zu)", + ret, sizeof(cmd)); + return -EINVAL; + } + + if (cmd.magic != OSSP_CMD_MAGIC) { + err("illegal command magic 0x%x", cmd.magic); + return -EINVAL; + } + + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; + cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_RIGHTS) + fd = *(int *)CMSG_DATA(cmsg); + else { + err("unknown cmsg %d:%d received (opcode %d)", + cmsg->cmsg_level, cmsg->cmsg_type, cmd.opcode); + return -EINVAL; + } + } + + if (cmd.opcode >= OSSP_NR_OPCODES) { + err("unknown opcode %d", cmd.opcode); + return -EINVAL; + } + + carg_size = ossp_arg_sizes[cmd.opcode].carg_size; + din_size = cmd.din_size; + rarg_size = ossp_arg_sizes[cmd.opcode].rarg_size; + dout_size = cmd.dout_size; + + if ((fd >= 0) != ossp_arg_sizes[cmd.opcode].has_fd) { + err("fd=%d unexpected for opcode %d", fd, cmd.opcode); + return -EINVAL; + } + + if (ensure_sbuf_size(&carg_sbuf, carg_size) || + ensure_sbuf_size(&din_sbuf, din_size) || + ensure_sbuf_size(&rarg_sbuf, rarg_size) || + ensure_sbuf_size(&dout_sbuf, dout_size)) { + err("failed to allocate command buffers"); + return -ENOMEM; + } + + if (carg_size) { + carg = carg_sbuf.buf; + ret = read_fill(cmd_fd, carg, carg_size); + if (ret < 0) + return ret; + } + if (din_size) { + din = din_sbuf.buf; + ret = read_fill(cmd_fd, din, din_size); + if (ret < 0) + return ret; + } + if (rarg_size) + rarg = rarg_sbuf.buf; + if (dout_size) + dout = dout_sbuf.buf; + + ret = -EINVAL; + if (action_fn_tbl[cmd.opcode]) { + ret = action_pre_fn(); + if (ret == 0) { + ret = action_fn_tbl[cmd.opcode](cmd.opcode, carg, + din, din_size, rarg, + dout, &dout_size, fd); + action_post_fn(); + } + } + + reply.result = ret; + if (ret >= 0) + reply.dout_size = dout_size; + else { + rarg_size = 0; + dout_size = 0; + } + + if (write_fill(cmd_fd, &reply, sizeof(reply)) < 0 || + write_fill(cmd_fd, rarg, rarg_size) < 0 || + write_fill(cmd_fd, dout, dout_size) < 0) + return -EIO; + + return 1; +} diff --git a/ossp-slave.h b/ossp-slave.h new file mode 100644 index 0000000..10c22cd --- /dev/null +++ b/ossp-slave.h @@ -0,0 +1,28 @@ +/* + * ossp-slave - OSS Proxy: Common codes for slaves + * + * Copyright (C) 2008-2010 SUSE Linux Products GmbH + * Copyright (C) 2008-2010 Tejun Heo + * + * This file is released under the GPLv2. + */ + +#ifndef _OSSP_SLAVE_H +#define _OSSP_SLAVE_H + +#include "ossp.h" +#include "ossp-util.h" + +#define OSSP_USER_NAME_LEN 128 + +extern char ossp_user_name[OSSP_USER_NAME_LEN]; +extern int ossp_cmd_fd, ossp_notify_fd; +extern void *ossp_mmap_addr[2]; + +void ossp_slave_init(int argc, char **argv); +int ossp_slave_process_command(int cmd_fd, + ossp_action_fn_t const *action_fn_tbl, + int (*action_pre_fn)(void), + void (*action_post_fn)(void)); + +#endif /* _OSSP_SLAVE_H */ diff --git a/ossp-util.c b/ossp-util.c new file mode 100644 index 0000000..325cefd --- /dev/null +++ b/ossp-util.c @@ -0,0 +1,369 @@ +/* + * ossp-util - OSS Proxy: Common utilities + * + * Copyright (C) 2008-2010 SUSE Linux Products GmbH + * Copyright (C) 2008-2010 Tejun Heo + * + * This file is released under the GPLv2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ossp-util.h" + +#define BIT(nr) (1UL << (nr)) +#define BIT_MASK(nr) (1UL << ((nr) % BITS_PER_LONG)) +#define BIT_WORD(nr) ((nr) / BITS_PER_LONG) +#define BITS_TO_LONGS(nr) DIV_ROUND_UP(nr, BITS_PER_BYTE * sizeof(long)) +#define BITOP_WORD(nr) ((nr) / BITS_PER_LONG) + +char ossp_log_name[OSSP_LOG_NAME_LEN]; +int ossp_log_level = OSSP_LOG_DFL; +int ossp_log_timestamp; + +static const char *severity_strs[] = { + [OSSP_LOG_CRIT] = "CRIT", + [OSSP_LOG_ERR] = " ERR", + [OSSP_LOG_WARN] = "WARN", + [OSSP_LOG_INFO] = NULL, + [OSSP_LOG_DBG0] = "DBG0", + [OSSP_LOG_DBG1] = "DBG1", +}; + +static int severity_map[] = { + [OSSP_LOG_CRIT] = LOG_ERR, + [OSSP_LOG_ERR] = LOG_ERR, + [OSSP_LOG_WARN] = LOG_WARNING, + [OSSP_LOG_INFO] = LOG_INFO, + [OSSP_LOG_DBG0] = LOG_DEBUG, + [OSSP_LOG_DBG1] = LOG_DEBUG, +}; + +void log_msg(int severity, const char *fmt, ...) +{ + static int syslog_opened = 0; + char buf[1024]; + size_t len = sizeof(buf), off = 0; + va_list ap; + + if (severity > abs(ossp_log_level)) + return; + + if (ossp_log_level < 0 && !syslog_opened) + openlog(ossp_log_name, 0, LOG_DAEMON); + + assert(severity >= 0 && severity < ARRAY_SIZE(severity_strs)); + + if (ossp_log_timestamp) { + static uint64_t start; + uint64_t now; + struct timeval tv; + gettimeofday(&tv, NULL); + now = tv.tv_sec * 1000 + tv.tv_usec / 1000; + if (!start) + start = now; + + off += snprintf(buf + off, len - off, "<%08"PRIu64"> ", + now - start); + } + + if (ossp_log_level > 0) { + char sev_buf[16] = ""; + if (severity_strs[severity]) + snprintf(sev_buf, sizeof(sev_buf), " %s", + severity_strs[severity]); + off += snprintf(buf + off, len - off, "%s%s: ", + ossp_log_name, sev_buf); + } else if (severity_strs[severity]) + off += snprintf(buf + off, len - off, "%s ", + severity_strs[severity]); + + va_start(ap, fmt); + off += vsnprintf(buf + off, len - off, fmt, ap); + va_end(ap); + + off += snprintf(buf + off, len - off, "\n"); + + if (ossp_log_level > 0) + fputs(buf, stderr); + else + syslog(severity_map[severity], "%s", buf); +} + +int read_fill(int fd, void *buf, size_t size) +{ + while (size) { + ssize_t ret; + int rc; + + ret = read(fd, buf, size); + if (ret <= 0) { + if (ret == 0) + rc = -EIO; + else + rc = -errno; + err_e(rc, "failed to read_fill %zu bytes from fd %d", + size, fd); + return rc; + } + buf += ret; + size -= ret; + } + return 0; +} + +int write_fill(int fd, const void *buf, size_t size) +{ + while (size) { + ssize_t ret; + int rc; + + ret = write(fd, buf, size); + if (ret <= 0) { + if (ret == 0) + rc = -EIO; + else + rc = -errno; + err_e(rc, "failed to write_fill %zu bytes to fd %d", + size, fd); + return rc; + } + buf += ret; + size -= ret; + } + return 0; +} + +void ring_fill(struct ring_buf *ring, const void *buf, size_t size) +{ + size_t tail; + + assert(ring_space(ring) >= size); + + tail = (ring->head + ring->size - ring->bytes) % ring->size; + + if (ring->head >= tail) { + size_t todo = min(size, ring->size - ring->head); + + memcpy(ring->buf + ring->head, buf, todo); + ring->head = (ring->head + todo) % ring->size; + ring->bytes += todo; + buf += todo; + size -= todo; + } + + assert(ring->size - ring->head >= size); + memcpy(ring->buf + ring->head, buf, size); + ring->head += size; + ring->bytes += size; +} + +void *ring_data(struct ring_buf *ring, size_t *sizep) +{ + size_t tail; + + if (!ring->bytes) + return NULL; + + tail = (ring->head + ring->size - ring->bytes) % ring->size; + + *sizep = min(ring->bytes, ring->size - tail); + return ring->buf + tail; +} + +int ring_resize(struct ring_buf *ring, size_t new_size) +{ + struct ring_buf new_ring = { .size = new_size }; + void *p; + size_t size; + + if (ring_bytes(ring) > new_size) + return -ENOSPC; + + new_ring.buf = calloc(1, new_size); + if (new_size && !new_ring.buf) + return -ENOMEM; + + while ((p = ring_data(ring, &size))) { + ring_fill(&new_ring, p, size); + ring_consume(ring, size); + } + + free(ring->buf); + *ring = new_ring; + return 0; +} + +int ensure_sbuf_size(struct sized_buf *sbuf, size_t size) +{ + char *new_buf; + + if (sbuf->size >= size) + return 0; + + new_buf = realloc(sbuf->buf, size); + if (size && !new_buf) + return -ENOMEM; + + sbuf->buf = new_buf; + sbuf->size = size; + return 0; +} + +static unsigned long __ffs(unsigned long word) +{ + int num = 0; + + if (BITS_PER_LONG == 64) { + if ((word & 0xffffffff) == 0) { + num += 32; + word >>= 32; + } + } + + if ((word & 0xffff) == 0) { + num += 16; + word >>= 16; + } + if ((word & 0xff) == 0) { + num += 8; + word >>= 8; + } + if ((word & 0xf) == 0) { + num += 4; + word >>= 4; + } + if ((word & 0x3) == 0) { + num += 2; + word >>= 2; + } + if ((word & 0x1) == 0) + num += 1; + return num; +} + +#define ffz(x) __ffs(~(x)) + +unsigned long find_next_zero_bit(const unsigned long *addr, unsigned long size, + unsigned long offset) +{ + const unsigned long *p = addr + BITOP_WORD(offset); + unsigned long result = offset & ~(BITS_PER_LONG-1); + unsigned long tmp; + + if (offset >= size) + return size; + size -= result; + offset %= BITS_PER_LONG; + if (offset) { + tmp = *(p++); + tmp |= ~0UL >> (BITS_PER_LONG - offset); + if (size < BITS_PER_LONG) + goto found_first; + if (~tmp) + goto found_middle; + size -= BITS_PER_LONG; + result += BITS_PER_LONG; + } + while (size & ~(BITS_PER_LONG-1)) { + if (~(tmp = *(p++))) + goto found_middle; + result += BITS_PER_LONG; + size -= BITS_PER_LONG; + } + if (!size) + return result; + tmp = *p; + +found_first: + tmp |= ~0UL << size; + if (tmp == ~0UL) /* Are any bits zero? */ + return result + size; /* Nope. */ +found_middle: + return result + ffz(tmp); +} + +void __set_bit(int nr, volatile unsigned long *addr) +{ + unsigned long mask = BIT_MASK(nr); + unsigned long *p = ((unsigned long *)addr) + BIT_WORD(nr); + + *p |= mask; +} + +void __clear_bit(int nr, volatile unsigned long *addr) +{ + unsigned long mask = BIT_MASK(nr); + unsigned long *p = ((unsigned long *)addr) + BIT_WORD(nr); + + *p &= ~mask; +} + +int get_proc_self_info(pid_t pid, pid_t *ppid_r, + char *cmd_buf, size_t cmd_buf_sz) + +{ + char path[64], buf[4096]; + int fd = -1; + char *cmd_start, *cmd_end, *ppid_start, *end; + ssize_t ret; + pid_t ppid; + int i, rc; + + snprintf(path, sizeof(path), "/proc/%ld/stat", (long)pid); + fd = open(path, O_RDONLY); + if (fd < 0) { + rc = -errno; + goto out; + } + + ret = read(fd, buf, sizeof(buf)); + if (ret < 0) + goto out; + if (ret == sizeof(buf)) { + rc = -EOVERFLOW; + goto out; + } + buf[ret] = '\0'; + + rc = -EINVAL; + cmd_start = strchr(buf, '('); + cmd_end = strrchr(buf, ')'); + if (!cmd_start || !cmd_end) + goto out; + cmd_start++; + + ppid_start = cmd_end; + for (i = 0; i < 3; i++) { + ppid_start = strchr(ppid_start, ' '); + if (!ppid_start) + goto out; + ppid_start++; + } + + ppid = strtoul(ppid_start, &end, 10); + if (end == ppid_start || *end != ' ') + goto out; + + if (ppid_r) + *ppid_r = ppid; + if (cmd_buf) { + size_t len = min_t(size_t, cmd_end - cmd_start, cmd_buf_sz - 1); + memcpy(cmd_buf, cmd_start, len); + cmd_buf[len] = '\0'; + } + + rc = 0; + out: + close(fd); + + return rc; +} diff --git a/ossp-util.h b/ossp-util.h new file mode 100644 index 0000000..f48d022 --- /dev/null +++ b/ossp-util.h @@ -0,0 +1,609 @@ +/* + * ossp-util - OSS Proxy: Common utilities + * + * Copyright (C) 2008-2010 SUSE Linux Products GmbH + * Copyright (C) 2008-2010 Tejun Heo + * + * This file is released under the GPLv2. + */ + +#ifndef _OSSP_UTIL_H +#define _OSSP_UTIL_H + +#include +#include +#include +#include +#include +#include +#include "ossp.h" + +#define OSSP_LOG_NAME_LEN 128 + +enum { + OSSP_LOG_CRIT = 1, + OSSP_LOG_ERR, + OSSP_LOG_WARN, + OSSP_LOG_INFO, + OSSP_LOG_DFL = OSSP_LOG_INFO, /* default log level */ + OSSP_LOG_DBG0, + OSSP_LOG_DBG1, + OSSP_LOG_MAX = OSSP_LOG_DBG1, +}; + +extern char ossp_log_name[OSSP_LOG_NAME_LEN]; +extern int ossp_log_level; +extern int ossp_log_timestamp; + +#define BITS_PER_BYTE 8 +#define BITS_PER_LONG (BITS_PER_BYTE * sizeof(long)) +#define DIV_ROUND_UP(n,d) (((n) + (d) - 1) / (d)) +#define BITS_TO_LONGS(nr) DIV_ROUND_UP(nr, BITS_PER_BYTE * sizeof(long)) + +/* ARRAY_SIZE and min/max macros stolen from linux/kernel.h */ +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) + +#define min(x, y) ({ \ + typeof(x) _min1 = (x); \ + typeof(y) _min2 = (y); \ + (void) (&_min1 == &_min2); \ + _min1 < _min2 ? _min1 : _min2; }) + +#define max(x, y) ({ \ + typeof(x) _max1 = (x); \ + typeof(y) _max2 = (y); \ + (void) (&_max1 == &_max2); \ + _max1 > _max2 ? _max1 : _max2; }) + +#define min_t(type, x, y) ({ \ + type __min1 = (x); \ + type __min2 = (y); \ + __min1 < __min2 ? __min1: __min2; }) + +#define max_t(type, x, y) ({ \ + type __max1 = (x); \ + type __max2 = (y); \ + __max1 > __max2 ? __max1: __max2; }) + +void log_msg(int severity, const char *fmt, ...) + __attribute__ ((format (printf, 2, 3))); + +#define fatal(fmt, args...) do { \ + log_msg(OSSP_LOG_CRIT, fmt , ##args); \ + _exit(1); \ +} while (0) +#define err(fmt, args...) log_msg(OSSP_LOG_ERR, fmt , ##args) +#define warn(fmt, args...) log_msg(OSSP_LOG_WARN, fmt , ##args) +#define info(fmt, args...) log_msg(OSSP_LOG_INFO, fmt , ##args) +#define dbg0(fmt, args...) log_msg(OSSP_LOG_DBG0, fmt , ##args) +#define dbg1(fmt, args...) log_msg(OSSP_LOG_DBG1, fmt , ##args) + +#define fatal_e(e, fmt, args...) \ + fatal(fmt" (%s)" , ##args, strerror(-(e))) +#define err_e(e, fmt, args...) \ + err(fmt" (%s)" , ##args, strerror(-(e))) +#define warn_e(e, fmt, args...) \ + warn(fmt" (%s)" , ##args, strerror(-(e))) +#define info_e(e, fmt, args...) \ + info(fmt" (%s)" , ##args, strerror(-(e))) +#define dbg0_e(e, fmt, args...) \ + dbg0(fmt" (%s)" , ##args, strerror(-(e))) +#define dbg1_e(e, fmt, args...) \ + dbg1(fmt" (%s)" , ##args, strerror(-(e))) + +struct ring_buf { + char *buf; + size_t size; + size_t head; + size_t bytes; +}; + +static inline size_t ring_size(struct ring_buf *ring) +{ + return ring->size; +} + +static inline size_t ring_bytes(struct ring_buf *ring) +{ + return ring->bytes; +} + +static inline size_t ring_space(struct ring_buf *ring) +{ + return ring->size - ring->bytes; +} + +static inline void ring_consume(struct ring_buf *ring, size_t size) +{ + assert(ring->bytes >= size); + ring->bytes -= size; +} + +static inline void ring_manual_init(struct ring_buf *ring, void *buf, + size_t size, size_t head, size_t bytes) +{ + ring->buf = buf; + ring->size = size; + ring->head = head; + ring->bytes = bytes; +} + +void ring_fill(struct ring_buf *ring, const void *buf, size_t size); +void *ring_data(struct ring_buf *ring, size_t *sizep); +int ring_resize(struct ring_buf *ring, size_t new_size); + +struct sized_buf { + char *buf; + size_t size; +}; + +int ensure_sbuf_size(struct sized_buf *sbuf, size_t size); + +int read_fill(int fd, void *buf, size_t size); +int write_fill(int fd, const void *buf, size_t size); + +/* + * Bitops lifted from linux asm-generic implementation. + */ +unsigned long find_next_zero_bit(const unsigned long *addr, unsigned + long size, unsigned long offset); +#define find_first_zero_bit(addr, size) find_next_zero_bit((addr), (size), 0) +extern void __set_bit(int nr, volatile unsigned long *addr); +extern void __clear_bit(int nr, volatile unsigned long *addr); + +typedef ssize_t (*ossp_action_fn_t)(enum ossp_opcode opcode, + void *carg, void *din, size_t din_sz, + void *rarg, void *dout, size_t *dout_szp, + int fd); + +int get_proc_self_info(pid_t tid, pid_t *pgrp, + char *cmd_buf, size_t cmd_buf_sz); + +/* + * Doubly linked list handling code shamelessly stolen from the Linux + * kernel 2.6.26 include/linux/list.h. + */ + +/** + * container_of - cast a member of a structure out to the containing structure + * @ptr: the pointer to the member. + * @type: the type of the container struct this is embedded in. + * @member: the name of the member within the struct. + * + */ +#define container_of(ptr, type, member) ({ \ + const typeof( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - offsetof(type,member) );}) + +#define LIST_POISON1 ((void *) 0x00100100) +#define LIST_POISON2 ((void *) 0x00200200) + +/* + * Simple doubly linked list implementation. + * + * Some of the internal functions ("__xxx") are useful when + * manipulating whole lists rather than single entries, as + * sometimes we already know the next/prev entries and we can + * generate better code by using them directly rather than + * using the generic single-entry routines. + */ + +struct list_head { + struct list_head *next, *prev; +}; + +#define LIST_HEAD_INIT(name) { &(name), &(name) } + +#define LIST_HEAD(name) \ + struct list_head name = LIST_HEAD_INIT(name) + +static inline void INIT_LIST_HEAD(struct list_head *list) +{ + list->next = list; + list->prev = list; +} + +/* + * Insert a new entry between two known consecutive entries. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static inline void __list_add(struct list_head *new, + struct list_head *prev, + struct list_head *next) +{ + next->prev = new; + new->next = next; + new->prev = prev; + prev->next = new; +} + +/** + * list_add - add a new entry + * @new: new entry to be added + * @head: list head to add it after + * + * Insert a new entry after the specified head. + * This is good for implementing stacks. + */ +static inline void list_add(struct list_head *new, struct list_head *head) +{ + __list_add(new, head, head->next); +} + +/** + * list_add_tail - add a new entry + * @new: new entry to be added + * @head: list head to add it before + * + * Insert a new entry before the specified head. + * This is useful for implementing queues. + */ +static inline void list_add_tail(struct list_head *new, struct list_head *head) +{ + __list_add(new, head->prev, head); +} + +/* + * Delete a list entry by making the prev/next entries + * point to each other. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static inline void __list_del(struct list_head * prev, struct list_head * next) +{ + next->prev = prev; + prev->next = next; +} + +/** + * list_del - deletes entry from list. + * @entry: the element to delete from the list. + * Note: list_empty() on entry does not return true after this, the entry is + * in an undefined state. + */ +static inline void list_del(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + entry->next = LIST_POISON1; + entry->prev = LIST_POISON2; +} + +/** + * list_replace - replace old entry by new one + * @old : the element to be replaced + * @new : the new element to insert + * + * If @old was empty, it will be overwritten. + */ +static inline void list_replace(struct list_head *old, + struct list_head *new) +{ + new->next = old->next; + new->next->prev = new; + new->prev = old->prev; + new->prev->next = new; +} + +static inline void list_replace_init(struct list_head *old, + struct list_head *new) +{ + list_replace(old, new); + INIT_LIST_HEAD(old); +} + +/** + * list_del_init - deletes entry from list and reinitialize it. + * @entry: the element to delete from the list. + */ +static inline void list_del_init(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + INIT_LIST_HEAD(entry); +} + +/** + * list_move - delete from one list and add as another's head + * @list: the entry to move + * @head: the head that will precede our entry + */ +static inline void list_move(struct list_head *list, struct list_head *head) +{ + __list_del(list->prev, list->next); + list_add(list, head); +} + +/** + * list_move_tail - delete from one list and add as another's tail + * @list: the entry to move + * @head: the head that will follow our entry + */ +static inline void list_move_tail(struct list_head *list, + struct list_head *head) +{ + __list_del(list->prev, list->next); + list_add_tail(list, head); +} + +/** + * list_is_last - tests whether @list is the last entry in list @head + * @list: the entry to test + * @head: the head of the list + */ +static inline int list_is_last(const struct list_head *list, + const struct list_head *head) +{ + return list->next == head; +} + +/** + * list_empty - tests whether a list is empty + * @head: the list to test. + */ +static inline int list_empty(const struct list_head *head) +{ + return head->next == head; +} + +/** + * list_empty_careful - tests whether a list is empty and not being modified + * @head: the list to test + * + * Description: + * tests whether a list is empty _and_ checks that no other CPU might be + * in the process of modifying either member (next or prev) + * + * NOTE: using list_empty_careful() without synchronization + * can only be safe if the only activity that can happen + * to the list entry is list_del_init(). Eg. it cannot be used + * if another CPU could re-list_add() it. + */ +static inline int list_empty_careful(const struct list_head *head) +{ + struct list_head *next = head->next; + return (next == head) && (next == head->prev); +} + +/** + * list_is_singular - tests whether a list has just one entry. + * @head: the list to test. + */ +static inline int list_is_singular(const struct list_head *head) +{ + return !list_empty(head) && (head->next == head->prev); +} + +static inline void __list_splice(const struct list_head *list, + struct list_head *head) +{ + struct list_head *first = list->next; + struct list_head *last = list->prev; + struct list_head *at = head->next; + + first->prev = head; + head->next = first; + + last->next = at; + at->prev = last; +} + +/** + * list_splice - join two lists + * @list: the new list to add. + * @head: the place to add it in the first list. + */ +static inline void list_splice(const struct list_head *list, + struct list_head *head) +{ + if (!list_empty(list)) + __list_splice(list, head); +} + +/** + * list_splice_init - join two lists and reinitialise the emptied list. + * @list: the new list to add. + * @head: the place to add it in the first list. + * + * The list at @list is reinitialised + */ +static inline void list_splice_init(struct list_head *list, + struct list_head *head) +{ + if (!list_empty(list)) { + __list_splice(list, head); + INIT_LIST_HEAD(list); + } +} + +/** + * list_entry - get the struct for this entry + * @ptr: the &struct list_head pointer. + * @type: the type of the struct this is embedded in. + * @member: the name of the list_struct within the struct. + */ +#define list_entry(ptr, type, member) \ + container_of(ptr, type, member) + +/** + * list_first_entry - get the first element from a list + * @ptr: the list head to take the element from. + * @type: the type of the struct this is embedded in. + * @member: the name of the list_struct within the struct. + * + * Note, that list is expected to be not empty. + */ +#define list_first_entry(ptr, type, member) \ + list_entry((ptr)->next, type, member) + +/** + * list_for_each - iterate over a list + * @pos: the &struct list_head to use as a loop cursor. + * @head: the head for your list. + */ +#define list_for_each(pos, head) \ + for (pos = (head)->next; pos != (head); pos = pos->next) + +/** + * list_for_each_prev - iterate over a list backwards + * @pos: the &struct list_head to use as a loop cursor. + * @head: the head for your list. + */ +#define list_for_each_prev(pos, head) \ + for (pos = (head)->prev; pos != (head); pos = pos->prev) + +/** + * list_for_each_safe - iterate over a list safe against removal of list entry + * @pos: the &struct list_head to use as a loop cursor. + * @n: another &struct list_head to use as temporary storage + * @head: the head for your list. + */ +#define list_for_each_safe(pos, n, head) \ + for (pos = (head)->next, n = pos->next; pos != (head); \ + pos = n, n = pos->next) + +/** + * list_for_each_prev_safe - iterate over a list backwards safe against removal of list entry + * @pos: the &struct list_head to use as a loop cursor. + * @n: another &struct list_head to use as temporary storage + * @head: the head for your list. + */ +#define list_for_each_prev_safe(pos, n, head) \ + for (pos = (head)->prev, n = pos->prev; \ + pos != (head); pos = n, n = pos->prev) + +/** + * list_for_each_entry - iterate over list of given type + * @pos: the type * to use as a loop cursor. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry(pos, head, member) \ + for (pos = list_entry((head)->next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = list_entry(pos->member.next, typeof(*pos), member)) + +/** + * list_for_each_entry_reverse - iterate backwards over list of given type. + * @pos: the type * to use as a loop cursor. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry_reverse(pos, head, member) \ + for (pos = list_entry((head)->prev, typeof(*pos), member); \ + &pos->member != (head); \ + pos = list_entry(pos->member.prev, typeof(*pos), member)) + +/** + * list_prepare_entry - prepare a pos entry for use in list_for_each_entry_continue() + * @pos: the type * to use as a start point + * @head: the head of the list + * @member: the name of the list_struct within the struct. + * + * Prepares a pos entry for use as a start point in list_for_each_entry_continue(). + */ +#define list_prepare_entry(pos, head, member) \ + ((pos) ? : list_entry(head, typeof(*pos), member)) + +/** + * list_for_each_entry_continue - continue iteration over list of given type + * @pos: the type * to use as a loop cursor. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + * + * Continue to iterate over list of given type, continuing after + * the current position. + */ +#define list_for_each_entry_continue(pos, head, member) \ + for (pos = list_entry(pos->member.next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = list_entry(pos->member.next, typeof(*pos), member)) + +/** + * list_for_each_entry_continue_reverse - iterate backwards from the given point + * @pos: the type * to use as a loop cursor. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + * + * Start to iterate over list of given type backwards, continuing after + * the current position. + */ +#define list_for_each_entry_continue_reverse(pos, head, member) \ + for (pos = list_entry(pos->member.prev, typeof(*pos), member); \ + &pos->member != (head); \ + pos = list_entry(pos->member.prev, typeof(*pos), member)) + +/** + * list_for_each_entry_from - iterate over list of given type from the current point + * @pos: the type * to use as a loop cursor. + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + * + * Iterate over list of given type, continuing from current position. + */ +#define list_for_each_entry_from(pos, head, member) \ + for (; &pos->member != (head); \ + pos = list_entry(pos->member.next, typeof(*pos), member)) + +/** + * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry + * @pos: the type * to use as a loop cursor. + * @n: another type * to use as temporary storage + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + */ +#define list_for_each_entry_safe(pos, n, head, member) \ + for (pos = list_entry((head)->next, typeof(*pos), member), \ + n = list_entry(pos->member.next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = n, n = list_entry(n->member.next, typeof(*n), member)) + +/** + * list_for_each_entry_safe_continue + * @pos: the type * to use as a loop cursor. + * @n: another type * to use as temporary storage + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + * + * Iterate over list of given type, continuing after current point, + * safe against removal of list entry. + */ +#define list_for_each_entry_safe_continue(pos, n, head, member) \ + for (pos = list_entry(pos->member.next, typeof(*pos), member), \ + n = list_entry(pos->member.next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = n, n = list_entry(n->member.next, typeof(*n), member)) + +/** + * list_for_each_entry_safe_from + * @pos: the type * to use as a loop cursor. + * @n: another type * to use as temporary storage + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + * + * Iterate over list of given type from current point, safe against + * removal of list entry. + */ +#define list_for_each_entry_safe_from(pos, n, head, member) \ + for (n = list_entry(pos->member.next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = n, n = list_entry(n->member.next, typeof(*n), member)) + +/** + * list_for_each_entry_safe_reverse + * @pos: the type * to use as a loop cursor. + * @n: another type * to use as temporary storage + * @head: the head for your list. + * @member: the name of the list_struct within the struct. + * + * Iterate backwards over list of given type, safe against removal + * of list entry. + */ +#define list_for_each_entry_safe_reverse(pos, n, head, member) \ + for (pos = list_entry((head)->prev, typeof(*pos), member), \ + n = list_entry(pos->member.prev, typeof(*pos), member); \ + &pos->member != (head); \ + pos = n, n = list_entry(n->member.prev, typeof(*n), member)) + +#endif /*_OSSP_UTIL_H*/ diff --git a/ossp.c b/ossp.c new file mode 100644 index 0000000..96f98fa --- /dev/null +++ b/ossp.c @@ -0,0 +1,83 @@ +/* + * ossp - OSS Proxy: emulate OSS device using CUSE + * + * Copyright (C) 2008-2010 SUSE Linux Products GmbH + * Copyright (C) 2008-2010 Tejun Heo + * + * This file is released under the GPLv2. + */ + +#include "ossp.h" + +const struct ossp_arg_size ossp_arg_sizes[OSSP_NR_OPCODES] = { + [OSSP_MIXER] = { sizeof(struct ossp_mixer_arg), + sizeof(struct ossp_mixer_arg), 0 }, + + [OSSP_DSP_OPEN] = { sizeof(struct ossp_dsp_open_arg), 0, 0 }, + [OSSP_DSP_READ] = { sizeof(struct ossp_dsp_rw_arg), 0, 0 }, + [OSSP_DSP_WRITE] = { sizeof(struct ossp_dsp_rw_arg), 0, 0 }, + [OSSP_DSP_POLL] = { sizeof(int), sizeof(unsigned), 0 }, + [OSSP_DSP_MMAP] = { sizeof(struct ossp_dsp_mmap_arg), 0, 0 }, + [OSSP_DSP_MUNMAP] = { sizeof(int), 0, 0 }, + + [OSSP_DSP_RESET] = { 0, 0, 0 }, + [OSSP_DSP_SYNC] = { 0, 0, 0 }, + [OSSP_DSP_POST] = { 0, 0, 0 }, + [OSSP_DSP_GET_RATE] = { 0, sizeof(int), 0 }, + [OSSP_DSP_GET_CHANNELS] = { 0, sizeof(int), 0 }, + [OSSP_DSP_GET_FORMAT] = { 0, sizeof(int), 0 }, + [OSSP_DSP_GET_BLKSIZE] = { 0, sizeof(int), 0 }, + [OSSP_DSP_GET_FORMATS] = { 0, sizeof(int), 0 }, + [OSSP_DSP_SET_RATE] = { sizeof(int), sizeof(int), 0 }, + [OSSP_DSP_SET_CHANNELS] = { sizeof(int), sizeof(int), 0 }, + [OSSP_DSP_SET_FORMAT] = { sizeof(int), sizeof(int), 0 }, + [OSSP_DSP_SET_SUBDIVISION] = { sizeof(int), sizeof(int), 0 }, + [OSSP_DSP_SET_FRAGMENT] = { sizeof(int), 0, 0 }, + [OSSP_DSP_GET_TRIGGER] = { 0, sizeof(int), 0 }, + [OSSP_DSP_SET_TRIGGER] = { sizeof(int), 0, 0 }, + [OSSP_DSP_GET_OSPACE] = { 0, sizeof(struct audio_buf_info), 0 }, + [OSSP_DSP_GET_ISPACE] = { 0, sizeof(struct audio_buf_info), 0 }, + [OSSP_DSP_GET_OPTR] = { 0, sizeof(struct count_info), 0 }, + [OSSP_DSP_GET_IPTR] = { 0, sizeof(struct count_info), 0 }, + [OSSP_DSP_GET_ODELAY] = { 0, sizeof(int), 0 }, +}; + +const char *ossp_cmd_str[OSSP_NR_OPCODES] = { + [OSSP_MIXER] = "MIXER", + + [OSSP_DSP_OPEN] = "OPEN", + [OSSP_DSP_READ] = "READ", + [OSSP_DSP_WRITE] = "WRITE", + [OSSP_DSP_POLL] = "POLL", + [OSSP_DSP_MMAP] = "MMAP", + [OSSP_DSP_MUNMAP] = "MUNMAP", + + [OSSP_DSP_RESET] = "RESET", + [OSSP_DSP_SYNC] = "SYNC", + [OSSP_DSP_POST] = "POST", + + [OSSP_DSP_GET_RATE] = "GET_RATE", + [OSSP_DSP_GET_CHANNELS] = "GET_CHANNELS", + [OSSP_DSP_GET_FORMAT] = "GET_FORMAT", + [OSSP_DSP_GET_BLKSIZE] = "GET_BLKSIZE", + [OSSP_DSP_GET_FORMATS] = "GET_FORMATS", + [OSSP_DSP_SET_RATE] = "SET_RATE", + [OSSP_DSP_SET_CHANNELS] = "SET_CHANNELS", + [OSSP_DSP_SET_FORMAT] = "SET_FORMAT", + [OSSP_DSP_SET_SUBDIVISION] = "SET_BUSDIVISION", + + [OSSP_DSP_SET_FRAGMENT] = "SET_FRAGMENT", + [OSSP_DSP_GET_TRIGGER] = "GET_TRIGGER", + [OSSP_DSP_SET_TRIGGER] = "SET_TRIGGER", + [OSSP_DSP_GET_OSPACE] = "GET_OSPACE", + [OSSP_DSP_GET_ISPACE] = "GET_ISPACE", + [OSSP_DSP_GET_OPTR] = "GET_OPTR", + [OSSP_DSP_GET_IPTR] = "GET_IPTR", + [OSSP_DSP_GET_ODELAY] = "GET_ODELAY", +}; + +const char *ossp_notify_str[OSSP_NR_NOTIFY_OPCODES] = { + [OSSP_NOTIFY_POLL] = "POLL", + [OSSP_NOTIFY_OBITUARY] = "OBITUARY", + [OSSP_NOTIFY_VOLCHG] = "VOLCHG", +}; diff --git a/ossp.h b/ossp.h new file mode 100644 index 0000000..9d03e63 --- /dev/null +++ b/ossp.h @@ -0,0 +1,117 @@ +/* + * ossp - OSS Proxy: emulate OSS device using CUSE + * + * Copyright (C) 2008-2010 SUSE Linux Products GmbH + * Copyright (C) 2008-2010 Tejun Heo + * + * This file is released under the GPLv2. + */ + +#ifndef _OSSP_H +#define _OSSP_H + +#include +#include +#include + +#define OSSP_VERSION "1.3.2" +#define OSSP_CMD_MAGIC 0xdeadbeef +#define OSSP_REPLY_MAGIC 0xbeefdead +#define OSSP_NOTIFY_MAGIC 0xbebebebe + +#define PLAY 0 +#define REC 1 +#define LEFT 0 +#define RIGHT 1 + +enum ossp_opcode { + OSSP_MIXER, + + OSSP_DSP_OPEN, + OSSP_DSP_READ, + OSSP_DSP_WRITE, + OSSP_DSP_POLL, + OSSP_DSP_MMAP, + OSSP_DSP_MUNMAP, + + OSSP_DSP_RESET, + OSSP_DSP_SYNC, + OSSP_DSP_POST, + + OSSP_DSP_GET_RATE, + OSSP_DSP_GET_CHANNELS, + OSSP_DSP_GET_FORMAT, + OSSP_DSP_GET_BLKSIZE, + OSSP_DSP_GET_FORMATS, + OSSP_DSP_SET_RATE, + OSSP_DSP_SET_CHANNELS, + OSSP_DSP_SET_FORMAT, + OSSP_DSP_SET_SUBDIVISION, + + OSSP_DSP_SET_FRAGMENT, + OSSP_DSP_GET_TRIGGER, + OSSP_DSP_SET_TRIGGER, + OSSP_DSP_GET_OSPACE, + OSSP_DSP_GET_ISPACE, + OSSP_DSP_GET_OPTR, + OSSP_DSP_GET_IPTR, + OSSP_DSP_GET_ODELAY, + + OSSP_NR_OPCODES, +}; + +enum ossp_notify_opcode { + OSSP_NOTIFY_POLL, + OSSP_NOTIFY_OBITUARY, + OSSP_NOTIFY_VOLCHG, + + OSSP_NR_NOTIFY_OPCODES, +}; + +struct ossp_mixer_arg { + int vol[2][2]; +}; + +struct ossp_dsp_open_arg { + int flags; + pid_t opener_pid; +}; + +struct ossp_dsp_rw_arg { + unsigned nonblock:1; +}; + +struct ossp_dsp_mmap_arg { + int dir; + size_t size; +}; + +struct ossp_cmd { + unsigned magic; + enum ossp_opcode opcode; + size_t din_size; + size_t dout_size; +}; + +struct ossp_reply { + unsigned magic; + int result; + size_t dout_size; /* <= cmd.data_in_size */ +}; + +struct ossp_notify { + unsigned magic; + enum ossp_notify_opcode opcode; +}; + +struct ossp_arg_size { + ssize_t carg_size; + ssize_t rarg_size; + unsigned has_fd:1; +}; + +extern const struct ossp_arg_size ossp_arg_sizes[OSSP_NR_OPCODES]; +extern const char *ossp_cmd_str[OSSP_NR_OPCODES]; +extern const char *ossp_notify_str[OSSP_NR_NOTIFY_OPCODES]; + +#endif /* _OSSP_H */ diff --git a/osspd.c b/osspd.c new file mode 100644 index 0000000..37c9b35 --- /dev/null +++ b/osspd.c @@ -0,0 +1,2295 @@ +/* + * osspd - OSS Proxy Daemon: emulate OSS device using CUSE + * + * Copyright (C) 2008-2010 SUSE Linux Products GmbH + * Copyright (C) 2008-2010 Tejun Heo + * + * This file is released under the GPLv2. + */ + +#define FUSE_USE_VERSION 28 +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ossp.h" +#include "ossp-util.h" + +/* + * MMAP support needs to be updated to the new fuse MMAP API. Disable + * it for the time being. + */ +#warning mmap support disabled for now +/* #define OSSP_MMAP */ + +#define DFL_MIXER_NAME "mixer" +#define DFL_DSP_NAME "dsp" +#define DFL_ADSP_NAME "adsp" +#define STRFMT "S[%u/%d]" +#define STRID(os) os->id, os->pid + +#define dbg1_os(os, fmt, args...) dbg1(STRFMT" "fmt, STRID(os) , ##args) +#define dbg0_os(os, fmt, args...) dbg0(STRFMT" "fmt, STRID(os) , ##args) +#define warn_os(os, fmt, args...) warn(STRFMT" "fmt, STRID(os) , ##args) +#define err_os(os, fmt, args...) err(STRFMT" "fmt, STRID(os) , ##args) +#define warn_ose(os, err, fmt, args...) \ + warn_e(err, STRFMT" "fmt, STRID(os) , ##args) +#define err_ose(os, err, fmt, args...) \ + err_e(err, STRFMT" "fmt, STRID(os) , ##args) + +enum { + SNDRV_OSS_VERSION = ((3<<16)|(8<<8)|(1<<4)|(0)), /* 3.8.1a */ + DFL_MIXER_MAJOR = 14, + DFL_MIXER_MINOR = 0, + DFL_DSP_MAJOR = 14, + DFL_DSP_MINOR = 3, + DFL_ADSP_MAJOR = 14, + DFL_ADSP_MINOR = 12, + DFL_MAX_STREAMS = 128, + MIXER_PUT_DELAY = 600, /* 10 mins */ + /* DSPS_MMAP_SIZE / 2 must be multiple of SHMLBA */ + DSPS_MMAP_SIZE = 2 * (512 << 10), /* 512k for each dir */ +}; + +struct ossp_uid_cnt { + struct list_head link; + uid_t uid; + unsigned nr_os; +}; + +struct ossp_mixer { + pid_t pgrp; + struct list_head link; + struct list_head delayed_put_link; + unsigned refcnt; + /* the following two fields are protected by mixer_mutex */ + int vol[2][2]; + int modify_counter; + time_t put_expires; +}; + +struct ossp_mixer_cmd { + struct ossp_mixer *mixer; + struct ossp_mixer_arg set; + int out_dir; + int rvol; +}; + +#define for_each_vol(i, j) \ + for (i = 0, j = 0; i < 2; j += i << 1, j++, i = j >> 1, j &= 1) + +struct ossp_stream { + unsigned id; /* stream ID */ + struct list_head link; + struct list_head pgrp_link; + struct list_head notify_link; + unsigned refcnt; + pthread_mutex_t cmd_mutex; + pthread_mutex_t mmap_mutex; + struct fuse_pollhandle *ph; + + /* stream owner info */ + pid_t pid; + pid_t pgrp; + uid_t uid; + gid_t gid; + + /* slave info */ + pid_t slave_pid; + int cmd_fd; + int notify_tx; + int notify_rx; + + /* the following dead flag is set asynchronously, keep it separate. */ + int dead; + + /* stream mixer state, protected by mixer_mutex */ + int mixer_pending; + int vol[2][2]; + int vol_set[2][2]; + + off_t mmap_off; + size_t mmap_size; + + struct ossp_uid_cnt *ucnt; + struct fuse_session *se; /* associated fuse session */ + struct ossp_mixer *mixer; +}; + +struct ossp_dsp_stream { + struct ossp_stream os; + unsigned rw; + unsigned mmapped; + int nonblock; +}; + +#define os_to_dsps(_os) container_of(_os, struct ossp_dsp_stream, os) + +static unsigned max_streams; +static unsigned umax_streams; +static unsigned hashtbl_size; +static char dsp_slave_path[PATH_MAX]; + +static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t mixer_mutex = PTHREAD_MUTEX_INITIALIZER; +static unsigned long *os_id_bitmap; +static unsigned nr_mixers; +static struct list_head *mixer_tbl; /* indexed by PGRP */ +static struct list_head *os_tbl; /* indexed by ID */ +static struct list_head *os_pgrp_tbl; /* indexed by PGRP */ +static struct list_head *os_notify_tbl; /* indexed by notify fd */ +static LIST_HEAD(uid_cnt_list); +static int notify_epfd; /* epoll used to monitor notify fds */ +static pthread_t notify_poller_thread; +static pthread_t slave_reaper_thread; +static pthread_t mixer_delayed_put_thread; +static pthread_t cuse_mixer_thread; +static pthread_t cuse_adsp_thread; +static pthread_cond_t notify_poller_kill_wait = PTHREAD_COND_INITIALIZER; +static pthread_cond_t slave_reaper_wait = PTHREAD_COND_INITIALIZER; +static LIST_HEAD(slave_corpse_list); +static LIST_HEAD(mixer_delayed_put_head); /* delayed reference */ +static pthread_cond_t mixer_delayed_put_cond = PTHREAD_COND_INITIALIZER; + +static int init_wait_fd = -1; +static int exit_on_idle; +static struct fuse_session *mixer_se; +static struct fuse_session *dsp_se; +static struct fuse_session *adsp_se; + +static void put_os(struct ossp_stream *os); + + +/*************************************************************************** + * Accessors + */ + +static struct list_head *mixer_tbl_head(pid_t pid) +{ + return &mixer_tbl[pid % hashtbl_size]; +} + +static struct list_head *os_tbl_head(uint64_t id) +{ + return &os_tbl[id % hashtbl_size]; +} + +static struct list_head *os_pgrp_tbl_head(pid_t pgrp) +{ + return &os_pgrp_tbl[pgrp % hashtbl_size]; +} + +static struct list_head *os_notify_tbl_head(int notify_rx) +{ + return &os_notify_tbl[notify_rx % hashtbl_size]; +} + +static struct ossp_mixer *find_mixer_locked(pid_t pgrp) +{ + struct ossp_mixer *mixer; + + list_for_each_entry(mixer, mixer_tbl_head(pgrp), link) + if (mixer->pgrp == pgrp) + return mixer; + return NULL; +} + +static struct ossp_mixer *find_mixer(pid_t pgrp) +{ + struct ossp_mixer *mixer; + + pthread_mutex_lock(&mutex); + mixer = find_mixer_locked(pgrp); + pthread_mutex_unlock(&mutex); + return mixer; +} + +static struct ossp_stream *find_os(unsigned id) +{ + struct ossp_stream *os, *found = NULL; + + pthread_mutex_lock(&mutex); + list_for_each_entry(os, os_tbl_head(id), link) + if (os->id == id) { + found = os; + break; + } + pthread_mutex_unlock(&mutex); + return found; +} + +static struct ossp_stream *find_os_by_notify_rx(int notify_rx) +{ + struct ossp_stream *os, *found = NULL; + + pthread_mutex_lock(&mutex); + list_for_each_entry(os, os_notify_tbl_head(notify_rx), notify_link) + if (os->notify_rx == notify_rx) { + found = os; + break; + } + pthread_mutex_unlock(&mutex); + return found; +} + + +/*************************************************************************** + * Command and ioctl helpers + */ + +static ssize_t exec_cmd_intern(struct ossp_stream *os, enum ossp_opcode opcode, + const void *carg, size_t carg_size, const void *din, size_t din_size, + void *rarg, size_t rarg_size, void *dout, size_t *dout_sizep, int fd) +{ + size_t dout_size = dout_sizep ? *dout_sizep : 0; + struct ossp_cmd cmd = { .magic = OSSP_CMD_MAGIC, .opcode = opcode, + .din_size = din_size, + .dout_size = dout_size }; + struct iovec iov = { &cmd, sizeof(cmd) }; + struct msghdr msg = { .msg_iov = &iov, .msg_iovlen = 1 }; + struct ossp_reply reply = { }; + char cmsg_buf[CMSG_SPACE(sizeof(fd))]; + char reason[512]; + int rc; + + if (os->dead) + return -EIO; + + dbg1_os(os, "%s carg=%zu din=%zu rarg=%zu dout=%zu", + ossp_cmd_str[opcode], carg_size, din_size, rarg_size, + dout_size); + + if (fd >= 0) { + struct cmsghdr *cmsg; + + msg.msg_control = cmsg_buf; + msg.msg_controllen = sizeof(cmsg_buf); + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(fd)); + *(int *)CMSG_DATA(cmsg) = fd; + msg.msg_controllen = cmsg->cmsg_len; + } + + if (sendmsg(os->cmd_fd, &msg, 0) <= 0) { + rc = -errno; + snprintf(reason, sizeof(reason), "command sendmsg failed: %s", + strerror(-rc)); + goto fail; + } + + if ((rc = write_fill(os->cmd_fd, carg, carg_size)) < 0 || + (rc = write_fill(os->cmd_fd, din, din_size)) < 0) { + snprintf(reason, sizeof(reason), + "can't tranfer command argument and/or data: %s", + strerror(-rc)); + goto fail; + } + if ((rc = read_fill(os->cmd_fd, &reply, sizeof(reply))) < 0) { + snprintf(reason, sizeof(reason), "can't read reply: %s", + strerror(-rc)); + goto fail; + } + + if (reply.magic != OSSP_REPLY_MAGIC) { + snprintf(reason, sizeof(reason), + "reply magic mismatch %x != %x", + reply.magic, OSSP_REPLY_MAGIC); + rc = -EINVAL; + goto fail; + } + + if (reply.result < 0) + goto out_unlock; + + if (reply.dout_size > dout_size) { + snprintf(reason, sizeof(reason), + "data out size overflow %zu > %zu", + reply.dout_size, dout_size); + rc = -EINVAL; + goto fail; + } + + dout_size = reply.dout_size; + if (dout_sizep) + *dout_sizep = dout_size; + + if ((rc = read_fill(os->cmd_fd, rarg, rarg_size)) < 0 || + (rc = read_fill(os->cmd_fd, dout, dout_size)) < 0) { + snprintf(reason, sizeof(reason), "can't read data out: %s", + strerror(-rc)); + goto fail; + } + +out_unlock: + dbg1_os(os, " completed, result=%d dout=%zu", + reply.result, dout_size); + return reply.result; + +fail: + warn_os(os, "communication with slave failed (%s)", reason); + os->dead = 1; + return rc; +} + +static ssize_t exec_cmd(struct ossp_stream *os, enum ossp_opcode opcode, + const void *carg, size_t carg_size, const void *din, size_t din_size, + void *rarg, size_t rarg_size, void *dout, size_t *dout_sizep, int fd) +{ + int is_mixer; + int i, j; + ssize_t ret, mret; + + /* mixer command is handled exlicitly below */ + is_mixer = opcode == OSSP_MIXER; + if (is_mixer) { + ret = -pthread_mutex_trylock(&os->cmd_mutex); + if (ret) + return ret; + } else { + pthread_mutex_lock(&os->cmd_mutex); + + ret = exec_cmd_intern(os, opcode, carg, carg_size, + din, din_size, rarg, rarg_size, + dout, dout_sizep, fd); + } + + /* lazy mixer handling */ + pthread_mutex_lock(&mixer_mutex); + + if (os->mixer_pending) { + struct ossp_mixer_arg marg; + repeat_mixer: + /* we have mixer command pending */ + memcpy(marg.vol, os->vol_set, sizeof(os->vol_set)); + memset(os->vol_set, -1, sizeof(os->vol_set)); + + pthread_mutex_unlock(&mixer_mutex); + mret = exec_cmd_intern(os, OSSP_MIXER, &marg, sizeof(marg), + NULL, 0, &marg, sizeof(marg), NULL, NULL, + -1); + pthread_mutex_lock(&mixer_mutex); + + /* was there mixer set request while executing mixer command? */ + for_each_vol(i, j) + if (os->vol_set[i][j] >= 0) + goto repeat_mixer; + + /* update internal mixer state */ + if (mret == 0) { + for_each_vol(i, j) { + if (marg.vol[i][j] >= 0) { + if (os->vol[i][j] != marg.vol[i][j]) + os->mixer->modify_counter++; + os->vol[i][j] = marg.vol[i][j]; + } + } + } + os->mixer_pending = 0; + } + + pthread_mutex_unlock(&os->cmd_mutex); + + /* + * mixer mutex must be released after cmd_mutex so that + * exec_mixer_cmd() can guarantee that mixer_pending flags + * will be handled immediately or when the currently + * in-progress command completes. + */ + pthread_mutex_unlock(&mixer_mutex); + + return is_mixer ? mret : ret; +} + +static ssize_t exec_simple_cmd(struct ossp_stream *os, + enum ossp_opcode opcode, void *carg, void *rarg) +{ + return exec_cmd(os, opcode, + carg, ossp_arg_sizes[opcode].carg_size, NULL, 0, + rarg, ossp_arg_sizes[opcode].rarg_size, NULL, NULL, -1); +} + +static int ioctl_prep_uarg(fuse_req_t req, void *in, size_t in_sz, void *out, + size_t out_sz, void *uarg, const void *in_buf, + size_t in_bufsz, size_t out_bufsz) +{ + struct iovec in_iov = { }, out_iov = { }; + int retry = 0; + + if (in) { + if (!in_bufsz) { + in_iov.iov_base = uarg; + in_iov.iov_len = in_sz; + retry = 1; + } else { + assert(in_bufsz == in_sz); + memcpy(in, in_buf, in_sz); + } + } + + if (out) { + if (!out_bufsz) { + out_iov.iov_base = uarg; + out_iov.iov_len = out_sz; + retry = 1; + } else + assert(out_bufsz == out_sz); + } + + if (retry) + fuse_reply_ioctl_retry(req, &in_iov, 1, &out_iov, 1); + + return retry; +} + +#define PREP_UARG(inp, outp) do { \ + if (ioctl_prep_uarg(req, (inp), sizeof(*(inp)), \ + (outp), sizeof(*(outp)), uarg, \ + in_buf, in_bufsz, out_bufsz)) \ + return; \ +} while (0) + +#define IOCTL_RETURN(result, outp) do { \ + if ((outp) != NULL) \ + fuse_reply_ioctl(req, result, (outp), sizeof(*(outp))); \ + else \ + fuse_reply_ioctl(req, result, NULL, 0); \ + return; \ +} while (0) + + +/*************************************************************************** + * Mixer implementation + */ + +static void put_mixer_real(struct ossp_mixer *mixer) +{ + if (!--mixer->refcnt) { + dbg0("DESTROY mixer(%d)", mixer->pgrp); + list_del_init(&mixer->link); + list_del_init(&mixer->delayed_put_link); + free(mixer); + nr_mixers--; + + /* + * If exit_on_idle, mixer for pgrp0 is touched during + * init and each stream has mixer attached. As mixers + * are destroyed after they have been idle for + * MIXER_PUT_DELAY seconds, we can use it for idle + * detection. Note that this might race with + * concurrent open. The race is inherent. + */ + if (exit_on_idle && !nr_mixers) { + info("idle, exiting"); + exit(0); + } + } +} + +static struct ossp_mixer *get_mixer(pid_t pgrp) +{ + struct ossp_mixer *mixer; + + pthread_mutex_lock(&mutex); + + /* is there a matching one? */ + mixer = find_mixer_locked(pgrp); + if (mixer) { + if (list_empty(&mixer->delayed_put_link)) + mixer->refcnt++; + else + list_del_init(&mixer->delayed_put_link); + goto out_unlock; + } + + /* reap delayed put list if there are too many mixers */ + while (nr_mixers > 2 * max_streams && + !list_empty(&mixer_delayed_put_head)) { + struct ossp_mixer *mixer = + list_first_entry(&mixer_delayed_put_head, + struct ossp_mixer, delayed_put_link); + + assert(mixer->refcnt == 1); + put_mixer_real(mixer); + } + + /* create a new one */ + mixer = calloc(1, sizeof(*mixer)); + if (!mixer) { + warn("failed to allocate mixer for %d", pgrp); + mixer = NULL; + goto out_unlock; + } + + mixer->pgrp = pgrp; + INIT_LIST_HEAD(&mixer->link); + INIT_LIST_HEAD(&mixer->delayed_put_link); + mixer->refcnt = 1; + memset(mixer->vol, -1, sizeof(mixer->vol)); + + list_add(&mixer->link, mixer_tbl_head(pgrp)); + nr_mixers++; + dbg0("CREATE mixer(%d)", pgrp); + +out_unlock: + pthread_mutex_unlock(&mutex); + return mixer; +} + +static void put_mixer(struct ossp_mixer *mixer) +{ + pthread_mutex_lock(&mutex); + + if (mixer) { + if (mixer->refcnt == 1) { + struct timespec ts; + + clock_gettime(CLOCK_REALTIME, &ts); + mixer->put_expires = ts.tv_sec + MIXER_PUT_DELAY; + list_add_tail(&mixer->delayed_put_link, + &mixer_delayed_put_head); + pthread_cond_signal(&mixer_delayed_put_cond); + } else + put_mixer_real(mixer); + } + + pthread_mutex_unlock(&mutex); +} + +static void *mixer_delayed_put_worker(void *arg) +{ + struct ossp_mixer *mixer; + struct timespec ts; + time_t now; + + pthread_mutex_lock(&mutex); +again: + clock_gettime(CLOCK_REALTIME, &ts); + now = ts.tv_sec; + + mixer = NULL; + while (!list_empty(&mixer_delayed_put_head)) { + mixer = list_first_entry(&mixer_delayed_put_head, + struct ossp_mixer, delayed_put_link); + + if (now <= mixer->put_expires) + break; + + assert(mixer->refcnt == 1); + put_mixer_real(mixer); + mixer = NULL; + } + + if (mixer) { + ts.tv_sec = mixer->put_expires + 1; + pthread_cond_timedwait(&mixer_delayed_put_cond, &mutex, &ts); + } else + pthread_cond_wait(&mixer_delayed_put_cond, &mutex); + + goto again; +} + +static void init_mixer_cmd(struct ossp_mixer_cmd *mxcmd, + struct ossp_mixer *mixer) +{ + memset(mxcmd, 0, sizeof(*mxcmd)); + memset(&mxcmd->set.vol, -1, sizeof(mxcmd->set.vol)); + mxcmd->mixer = mixer; + mxcmd->out_dir = -1; +} + +static int exec_mixer_cmd(struct ossp_mixer_cmd *mxcmd, struct ossp_stream *os) +{ + int i, j, rc; + + /* + * Set pending flags before trying to execute mixer command. + * Combined with lock release order in exec_cmd(), this + * guarantees that the mixer command will be executed + * immediately or when the current command completes. + */ + pthread_mutex_lock(&mixer_mutex); + os->mixer_pending = 1; + for_each_vol(i, j) + if (mxcmd->set.vol[i][j] >= 0) + os->vol_set[i][j] = mxcmd->set.vol[i][j]; + pthread_mutex_unlock(&mixer_mutex); + + rc = exec_simple_cmd(os, OSSP_MIXER, NULL, NULL); + if (rc >= 0) { + dbg0_os(os, "volume set=%d/%d:%d/%d get=%d/%d:%d/%d", + mxcmd->set.vol[PLAY][LEFT], mxcmd->set.vol[PLAY][RIGHT], + mxcmd->set.vol[REC][LEFT], mxcmd->set.vol[REC][RIGHT], + os->vol[PLAY][LEFT], os->vol[PLAY][RIGHT], + os->vol[REC][LEFT], os->vol[REC][RIGHT]); + } else if (rc != -EBUSY) + warn_ose(os, rc, "mixer command failed"); + + return rc; +} + +static void finish_mixer_cmd(struct ossp_mixer_cmd *mxcmd) +{ + struct ossp_mixer *mixer = mxcmd->mixer; + struct ossp_stream *os; + int dir = mxcmd->out_dir; + int vol[2][2] = { }; + int cnt[2][2] = { }; + int i, j; + + pthread_mutex_lock(&mixer_mutex); + + /* get volume of all streams attached to this mixer */ + pthread_mutex_lock(&mutex); + list_for_each_entry(os, os_pgrp_tbl_head(mixer->pgrp), pgrp_link) { + if (os->pgrp != mixer->pgrp) + continue; + for_each_vol(i, j) { + if (os->vol[i][j] < 0) + continue; + vol[i][j] += os->vol[i][j]; + cnt[i][j]++; + } + } + pthread_mutex_unlock(&mutex); + + /* calculate the summary volume values */ + for_each_vol(i, j) { + if (mxcmd->set.vol[i][j] >= 0) + vol[i][j] = mxcmd->set.vol[i][j]; + else if (cnt[i][j]) + vol[i][j] = vol[i][j] / cnt[i][j]; + else if (mixer->vol[i][j] >= 0) + vol[i][j] = mixer->vol[i][j]; + else + vol[i][j] = 100; + + vol[i][j] = min(max(0, vol[i][j]), 100); + } + + if (dir >= 0) + mxcmd->rvol = vol[dir][LEFT] | (vol[dir][RIGHT] << 8); + + pthread_mutex_unlock(&mixer_mutex); +} + +static void mixer_simple_ioctl(fuse_req_t req, struct ossp_mixer *mixer, + unsigned cmd, void *uarg, const void *in_buf, + size_t in_bufsz, size_t out_bufsz, + int *not_minep) +{ + const char *id = "OSS Proxy", *name = "Mixer"; + int i; + + switch (cmd) { + case SOUND_MIXER_INFO: { + struct mixer_info info = { }; + + PREP_UARG(NULL, &info); + strncpy(info.id, id, sizeof(info.id) - 1); + strncpy(info.name, name, sizeof(info.name) - 1); + info.modify_counter = mixer->modify_counter; + IOCTL_RETURN(0, &info); + } + + case SOUND_OLD_MIXER_INFO: { + struct _old_mixer_info info = { }; + + PREP_UARG(NULL, &info); + strncpy(info.id, id, sizeof(info.id) - 1); + strncpy(info.name, name, sizeof(info.name) - 1); + IOCTL_RETURN(0, &info); + } + + case OSS_GETVERSION: + i = SNDRV_OSS_VERSION; + goto puti; + case SOUND_MIXER_READ_DEVMASK: + case SOUND_MIXER_READ_STEREODEVS: + i = SOUND_MASK_PCM | SOUND_MASK_IGAIN; + goto puti; + case SOUND_MIXER_READ_CAPS: + i = SOUND_CAP_EXCL_INPUT; + goto puti; + case SOUND_MIXER_READ_RECMASK: + case SOUND_MIXER_READ_RECSRC: + i = SOUND_MASK_IGAIN; + goto puti; + puti: + PREP_UARG(NULL, &i); + IOCTL_RETURN(0, &i); + + case SOUND_MIXER_WRITE_RECSRC: + IOCTL_RETURN(0, NULL); + + default: + *not_minep = 1; + return; + } + assert(0); +} + +static void mixer_do_ioctl(fuse_req_t req, struct ossp_mixer *mixer, + unsigned cmd, void *uarg, const void *in_buf, + size_t in_bufsz, size_t out_bufsz) +{ + struct ossp_mixer_cmd mxcmd; + struct ossp_stream *os, **osa; + int not_mine = 0; + int slot = cmd & 0xff, dir; + int nr_os; + int i, rc; + + mixer_simple_ioctl(req, mixer, cmd, uarg, in_buf, in_bufsz, out_bufsz, + ¬_mine); + if (!not_mine) + return; + + rc = -ENXIO; + if (!(cmd & (SIOC_IN | SIOC_OUT))) + goto err; + + /* + * Okay, it's not one of the easy ones. Build mxcmd for + * actual volume control. + */ + if (cmd & SIOC_IN) + PREP_UARG(&i, &i); + else + PREP_UARG(NULL, &i); + + switch (slot) { + case SOUND_MIXER_PCM: + dir = PLAY; + break; + case SOUND_MIXER_IGAIN: + dir = REC; + break; + default: + i = 0; + IOCTL_RETURN(0, &i); + } + + init_mixer_cmd(&mxcmd, mixer); + + if (cmd & SIOC_IN) { + unsigned l, r; + + rc = -EINVAL; + l = i & 0xff; + r = (i >> 8) & 0xff; + if (l > 100 || r > 100) + goto err; + + mixer->vol[dir][LEFT] = mxcmd.set.vol[dir][LEFT] = l; + mixer->vol[dir][RIGHT] = mxcmd.set.vol[dir][RIGHT] = r; + } + mxcmd.out_dir = dir; + + /* + * Apply volume conrol + */ + /* acquire target streams */ + pthread_mutex_lock(&mutex); + osa = calloc(max_streams, sizeof(osa[0])); + if (!osa) { + pthread_mutex_unlock(&mutex); + rc = -ENOMEM; + goto err; + } + + nr_os = 0; + list_for_each_entry(os, os_pgrp_tbl_head(mixer->pgrp), pgrp_link) { + if (os->pgrp == mixer->pgrp) { + osa[nr_os++] = os; + os->refcnt++; + } + } + + pthread_mutex_unlock(&mutex); + + /* execute mxcmd for each stream and put it */ + for (i = 0; i < nr_os; i++) { + exec_mixer_cmd(&mxcmd, osa[i]); + put_os(osa[i]); + } + + finish_mixer_cmd(&mxcmd); + free(osa); + + IOCTL_RETURN(0, out_bufsz ? &mxcmd.rvol : NULL); + +err: + fuse_reply_err(req, -rc); +} + +static void mixer_open(fuse_req_t req, struct fuse_file_info *fi) +{ + pid_t pid = fuse_req_ctx(req)->pid, pgrp; + struct ossp_mixer *mixer; + int rc; + + rc = get_proc_self_info(pid, &pgrp, NULL, 0); + if (rc) { + err_e(rc, "get_proc_self_info(%d) failed", pid); + fuse_reply_err(req, -rc); + return; + } + + mixer = get_mixer(pgrp); + fi->fh = pgrp; + + if (mixer) + fuse_reply_open(req, fi); + else + fuse_reply_err(req, ENOMEM); +} + +static void mixer_ioctl(fuse_req_t req, int signed_cmd, void *uarg, + struct fuse_file_info *fi, unsigned int flags, + const void *in_buf, size_t in_bufsz, size_t out_bufsz) +{ + struct ossp_mixer *mixer; + + mixer = find_mixer(fi->fh); + if (!mixer) { + fuse_reply_err(req, EBADF); + return; + } + + mixer_do_ioctl(req, mixer, signed_cmd, uarg, in_buf, in_bufsz, + out_bufsz); +} + +static void mixer_release(fuse_req_t req, struct fuse_file_info *fi) +{ + struct ossp_mixer *mixer; + + mixer = find_mixer(fi->fh); + if (mixer) { + put_mixer(mixer); + fuse_reply_err(req, 0); + } else + fuse_reply_err(req, EBADF); +} + + +/*************************************************************************** + * Stream implementation + */ + +static int alloc_os(size_t stream_size, size_t mmap_size, pid_t pid, uid_t pgrp, + uid_t uid, gid_t gid, int cmd_sock, + const int *notify, struct fuse_session *se, + struct ossp_stream **osp) +{ + struct ossp_uid_cnt *tmp_ucnt, *ucnt = NULL; + struct ossp_stream *os; + int rc; + + assert(stream_size >= sizeof(struct ossp_stream)); + os = calloc(1, stream_size); + if (!os) + return -ENOMEM; + + INIT_LIST_HEAD(&os->link); + INIT_LIST_HEAD(&os->pgrp_link); + INIT_LIST_HEAD(&os->notify_link); + os->refcnt = 1; + + rc = -pthread_mutex_init(&os->cmd_mutex, NULL); + if (rc) + goto err_free; + + rc = -pthread_mutex_init(&os->mmap_mutex, NULL); + if (rc) + goto err_destroy_cmd_mutex; + + pthread_mutex_lock(&mutex); + + list_for_each_entry(tmp_ucnt, &uid_cnt_list, link) + if (tmp_ucnt->uid == uid) { + ucnt = tmp_ucnt; + break; + } + if (!ucnt) { + rc = -ENOMEM; + ucnt = calloc(1, sizeof(*ucnt)); + if (!ucnt) + goto err_unlock; + ucnt->uid = uid; + list_add(&ucnt->link, &uid_cnt_list); + } + + rc = -EBUSY; + if (ucnt->nr_os + 1 > umax_streams) + goto err_unlock; + + /* everything looks fine, allocate id and init stream */ + rc = -EBUSY; + os->id = find_next_zero_bit(os_id_bitmap, max_streams, 0); + if (os->id >= max_streams) + goto err_unlock; + __set_bit(os->id, os_id_bitmap); + + os->cmd_fd = cmd_sock; + os->notify_tx = notify[1]; + os->notify_rx = notify[0]; + os->pid = pid; + os->pgrp = pgrp; + os->uid = uid; + os->gid = gid; + if (mmap_size) { + os->mmap_off = os->id * mmap_size; + os->mmap_size = mmap_size; + } + os->ucnt = ucnt; + os->se = se; + + memset(os->vol, -1, sizeof(os->vol)); + memset(os->vol_set, -1, sizeof(os->vol)); + + list_add(&os->link, os_tbl_head(os->id)); + list_add(&os->pgrp_link, os_pgrp_tbl_head(os->pgrp)); + + ucnt->nr_os++; + *osp = os; + pthread_mutex_unlock(&mutex); + return 0; + +err_unlock: + pthread_mutex_unlock(&mutex); + pthread_mutex_destroy(&os->mmap_mutex); +err_destroy_cmd_mutex: + pthread_mutex_destroy(&os->cmd_mutex); +err_free: + free(os); + return rc; +} + +static void shutdown_notification(struct ossp_stream *os) +{ + struct ossp_notify obituary = { .magic = OSSP_NOTIFY_MAGIC, + .opcode = OSSP_NOTIFY_OBITUARY }; + ssize_t ret; + + /* + * Shutdown notification for this stream. We politely ask + * notify_poller to shut the receive side down to avoid racing + * with it. + */ + while (os->notify_rx >= 0) { + ret = write(os->notify_tx, &obituary, sizeof(obituary)); + if (ret <= 0) { + if (ret == 0) + warn_os(os, "unexpected EOF on notify_tx"); + else if (errno != EPIPE) + warn_ose(os, -errno, + "unexpected error on notify_tx"); + close(os->notify_rx); + os->notify_rx = -1; + break; + } + + if (ret != sizeof(obituary)) + warn_os(os, "short transfer on notify_tx"); + pthread_cond_wait(¬ify_poller_kill_wait, &mutex); + } +} + +static void put_os(struct ossp_stream *os) +{ + if (!os) + return; + + pthread_mutex_lock(&mutex); + + assert(os->refcnt); + if (--os->refcnt) { + pthread_mutex_unlock(&mutex); + return; + } + + os->dead = 1; + shutdown_notification(os); + + dbg0_os(os, "DESTROY"); + + list_del_init(&os->link); + list_del_init(&os->pgrp_link); + list_del_init(&os->notify_link); + os->ucnt->nr_os--; + + pthread_mutex_unlock(&mutex); + + close(os->cmd_fd); + close(os->notify_tx); + put_mixer(os->mixer); + pthread_mutex_destroy(&os->cmd_mutex); + pthread_mutex_destroy(&os->mmap_mutex); + + pthread_mutex_lock(&mutex); + dbg1_os(os, "stream dead, requesting reaping"); + list_add_tail(&os->link, &slave_corpse_list); + pthread_cond_signal(&slave_reaper_wait); + pthread_mutex_unlock(&mutex); +} + +static void set_extra_env(pid_t pid) +{ + char procenviron[32]; + const int step = 1024; + char *data = malloc(step + 1); + int ofs = 0; + int fd; + int ret; + + if (!data) + return; + + sprintf(procenviron, "/proc/%d/environ", pid); + fd = open(procenviron, O_RDONLY); + if (fd < 0) + return; + + /* + * There should really be a 'read whole file to a newly allocated + * buffer' function. + */ + while ((ret = read(fd, data + ofs, step)) > 0) { + char *newdata; + ofs += ret; + newdata = realloc(data, ofs + step + 1); + if (!newdata) { + ret = -1; + break; + } + data = newdata; + } + if (ret == 0) { + char *ptr = data; + /* Append the extra 0 for end condition */ + data[ofs] = 0; + + while ((ret = strlen(ptr)) > 0) { + /* + * Copy all PULSE variables and DISPLAY so that + * ssh -X remotehost 'mplayer -ao oss' will work + */ + if (!strncmp(ptr, "DISPLAY=", 8) || + !strncmp(ptr, "PULSE_", 6)) + putenv(ptr); + ptr += ret + 1; + } + } + + free(data); + close(fd); +} + +static int create_os(const char *slave_path, + size_t stream_size, size_t mmap_size, + pid_t pid, pid_t pgrp, uid_t uid, gid_t gid, + struct fuse_session *se, struct ossp_stream **osp) +{ + static pthread_mutex_t create_mutex = PTHREAD_MUTEX_INITIALIZER; + int cmd_sock[2] = { -1, -1 }; + int notify_sock[2] = { -1, -1 }; + struct ossp_stream *os = NULL; + struct epoll_event ev = { }; + int i, rc; + + /* + * Only one thread can be creating a stream. This is to avoid + * leaking unwanted fds into slaves. + */ + pthread_mutex_lock(&create_mutex); + + /* prepare communication channels */ + if (socketpair(AF_UNIX, SOCK_STREAM, 0, cmd_sock) || + socketpair(AF_UNIX, SOCK_STREAM, 0, notify_sock)) { + rc = -errno; + warn_e(rc, "failed to create slave command channel"); + goto close_all; + } + + if (fcntl(notify_sock[0], F_SETFL, O_NONBLOCK) < 0) { + rc = -errno; + warn_e(rc, "failed to set NONBLOCK on notify sock"); + goto close_all; + } + + /* + * Alloc stream which will be responsible for all server side + * resources from now on. + */ + rc = alloc_os(stream_size, mmap_size, pid, pgrp, uid, gid, cmd_sock[0], + notify_sock, se, &os); + if (rc) { + warn_e(rc, "failed to allocate stream for %d", pid); + goto close_all; + } + + rc = -ENOMEM; + os->mixer = get_mixer(pgrp); + if (!os->mixer) + goto put_os; + + /* + * Register notification. If successful, notify_poller has + * custody of notify_rx fd. + */ + pthread_mutex_lock(&mutex); + list_add(&os->notify_link, os_notify_tbl_head(os->notify_rx)); + pthread_mutex_unlock(&mutex); + + ev.events = EPOLLIN; + ev.data.fd = notify_sock[0]; + if (epoll_ctl(notify_epfd, EPOLL_CTL_ADD, notify_sock[0], &ev)) { + /* + * Without poller watching this notify sock, poller + * shutdown sequence in shutdown_notification() can't + * be used. Kill notification rx manually. + */ + rc = -errno; + warn_ose(os, rc, "failed to add notify epoll"); + close(os->notify_rx); + os->notify_rx = -1; + goto put_os; + } + + /* start slave */ + os->slave_pid = fork(); + if (os->slave_pid < 0) { + rc = -errno; + warn_ose(os, rc, "failed to fork slave"); + goto put_os; + } + + if (os->slave_pid == 0) { + /* child */ + char id_str[2][16], fd_str[3][16]; + char mmap_off_str[32], mmap_size_str[32]; + char log_str[16], slave_path_copy[PATH_MAX]; + char *argv[] = { slave_path_copy, "-u", id_str[0], + "-g", id_str[1], "-c", fd_str[0], + "-n", fd_str[1], "-m", fd_str[2], + "-o", mmap_off_str, "-s", mmap_size_str, + "-l", log_str, NULL, NULL }; + struct passwd *pwd; + + /* drop stuff we don't need */ + if (close(cmd_sock[0]) || close(notify_sock[0])) + fatal_e(-errno, "failed to close server pipe fds"); + +#ifdef OSSP_MMAP + if (!mmap_size) + close(fuse_mmap_fd(se)); +#endif + + clearenv(); + pwd = getpwuid(os->uid); + if (pwd) { + setenv("LOGNAME", pwd->pw_name, 1); + setenv("USER", pwd->pw_name, 1); + setenv("HOME", pwd->pw_dir, 1); + } + /* Set extra environment variables from the caller */ + set_extra_env(pid); + + /* prep and exec */ + slave_path_copy[sizeof(slave_path_copy) - 1] = '\0'; + strncpy(slave_path_copy, slave_path, sizeof(slave_path_copy) - 1); + if (slave_path_copy[sizeof(slave_path_copy) - 1] != '\0') { + rc = -errno; + err_ose(os, rc, "slave path too long"); + goto child_fail; + } + + snprintf(id_str[0], sizeof(id_str[0]), "%d", os->uid); + snprintf(id_str[1], sizeof(id_str[0]), "%d", os->gid); + snprintf(fd_str[0], sizeof(fd_str[0]), "%d", cmd_sock[1]); + snprintf(fd_str[1], sizeof(fd_str[1]), "%d", notify_sock[1]); + snprintf(fd_str[2], sizeof(fd_str[2]), "%d", +#ifdef OSSP_MMAP + mmap_size ? fuse_mmap_fd(se) : +#endif + -1); + snprintf(mmap_off_str, sizeof(mmap_off_str), "0x%llx", + (unsigned long long)os->mmap_off); + snprintf(mmap_size_str, sizeof(mmap_size_str), "0x%zx", + mmap_size); + snprintf(log_str, sizeof(log_str), "%d", ossp_log_level); + if (ossp_log_timestamp) + argv[ARRAY_SIZE(argv) - 2] = "-t"; + + execv(slave_path, argv); + rc = -errno; + err_ose(os, rc, "execv failed for <%d>", pid); + child_fail: + _exit(1); + } + + /* turn on CLOEXEC on all server side fds */ + if (fcntl(os->cmd_fd, F_SETFD, FD_CLOEXEC) < 0 || + fcntl(os->notify_tx, F_SETFD, FD_CLOEXEC) < 0 || + fcntl(os->notify_rx, F_SETFD, FD_CLOEXEC) < 0) { + rc = -errno; + err_ose(os, rc, "failed to set CLOEXEC on server side fds"); + goto put_os; + } + + dbg0_os(os, "CREATE slave=%d %s", os->slave_pid, slave_path); + dbg0_os(os, " client=%d cmd=%d:%d notify=%d:%d mmap=%d:0x%llx:%zu", + pid, cmd_sock[0], cmd_sock[1], notify_sock[0], notify_sock[1], +#ifdef OSSP_MMAP + os->mmap_size ? fuse_mmap_fd(se) : +#endif + -1, + (unsigned long long)os->mmap_off, os->mmap_size); + + *osp = os; + rc = 0; + goto close_client_fds; + +put_os: + put_os(os); +close_client_fds: + close(cmd_sock[1]); + pthread_mutex_unlock(&create_mutex); + return rc; + +close_all: + for (i = 0; i < 2; i++) { + close(cmd_sock[i]); + close(notify_sock[i]); + } + pthread_mutex_unlock(&create_mutex); + return rc; +} + +static void dsp_open_common(fuse_req_t req, struct fuse_file_info *fi, + struct fuse_session *se) +{ + const struct fuse_ctx *fuse_ctx = fuse_req_ctx(req); + struct ossp_dsp_open_arg arg = { }; + struct ossp_stream *os = NULL; + struct ossp_mixer *mixer; + struct ossp_dsp_stream *dsps; + struct ossp_mixer_cmd mxcmd; + pid_t pgrp; + ssize_t ret; + + ret = get_proc_self_info(fuse_ctx->pid, &pgrp, NULL, 0); + if (ret) { + err_e(ret, "get_proc_self_info(%d) failed", fuse_ctx->pid); + goto err; + } + + ret = create_os(dsp_slave_path, sizeof(*dsps), DSPS_MMAP_SIZE, + fuse_ctx->pid, pgrp, fuse_ctx->uid, fuse_ctx->gid, + se, &os); + if (ret) + goto err; + dsps = os_to_dsps(os); + mixer = os->mixer; + + switch (fi->flags & O_ACCMODE) { + case O_WRONLY: + dsps->rw |= 1 << PLAY; + break; + case O_RDONLY: + dsps->rw |= 1 << REC; + break; + case O_RDWR: + dsps->rw |= (1 << PLAY) | (1 << REC); + break; + default: + assert(0); + } + + arg.flags = fi->flags; + arg.opener_pid = os->pid; + ret = exec_simple_cmd(&dsps->os, OSSP_DSP_OPEN, &arg, NULL); + if (ret < 0) { + put_os(os); + goto err; + } + + memcpy(os->vol, mixer->vol, sizeof(os->vol)); + if (os->vol[PLAY][0] >= 0 || os->vol[REC][0] >= 0) { + init_mixer_cmd(&mxcmd, mixer); + memcpy(mxcmd.set.vol, os->vol, sizeof(os->vol)); + exec_mixer_cmd(&mxcmd, os); + finish_mixer_cmd(&mxcmd); + } + + fi->direct_io = 1; + fi->nonseekable = 1; + fi->fh = os->id; + + fuse_reply_open(req, fi); + return; + +err: + fuse_reply_err(req, -ret); +} + +static void dsp_open(fuse_req_t req, struct fuse_file_info *fi) +{ + dsp_open_common(req, fi, dsp_se); +} + +static void adsp_open(fuse_req_t req, struct fuse_file_info *fi) +{ + dsp_open_common(req, fi, adsp_se); +} + +static void dsp_release(fuse_req_t req, struct fuse_file_info *fi) +{ + struct ossp_stream *os; + + os = find_os(fi->fh); + if (os) { + put_os(os); + fuse_reply_err(req, 0); + } else + fuse_reply_err(req, EBADF); +} + +static void dsp_read(fuse_req_t req, size_t size, off_t off, + struct fuse_file_info *fi) +{ + struct ossp_dsp_rw_arg arg = { }; + struct ossp_stream *os; + struct ossp_dsp_stream *dsps; + void *buf = NULL; + ssize_t ret; + + ret = -EBADF; + os = find_os(fi->fh); + if (!os) + goto out; + dsps = os_to_dsps(os); + + ret = -EINVAL; + if (!(dsps->rw & (1 << REC))) + goto out; + + ret = -ENXIO; + if (dsps->mmapped) + goto out; + + ret = -ENOMEM; + buf = malloc(size); + if (!buf) + goto out; + + arg.nonblock = (fi->flags & O_NONBLOCK) || dsps->nonblock; + + ret = exec_cmd(os, OSSP_DSP_READ, &arg, sizeof(arg), + NULL, 0, NULL, 0, buf, &size, -1); +out: + if (ret >= 0) + fuse_reply_buf(req, buf, size); + else + fuse_reply_err(req, -ret); + + free(buf); +} + +static void dsp_write(fuse_req_t req, const char *buf, size_t size, off_t off, + struct fuse_file_info *fi) +{ + struct ossp_dsp_rw_arg arg = { }; + struct ossp_stream *os; + struct ossp_dsp_stream *dsps; + ssize_t ret; + + ret = -EBADF; + os = find_os(fi->fh); + if (!os) + goto out; + dsps = os_to_dsps(os); + + ret = -EINVAL; + if (!(dsps->rw & (1 << PLAY))) + goto out; + + ret = -ENXIO; + if (dsps->mmapped) + goto out; + + arg.nonblock = (fi->flags & O_NONBLOCK) || dsps->nonblock; + + ret = exec_cmd(os, OSSP_DSP_WRITE, &arg, sizeof(arg), + buf, size, NULL, 0, NULL, NULL, -1); +out: + if (ret >= 0) + fuse_reply_write(req, ret); + else + fuse_reply_err(req, -ret); +} + +static void dsp_poll(fuse_req_t req, struct fuse_file_info *fi, + struct fuse_pollhandle *ph) +{ + int notify = ph != NULL; + unsigned revents = 0; + struct ossp_stream *os; + ssize_t ret; + + ret = -EBADF; + os = find_os(fi->fh); + if (!os) + goto out; + + if (ph) { + pthread_mutex_lock(&mutex); + if (os->ph) + fuse_pollhandle_destroy(os->ph); + os->ph = ph; + pthread_mutex_unlock(&mutex); + } + + ret = exec_simple_cmd(os, OSSP_DSP_POLL, ¬ify, &revents); +out: + if (ret >= 0) + fuse_reply_poll(req, revents); + else + fuse_reply_err(req, -ret); +} + +static void dsp_ioctl(fuse_req_t req, int signed_cmd, void *uarg, + struct fuse_file_info *fi, unsigned int flags, + const void *in_buf, size_t in_bufsz, size_t out_bufsz) +{ + /* some ioctl constants are long and has the highest bit set */ + unsigned cmd = signed_cmd; + struct ossp_stream *os; + struct ossp_dsp_stream *dsps; + enum ossp_opcode op; + ssize_t ret; + int i; + + ret = -EBADF; + os = find_os(fi->fh); + if (!os) + goto err; + dsps = os_to_dsps(os); + + /* mixer commands are allowed on DSP devices */ + if (((cmd >> 8) & 0xff) == 'M') { + mixer_do_ioctl(req, os->mixer, cmd, uarg, in_buf, in_bufsz, + out_bufsz); + return; + } + + /* and the rest */ + switch (cmd) { + case OSS_GETVERSION: + i = SNDRV_OSS_VERSION; + PREP_UARG(NULL, &i); + IOCTL_RETURN(0, &i); + + case SNDCTL_DSP_GETCAPS: + i = DSP_CAP_DUPLEX | DSP_CAP_REALTIME | DSP_CAP_TRIGGER | +#ifdef OSSP_MMAP + DSP_CAP_MMAP | +#endif + DSP_CAP_MULTI; + PREP_UARG(NULL, &i); + IOCTL_RETURN(0, &i); + + case SNDCTL_DSP_NONBLOCK: + dsps->nonblock = 1; + ret = 0; + IOCTL_RETURN(0, NULL); + + case SNDCTL_DSP_RESET: op = OSSP_DSP_RESET; goto nd; + case SNDCTL_DSP_SYNC: op = OSSP_DSP_SYNC; goto nd; + case SNDCTL_DSP_POST: op = OSSP_DSP_POST; goto nd; + nd: + ret = exec_simple_cmd(&dsps->os, op, NULL, NULL); + if (ret) + goto err; + IOCTL_RETURN(0, NULL); + + case SOUND_PCM_READ_RATE: op = OSSP_DSP_GET_RATE; goto ri; + case SOUND_PCM_READ_BITS: op = OSSP_DSP_GET_FORMAT; goto ri; + case SOUND_PCM_READ_CHANNELS: op = OSSP_DSP_GET_CHANNELS; goto ri; + case SNDCTL_DSP_GETBLKSIZE: op = OSSP_DSP_GET_BLKSIZE; goto ri; + case SNDCTL_DSP_GETFMTS: op = OSSP_DSP_GET_FORMATS; goto ri; + case SNDCTL_DSP_GETTRIGGER: op = OSSP_DSP_GET_TRIGGER; goto ri; + ri: + PREP_UARG(NULL, &i); + ret = exec_simple_cmd(&dsps->os, op, NULL, &i); + if (ret) + goto err; + IOCTL_RETURN(0, &i); + + case SNDCTL_DSP_SPEED: op = OSSP_DSP_SET_RATE; goto wi; + case SNDCTL_DSP_SETFMT: op = OSSP_DSP_SET_FORMAT; goto wi; + case SNDCTL_DSP_CHANNELS: op = OSSP_DSP_SET_CHANNELS; goto wi; + case SNDCTL_DSP_SUBDIVIDE: op = OSSP_DSP_SET_SUBDIVISION; goto wi; + wi: + PREP_UARG(&i, &i); + ret = exec_simple_cmd(&dsps->os, op, &i, &i); + if (ret) + goto err; + IOCTL_RETURN(0, &i); + + case SNDCTL_DSP_STEREO: + PREP_UARG(NULL, &i); + i = 2; + ret = exec_simple_cmd(&dsps->os, OSSP_DSP_SET_CHANNELS, &i, &i); + i--; + if (ret) + goto err; + IOCTL_RETURN(0, &i); + + case SNDCTL_DSP_SETFRAGMENT: + PREP_UARG(&i, NULL); + ret = exec_simple_cmd(&dsps->os, + OSSP_DSP_SET_FRAGMENT, &i, NULL); + if (ret) + goto err; + IOCTL_RETURN(0, NULL); + + case SNDCTL_DSP_SETTRIGGER: + PREP_UARG(&i, NULL); + ret = exec_simple_cmd(&dsps->os, + OSSP_DSP_SET_TRIGGER, &i, NULL); + if (ret) + goto err; + IOCTL_RETURN(0, NULL); + + case SNDCTL_DSP_GETOSPACE: + case SNDCTL_DSP_GETISPACE: { + struct audio_buf_info info; + + ret = -EINVAL; + if (cmd == SNDCTL_DSP_GETOSPACE) { + if (!(dsps->rw & (1 << PLAY))) + goto err; + op = OSSP_DSP_GET_OSPACE; + } else { + if (!(dsps->rw & (1 << REC))) + goto err; + op = OSSP_DSP_GET_ISPACE; + } + + PREP_UARG(NULL, &info); + ret = exec_simple_cmd(&dsps->os, op, NULL, &info); + if (ret) + goto err; + IOCTL_RETURN(0, &info); + } + + case SNDCTL_DSP_GETOPTR: + case SNDCTL_DSP_GETIPTR: { + struct count_info info; + + op = cmd == SNDCTL_DSP_GETOPTR ? OSSP_DSP_GET_OPTR + : OSSP_DSP_GET_IPTR; + PREP_UARG(NULL, &info); + ret = exec_simple_cmd(&dsps->os, op, NULL, &info); + if (ret) + goto err; + IOCTL_RETURN(0, &info); + } + + case SNDCTL_DSP_GETODELAY: + PREP_UARG(NULL, &i); + i = 0; + ret = exec_simple_cmd(&dsps->os, OSSP_DSP_GET_ODELAY, NULL, &i); + IOCTL_RETURN(ret, &i); /* always copy out result, 0 on err */ + + case SOUND_PCM_WRITE_FILTER: + case SOUND_PCM_READ_FILTER: + ret = -EIO; + goto err; + + case SNDCTL_DSP_MAPINBUF: + case SNDCTL_DSP_MAPOUTBUF: + ret = -EINVAL; + goto err; + + case SNDCTL_DSP_SETSYNCRO: + case SNDCTL_DSP_SETDUPLEX: + case SNDCTL_DSP_PROFILE: + IOCTL_RETURN(0, NULL); + + default: + warn_os(os, "unknown ioctl 0x%x", cmd); + ret = -EINVAL; + goto err; + } + assert(0); /* control shouldn't reach here */ +err: + fuse_reply_err(req, -ret); +} + +#ifdef OSSP_MMAP +static int dsp_mmap_dir(int prot) +{ + if (!(prot & PROT_WRITE)) + return REC; + return PLAY; +} + +static void dsp_mmap(fuse_req_t req, void *addr, size_t len, int prot, + int flags, off_t offset, struct fuse_file_info *fi, + uint64_t mh) +{ + int dir = dsp_mmap_dir(prot); + struct ossp_dsp_mmap_arg arg = { }; + struct ossp_stream *os; + struct ossp_dsp_stream *dsps; + ssize_t ret; + + os = find_os(fi->fh); + if (!os) { + fuse_reply_err(req, EBADF); + return; + } + dsps = os_to_dsps(os); + + if (!os->mmap_off || len > os->mmap_size / 2) { + fuse_reply_err(req, EINVAL); + return; + } + + pthread_mutex_lock(&os->mmap_mutex); + + ret = -EBUSY; + if (dsps->mmapped & (1 << dir)) + goto out_unlock; + + arg.dir = dir; + arg.size = len; + + ret = exec_simple_cmd(os, OSSP_DSP_MMAP, &arg, NULL); + if (ret == 0) + dsps->mmapped |= 1 << dir; + +out_unlock: + pthread_mutex_unlock(&os->mmap_mutex); + + if (ret == 0) + fuse_reply_mmap(req, os->mmap_off + dir * os->mmap_size / 2, 0); + else + fuse_reply_err(req, -ret); +} + +static void dsp_munmap(fuse_req_t req, size_t len, struct fuse_file_info *fi, + off_t offset, uint64_t mh) +{ + struct ossp_stream *os; + struct ossp_dsp_stream *dsps; + int dir, rc; + + os = find_os(fi->fh); + if (!os) + goto out; + dsps = os_to_dsps(os); + + pthread_mutex_lock(&os->mmap_mutex); + + for (dir = 0; dir < 2; dir++) + if (offset == os->mmap_off + dir * os->mmap_size / 2) + break; + if (dir == 2 || len > os->mmap_size / 2) { + warn_os(os, "invalid munmap request " + "offset=%llu len=%zu mmapped=0x%x", + (unsigned long long)offset, len, dsps->mmapped); + goto out_unlock; + } + + rc = exec_simple_cmd(os, OSSP_DSP_MUNMAP, &dir, NULL); + if (rc) + warn_ose(os, rc, "MUNMAP failed for dir=%d", dir); + + dsps->mmapped &= ~(1 << dir); + +out_unlock: + pthread_mutex_unlock(&os->mmap_mutex); +out: + fuse_reply_none(req); +} +#endif + + +/*************************************************************************** + * Notify poller + */ + +static void *notify_poller(void *arg) +{ + struct epoll_event events[1024]; + int i, nfds; + +repeat: + nfds = epoll_wait(notify_epfd, events, ARRAY_SIZE(events), -1); + for (i = 0; i < nfds; i++) { + int do_notify = 0; + struct ossp_stream *os; + struct ossp_notify notify; + ssize_t ret; + + os = find_os_by_notify_rx(events[i].data.fd); + if (!os) { + err("can't find stream for notify_rx fd %d", + events[i].data.fd); + epoll_ctl(notify_epfd, EPOLL_CTL_DEL, events[i].data.fd, + NULL); + /* we don't know what's going on, don't close the fd */ + continue; + } + + while ((ret = read(os->notify_rx, + ¬ify, sizeof(notify))) > 0) { + if (os->dead) + continue; + if (ret != sizeof(notify)) { + warn_os(os, "short read on notify_rx (%zu, " + "expected %zu), killing the stream", + ret, sizeof(notify)); + os->dead = 1; + break; + } + if (notify.magic != OSSP_NOTIFY_MAGIC) { + warn_os(os, "invalid magic on notification, " + "killing the stream"); + os->dead = 1; + break; + } + + if (notify.opcode >= OSSP_NR_NOTIFY_OPCODES) + goto unknown; + + dbg1_os(os, "NOTIFY %s", ossp_notify_str[notify.opcode]); + + switch (notify.opcode) { + case OSSP_NOTIFY_POLL: + do_notify = 1; + break; + case OSSP_NOTIFY_OBITUARY: + os->dead = 1; + break; + case OSSP_NOTIFY_VOLCHG: + pthread_mutex_lock(&mixer_mutex); + os->mixer->modify_counter++; + pthread_mutex_unlock(&mixer_mutex); + break; + default: + unknown: + warn_os(os, "unknown notification %d", + notify.opcode); + } + } + if (ret == 0) + os->dead = 1; + else if (ret < 0 && errno != EAGAIN) { + warn_ose(os, -errno, "read fail on notify fd"); + os->dead = 1; + } + + if (!do_notify && !os->dead) + continue; + + pthread_mutex_lock(&mutex); + + if (os->ph) { + fuse_lowlevel_notify_poll(os->ph); + fuse_pollhandle_destroy(os->ph); + os->ph = NULL; + } + + if (os->dead) { + dbg0_os(os, "removing %d from notify poll list", + os->notify_rx); + epoll_ctl(notify_epfd, EPOLL_CTL_DEL, os->notify_rx, + NULL); + close(os->notify_rx); + os->notify_rx = -1; + pthread_cond_broadcast(¬ify_poller_kill_wait); + } + + pthread_mutex_unlock(&mutex); + } + goto repeat; +} + + +/*************************************************************************** + * Slave corpse reaper + */ + +static void *slave_reaper(void *arg) +{ + struct ossp_stream *os; + int status; + pid_t pid; + + pthread_mutex_lock(&mutex); +repeat: + while (list_empty(&slave_corpse_list)) + pthread_cond_wait(&slave_reaper_wait, &mutex); + + os = list_first_entry(&slave_corpse_list, struct ossp_stream, link); + list_del_init(&os->link); + + pthread_mutex_unlock(&mutex); + + do { + pid = waitpid(os->slave_pid, &status, 0); + } while (pid < 0 && errno == EINTR); + + if (pid < 0) { + if (errno == ECHILD) + warn_ose(os, -errno, "slave %d already gone?", + os->slave_pid); + else + fatal_e(-errno, "waitpid(%d) failed", os->slave_pid); + } + + pthread_mutex_lock(&mutex); + + dbg1_os(os, "slave %d reaped", os->slave_pid); + __clear_bit(os->id, os_id_bitmap); + free(os); + + goto repeat; +} + + +/*************************************************************************** + * Stuff to bind and start everything + */ + +static void ossp_daemonize(void) +{ + int fd, pfd[2]; + pid_t pid; + ssize_t ret; + int err; + + fd = open("/dev/null", O_RDWR); + if (fd >= 0) { + dup2(fd, 0); + dup2(fd, 1); + dup2(fd, 2); + if (fd > 2) + close(fd); + } + + if (pipe(pfd)) + fatal_e(-errno, "failed to create pipe for init wait"); + + if (fcntl(pfd[0], F_SETFD, FD_CLOEXEC) < 0 || + fcntl(pfd[1], F_SETFD, FD_CLOEXEC) < 0) + fatal_e(-errno, "failed to set CLOEXEC on init wait pipe"); + + pid = fork(); + if (pid < 0) + fatal_e(-errno, "failed to fork for daemon"); + + if (pid == 0) { + close(pfd[0]); + init_wait_fd = pfd[1]; + + /* be evil, my child */ + chdir("/"); + setsid(); + return; + } + + /* wait for init completion and pass over success indication */ + close(pfd[1]); + + do { + ret = read(pfd[0], &err, sizeof(err)); + } while (ret < 0 && errno == EINTR); + + if (ret == sizeof(err) && err == 0) + exit(0); + + fatal("daemon init failed ret=%zd err=%d", ret, err); + exit(1); +} + +static void ossp_init_done(void *userdata) +{ + /* init complete, notify parent if it's waiting */ + if (init_wait_fd >= 0) { + ssize_t ret; + int err = 0; + + ret = write(init_wait_fd, &err, sizeof(err)); + if (ret != sizeof(err)) + fatal_e(-errno, "failed to notify init completion, " + "ret=%zd", ret); + close(init_wait_fd); + init_wait_fd = -1; + } +} + +static const struct cuse_lowlevel_ops mixer_ops = { + .open = mixer_open, + .release = mixer_release, + .ioctl = mixer_ioctl, +}; + +static const struct cuse_lowlevel_ops dsp_ops = { + .init_done = ossp_init_done, + .open = dsp_open, + .release = dsp_release, + .read = dsp_read, + .write = dsp_write, + .poll = dsp_poll, + .ioctl = dsp_ioctl, +#ifdef OSSP_MMAP + .mmap = dsp_mmap, + .munmap = dsp_munmap, +#endif +}; + +static const struct cuse_lowlevel_ops adsp_ops = { + .open = adsp_open, + .release = dsp_release, + .read = dsp_read, + .write = dsp_write, + .poll = dsp_poll, + .ioctl = dsp_ioctl, +#ifdef OSSP_MMAP + .mmap = dsp_mmap, + .munmap = dsp_munmap, +#endif +}; + +static const char *usage = +"usage: osspd [options]\n" +"\n" +"options:\n" +" --help print this help message\n" +" --dsp=NAME DSP device name (default dsp)\n" +" --dsp-maj=MAJ DSP device major number (default 14)\n" +" --dsp-min=MIN DSP device minor number (default 3)\n" +" --adsp=NAME Aux DSP device name (default adsp, blank to disable)\n" +" --adsp-maj=MAJ Aux DSP device major number (default 14)\n" +" --adsp-min=MIN Aux DSP device minor number (default 12)\n" +" --mixer=NAME mixer device name (default mixer, blank to disable)\n" +" --mixer-maj=MAJ mixer device major number (default 14)\n" +" --mixer-min=MIN mixer device minor number (default 0)\n" +" --max=MAX maximum number of open streams (default 256)\n" +" --umax=MAX maximum number of open streams per UID (default --max)\n" +" --exit-on-idle exit if idle\n" +" --dsp-slave=PATH DSP slave (default ossp-padsp in the same dir)\n" +" --log=LEVEL log level (0..6)\n" +" --timestamp timestamp log messages\n" +" -v increase verbosity, can be specified multiple times\n" +" -f Run in foreground (don't daemonize)\n" +"\n"; + +struct ossp_param { + char *dsp_name; + unsigned dsp_major; + unsigned dsp_minor; + char *adsp_name; + unsigned adsp_major; + unsigned adsp_minor; + char *mixer_name; + unsigned mixer_major; + unsigned mixer_minor; + unsigned max_streams; + unsigned umax_streams; + char *dsp_slave_path; + unsigned log_level; + int exit_on_idle; + int timestamp; + int fg; + int help; +}; + +#define OSSP_OPT(t, p) { t, offsetof(struct ossp_param, p), 1 } + +static const struct fuse_opt ossp_opts[] = { + OSSP_OPT("--dsp=%s", dsp_name), + OSSP_OPT("--dsp-maj=%u", dsp_major), + OSSP_OPT("--dsp-min=%u", dsp_minor), + OSSP_OPT("--adsp=%s", adsp_name), + OSSP_OPT("--adsp-maj=%u", adsp_major), + OSSP_OPT("--adsp-min=%u", adsp_minor), + OSSP_OPT("--mixer=%s", mixer_name), + OSSP_OPT("--mixer-maj=%u", mixer_major), + OSSP_OPT("--mixer-min=%u", mixer_minor), + OSSP_OPT("--max=%u", max_streams), + OSSP_OPT("--umax=%u", umax_streams), + OSSP_OPT("--exit-on-idle", exit_on_idle), + OSSP_OPT("--dsp-slave=%s", dsp_slave_path), + OSSP_OPT("--timestamp", timestamp), + OSSP_OPT("--log=%u", log_level), + OSSP_OPT("-f", fg), + FUSE_OPT_KEY("-h", 0), + FUSE_OPT_KEY("--help", 0), + FUSE_OPT_KEY("-v", 1), + FUSE_OPT_END +}; + +static struct fuse_session *setup_ossp_cuse(const struct cuse_lowlevel_ops *ops, + const char *name, int major, + int minor, int argc, char **argv) +{ + char name_buf[128]; + const char *bufp = name_buf; + struct cuse_info ci = { .dev_major = major, .dev_minor = minor, + .dev_info_argc = 1, .dev_info_argv = &bufp, + .flags = CUSE_UNRESTRICTED_IOCTL }; + struct fuse_session *se; + int fd; + + snprintf(name_buf, sizeof(name_buf), "DEVNAME=%s", name); + + se = cuse_lowlevel_setup(argc, argv, &ci, ops, NULL, NULL); + if (!se) { + err("failed to setup %s CUSE", name); + return NULL; + } + + fd = fuse_chan_fd(fuse_session_next_chan(se, NULL)); + if ( +#ifdef OSSP_MMAP + fd != fuse_mmap_fd(se) && +#endif + fcntl(fd, F_SETFD, FD_CLOEXEC) < 0) { + err_e(-errno, "failed to set CLOEXEC on %s CUSE fd", name); + cuse_lowlevel_teardown(se); + return NULL; + } + + return se; +} + +static void *cuse_worker(void *arg) +{ + struct fuse_session *se = arg; + int rc; + + rc = fuse_session_loop_mt(se); + cuse_lowlevel_teardown(se); + + return (void *)(unsigned long)rc; +} + +static int process_arg(void *data, const char *arg, int key, + struct fuse_args *outargs) +{ + struct ossp_param *param = data; + + switch (key) { + case 0: + fprintf(stderr, usage); + param->help = 1; + return 0; + case 1: + param->log_level++; + return 0; + } + return 1; +} + +int main(int argc, char **argv) +{ + static struct ossp_param param = { + .dsp_name = DFL_DSP_NAME, + .dsp_major = DFL_DSP_MAJOR, .dsp_minor = DFL_DSP_MINOR, + .adsp_name = DFL_ADSP_NAME, + .adsp_major = DFL_ADSP_MAJOR, .adsp_minor = DFL_ADSP_MINOR, + .mixer_name = DFL_MIXER_NAME, + .mixer_major = DFL_MIXER_MAJOR, .mixer_minor = DFL_MIXER_MINOR, + .max_streams = DFL_MAX_STREAMS, + }; + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + char path_buf[PATH_MAX], *dir; + char adsp_buf[64] = "", mixer_buf[64] = ""; + struct sigaction sa; + struct stat stat_buf; + ssize_t ret; + unsigned u; + + snprintf(ossp_log_name, sizeof(ossp_log_name), "osspd"); + param.log_level = ossp_log_level; + + if (fuse_opt_parse(&args, ¶m, ossp_opts, process_arg)) + fatal("failed to parse arguments"); + + if (param.help) + return 0; + + max_streams = param.max_streams; + hashtbl_size = max_streams / 2 + 13; + + umax_streams = max_streams; + if (param.umax_streams) + umax_streams = param.umax_streams; + if (param.log_level > OSSP_LOG_MAX) + param.log_level = OSSP_LOG_MAX; + if (!param.fg) + param.log_level = -param.log_level; + ossp_log_level = param.log_level; + ossp_log_timestamp = param.timestamp; + + if (!param.fg) + ossp_daemonize(); + + /* daemonization already handled, prevent forking inside FUSE */ + fuse_opt_add_arg(&args, "-f"); + + info("OSS Proxy v%s (C) 2008-2010 by Tejun Heo ", + OSSP_VERSION); + + /* ignore stupid SIGPIPEs */ + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = SIG_IGN; + if (sigaction(SIGPIPE, &sa, NULL)) + fatal_e(-errno, "failed to ignore SIGPIPE"); + + /* determine slave path and check for availability */ + ret = readlink("/proc/self/exe", path_buf, PATH_MAX - 1); + if (ret < 0) + fatal_e(-errno, "failed to determine executable path"); + path_buf[ret] = '\0'; + dir = dirname(path_buf); + + if (param.dsp_slave_path) { + strncpy(dsp_slave_path, param.dsp_slave_path, PATH_MAX - 1); + dsp_slave_path[PATH_MAX - 1] = '\0'; + } else { + ret = snprintf(dsp_slave_path, PATH_MAX, "%s/%s", + dir, "ossp-padsp"); + if (ret >= PATH_MAX) + fatal("dsp slave pathname too long"); + } + + if (stat(dsp_slave_path, &stat_buf)) + fatal_e(-errno, "failed to stat %s", dsp_slave_path); + if (!S_ISREG(stat_buf.st_mode) || !(stat_buf.st_mode & 0444)) + fatal("%s is not executable", dsp_slave_path); + + /* allocate tables */ + os_id_bitmap = calloc(BITS_TO_LONGS(max_streams), sizeof(long)); + mixer_tbl = calloc(hashtbl_size, sizeof(mixer_tbl[0])); + os_tbl = calloc(hashtbl_size, sizeof(os_tbl[0])); + os_pgrp_tbl = calloc(hashtbl_size, sizeof(os_pgrp_tbl[0])); + os_notify_tbl = calloc(hashtbl_size, sizeof(os_notify_tbl[0])); + if (!os_id_bitmap || !mixer_tbl || !os_tbl || !os_pgrp_tbl || + !os_notify_tbl) + fatal("failed to allocate stream hash tables"); + for (u = 0; u < hashtbl_size; u++) { + INIT_LIST_HEAD(&mixer_tbl[u]); + INIT_LIST_HEAD(&os_tbl[u]); + INIT_LIST_HEAD(&os_pgrp_tbl[u]); + INIT_LIST_HEAD(&os_notify_tbl[u]); + } + __set_bit(0, os_id_bitmap); /* don't use id 0 */ + + /* create mixer delayed reference worker */ + ret = -pthread_create(&mixer_delayed_put_thread, NULL, + mixer_delayed_put_worker, NULL); + if (ret) + fatal_e(ret, "failed to create mixer delayed put worker"); + + /* if exit_on_idle, touch mixer for pgrp0 */ + exit_on_idle = param.exit_on_idle; + if (exit_on_idle) { + struct ossp_mixer *mixer; + + mixer = get_mixer(0); + if (!mixer) + fatal("failed to touch idle mixer"); + put_mixer(mixer); + } + + /* create notify epoll and kick off watcher thread */ + notify_epfd = epoll_create(max_streams); + if (notify_epfd < 0) + fatal_e(-errno, "failed to create notify epoll"); + if (fcntl(notify_epfd, F_SETFD, FD_CLOEXEC) < 0) + fatal_e(-errno, "failed to set CLOEXEC on notify epfd"); + + ret = -pthread_create(¬ify_poller_thread, NULL, notify_poller, NULL); + if (ret) + fatal_e(ret, "failed to create notify poller thread"); + + /* create reaper for slave corpses */ + ret = -pthread_create(&slave_reaper_thread, NULL, slave_reaper, NULL); + if (ret) + fatal_e(ret, "failed to create slave reaper thread"); + + /* we're set, let's setup fuse structures */ + if (strlen(param.mixer_name)) + mixer_se = setup_ossp_cuse(&mixer_ops, param.mixer_name, + param.mixer_major, param.mixer_minor, + args.argc, args.argv); + if (strlen(param.adsp_name)) + adsp_se = setup_ossp_cuse(&dsp_ops, param.adsp_name, + param.adsp_major, param.adsp_minor, + args.argc, args.argv); + + dsp_se = setup_ossp_cuse(&dsp_ops, param.dsp_name, + param.dsp_major, param.dsp_minor, + args.argc, args.argv); + if (!dsp_se) + fatal("can't create dsp, giving up"); + + if (mixer_se) + snprintf(mixer_buf, sizeof(mixer_buf), ", %s (%d:%d)", + param.mixer_name, param.mixer_major, param.mixer_minor); + if (adsp_se) + snprintf(adsp_buf, sizeof(adsp_buf), ", %s (%d:%d)", + param.adsp_name, param.adsp_major, param.adsp_minor); + + info("Creating %s (%d:%d)%s%s", param.dsp_name, param.dsp_major, + param.dsp_minor, adsp_buf, mixer_buf); + + /* start threads for mixer and adsp */ + if (mixer_se) { + ret = -pthread_create(&cuse_mixer_thread, NULL, + cuse_worker, mixer_se); + if (ret) + err_e(ret, "failed to create mixer worker"); + } + if (adsp_se) { + ret = -pthread_create(&cuse_adsp_thread, NULL, + cuse_worker, adsp_se); + if (ret) + err_e(ret, "failed to create adsp worker"); + } + + /* run CUSE for /dev/dsp in the main thread */ + ret = (ssize_t)cuse_worker(dsp_se); + if (ret < 0) + fatal("dsp worker failed"); + return 0; +} diff --git a/osstest.c b/osstest.c new file mode 100644 index 0000000..53c3edc --- /dev/null +++ b/osstest.c @@ -0,0 +1,266 @@ +/* Simple oss testsuite + * + * Copyright (C) 2009 Maarten Lankhorst + * + * This file is released under the GPLv2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MIXERDEV "/dev/mixer" +#define DSPDEV "/dev/dsp" + +/* Test macros */ + +static int errors, success; +static int report_success = 1; + +#define ok(a, b, c...) do { \ + if (!(a)) { \ + fprintf(stderr, "%s@%d test failed (%s): " b "\n", __func__, __LINE__, #a, ##c); \ + ++errors; \ + } else { \ + if (report_success) \ + printf("%s@%d test succeeded (%s)\n", __func__, __LINE__, #a); \ + ++success; \ + } } while (0) + +static int mixerfd, dspfd; + +static int reopen(int blocking) +{ + close(dspfd); + if (!blocking) + blocking = O_NDELAY; + else + blocking = 0; + dspfd = open(DSPDEV, O_RDWR|blocking); + return dspfd; +} + +static void test_ro(int fd) +{ + int ret; + char buf[1024]; + struct audio_buf_info abi; + memset(buf, 0, sizeof(buf)); + + ret = read(fd, buf, sizeof(buf)); + ok(ret >= 0, "%s", strerror(errno)); + + ret = write(fd, buf, sizeof(buf)); + ok(ret < 0, "read %d bytes", ret); + + ret = ioctl(fd, SNDCTL_DSP_GETISPACE, &abi); + ok(ret >= 0, "%s", strerror(errno)); + + ret = ioctl(fd, SNDCTL_DSP_GETOSPACE, &abi); + ok(ret < 0, "%s", strerror(errno)); + if (ret < 0) + ok(errno == EINVAL, "Invalid errno: %s", strerror(errno)); +} + +static void test_wo(int fd) +{ + int ret; + char buf[1024]; + struct audio_buf_info abi; + memset(buf, 0, sizeof(buf)); + + ret = read(fd, buf, sizeof(buf)); + ok(ret < 0, "read %d bytes", ret); + + ret = write(fd, buf, sizeof(buf)); + ok(ret >= 0, "%s", strerror(errno)); + + ret = ioctl(fd, SNDCTL_DSP_GETISPACE, &abi); + ok(ret < 0, "%s", strerror(errno)); + if (ret < 0) + ok(errno == EINVAL, "Invalid errno: %s", strerror(errno)); + + ret = ioctl(fd, SNDCTL_DSP_GETOSPACE, &abi); + ok(ret >= 0, "%s", strerror(errno)); +} + +static void test_rw(int fd) +{ + int ret; + char buf[1024]; + struct audio_buf_info abi; + memset(buf, 0, sizeof(buf)); + + ret = read(fd, buf, sizeof(buf)); + ok(ret >= 0, "%s", strerror(errno)); + + ret = write(fd, buf, sizeof(buf)); + ok(ret >= 0, "%s", strerror(errno)); + + ret = ioctl(fd, SNDCTL_DSP_GETISPACE, &abi); + ok(ret >= 0, "%s", strerror(errno)); + + ret = ioctl(fd, SNDCTL_DSP_GETOSPACE, &abi); + ok(ret >= 0, "%s", strerror(errno)); +} + +static void test_open(void) +{ + int ro_fd, rw_fd, wo_fd; + + mixerfd = open(MIXERDEV, O_RDONLY|O_NDELAY); + ok(mixerfd >= 0, "%s", strerror(errno)); + + + /* In order to make this work it has to be serialized + * alsa's kernel emulation can only have device open once + * so do some specific smokescreen tests here + * and then open dsp for testing + */ + ro_fd = open(DSPDEV, O_RDONLY); + ok(ro_fd >= 0, "%s", strerror(errno)); + + if (ro_fd >= 0) + test_ro(ro_fd); + + close(ro_fd); + + wo_fd = open(DSPDEV, O_WRONLY); + ok(wo_fd >= 0, "%s", strerror(errno)); + + if (wo_fd >= 0) + test_wo(wo_fd); + + close(wo_fd); + + rw_fd = open(DSPDEV, O_RDWR); + ok(rw_fd >= 0, "%s", strerror(errno)); + + if (rw_fd >= 0) + test_rw(rw_fd); + + dspfd = rw_fd; +} + +static void test_mixer(void) +{ + int ret; + struct mixer_info info; + memset(&info, 0, sizeof(info)); + + ret = ioctl(mixerfd, SOUND_MIXER_INFO, &info); + ok(ret >= 0, "%s", strerror(errno)); + if (ret >= 0) { + printf("Mixer id: %s\n", info.id); + printf("Name: %s\n", info.name); + } +} + +static void test_trigger(int fd) +{ + int ret, i; + + ret = ioctl(fd, SNDCTL_DSP_GETTRIGGER, &i); + ok(ret == 0, "Returned error %s", strerror(errno)); + ok(i == (PCM_ENABLE_INPUT|PCM_ENABLE_OUTPUT), "i is set to %d", i); + + i = 0; + ret = ioctl(fd, SNDCTL_DSP_SETTRIGGER, &i); + ok(ret == 0, "Returned error %s", strerror(errno)); + ok(i == 0, "Wrong i returned"); + + i = PCM_ENABLE_INPUT|PCM_ENABLE_OUTPUT; + ret = ioctl(fd, SNDCTL_DSP_SETTRIGGER, &i); + ok(ret == 0, "Returned error %s", strerror(errno)); + ok(i == (PCM_ENABLE_INPUT|PCM_ENABLE_OUTPUT), "i has value %d", i); + + ret = ioctl(fd, SNDCTL_DSP_POST, NULL); + ok(ret == 0, "Returned error %s", strerror(errno)); +} + +static void test_mmap(int fd) +{ + char *area; + int ret; + char buf[24]; + + area = mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); + ok(area != MAP_FAILED, "Failed to map: %s\n", strerror(errno)); + + if (area == MAP_FAILED) + return; + + ret = write(fd, &buf, sizeof(buf)); + ok(ret == -1, "write after mmap returned %i\n", ret); + if (ret == -1) + ok(errno == ENXIO, "Error returned is %s\n", strerror(errno)); + + munmap(area, 8192); +} + +static void test_notify(int fd) +{ + struct audio_buf_info bi; + char *bytes = NULL; + int ret, written; + struct pollfd pfd = { fd, POLLOUT }; + int rounds = 20; + + ioctl(fd, SNDCTL_DSP_GETOSPACE, &bi); + + bytes = calloc(1, bi.fragsize); + written = 0; + ok(0, "Fragsize: %i, bytes: %i\n", bi.fragsize, bi.bytes); + while (written + bi.fragsize - 1 < bi.bytes) + { + ret = write(fd, bytes, bi.fragsize); + ok(ret == bi.fragsize, "Returned: %i instead of %i\n", + ret, bi.fragsize); + if (ret > 0) + written += ret; + }; + + while (rounds--) + { + ret = poll(&pfd, 1, -1); + ok(ret > 0, "Poll returned %i\n", ret); + if (ret < 0) + break; + ret = write(fd, bytes, bi.fragsize); + if (ret < 0) ret = -errno; + ok(ret == bi.fragsize, "Returned: %i instead of %i\n", + ret, bi.fragsize); + } +} + +int main() +{ + test_open(); + if (mixerfd >= 0) + test_mixer(); + + if (reopen(1) >= 0) + test_trigger(dspfd); + + if (reopen(0) >= 0) + test_notify(dspfd); + + if (reopen(1) >= 0) + test_mmap(dspfd); + + close(mixerfd); + close(dspfd); + printf("Tests: %d errors %d success\n", errors, success); + return errors > 127 ? 127 : errors; +} +