Imported Upstream version 1.3.2 upstream upstream/1.3.2
authorRalf Jung <post@ralfj.de>
Thu, 13 Sep 2012 16:16:04 +0000 (18:16 +0200)
committerRalf Jung <post@ralfj.de>
Thu, 13 Sep 2012 16:16:04 +0000 (18:16 +0200)
15 files changed:
.gitignore [new file with mode: 0644]
98-osscuse.rules [new file with mode: 0644]
LICENSE [new file with mode: 0644]
Makefile [new file with mode: 0644]
README [new file with mode: 0644]
ossp-alsap.c [new file with mode: 0644]
ossp-padsp.c [new file with mode: 0644]
ossp-slave.c [new file with mode: 0644]
ossp-slave.h [new file with mode: 0644]
ossp-util.c [new file with mode: 0644]
ossp-util.h [new file with mode: 0644]
ossp.c [new file with mode: 0644]
ossp.h [new file with mode: 0644]
osspd.c [new file with mode: 0644]
osstest.c [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..b578114
--- /dev/null
@@ -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 (file)
index 0000000..c1430fd
--- /dev/null
@@ -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 (file)
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.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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.
+
+  <signature of Ty Coon>, 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 (file)
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 (file)
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 <tj@kernel.org>
+
+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 (file)
index 0000000..72f3bd5
--- /dev/null
@@ -0,0 +1,592 @@
+/*
+ * ossp-alsap - ossp DSP slave which forwards to alsa
+ *
+ * Copyright (C)      2009 Maarten Lankhorst <m.b.lankhorst@gmail.com>
+ *
+ * This file is released under the GPLv2.
+ *
+ * Why an alsa plugin as well? Just to show how much
+ * the alsa userspace api sucks ;-)
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <libgen.h>
+#include <limits.h>
+#include <poll.h>
+#include <pthread.h>
+#include <pwd.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <alsa/asoundlib.h>
+#include <sys/soundcard.h>
+
+#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 (file)
index 0000000..1871f5b
--- /dev/null
@@ -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 <tj@kernel.org>
+ *
+ * This file is released under the GPLv2.
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <libgen.h>
+#include <limits.h>
+#include <poll.h>
+#include <pthread.h>
+#include <pwd.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <pulse/pulseaudio.h>
+#include <sys/soundcard.h>
+
+#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 (file)
index 0000000..4c5cb2d
--- /dev/null
@@ -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 <tj@kernel.org>
+ *
+ * This file is released under the GPLv2.
+ */
+
+#define _GNU_SOURCE
+
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <pwd.h>
+#include <signal.h>
+
+#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 (file)
index 0000000..10c22cd
--- /dev/null
@@ -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 <tj@kernel.org>
+ *
+ * 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 (file)
index 0000000..325cefd
--- /dev/null
@@ -0,0 +1,369 @@
+/*
+ * ossp-util - OSS Proxy: Common utilities
+ *
+ * Copyright (C) 2008-2010  SUSE Linux Products GmbH
+ * Copyright (C) 2008-2010  Tejun Heo <tj@kernel.org>
+ *
+ * This file is released under the GPLv2.
+ */
+
+#include <ctype.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/time.h>
+#include <syslog.h>
+#include <unistd.h>
+#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 (file)
index 0000000..f48d022
--- /dev/null
@@ -0,0 +1,609 @@
+/*
+ * ossp-util - OSS Proxy: Common utilities
+ *
+ * Copyright (C) 2008-2010  SUSE Linux Products GmbH
+ * Copyright (C) 2008-2010  Tejun Heo <tj@kernel.org>
+ *
+ * This file is released under the GPLv2.
+ */
+
+#ifndef _OSSP_UTIL_H
+#define _OSSP_UTIL_H
+
+#include <assert.h>
+#include <stddef.h>
+#include <string.h>
+#include <sys/types.h>
+#include <errno.h>
+#include <unistd.h>
+#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 (file)
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 <tj@kernel.org>
+ *
+ * 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 (file)
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 <tj@kernel.org>
+ *
+ * This file is released under the GPLv2.
+ */
+
+#ifndef _OSSP_H
+#define _OSSP_H
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <sys/soundcard.h>
+
+#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 (file)
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 <tj@kernel.org>
+ *
+ * This file is released under the GPLv2.
+ */
+
+#define FUSE_USE_VERSION 28
+#define _GNU_SOURCE
+
+#include <assert.h>
+#include <cuse_lowlevel.h>
+#include <fcntl.h>
+#include <fuse_opt.h>
+#include <libgen.h>
+#include <limits.h>
+#include <pthread.h>
+#include <pwd.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/epoll.h>
+#include <sys/socket.h>
+#include <sys/soundcard.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#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,
+                          &not_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(&notify_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, &notify, &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,
+                                  &notify, 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(&notify_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, &param, 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 <teheo@suse.de>",
+            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(&notify_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 (file)
index 0000000..53c3edc
--- /dev/null
+++ b/osstest.c
@@ -0,0 +1,266 @@
+/* Simple oss testsuite
+ *
+ * Copyright (C) 2009 Maarten Lankhorst <m.b.lankhorst@gmail.com>
+ *
+ * This file is released under the GPLv2.
+ */
+
+#include <sys/types.h>
+#include <sys/soundcard.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <poll.h>
+#include <sys/mman.h>
+#include <stdlib.h>
+
+#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;
+}
+