├── .github └── workflows │ └── build.yml ├── .gitignore ├── 98-osscuse.rules ├── CMakeLists.txt ├── LICENSE ├── README ├── cmake └── ossp-util.cmake ├── ossp-alsap.c ├── ossp-padsp.c ├── ossp-slave.c ├── ossp-slave.h ├── ossp-util.c ├── ossp-util.h ├── ossp.c ├── ossp.h ├── osspd.c └── osstest.c /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ${{matrix.os}} 8 | strategy: 9 | fail-fast: false 10 | matrix: 11 | include: 12 | - name: 'Ubuntu (Clang)' 13 | os: ubuntu-latest 14 | cCompiler: 'clang' 15 | cppCompiler: 'clang++' 16 | - name: 'Ubuntu (GCC)' 17 | os: ubuntu-latest 18 | cCompiler: 'gcc' 19 | cppCompiler: 'g++' 20 | 21 | steps: 22 | - name: Install packages 23 | run: sudo apt-get install -y cmake 24 | ninja-build 25 | libfuse3-dev 26 | libasound-dev 27 | libpulse-dev 28 | 29 | - uses: actions/checkout@v4 30 | with: 31 | submodules: recursive 32 | 33 | - name: Build 34 | uses: threeal/cmake-action@v2 35 | with: 36 | generator: Ninja 37 | c-compiler: ${{matrix.cCompiler}} 38 | cxx-compiler: ${{matrix.cppCompiler}} 39 | options: | 40 | alsa=ON 41 | test=ON 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | -------------------------------------------------------------------------------- /98-osscuse.rules: -------------------------------------------------------------------------------- 1 | # Allow everyone to use these devices 2 | # /dev/cuse can stay mode 0660 root:root since osspd is run as root 3 | # and drops privileges to user level when opened by user 4 | KERNEL=="dsp", SUBSYSTEM=="cuse", MODE="0666" 5 | KERNEL=="mixer", SUBSYSTEM=="cuse", MODE="0666" 6 | KERNEL=="adsp", SUBSYSTEM=="cuse", MODE="0666" 7 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.1) 2 | 3 | list(APPEND CMAKE_MODULE_PATH 4 | "${CMAKE_CURRENT_SOURCE_DIR}/cmake" 5 | ) 6 | 7 | project("OSS Proxy" 8 | VERSION 1.3.3 9 | DESCRIPTION "Emulate OSS device(s) using CUSE." 10 | HOMEPAGE_URL "https://github.com/OpenMandrivaSoftware/ossp" 11 | LANGUAGES "C" 12 | ) 13 | 14 | option(alsa "Build ALSA slave." OFF) 15 | option(daemon "Build daemon." ON) 16 | option(pulseaudio "Build PulseAudio slave." ON) 17 | option(test "Build test." OFF) 18 | 19 | include(ossp-util) 20 | 21 | if(test) 22 | include(CTest) 23 | endif() 24 | 25 | # Shared library 26 | add_library(libossp OBJECT 27 | "ossp.c" 28 | "ossp.h" 29 | "ossp-slave.c" 30 | "ossp-slave.h" 31 | "ossp-util.c" 32 | "ossp-util.h" 33 | ) 34 | 35 | target_compile_definitions(libossp 36 | PUBLIC 37 | "OSSP_VERSION=\"${PROJECT_VERSION}\"" 38 | ) 39 | 40 | # Daemon 41 | if(daemon) 42 | add_executable(osspd "osspd.c") 43 | set_output_dir(osspd) 44 | 45 | link_pkg(osspd "fuse3") 46 | target_link_libraries(osspd PRIVATE libossp) 47 | 48 | install_daemon(osspd) 49 | endif() 50 | 51 | # ALSA slave 52 | if(alsa) 53 | add_executable(ossp-alsap "ossp-alsap.c") 54 | set_output_dir(ossp-alsap) 55 | 56 | link_pkg(ossp-alsap "alsa") 57 | target_link_libraries(ossp-alsap PRIVATE libossp) 58 | 59 | install_slave(ossp-alsap) 60 | endif() 61 | 62 | # PulseAudio slave 63 | if(pulseaudio) 64 | add_executable(ossp-padsp "ossp-padsp.c") 65 | set_output_dir(ossp-padsp) 66 | 67 | link_pkg(ossp-padsp "libpulse") 68 | target_link_libraries(ossp-padsp PRIVATE libossp) 69 | 70 | install_slave(ossp-padsp) 71 | endif() 72 | 73 | if(test) 74 | add_executable(osstest "osstest.c") 75 | 76 | add_test( 77 | NAME osstest 78 | COMMAND osstest 79 | WORKING_DIRECTORY ${PROJECT_BINARY_DIR} 80 | ) 81 | endif() 82 | 83 | install_udev_rules("98-osscuse.rules") 84 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | 2 | OSS Proxy - emulate OSS device using CUSE 3 | 4 | 1. What is it? 5 | -------------- 6 | 7 | Well, first, OSS refers to Open Sound System. If it still doesn't 8 | ring a bell, think /dev/dsp, /dev/adsp and /dev/mixer. 9 | 10 | Currently, Linux supports two audio programming interface - ALSA and 11 | OSS. The latter one is deprecated and has been that way for a long 12 | time but there still are applications which still use them including 13 | UML (usermode Linux) host sound support. 14 | 15 | ALSA contains OSS emulation but sadly the emulation is behind 16 | multiplexing layer (which is in userland) which means that if your 17 | sound card doesn't support multiple audio streams, only either one of 18 | ALSA or OSS interface would be usable at any given moment. 19 | 20 | There have been also attempts to emulate OSS in userland using dynamic 21 | library preloading - aoss and more recently padsp. This works for 22 | many applications but it's just not easy to emulate everything using 23 | the technique. Things like polling, signals, forking, privilege 24 | changes make it very difficult to emulate things reliably. 25 | 26 | OSS Proxy uses CUSE (extension of FUSE allowing character devices to 27 | be implemented in userspace) to implement OSS interface - /dev/dsp, 28 | /dev/adsp and /dev/mixer. From the POV of the applications, these 29 | devices are proper character devices and behave exactly the same way 30 | so it can be made quite versatile. 31 | 32 | 33 | 2. Hmmm... So, how does the whole thing work? 34 | --------------------------------------------- 35 | 36 | The OSS Proxy daemon - osspd - should be started first. Note that 37 | osspd will fail to start if sound device number regions are already 38 | occupied. You'll need to turn off OSS or its emulation[1]. 39 | 40 | On startup, osspd creates /dev/dsp, /dev/adsp and /dev/mixer using 41 | CUSE. When an application access one of the devices, all IOs are 42 | redirected to osspd via CUSE. Upon receiving a new DSP open request, 43 | osspd creates a slave process which drops the root privilege and 44 | assumes the opening process's credentials. After handshaking, osspd 45 | forwards all relevant IOs to the slave which is responsible for 46 | actually playing the sound. 47 | 48 | Currently there's only one slave implemented - ossp-padsp, which as 49 | the name suggests forwards (again) the sound to pulseaudio. To sum 50 | up, the whole pipe looks like the following. 51 | 52 | App <-> /dev/dsp <-> CUSE <-> osspd <-> ossp-padsp <-> pulseaudio 53 | 54 | Which is a lot of forwarding, but on modern machines, it won't be too 55 | noticeable. 56 | 57 | 58 | 3. What works? 59 | -------------- 60 | 61 | Well, MIDI part isn't implemented and I doubt it will be in any near 62 | future but except that everything should work. Playing, recording, 63 | 5.1ch, A-V syncing, all should work. If not, it's a bug, so please 64 | report. 65 | 66 | The mixer behaves a bit differently tho. In the original OSS, 67 | /dev/mixer is the hardware mixer, so adjusting volumes there affects 68 | all audio streams. When using ossp, each process group gets its own 69 | mixer and the mixer always contains only two knobs - PCM and IGAIN. 70 | Combined with per-stream volume control of pulseaudio, this scheme 71 | works quite well for applications with embedded volume control 72 | although it makes standalone OSS mixer programs virtually useless[2]. 73 | 74 | 75 | 4. How do I use it? 76 | ------------------- 77 | 78 | First you need CUSE support in kernel which might land on 2.6.28 with 79 | sufficient luck[3] and then you also need libfuse which supports 80 | CUSE[4]. Once you have both, it should be easy. First build it by 81 | running `make'. You can set OSSPD_CFLAGS, OSSPD_LDFLAGS, 82 | OSSP_PADSP_CFLAGS and OSSP_PADSP_LDFLAGS if you have stuff at 83 | non-default locations. 84 | 85 | After build completes, there will be two executables - `osspd' and 86 | `ossp-padsp'. Just copy them to where other system executables live. 87 | Specific location doesn't matter as long as both files end up in the 88 | same directory. 89 | 90 | Execute `osspd'. It will create the device files and you're all set. 91 | `osspd' uses syslog with LOG_DAEMON facility, so if something doesn't 92 | work take a look at what osspd complains about. 93 | 94 | 95 | [1] As of this writing, turning on any sound support makes the 96 | soundcore module claim OSS device regions. Patch to make it claim 97 | OSS device regions only when OSS support or emulation is enabled 98 | is scheduled for 2.6.28. Even with the patch, soundcore will 99 | claim OSS device regions if OSS support or ALSA OSS emulation is 100 | enabled. Make sure they're turned off. 101 | 102 | [2] If you have a strong reason to use standalone OSS mixer program, 103 | you can play some shell tricks to put it into the same process 104 | group as the target audio application. e.g. To use aumix with 105 | mpg123 - `(mpg123 asdf.mp3 > /dev/null 2>&1 & aumix)', but 106 | seriously, just use PA or ALSA one. 107 | 108 | [3] For the time being, here's the git tree with all the necessary 109 | changes. This tree is base on top of 2.6.27-rc3. 110 | 111 | http://git.kernel.org/?p=linux/kernel/git/tj/misc.git;a=shortlog;h=cuse 112 | git://git.kernel.org/pub/scm/linux/kernel/git/tj/misc.git cuse 113 | 114 | [4] And libfuse with the modifications can be found at... 115 | 116 | http://userweb.kernel.org/~tj/ossp/fuse-cuse.tar.gz 117 | -------------------------------------------------------------------------------- /cmake/ossp-util.cmake: -------------------------------------------------------------------------------- 1 | include(GNUInstallDirs) 2 | 3 | find_package(PkgConfig REQUIRED) 4 | 5 | pkg_check_modules(PKGCONFIG_UDEV udev QUIET) 6 | if(PKGCONFIG_UDEV_FOUND) 7 | pkg_get_variable(UDEVDIR udev udevdir) 8 | else() 9 | set(UDEVDIR "${CMAKE_INSTALL_PREFIX}/lib/udev") 10 | endif() 11 | 12 | set(INSTALL_UDEVRULESDIR 13 | "${UDEVDIR}/rules.d" 14 | CACHE PATH 15 | "Install path for udev rules." 16 | ) 17 | 18 | function(link_pkg TARGET PKG) 19 | pkg_search_module(${PKG} ${PKG} REQUIRED) 20 | 21 | target_compile_options(${TARGET} PRIVATE ${${PKG}_CFLAGS}) 22 | target_include_directories(${TARGET} PRIVATE ${${PKG}_INCLUDE_DIRS}) 23 | target_link_libraries(${TARGET} PRIVATE ${${PKG}_LINK_LIBRARIES}) 24 | endfunction() 25 | 26 | macro(set_output_dir TARGET) 27 | set_target_properties(${TARGET} 28 | PROPERTIES 29 | RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR} 30 | ) 31 | endmacro() 32 | 33 | macro(install_daemon TARGET) 34 | install(TARGETS ${TARGET} 35 | RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR} 36 | ) 37 | endmacro() 38 | 39 | macro(install_slave TARGET) 40 | install(TARGETS ${TARGET} 41 | RUNTIME DESTINATION "${CMAKE_INSTALL_LIBEXECDIR}/ossp" 42 | ) 43 | endmacro() 44 | 45 | macro(install_udev_rules FILES) 46 | install(FILES ${FILES} 47 | DESTINATION ${INSTALL_UDEVRULESDIR} 48 | ) 49 | endmacro() 50 | -------------------------------------------------------------------------------- /ossp-alsap.c: -------------------------------------------------------------------------------- 1 | /* 2 | * ossp-alsap - ossp DSP slave which forwards to ALSA 3 | * 4 | * This file is released under the GPLv2. 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | 26 | #include "ossp-slave.h" 27 | 28 | enum { 29 | AFMT_FLOAT = 0x00004000, 30 | AFMT_S32_LE = 0x00001000, 31 | AFMT_S32_BE = 0x00002000, 32 | }; 33 | 34 | static size_t page_size; 35 | 36 | /* alsa structures */ 37 | static snd_pcm_t *pcm[2]; 38 | static snd_pcm_hw_params_t *hw_params; 39 | static snd_pcm_sw_params_t *sw_params; 40 | static int block; 41 | 42 | static unsigned int byte_counter[2]; 43 | static snd_pcm_uframes_t mmap_pos[2]; 44 | static int stream_corked[2]; 45 | static int stream_notify; 46 | 47 | static struct format { 48 | snd_pcm_format_t format; 49 | snd_pcm_sframes_t rate; 50 | int channels; 51 | } hw_format = { SND_PCM_FORMAT_U8, 8000, 1 }; 52 | 53 | #if 0 54 | /* future mmap stuff */ 55 | static size_t mmap_raw_size, mmap_size; 56 | static int mmap_fd[2] = { -1, -1 }; 57 | static void *mmap_map[2]; 58 | static uint64_t mmap_idx[2]; /* mmap pointer */ 59 | static uint64_t mmap_last_idx[2]; /* last idx for get_ptr */ 60 | static struct ring_buf mmap_stg[2]; /* staging ring buffer */ 61 | static size_t mmap_lead[2]; /* lead bytes */ 62 | static int mmap_sync[2]; /* sync with backend stream */ 63 | #endif 64 | 65 | static snd_pcm_format_t fmt_oss_to_alsa(int fmt) 66 | { 67 | switch (fmt) { 68 | case AFMT_U8: return SND_PCM_FORMAT_U8; 69 | case AFMT_A_LAW: return SND_PCM_FORMAT_A_LAW; 70 | case AFMT_MU_LAW: return SND_PCM_FORMAT_MU_LAW; 71 | case AFMT_S16_LE: return SND_PCM_FORMAT_S16_LE; 72 | case AFMT_S16_BE: return SND_PCM_FORMAT_S16_BE; 73 | case AFMT_FLOAT: return SND_PCM_FORMAT_FLOAT; 74 | case AFMT_S32_LE: return SND_PCM_FORMAT_S32_LE; 75 | case AFMT_S32_BE: return SND_PCM_FORMAT_S32_BE; 76 | default: return SND_PCM_FORMAT_U8; 77 | } 78 | } 79 | 80 | static int fmt_alsa_to_oss(snd_pcm_format_t fmt) 81 | { 82 | switch (fmt) { 83 | case SND_PCM_FORMAT_U8: return AFMT_U8; 84 | case SND_PCM_FORMAT_A_LAW: return AFMT_A_LAW; 85 | case SND_PCM_FORMAT_MU_LAW: return AFMT_MU_LAW; 86 | case SND_PCM_FORMAT_S16_LE: return AFMT_S16_LE; 87 | case SND_PCM_FORMAT_S16_BE: return AFMT_S16_BE; 88 | case SND_PCM_FORMAT_FLOAT: return AFMT_FLOAT; 89 | case SND_PCM_FORMAT_S32_LE: return AFMT_S32_LE; 90 | case SND_PCM_FORMAT_S32_BE: return AFMT_S32_BE; 91 | default: return AFMT_U8; 92 | } 93 | } 94 | 95 | static void flush_streams(int drain) 96 | { 97 | /* FIXME: snd_pcm_drain appears to be able to deadlock, 98 | * always drop or check state? */ 99 | if (drain) { 100 | if (pcm[PLAY]) 101 | snd_pcm_drain(pcm[PLAY]); 102 | if (pcm[REC]) 103 | snd_pcm_drain(pcm[REC]); 104 | } else { 105 | if (pcm[PLAY]) 106 | snd_pcm_drop(pcm[PLAY]); 107 | if (pcm[REC]) 108 | snd_pcm_drop(pcm[REC]); 109 | } 110 | 111 | /* XXX: Really needed? */ 112 | #if 0 113 | if (pcm[PLAY]) { 114 | snd_pcm_close(pcm[PLAY]); 115 | snd_pcm_open(&pcm[PLAY], "default", 116 | SND_PCM_STREAM_PLAYBACK, block); 117 | } 118 | if (pcm[REC]) { 119 | snd_pcm_close(pcm[REC]); 120 | snd_pcm_open(&pcm[REC], "default", 121 | SND_PCM_STREAM_CAPTURE, block); 122 | } 123 | #endif 124 | } 125 | 126 | static void kill_streams(void) 127 | { 128 | flush_streams(0); 129 | } 130 | 131 | static int trigger_streams(int play, int rec) 132 | { 133 | int ret = 0; 134 | 135 | if (pcm[PLAY] && play >= 0) { 136 | ret = snd_pcm_sw_params_set_start_threshold(pcm[PLAY], sw_params, 137 | play ? 1 : -1); 138 | if (ret >= 0) 139 | snd_pcm_sw_params(pcm[PLAY], sw_params); 140 | } 141 | if (ret >= 0 && pcm[REC] && rec >= 0) { 142 | ret = snd_pcm_sw_params_set_start_threshold(pcm[REC], sw_params, 143 | rec ? 1 : -1); 144 | if (ret >= 0) 145 | snd_pcm_sw_params(pcm[REC], sw_params); 146 | } 147 | 148 | return ret; 149 | } 150 | 151 | static ssize_t alsap_mixer(enum ossp_opcode opcode, 152 | void *carg, void *din, size_t din_sz, 153 | void *rarg, void *dout, size_t *dout_szp, int tfd) 154 | { 155 | return -EBUSY; 156 | } 157 | 158 | static int set_hw_params(snd_pcm_t *pcm) 159 | { 160 | int ret; 161 | unsigned rate; 162 | 163 | ret = snd_pcm_hw_params_any(pcm, hw_params); 164 | if (ret >= 0) 165 | ret = snd_pcm_hw_params_set_access(pcm, hw_params, 166 | SND_PCM_ACCESS_RW_INTERLEAVED); 167 | rate = hw_format.rate; 168 | if (ret >= 0) 169 | ret = snd_pcm_hw_params_set_rate_minmax(pcm, hw_params, 170 | &rate, NULL, 171 | &rate, NULL); 172 | if (ret >= 0) 173 | ret = snd_pcm_hw_params_set_format(pcm, hw_params, hw_format.format); 174 | if (ret >= 0) 175 | ret = snd_pcm_hw_params_set_channels(pcm, hw_params, 176 | hw_format.channels); 177 | if (ret >= 0) 178 | ret = snd_pcm_hw_params(pcm, hw_params); 179 | if (ret >= 0) 180 | ret = snd_pcm_sw_params_current(pcm, sw_params); 181 | if (ret >= 0) 182 | ret = snd_pcm_sw_params(pcm, sw_params); 183 | return ret; 184 | } 185 | 186 | static ssize_t alsap_open(enum ossp_opcode opcode, 187 | void *carg, void *din, size_t din_sz, 188 | void *rarg, void *dout, size_t *dout_szp, int tfd) 189 | { 190 | struct ossp_dsp_open_arg *arg = carg; 191 | int ret; 192 | block = arg->flags & O_NONBLOCK ? SND_PCM_NONBLOCK : 0; 193 | int access; 194 | // block |= SND_PCM_ASYNC; 195 | /* Woop dee dooo.. I love handling things in SIGIO (PAIN!!) 196 | * Probably needed for MMAP 197 | */ 198 | 199 | if (!hw_params) 200 | ret = snd_pcm_hw_params_malloc(&hw_params); 201 | if (ret < 0) 202 | return ret; 203 | 204 | if (!sw_params) 205 | ret = snd_pcm_sw_params_malloc(&sw_params); 206 | if (ret < 0) 207 | return ret; 208 | 209 | if (pcm[PLAY]) 210 | snd_pcm_close(pcm[PLAY]); 211 | if (pcm[REC]) 212 | snd_pcm_close(pcm[REC]); 213 | pcm[REC] = pcm[PLAY] = NULL; 214 | 215 | access = arg->flags & O_ACCMODE; 216 | if (access == O_WRONLY || access == O_RDWR) { 217 | ret = snd_pcm_open(&pcm[PLAY], "default", 218 | SND_PCM_STREAM_PLAYBACK, block); 219 | if (ret >= 0) 220 | ret = set_hw_params(pcm[PLAY]); 221 | } 222 | 223 | if (ret >= 0 && (access == O_RDONLY || access == O_RDWR)) { 224 | ret = snd_pcm_open(&pcm[REC], "default", 225 | SND_PCM_STREAM_CAPTURE, block); 226 | if (ret >= 0) 227 | ret = set_hw_params(pcm[REC]); 228 | } 229 | 230 | if (ret < 0) { 231 | if (pcm[PLAY]) 232 | snd_pcm_close(pcm[PLAY]); 233 | if (pcm[REC]) 234 | snd_pcm_close(pcm[REC]); 235 | pcm[REC] = pcm[PLAY] = NULL; 236 | return ret; 237 | } 238 | return 0; 239 | } 240 | 241 | static ssize_t alsap_write(enum ossp_opcode opcode, 242 | void *carg, void *din, size_t din_sz, 243 | void *rarg, void *dout, size_t *dout_szp, int tfd) 244 | { 245 | // struct ossp_dsp_rw_arg *arg = carg; 246 | int ret, insize; 247 | 248 | insize = snd_pcm_bytes_to_frames(pcm[PLAY], din_sz); 249 | 250 | if (snd_pcm_state(pcm[PLAY]) == SND_PCM_STATE_SETUP) 251 | snd_pcm_prepare(pcm[PLAY]); 252 | 253 | // snd_pcm_start(pcm[PLAY]); 254 | ret = snd_pcm_writei(pcm[PLAY], din, insize); 255 | if (ret < 0) 256 | ret = snd_pcm_recover(pcm[PLAY], ret, 1); 257 | 258 | if (ret >= 0) 259 | return snd_pcm_frames_to_bytes(pcm[PLAY], ret); 260 | else 261 | return ret; 262 | } 263 | 264 | static ssize_t alsap_read(enum ossp_opcode opcode, 265 | void *carg, void *din, size_t din_sz, 266 | void *rarg, void *dout, size_t *dout_szp, int tfd) 267 | { 268 | // struct ossp_dsp_rw_arg *arg = carg; 269 | int ret, outsize; 270 | 271 | outsize = snd_pcm_bytes_to_frames(pcm[REC], *dout_szp); 272 | 273 | if (snd_pcm_state(pcm[REC]) == SND_PCM_STATE_SETUP) 274 | snd_pcm_prepare(pcm[REC]); 275 | 276 | ret = snd_pcm_readi(pcm[REC], dout, outsize); 277 | if (ret < 0) 278 | ret = snd_pcm_recover(pcm[REC], ret, 1); 279 | if (ret >= 0) 280 | *dout_szp = ret = snd_pcm_frames_to_bytes(pcm[REC], ret); 281 | else 282 | *dout_szp = 0; 283 | 284 | return ret; 285 | } 286 | 287 | static ssize_t alsap_poll(enum ossp_opcode opcode, 288 | void *carg, void *din, size_t din_sz, 289 | void *rarg, void *dout, size_t *dout_szp, int tfd) 290 | { 291 | unsigned revents = 0; 292 | 293 | stream_notify |= *(int *)carg; 294 | 295 | if (pcm[PLAY]) 296 | revents |= POLLOUT; 297 | if (pcm[REC]) 298 | revents |= POLLIN; 299 | 300 | *(unsigned *)rarg = revents; 301 | return 0; 302 | } 303 | 304 | 305 | static ssize_t alsap_flush(enum ossp_opcode opcode, 306 | void *carg, void *din, size_t din_sz, 307 | void *rarg, void *dout, size_t *dout_szp, int tfd) 308 | { 309 | flush_streams(opcode == OSSP_DSP_SYNC); 310 | return 0; 311 | } 312 | 313 | static ssize_t alsap_post(enum ossp_opcode opcode, 314 | void *carg, void *din, size_t din_sz, 315 | void *rarg, void *dout, size_t *dout_szp, int tfd) 316 | { 317 | int ret; 318 | 319 | ret = trigger_streams(1, 1); 320 | if (ret >= 0 && pcm[PLAY]) 321 | ret = snd_pcm_start(pcm[PLAY]); 322 | if (pcm[REC]) 323 | ret = snd_pcm_start(pcm[REC]); 324 | return ret; 325 | } 326 | 327 | static ssize_t alsap_get_param(enum ossp_opcode opcode, 328 | void *carg, void *din, size_t din_sz, 329 | void *rarg, void *dout, size_t *dout_szp, 330 | int tfd) 331 | { 332 | int v = 0; 333 | 334 | switch (opcode) { 335 | case OSSP_DSP_GET_RATE: 336 | return hw_format.rate; 337 | 338 | case OSSP_DSP_GET_CHANNELS: 339 | return hw_format.channels; 340 | 341 | case OSSP_DSP_GET_FORMAT: { 342 | v = fmt_alsa_to_oss(hw_format.format); 343 | break; 344 | } 345 | 346 | case OSSP_DSP_GET_BLKSIZE: { 347 | snd_pcm_uframes_t psize; 348 | snd_pcm_hw_params_get_period_size(hw_params, &psize, NULL); 349 | v = psize; 350 | break; 351 | } 352 | 353 | case OSSP_DSP_GET_FORMATS: 354 | v = AFMT_U8 | AFMT_A_LAW | AFMT_MU_LAW | AFMT_S16_LE | 355 | AFMT_S16_BE | AFMT_FLOAT | AFMT_S32_LE | AFMT_S32_BE; 356 | break; 357 | 358 | case OSSP_DSP_GET_TRIGGER: 359 | if (!stream_corked[PLAY]) 360 | v |= PCM_ENABLE_OUTPUT; 361 | if (!stream_corked[REC]) 362 | v |= PCM_ENABLE_INPUT; 363 | break; 364 | 365 | default: 366 | assert(0); 367 | } 368 | 369 | *(int *)rarg = v; 370 | 371 | return 0; 372 | } 373 | 374 | static ssize_t alsap_set_param(enum ossp_opcode opcode, 375 | void *carg, void *din, size_t din_sz, 376 | void *rarg, void *dout, size_t *dout_szp, 377 | int tfd) 378 | { 379 | int v = *(int *)carg; 380 | int ret = 0; 381 | 382 | /* kill the streams before changing parameters */ 383 | kill_streams(); 384 | 385 | switch (opcode) { 386 | case OSSP_DSP_SET_RATE: { 387 | hw_format.rate = v; 388 | break; 389 | } 390 | 391 | case OSSP_DSP_SET_CHANNELS: { 392 | hw_format.channels = v; 393 | break; 394 | } 395 | 396 | case OSSP_DSP_SET_FORMAT: { 397 | snd_pcm_format_t format = fmt_oss_to_alsa(v); 398 | hw_format.format = format; 399 | break; 400 | } 401 | 402 | case OSSP_DSP_SET_SUBDIVISION: 403 | if (!v) 404 | v = 1; 405 | #if 0 406 | if (!v) { 407 | v = user_subdivision ?: 1; 408 | break; 409 | } 410 | user_frag_size = 0; 411 | user_subdivision = v; 412 | break; 413 | 414 | case OSSP_DSP_SET_FRAGMENT: 415 | user_subdivision = 0; 416 | user_frag_size = 1 << (v & 0xffff); 417 | user_max_frags = (v >> 16) & 0xffff; 418 | if (user_frag_size < 4) 419 | user_frag_size = 4; 420 | if (user_max_frags < 2) 421 | user_max_frags = 2; 422 | #else 423 | case OSSP_DSP_SET_FRAGMENT: 424 | #endif 425 | break; 426 | default: 427 | assert(0); 428 | } 429 | 430 | if (pcm[PLAY]) 431 | ret = set_hw_params(pcm[PLAY]); 432 | if (ret >= 0 && pcm[REC]) 433 | ret = set_hw_params(pcm[REC]); 434 | 435 | if (rarg) 436 | *(int *)rarg = v; 437 | return 0; 438 | } 439 | 440 | static ssize_t alsap_set_trigger(enum ossp_opcode opcode, 441 | void *carg, void *din, size_t din_sz, 442 | void *rarg, void *dout, size_t *dout_szp, 443 | int fd) 444 | { 445 | int enable = *(int *)carg; 446 | 447 | stream_corked[PLAY] = !!(enable & PCM_ENABLE_OUTPUT); 448 | stream_corked[REC] = !!(enable & PCM_ENABLE_INPUT); 449 | 450 | return trigger_streams(enable & PCM_ENABLE_OUTPUT, 451 | enable & PCM_ENABLE_INPUT); 452 | } 453 | 454 | static ssize_t alsap_get_space(enum ossp_opcode opcode, 455 | void *carg, void *din, size_t din_sz, 456 | void *rarg, void *dout, size_t *dout_szp, int tfd) 457 | { 458 | int dir = (opcode == OSSP_DSP_GET_OSPACE) ? PLAY : REC; 459 | int underrun = 0; 460 | struct audio_buf_info info = { }; 461 | unsigned long bufsize; 462 | snd_pcm_uframes_t avail, fragsize; 463 | snd_pcm_state_t state; 464 | 465 | if (!pcm[dir]) 466 | return -EINVAL; 467 | 468 | state = snd_pcm_state(pcm[dir]); 469 | if (state == SND_PCM_STATE_XRUN) { 470 | snd_pcm_recover(pcm[dir], -EPIPE, 0); 471 | underrun = 1; 472 | } else if (state == SND_PCM_STATE_SUSPENDED) { 473 | snd_pcm_recover(pcm[dir], -ESTRPIPE, 0); 474 | underrun = 1; 475 | } 476 | 477 | snd_pcm_hw_params_current(pcm[dir], hw_params); 478 | snd_pcm_hw_params_get_period_size(hw_params, &fragsize, NULL); 479 | snd_pcm_hw_params_get_buffer_size(hw_params, &bufsize); 480 | info.fragsize = snd_pcm_frames_to_bytes(pcm[dir], fragsize); 481 | info.fragstotal = bufsize / fragsize; 482 | if (!underrun) { 483 | avail = snd_pcm_avail_update(pcm[dir]); 484 | info.fragments = avail / fragsize; 485 | } else 486 | info.fragments = info.fragstotal; 487 | 488 | info.bytes = info.fragsize * info.fragments; 489 | 490 | *(struct audio_buf_info *)rarg = info; 491 | return 0; 492 | } 493 | 494 | static ssize_t alsap_get_ptr(enum ossp_opcode opcode, 495 | void *carg, void *din, size_t din_sz, 496 | void *rarg, void *dout, size_t *dout_szp, int tfd) 497 | { 498 | int dir = (opcode == OSSP_DSP_GET_OPTR) ? PLAY : REC; 499 | struct count_info info = { }; 500 | 501 | if (!pcm[dir]) 502 | return -EIO; 503 | 504 | snd_pcm_hw_params_current(pcm[dir], hw_params); 505 | info.bytes = byte_counter[dir]; 506 | snd_pcm_hw_params_get_periods(hw_params, (unsigned int *)&info.blocks, NULL); 507 | info.ptr = mmap_pos[dir]; 508 | 509 | *(struct count_info *)rarg = info; 510 | return 0; 511 | } 512 | 513 | static ssize_t alsap_get_odelay(enum ossp_opcode opcode, 514 | void *carg, void *din, size_t din_sz, 515 | void *rarg, void *dout, size_t *dout_szp, 516 | int fd) 517 | { 518 | snd_pcm_sframes_t delay; 519 | 520 | if (!pcm[PLAY]) 521 | return -EIO; 522 | 523 | if (snd_pcm_delay(pcm[PLAY], &delay) < 0) 524 | return -EIO; 525 | 526 | *(int *)rarg = snd_pcm_frames_to_bytes(pcm[PLAY], delay); 527 | return 0; 528 | } 529 | 530 | static ossp_action_fn_t action_fn_tbl[OSSP_NR_OPCODES] = { 531 | [OSSP_MIXER] = alsap_mixer, 532 | [OSSP_DSP_OPEN] = alsap_open, 533 | [OSSP_DSP_READ] = alsap_read, 534 | [OSSP_DSP_WRITE] = alsap_write, 535 | [OSSP_DSP_POLL] = alsap_poll, 536 | #if 0 537 | [OSSP_DSP_MMAP] = alsap_mmap, 538 | [OSSP_DSP_MUNMAP] = alsap_munmap, 539 | #endif 540 | [OSSP_DSP_RESET] = alsap_flush, 541 | [OSSP_DSP_SYNC] = alsap_flush, 542 | [OSSP_DSP_POST] = alsap_post, 543 | [OSSP_DSP_GET_RATE] = alsap_get_param, 544 | [OSSP_DSP_GET_CHANNELS] = alsap_get_param, 545 | [OSSP_DSP_GET_FORMAT] = alsap_get_param, 546 | [OSSP_DSP_GET_BLKSIZE] = alsap_get_param, 547 | [OSSP_DSP_GET_FORMATS] = alsap_get_param, 548 | [OSSP_DSP_SET_RATE] = alsap_set_param, 549 | [OSSP_DSP_SET_CHANNELS] = alsap_set_param, 550 | [OSSP_DSP_SET_FORMAT] = alsap_set_param, 551 | [OSSP_DSP_SET_SUBDIVISION] = alsap_set_param, 552 | [OSSP_DSP_SET_FRAGMENT] = alsap_set_param, 553 | [OSSP_DSP_GET_TRIGGER] = alsap_get_param, 554 | [OSSP_DSP_SET_TRIGGER] = alsap_set_trigger, 555 | [OSSP_DSP_GET_OSPACE] = alsap_get_space, 556 | [OSSP_DSP_GET_ISPACE] = alsap_get_space, 557 | [OSSP_DSP_GET_OPTR] = alsap_get_ptr, 558 | [OSSP_DSP_GET_IPTR] = alsap_get_ptr, 559 | [OSSP_DSP_GET_ODELAY] = alsap_get_odelay, 560 | }; 561 | 562 | static int action_pre(void) 563 | { 564 | return 0; 565 | } 566 | 567 | static void action_post(void) 568 | { 569 | } 570 | 571 | int main(int argc, char **argv) 572 | { 573 | int rc; 574 | 575 | ossp_slave_init("ossp-alsap", argc, argv); 576 | 577 | page_size = sysconf(_SC_PAGE_SIZE); 578 | 579 | /* Okay, now we're open for business */ 580 | rc = 0; 581 | do { 582 | rc = ossp_slave_process_command(ossp_cmd_fd, action_fn_tbl, 583 | action_pre, action_post); 584 | } while (rc > 0); 585 | 586 | return rc ? 1 : 0; 587 | } 588 | -------------------------------------------------------------------------------- /ossp-padsp.c: -------------------------------------------------------------------------------- 1 | /* 2 | * ossp-padsp - ossp DSP slave which forwards to PulseAudio 3 | * 4 | * This file is released under the GPLv2. 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include 26 | #include 27 | 28 | #include "ossp-slave.h" 29 | 30 | enum { 31 | AFMT_FLOAT = 0x00004000, 32 | AFMT_S32_LE = 0x00001000, 33 | AFMT_S32_BE = 0x00002000, 34 | }; 35 | 36 | /* everything is in millisecs */ 37 | struct stream_params { 38 | size_t min_process; 39 | size_t min_latency; 40 | size_t dfl_process; 41 | size_t dfl_latency; 42 | size_t mmap_process; 43 | size_t mmap_latency; 44 | size_t mmap_lead; 45 | size_t mmap_staging; 46 | }; 47 | 48 | /* TODO: make this configurable */ 49 | static struct stream_params stream_params[] = { 50 | [ PLAY ] = { .min_process = 25, .min_latency = 100, 51 | .dfl_process = 50, .dfl_latency = 200, 52 | .mmap_process = 25, .mmap_latency = 50, 53 | .mmap_lead = 25, .mmap_staging = 100 }, 54 | [ REC ] = { .min_process = 25, .min_latency = 200, 55 | .dfl_process = 50, .dfl_latency = 400, 56 | .mmap_process = 25, .mmap_latency = 50, 57 | .mmap_lead = 25, .mmap_staging = 1000 }, 58 | }; 59 | 60 | static size_t page_size; 61 | static pa_context *context; 62 | static pa_threaded_mainloop *mainloop; 63 | static pa_mainloop_api *mainloop_api; 64 | static char stream_name[128]; 65 | static int stream_enabled[2]; 66 | static int stream_corked[2]; 67 | static int stream_waiting; 68 | static int stream_notify; 69 | static pa_channel_map channel_map_stor; 70 | static pa_channel_map *channel_map; 71 | static pa_stream *stream[2]; 72 | static pa_usec_t stream_ptr_timestamp[2]; 73 | static struct ring_buf rec_buf; 74 | static int stored_oss_vol[2][2] = { { -1, -1 }, { -1, -1 } }; 75 | static int fail_code; 76 | 77 | static pa_sample_spec sample_spec = { 78 | .format = PA_SAMPLE_U8, 79 | .rate = 8000, 80 | .channels = 1, 81 | }; 82 | static size_t sample_bps = 8000; 83 | static size_t frame_size = 1; 84 | 85 | /* user visible stream parameters */ 86 | static size_t user_frag_size; 87 | static size_t user_subdivision; /* alternative way to determine frag_size */ 88 | static size_t user_max_frags; /* maximum number of fragments */ 89 | static size_t user_max_length; 90 | 91 | /* actual stream parameters */ 92 | static size_t frag_size; 93 | static size_t target_length; 94 | static size_t max_length; 95 | static size_t prebuf_size; 96 | 97 | /* mmap stuff */ 98 | static size_t mmap_raw_size, mmap_size; 99 | static void *mmap_map[2]; 100 | static uint64_t mmap_idx[2]; /* mmap pointer */ 101 | static uint64_t mmap_last_idx[2]; /* last idx for get_ptr */ 102 | static struct ring_buf mmap_stg[2]; /* staging ring buffer */ 103 | static size_t mmap_lead[2]; /* lead bytes */ 104 | static int mmap_sync[2]; /* sync with backend stream */ 105 | 106 | static const char *dir_str[] = { 107 | [PLAY] = "PLAY", 108 | [REC] = "REC", 109 | }; 110 | 111 | static void stream_rw_callback(pa_stream *s, size_t length, void *userdata); 112 | 113 | #define __pa_err pa_strerror(pa_context_errno(context)) 114 | #define dbg1_pa(fmt, args...) dbg1(fmt" (%s)" , ##args, __pa_err) 115 | #define dbg0_pa(fmt, args...) dbg0(fmt" (%s)" , ##args, __pa_err) 116 | #define info_pa(fmt, args...) info(fmt" (%s)" , ##args, __pa_err) 117 | #define warn_pa(fmt, args...) warn(fmt" (%s)" , ##args, __pa_err) 118 | #define err_pa(fmt, args...) err(fmt" (%s)" , ##args, __pa_err) 119 | 120 | #define round_down(v, t) ((v) / (t) * (t)) 121 | #define round_up(v, t) (((v) + (t) - 1) / (t) * (t)) 122 | #define is_power2(v) !((v) & ((v) - 1)) 123 | 124 | static int do_mixer(int dir, int *vol); 125 | 126 | static int padsp_done(void) 127 | { 128 | fail_code = -EIO; 129 | mainloop_api->quit(mainloop_api, 1); 130 | return fail_code; 131 | } 132 | 133 | static int fmt_oss_to_pa(int fmt) 134 | { 135 | switch (fmt) { 136 | case AFMT_U8: return PA_SAMPLE_U8; 137 | case AFMT_A_LAW: return PA_SAMPLE_ALAW; 138 | case AFMT_MU_LAW: return PA_SAMPLE_ULAW; 139 | case AFMT_S16_LE: return PA_SAMPLE_S16LE; 140 | case AFMT_S16_BE: return PA_SAMPLE_S16BE; 141 | case AFMT_FLOAT: return PA_SAMPLE_FLOAT32NE; 142 | case AFMT_S32_LE: return PA_SAMPLE_S32LE; 143 | case AFMT_S32_BE: return PA_SAMPLE_S32BE; 144 | default: return PA_SAMPLE_U8; 145 | } 146 | } 147 | 148 | static int fmt_pa_to_oss(int fmt) 149 | { 150 | switch (fmt) { 151 | case PA_SAMPLE_U8: return AFMT_U8; 152 | case PA_SAMPLE_ALAW: return AFMT_A_LAW; 153 | case PA_SAMPLE_ULAW: return AFMT_MU_LAW; 154 | case PA_SAMPLE_S16LE: return AFMT_S16_LE; 155 | case PA_SAMPLE_S16BE: return AFMT_S16_BE; 156 | case PA_SAMPLE_FLOAT32NE: return AFMT_FLOAT; 157 | case PA_SAMPLE_S32LE: return AFMT_S32_LE; 158 | case PA_SAMPLE_S32BE: return AFMT_S32_BE; 159 | default: return AFMT_U8; 160 | } 161 | } 162 | 163 | #define EXEC_OP(op, args...) do { \ 164 | pa_operation *_o; \ 165 | _o = op(args); \ 166 | if (_o) { \ 167 | while (pa_operation_get_state(_o) != PA_OPERATION_DONE) \ 168 | pa_threaded_mainloop_wait(mainloop); \ 169 | pa_operation_unref(_o); \ 170 | } } while (0) 171 | 172 | static void context_op_callback(pa_context *s, int success, void *userdata) 173 | { 174 | *(int *)userdata = success; 175 | pa_threaded_mainloop_signal(mainloop, 0); 176 | } 177 | 178 | static void stream_op_callback(pa_stream *s, int success, void *userdata) 179 | { 180 | *(int *)userdata = success; 181 | pa_threaded_mainloop_signal(mainloop, 0); 182 | } 183 | 184 | #define EXEC_CONTEXT_OP(op, args...) ({ \ 185 | int _success; \ 186 | EXEC_OP(op , ##args, context_op_callback, &_success); \ 187 | if (!_success) \ 188 | warn_pa("%s() failed", #op); \ 189 | _success ? 0 : -EIO; }) 190 | 191 | #define EXEC_STREAM_OP(op, args...) ({ \ 192 | int _success; \ 193 | EXEC_OP(op , ##args, stream_op_callback, &_success); \ 194 | if (!_success) \ 195 | warn_pa("%s() failed", #op); \ 196 | _success ? 0 : -EIO; }) 197 | 198 | static int mmapped(void) 199 | { 200 | return mmap_map[PLAY] || mmap_map[REC]; 201 | } 202 | 203 | static uint64_t get_mmap_idx(int dir) 204 | { 205 | uint64_t idx; 206 | pa_usec_t time; 207 | 208 | if (!stream[dir]) 209 | return mmap_idx[dir]; 210 | 211 | if (pa_stream_get_time(stream[dir], &time) < 0) { 212 | dbg1_pa("pa_stream_get_time() failed"); 213 | return mmap_idx[dir]; 214 | } 215 | 216 | /* calculate the current index from time elapsed */ 217 | idx = ((uint64_t)time * sample_bps / 1000000); 218 | /* round down to the nearest frame boundary */ 219 | idx = idx / frame_size * frame_size; 220 | 221 | return idx; 222 | } 223 | 224 | static void flush_streams(int drain) 225 | { 226 | int i; 227 | 228 | if (!(stream[PLAY] || stream[REC])) 229 | return; 230 | 231 | dbg0("FLUSH drain=%d", drain); 232 | 233 | /* mmapped streams run forever, can't drain */ 234 | if (drain && !mmapped() && stream[PLAY]) 235 | EXEC_STREAM_OP(pa_stream_drain, stream[PLAY]); 236 | 237 | for (i = 0; i < 2; i++) 238 | if (stream[i]) 239 | EXEC_STREAM_OP(pa_stream_flush, stream[i]); 240 | 241 | ring_consume(&rec_buf, ring_bytes(&rec_buf)); 242 | } 243 | 244 | static void kill_streams(void) 245 | { 246 | int dir; 247 | 248 | if (!(stream[PLAY] || stream[REC])) 249 | return; 250 | 251 | flush_streams(1); 252 | 253 | dbg0("KILL"); 254 | 255 | for (dir = 0; dir < 2; dir++) { 256 | if (!stream[dir]) 257 | continue; 258 | pa_stream_disconnect(stream[dir]); 259 | pa_stream_unref(stream[dir]); 260 | stream[dir] = NULL; 261 | stream_ptr_timestamp[dir] = 0; 262 | 263 | ring_consume(&mmap_stg[dir], ring_bytes(&mmap_stg[dir])); 264 | ring_resize(&mmap_stg[dir], 0); 265 | } 266 | } 267 | 268 | static int trigger_streams(int play, int rec) 269 | { 270 | int ret = 0, dir, rc; 271 | 272 | if (play >= 0) 273 | stream_corked[PLAY] = !play; 274 | if (rec >= 0) 275 | stream_corked[REC] = !rec; 276 | 277 | for (dir = 0; dir < 2; dir++) { 278 | if (!stream[dir]) 279 | continue; 280 | 281 | rc = EXEC_STREAM_OP(pa_stream_cork, stream[dir], 282 | stream_corked[dir]); 283 | if (!rc && dir == PLAY && !mmap_map[dir] && !stream_corked[dir]) 284 | rc = EXEC_STREAM_OP(pa_stream_trigger, stream[dir]); 285 | if (!ret) 286 | ret = rc; 287 | } 288 | 289 | return ret; 290 | } 291 | 292 | static void stream_state_callback(pa_stream *s, void *userdata) 293 | { 294 | pa_threaded_mainloop_signal(mainloop, 0); 295 | } 296 | 297 | static void stream_underflow_callback(pa_stream *s, void *userdata) 298 | { 299 | int dir = (s == stream[PLAY]) ? PLAY : REC; 300 | 301 | dbg0("%s stream underrun", dir_str[dir]); 302 | } 303 | 304 | static void stream_overflow_callback(pa_stream *s, void *userdata) 305 | { 306 | int dir = (s == stream[PLAY]) ? PLAY : REC; 307 | 308 | dbg0("%s stream overrun", dir_str[dir]); 309 | } 310 | 311 | static size_t duration_to_bytes(size_t dur) 312 | { 313 | return round_up(dur * sample_bps / 1000, frame_size); 314 | } 315 | 316 | static int prepare_streams(void) 317 | { 318 | const struct stream_params *sp; 319 | size_t min_frag_size, min_target_length, tmp; 320 | int dir, rc; 321 | 322 | /* nothing to do? */ 323 | if ((!stream_enabled[PLAY] || stream[PLAY]) && 324 | (!stream_enabled[REC] || stream[REC])) 325 | return 0; 326 | 327 | /* determine sample parameters */ 328 | sample_bps = pa_bytes_per_second(&sample_spec); 329 | frame_size = pa_frame_size(&sample_spec); 330 | 331 | sp = &stream_params[PLAY]; 332 | if (stream_enabled[REC]) 333 | sp = &stream_params[REC]; 334 | 335 | min_frag_size = duration_to_bytes(sp->min_process); 336 | min_target_length = duration_to_bytes(sp->min_latency); 337 | 338 | /* determine frag_size */ 339 | if (user_frag_size % frame_size) { 340 | warn("requested frag_size (%zu) isn't multiple of frame (%zu)", 341 | user_frag_size, frame_size); 342 | user_frag_size = round_up(user_frag_size, frame_size); 343 | } 344 | 345 | if (user_subdivision) 346 | user_frag_size = round_up(sample_bps / user_subdivision, 347 | frame_size); 348 | 349 | if (user_frag_size) { 350 | frag_size = user_frag_size; 351 | if (frag_size < min_frag_size) { 352 | dbg0("requested frag_size (%zu) is smaller than " 353 | "minimum (%zu)", frag_size, min_frag_size); 354 | frag_size = min_frag_size; 355 | } 356 | } else { 357 | tmp = round_up(sp->dfl_process * sample_bps / 1000, frame_size); 358 | frag_size = tmp; 359 | /* if frame_size is power of two, make frag_size so too */ 360 | if (is_power2(frame_size)) { 361 | frag_size = frame_size; 362 | while (frag_size < tmp) 363 | frag_size <<= 1; 364 | } 365 | user_frag_size = frag_size; 366 | } 367 | 368 | /* determine target and max length */ 369 | if (user_max_frags) { 370 | target_length = user_max_frags * user_frag_size; 371 | if (target_length < min_target_length) { 372 | dbg0("requested target_length (%zu) is smaller than " 373 | "minimum (%zu)", target_length, min_target_length); 374 | target_length = min_target_length; 375 | } 376 | } else { 377 | tmp = round_up(sp->dfl_latency * sample_bps / 1000, frag_size); 378 | target_length = tmp; 379 | /* if frag_size is power of two, make target_length so 380 | * too and align it to page_size. 381 | */ 382 | if (is_power2(frag_size)) { 383 | target_length = frag_size; 384 | while (target_length < max(tmp, page_size)) 385 | target_length <<= 1; 386 | } 387 | user_max_frags = target_length / frag_size; 388 | } 389 | 390 | user_max_length = user_frag_size * user_max_frags; 391 | max_length = target_length + 2 * frag_size; 392 | 393 | /* If mmapped, create backend stream with fixed parameters to 394 | * create illusion of hardware buffer with acceptable latency. 395 | */ 396 | if (mmapped()) { 397 | /* set parameters for backend streams */ 398 | frag_size = duration_to_bytes(sp->mmap_process); 399 | target_length = duration_to_bytes(sp->mmap_latency); 400 | max_length = target_length + frag_size; 401 | prebuf_size = 0; 402 | 403 | mmap_size = round_down(mmap_raw_size, frame_size); 404 | if (mmap_size != mmap_raw_size) 405 | warn("mmap_raw_size (%zu) unaligned to frame_size " 406 | "(%zu), mmap_size adjusted to %zu", 407 | mmap_raw_size, frame_size, mmap_size); 408 | } else { 409 | prebuf_size = min(user_frag_size * 2, user_max_length / 2); 410 | prebuf_size = round_down(prebuf_size, frame_size); 411 | } 412 | 413 | for (dir = 0; dir < 2; dir++) { 414 | pa_buffer_attr new_ba = { }; 415 | char buf[128]; 416 | pa_stream *s; 417 | pa_stream_flags_t flags; 418 | int vol[2]; 419 | size_t size; 420 | 421 | if (!stream_enabled[dir] || stream[dir]) 422 | continue; 423 | 424 | dbg0("CREATE %s %s fsz=%zu:%zu", dir_str[dir], 425 | pa_sample_spec_snprint(buf, sizeof(buf), &sample_spec), 426 | frag_size, frag_size * 1000 / sample_bps); 427 | dbg0(" tlen=%zu:%zu max=%zu:%zu pre=%zu:%zu", 428 | target_length, target_length * 1000 / sample_bps, 429 | max_length, max_length * 1000 / sample_bps, 430 | prebuf_size, prebuf_size * 1000 / sample_bps); 431 | dbg0(" u_sd=%zu u_fsz=%zu:%zu u_maxf=%zu", 432 | user_subdivision, user_frag_size, 433 | user_frag_size * 1000 / sample_bps, user_max_frags); 434 | 435 | channel_map = pa_channel_map_init_auto(&channel_map_stor, 436 | sample_spec.channels, 437 | PA_CHANNEL_MAP_OSS); 438 | 439 | s = pa_stream_new(context, stream_name, &sample_spec, 440 | channel_map); 441 | if (!s) { 442 | err_pa("can't create streams"); 443 | goto fail; 444 | } 445 | stream[dir] = s; 446 | 447 | pa_stream_set_state_callback(s, stream_state_callback, NULL); 448 | if (dir == PLAY) { 449 | pa_stream_set_write_callback(s, 450 | stream_rw_callback, NULL); 451 | pa_stream_set_underflow_callback(s, 452 | stream_underflow_callback, NULL); 453 | } else { 454 | pa_stream_set_read_callback(s, 455 | stream_rw_callback, NULL); 456 | pa_stream_set_overflow_callback(s, 457 | stream_overflow_callback, NULL); 458 | } 459 | 460 | flags = PA_STREAM_AUTO_TIMING_UPDATE | 461 | PA_STREAM_INTERPOLATE_TIMING; 462 | if (stream_corked[dir]) 463 | flags |= PA_STREAM_START_CORKED; 464 | 465 | new_ba.maxlength = max_length; 466 | new_ba.tlength = target_length; 467 | new_ba.prebuf = prebuf_size; 468 | new_ba.minreq = frag_size; 469 | new_ba.fragsize = frag_size; 470 | 471 | if (dir == PLAY) { 472 | if (pa_stream_connect_playback(s, NULL, &new_ba, flags, 473 | NULL, NULL)) { 474 | err_pa("failed to connect playback stream"); 475 | goto fail; 476 | } 477 | } else { 478 | if (pa_stream_connect_record(s, NULL, &new_ba, flags)) { 479 | err_pa("failed to connect record stream"); 480 | goto fail; 481 | } 482 | } 483 | 484 | while (pa_stream_get_state(s) == PA_STREAM_CREATING) 485 | pa_threaded_mainloop_wait(mainloop); 486 | if (pa_stream_get_state(s) != PA_STREAM_READY) { 487 | err_pa("failed to connect stream (state=%d)", 488 | pa_stream_get_state(s)); 489 | goto fail; 490 | } 491 | 492 | /* apply stored OSS volume */ 493 | memcpy(vol, stored_oss_vol[dir], sizeof(vol)); 494 | if (do_mixer(dir, vol)) 495 | warn_pa("initial volume control failed"); 496 | 497 | /* stream is ready setup mmap stuff */ 498 | if (!mmap_map[dir]) 499 | continue; 500 | 501 | /* prep mmap staging buffer */ 502 | size = round_up(sp->mmap_staging * sample_bps / 1000, 503 | frag_size); 504 | rc = ring_resize(&mmap_stg[dir], size); 505 | if (rc) 506 | return rc; 507 | 508 | mmap_idx[dir] = mmap_last_idx[dir] = get_mmap_idx(dir); 509 | mmap_lead[dir] = round_up(sp->mmap_lead * sample_bps / 1000, 510 | frame_size); 511 | mmap_sync[dir] = 1; 512 | 513 | /* apply the current trigger settings */ 514 | trigger_streams(-1, -1); 515 | } 516 | 517 | return 0; 518 | fail: 519 | return padsp_done(); 520 | } 521 | 522 | struct volume_ret { 523 | int success; 524 | pa_cvolume *cv; 525 | }; 526 | 527 | static void play_volume_callback(pa_context *c, const pa_sink_input_info *i, 528 | int eol, void *userdata) 529 | { 530 | struct volume_ret *vr = userdata; 531 | 532 | if (i) { 533 | *vr->cv = i->volume; 534 | vr->success = 1; 535 | } 536 | pa_threaded_mainloop_signal(mainloop, 0); 537 | } 538 | 539 | static void rec_volume_callback(pa_context *c, const pa_source_info *i, 540 | int eol, void *userdata) 541 | { 542 | struct volume_ret *vr = userdata; 543 | 544 | if (i) { 545 | *vr->cv = i->volume; 546 | vr->success = 1; 547 | } 548 | pa_threaded_mainloop_signal(mainloop, 0); 549 | } 550 | 551 | static int get_volume(int dir, pa_cvolume *cv) 552 | { 553 | struct volume_ret vr = { .cv = cv }; 554 | uint32_t idx; 555 | 556 | if (dir == PLAY) { 557 | idx = pa_stream_get_index(stream[PLAY]); 558 | EXEC_OP(pa_context_get_sink_input_info, 559 | context, idx, play_volume_callback, &vr); 560 | } else { 561 | idx = pa_stream_get_device_index(stream[REC]); 562 | EXEC_OP(pa_context_get_source_info_by_index, 563 | context, idx, rec_volume_callback, &vr); 564 | } 565 | if (!vr.success) { 566 | warn_pa("failed to get %s volume", dir_str[dir]); 567 | return -EIO; 568 | } 569 | return 0; 570 | } 571 | 572 | static int set_volume(int dir, pa_cvolume *cv) 573 | { 574 | uint32_t idx; 575 | int rc; 576 | 577 | if (dir == PLAY) { 578 | idx = pa_stream_get_index(stream[PLAY]); 579 | rc = EXEC_CONTEXT_OP(pa_context_set_sink_input_volume, 580 | context, idx, cv); 581 | } else { 582 | idx = pa_stream_get_device_index(stream[REC]); 583 | rc = EXEC_CONTEXT_OP(pa_context_set_source_volume_by_index, 584 | context, idx, cv); 585 | } 586 | return rc; 587 | } 588 | 589 | static int chan_left_right(int ch) 590 | { 591 | if (!channel_map || channel_map->channels <= ch) { 592 | switch (ch) { 593 | case 0: 594 | return LEFT; 595 | case 1: 596 | return RIGHT; 597 | default: 598 | return -1; 599 | } 600 | } 601 | 602 | switch (channel_map->map[ch]) { 603 | /*case PA_CHANNEL_POSITION_LEFT:*/ /* same as FRONT_LEFT */ 604 | case PA_CHANNEL_POSITION_FRONT_LEFT: 605 | case PA_CHANNEL_POSITION_REAR_LEFT: 606 | case PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER: 607 | case PA_CHANNEL_POSITION_SIDE_LEFT: 608 | case PA_CHANNEL_POSITION_TOP_FRONT_LEFT: 609 | case PA_CHANNEL_POSITION_TOP_REAR_LEFT: 610 | return LEFT; 611 | /*case PA_CHANNEL_POSITION_RIGHT:*/ /* same as FRONT_RIGHT */ 612 | case PA_CHANNEL_POSITION_FRONT_RIGHT: 613 | case PA_CHANNEL_POSITION_REAR_RIGHT: 614 | case PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER: 615 | case PA_CHANNEL_POSITION_SIDE_RIGHT: 616 | case PA_CHANNEL_POSITION_TOP_FRONT_RIGHT: 617 | case PA_CHANNEL_POSITION_TOP_REAR_RIGHT: 618 | return RIGHT; 619 | default: 620 | return -1; 621 | } 622 | } 623 | 624 | static int do_mixer(int dir, int *vol) 625 | { 626 | pa_cvolume cv; 627 | unsigned lv, rv; 628 | int i, rc; 629 | 630 | if (vol[0] >= 0) { 631 | int avg; 632 | 633 | stored_oss_vol[dir][LEFT] = vol[LEFT]; 634 | stored_oss_vol[dir][RIGHT] = vol[RIGHT]; 635 | vol[LEFT] = vol[LEFT] * PA_VOLUME_NORM / 100; 636 | vol[RIGHT] = vol[RIGHT] * PA_VOLUME_NORM / 100; 637 | avg = (vol[LEFT] + vol[RIGHT]) / 2; 638 | 639 | pa_cvolume_mute(&cv, sample_spec.channels); 640 | 641 | for (i = 0; i < cv.channels; i++) 642 | switch (chan_left_right(i)) { 643 | case LEFT: cv.values[i] = vol[LEFT]; break; 644 | case RIGHT: cv.values[i] = vol[RIGHT]; break; 645 | default: cv.values[i] = avg; break; 646 | } 647 | 648 | rc = set_volume(dir, &cv); 649 | if (rc) 650 | return rc; 651 | } 652 | 653 | rc = get_volume(dir, &cv); 654 | if (rc) 655 | return rc; 656 | 657 | if (cv.channels == 1) 658 | lv = rv = pa_cvolume_avg(&cv); 659 | else { 660 | unsigned lcnt = 0, rcnt = 0; 661 | 662 | for (i = 0, lv = 0, rv = 0; i < cv.channels; i++) 663 | switch (chan_left_right(i)) { 664 | case LEFT: lv += cv.values[i]; lcnt++; break; 665 | case RIGHT: rv += cv.values[i]; rcnt++; break; 666 | } 667 | 668 | if (lcnt) 669 | lv /= lcnt; 670 | if (rcnt) 671 | rv /= rcnt; 672 | } 673 | 674 | vol[LEFT] = lv * 100 / PA_VOLUME_NORM; 675 | vol[RIGHT] = rv * 100 / PA_VOLUME_NORM; 676 | 677 | return 0; 678 | } 679 | 680 | static ssize_t padsp_mixer(enum ossp_opcode opcode, 681 | void *carg, void *din, size_t din_sz, 682 | void *rarg, void *dout, size_t *dout_szp, int tfd) 683 | { 684 | struct ossp_mixer_arg *arg = carg; 685 | int i, rc[2] = { }; 686 | 687 | if (prepare_streams()) 688 | return -EIO; 689 | 690 | for (i = 0; i < 2; i++) 691 | if (stream[i]) 692 | rc[i] = do_mixer(i, arg->vol[i]); 693 | else 694 | memset(arg->vol[i], -1, sizeof(arg->vol[i])); 695 | 696 | *(struct ossp_mixer_arg *)rarg = *arg; 697 | return rc[0] ?: rc[1]; 698 | } 699 | 700 | static void context_state_callback(pa_context *cxt, void *userdata) 701 | { 702 | pa_threaded_mainloop_signal(mainloop, 0); 703 | } 704 | 705 | static void context_subscribe_callback(pa_context *context, 706 | pa_subscription_event_type_t type, 707 | uint32_t idx, void *userdata) 708 | { 709 | struct ossp_notify event = { .magic = OSSP_NOTIFY_MAGIC, 710 | .opcode = OSSP_NOTIFY_VOLCHG }; 711 | ssize_t ret; 712 | 713 | if ((type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != 714 | PA_SUBSCRIPTION_EVENT_CHANGE) 715 | return; 716 | 717 | ret = write(ossp_notify_fd, &event, sizeof(event)); 718 | if (ret != sizeof(event) && errno != EPIPE) 719 | warn_e(-errno, "write to notify_fd failed"); 720 | } 721 | 722 | static ssize_t padsp_open(enum ossp_opcode opcode, 723 | void *carg, void *din, size_t din_sz, 724 | void *rarg, void *dout, size_t *dout_szp, int tfd) 725 | { 726 | struct ossp_dsp_open_arg *arg = carg; 727 | char host_name[128] = "(unknown)", opener[128] = "(unknown)"; 728 | int state; 729 | 730 | switch (arg->flags & O_ACCMODE) { 731 | case O_WRONLY: 732 | stream_enabled[PLAY] = 1; 733 | break; 734 | case O_RDONLY: 735 | stream_enabled[REC] = 1; 736 | break; 737 | case O_RDWR: 738 | stream_enabled[PLAY] = 1; 739 | stream_enabled[REC] = 1; 740 | break; 741 | default: 742 | assert(0); 743 | } 744 | 745 | /* determine stream name */ 746 | gethostname(host_name, sizeof(host_name) - 1); 747 | snprintf(stream_name, sizeof(stream_name), "OSS Proxy %s/%s:%ld", 748 | host_name, ossp_user_name, (long)arg->opener_pid); 749 | 750 | /* create and connect PA context */ 751 | get_proc_self_info(arg->opener_pid, NULL, opener, sizeof(opener)); 752 | context = pa_context_new(mainloop_api, opener); 753 | if (!context) { 754 | err("pa_context_new() failed"); 755 | return -EIO; 756 | } 757 | 758 | pa_context_set_state_callback(context, context_state_callback, NULL); 759 | pa_context_set_subscribe_callback(context, context_subscribe_callback, 760 | NULL); 761 | 762 | pa_context_connect(context, NULL, 0, NULL); 763 | while (1) { 764 | state = pa_context_get_state(context); 765 | if (state != PA_CONTEXT_CONNECTING && 766 | state != PA_CONTEXT_AUTHORIZING && 767 | state != PA_CONTEXT_SETTING_NAME) 768 | break; 769 | 770 | pa_threaded_mainloop_wait(mainloop); 771 | } 772 | 773 | if (EXEC_CONTEXT_OP(pa_context_subscribe, context, 774 | PA_SUBSCRIPTION_MASK_SINK_INPUT | 775 | PA_SUBSCRIPTION_MASK_SOURCE)) 776 | warn_pa("failed to subscribe to context events"); 777 | 778 | if (state != PA_CONTEXT_READY) { 779 | err_pa("failed to connect context, state=%d", state); 780 | return -EIO; 781 | } 782 | 783 | return 0; 784 | } 785 | 786 | static void notify_mmap_fill_play(size_t mmap_size, size_t pos, size_t bytes) 787 | { 788 | while (bytes) { 789 | ssize_t ret; 790 | size_t count = min(bytes, mmap_size - pos); 791 | struct ossp_notify event = { .magic = OSSP_NOTIFY_MAGIC, 792 | .opcode = OSSP_NOTIFY_FILL }; 793 | 794 | ossp_mmap_transfer[PLAY].pos = pos; 795 | ossp_mmap_transfer[PLAY].bytes = count; 796 | 797 | ret = write(ossp_notify_fd, &event, sizeof(event)); 798 | if (ret != sizeof(event)) { 799 | if (errno != EPIPE) 800 | err_e(-errno, "write to notify_fd failed"); 801 | 802 | padsp_done(); 803 | return; 804 | } 805 | sem_wait(&ossp_mmap_transfer[PLAY].sem); 806 | 807 | bytes -= count; 808 | pos = (pos + count) % mmap_size; 809 | } 810 | } 811 | 812 | static void notify_mmap_store_rec(size_t mmap_size, size_t pos, size_t bytes) 813 | { 814 | while (bytes) { 815 | ssize_t ret; 816 | size_t count = min(bytes, mmap_size - pos); 817 | struct ossp_notify event = { .magic = OSSP_NOTIFY_MAGIC, 818 | .opcode = OSSP_NOTIFY_STORE }; 819 | 820 | ossp_mmap_transfer[REC].pos = pos; 821 | ossp_mmap_transfer[REC].bytes = count; 822 | 823 | ret = write(ossp_notify_fd, &event, sizeof(event)); 824 | if (ret != sizeof(event)) { 825 | if (errno != EPIPE) 826 | err_e(-errno, "write to notify_fd failed"); 827 | 828 | padsp_done(); 829 | return; 830 | } 831 | sem_wait(&ossp_mmap_transfer[REC].sem); 832 | 833 | bytes -= count; 834 | pos = (pos + count) % mmap_size; 835 | } 836 | } 837 | 838 | static void mmap_fill_pstg(void) 839 | { 840 | struct ring_buf *stg = &mmap_stg[PLAY]; 841 | struct ring_buf mmap; 842 | uint64_t new_idx = get_mmap_idx(PLAY); 843 | size_t bytes, space, size; 844 | void *data; 845 | 846 | if (new_idx <= mmap_idx[PLAY]) 847 | return; 848 | 849 | bytes = new_idx - mmap_idx[PLAY]; 850 | space = ring_space(stg); 851 | 852 | if (bytes > mmap_size) { 853 | dbg0("mmap playback transfer chunk bigger than " 854 | "mmap size (bytes=%zu mmap_size=%zu)", bytes, mmap_size); 855 | mmap_sync[PLAY] = 1; 856 | bytes = mmap_size; 857 | } 858 | 859 | if (bytes > space) { 860 | dbg0("mmap playback staging buffer overflow " 861 | "(bytes=%zu space=%zu)", bytes, space); 862 | mmap_sync[PLAY] = 1; 863 | bytes = space; 864 | } 865 | 866 | notify_mmap_fill_play(mmap_size, mmap_idx[PLAY] % mmap_size, bytes); 867 | 868 | ring_manual_init(&mmap, mmap_map[PLAY], mmap_size, 869 | new_idx % mmap_size, bytes); 870 | 871 | while ((data = ring_data(&mmap, &size))) { 872 | ring_fill(stg, data, size); 873 | ring_consume(&mmap, size); 874 | } 875 | 876 | mmap_idx[PLAY] = new_idx; 877 | } 878 | 879 | static void mmap_consume_rstg(void) 880 | { 881 | struct ring_buf *stg = &mmap_stg[REC]; 882 | struct ring_buf mmap; 883 | uint64_t new_idx = get_mmap_idx(REC); 884 | uint64_t fill_idx = mmap_idx[REC]; 885 | size_t bytes, space; 886 | 887 | if (new_idx <= mmap_idx[REC]) 888 | return; 889 | 890 | space = new_idx - mmap_idx[REC]; /* mmapped space to fill in */ 891 | bytes = ring_bytes(stg); /* recorded bytes in staging */ 892 | 893 | if (space > bytes) { 894 | if (!mmap_sync[REC]) 895 | dbg0("mmap recording staging buffer underflow " 896 | "(space=%zu bytes=%zu)", space, bytes); 897 | mmap_sync[REC] = 1; 898 | } 899 | 900 | if (space > mmap_size) { 901 | if (!mmap_sync[REC]) 902 | dbg0("mmap recording transfer chunk bigger than " 903 | "mmap size (space=%zu mmap_size=%zu)", 904 | bytes, mmap_size); 905 | mmap_sync[REC] = 1; 906 | space = mmap_size; 907 | } 908 | 909 | /* If resync is requested, leave lead bytes in the staging 910 | * buffer and copy everything else such that data is filled 911 | * upto the new_idx. If there are more bytes in staging than 912 | * available space, those will be dropped. 913 | */ 914 | if (mmap_sync[REC]) { 915 | ssize_t avail = bytes - mmap_lead[REC]; 916 | 917 | /* make sure we always have lead bytes in staging */ 918 | if (avail < 0) 919 | goto skip; 920 | 921 | if (avail > space) { 922 | dbg0("dropping %zu bytes from record staging buffer", 923 | avail - space); 924 | ring_consume(&mmap_stg[REC], avail - space); 925 | avail = space; 926 | } else { 927 | dbg0("skippping %zu bytes in record mmap map", 928 | space - avail); 929 | space = avail; 930 | } 931 | 932 | assert(new_idx >= avail); 933 | fill_idx = new_idx - avail; 934 | mmap_sync[REC] = 0; 935 | } 936 | 937 | ring_manual_init(&mmap, mmap_map[REC], mmap_size, 938 | fill_idx % mmap_size, 0); 939 | 940 | while (space) { 941 | void *data; 942 | size_t size, todo; 943 | 944 | data = ring_data(stg, &size); 945 | assert(data); 946 | 947 | todo = min(size, space); 948 | ring_fill(&mmap, data, todo); 949 | 950 | ring_consume(stg, todo); 951 | space -= todo; 952 | } 953 | 954 | notify_mmap_store_rec(mmap_size, fill_idx % mmap_size, ring_bytes(&mmap)); 955 | 956 | skip: 957 | mmap_idx[REC] = new_idx; 958 | } 959 | 960 | static void do_mmap_write(size_t space) 961 | { 962 | struct ring_buf *stg = &mmap_stg[PLAY]; 963 | size_t todo; 964 | void *data; 965 | 966 | space = round_down(space, frame_size); 967 | mmap_fill_pstg(); 968 | 969 | while (space && (data = ring_data(stg, &todo))) { 970 | pa_seek_mode_t mode = PA_SEEK_RELATIVE_END; 971 | int64_t offset = 0; 972 | 973 | todo = min(todo, space); 974 | 975 | if (mmap_sync[PLAY]) { 976 | mode = PA_SEEK_RELATIVE_ON_READ; 977 | offset = (int64_t)mmap_lead[PLAY] - ring_bytes(stg); 978 | dbg0("mmap resync, offset=%ld", (long)offset); 979 | } 980 | 981 | if (pa_stream_write(stream[PLAY], data, todo, NULL, 982 | offset, mode) < 0) { 983 | err_pa("pa_stream_write() failed"); 984 | padsp_done(); 985 | return; 986 | } 987 | 988 | mmap_sync[PLAY] = 0; 989 | ring_consume(stg, todo); 990 | space -= todo; 991 | } 992 | } 993 | 994 | static void do_mmap_read(size_t bytes) 995 | { 996 | struct ring_buf *stg = &mmap_stg[REC]; 997 | 998 | bytes = round_down(bytes, frame_size); 999 | mmap_consume_rstg(); 1000 | 1001 | while (bytes) { 1002 | const void *peek_data; 1003 | size_t size; 1004 | 1005 | if (pa_stream_peek(stream[REC], &peek_data, &size)) { 1006 | err_pa("pa_stream_peek() failed"); 1007 | padsp_done(); 1008 | return; 1009 | } 1010 | 1011 | if (!peek_data) 1012 | break; 1013 | 1014 | if (size <= ring_space(stg)) 1015 | ring_fill(stg, peek_data, size); 1016 | else { 1017 | if (!mmap_sync[REC]) 1018 | dbg0("recording staging buffer overflow, " 1019 | "requesting resync"); 1020 | mmap_sync[REC] = 1; 1021 | } 1022 | 1023 | pa_stream_drop(stream[REC]); 1024 | bytes -= size; 1025 | } 1026 | } 1027 | 1028 | static void stream_rw_callback(pa_stream *s, size_t length, void *userdata) 1029 | { 1030 | size_t size; 1031 | 1032 | if (s == stream[PLAY]) { 1033 | size = pa_stream_writable_size(s); 1034 | if (mmap_map[PLAY]) 1035 | do_mmap_write(size); 1036 | } else if (s == stream[REC]) { 1037 | size = pa_stream_readable_size(s); 1038 | if (mmap_map[REC]) 1039 | do_mmap_read(size); 1040 | } else { 1041 | dbg0("stream_rw_callback(): unknown stream %p PLAY/REC=%p/%p\n", 1042 | s, stream[PLAY], stream[REC]); 1043 | return; 1044 | } 1045 | 1046 | if (size < user_frag_size) 1047 | return; 1048 | if (stream_waiting) 1049 | pa_threaded_mainloop_signal(mainloop, 0); 1050 | if (stream_notify) { 1051 | struct ossp_notify event = { .magic = OSSP_NOTIFY_MAGIC, 1052 | .opcode = OSSP_NOTIFY_POLL }; 1053 | ssize_t ret; 1054 | 1055 | ret = write(ossp_notify_fd, &event, sizeof(event)); 1056 | if (ret != sizeof(event)) { 1057 | if (errno != EPIPE) 1058 | err_e(-errno, "write to notify_fd failed"); 1059 | 1060 | /* This function is run from PA mainloop and 1061 | * thus the following padsp_done() won't be 1062 | * noticed before the mainthread tries to run 1063 | * the next command. Well, that's good enough. 1064 | */ 1065 | padsp_done(); 1066 | } 1067 | stream_notify = 0; 1068 | } 1069 | } 1070 | 1071 | static ssize_t padsp_write(enum ossp_opcode opcode, 1072 | void *carg, void *din, size_t din_sz, 1073 | void *rarg, void *dout, size_t *dout_szp, int tfd) 1074 | { 1075 | struct ossp_dsp_rw_arg *arg = carg; 1076 | size_t size; 1077 | 1078 | if (prepare_streams() || !stream[PLAY]) 1079 | return -EIO; 1080 | 1081 | stream_waiting++; 1082 | while (1) { 1083 | size = pa_stream_writable_size(stream[PLAY]); 1084 | if (arg->nonblock || size >= user_frag_size) 1085 | break; 1086 | pa_threaded_mainloop_wait(mainloop); 1087 | } 1088 | stream_waiting--; 1089 | 1090 | size = round_down(size, user_frag_size); 1091 | if (!size) 1092 | return -EAGAIN; 1093 | 1094 | size = min(size, din_sz); 1095 | 1096 | if (pa_stream_write(stream[PLAY], din, size, NULL, 1097 | 0, PA_SEEK_RELATIVE) < 0) { 1098 | err_pa("pa_stream_write() failed"); 1099 | return padsp_done(); 1100 | } 1101 | 1102 | return size; 1103 | } 1104 | 1105 | static ssize_t padsp_read(enum ossp_opcode opcode, 1106 | void *carg, void *din, size_t din_sz, 1107 | void *rarg, void *dout, size_t *dout_szp, int tfd) 1108 | { 1109 | struct ossp_dsp_rw_arg *arg = carg; 1110 | size_t size; 1111 | void *data; 1112 | 1113 | if (prepare_streams() || !stream[REC]) 1114 | return -EIO; 1115 | again: 1116 | if (!arg->nonblock) { 1117 | stream_waiting++; 1118 | while (1) { 1119 | size = pa_stream_readable_size(stream[REC]); 1120 | if (size + ring_bytes(&rec_buf) >= user_frag_size) 1121 | break; 1122 | pa_threaded_mainloop_wait(mainloop); 1123 | } 1124 | stream_waiting--; 1125 | } 1126 | 1127 | while (ring_bytes(&rec_buf) < max(user_frag_size, *dout_szp)) { 1128 | const void *peek_data; 1129 | 1130 | if (pa_stream_peek(stream[REC], &peek_data, &size) < 0) { 1131 | err_pa("pa_stream_peek() failed"); 1132 | return padsp_done(); 1133 | } 1134 | 1135 | if (!peek_data) 1136 | break; 1137 | 1138 | if (ring_space(&rec_buf) < size) { 1139 | size_t bufsz; 1140 | 1141 | bufsz = ring_size(&rec_buf); 1142 | bufsz = max(2 * bufsz, bufsz + 2 * size); 1143 | 1144 | if (ring_resize(&rec_buf, bufsz)) { 1145 | err("failed to allocate recording buffer"); 1146 | return padsp_done(); 1147 | } 1148 | } 1149 | 1150 | ring_fill(&rec_buf, peek_data, size); 1151 | pa_stream_drop(stream[REC]); 1152 | } 1153 | 1154 | size = round_down(ring_bytes(&rec_buf), user_frag_size); 1155 | if (!size) { 1156 | if (arg->nonblock) 1157 | return -EAGAIN; 1158 | else 1159 | goto again; 1160 | } 1161 | 1162 | *dout_szp = size = min(size, *dout_szp); 1163 | 1164 | while (size) { 1165 | size_t cnt; 1166 | 1167 | data = ring_data(&rec_buf, &cnt); 1168 | assert(data); 1169 | 1170 | cnt = min(size, cnt); 1171 | memcpy(dout, data, cnt); 1172 | ring_consume(&rec_buf, cnt); 1173 | dout += cnt; 1174 | size -= cnt; 1175 | } 1176 | 1177 | return *dout_szp; 1178 | } 1179 | 1180 | static ssize_t padsp_poll(enum ossp_opcode opcode, 1181 | void *carg, void *din, size_t din_sz, 1182 | void *rarg, void *dout, size_t *dout_szp, int tfd) 1183 | { 1184 | unsigned revents = 0; 1185 | 1186 | if (prepare_streams() < 0) 1187 | return -EIO; 1188 | 1189 | stream_notify |= *(int *)carg; 1190 | 1191 | if (stream[PLAY] && 1192 | pa_stream_writable_size(stream[PLAY]) >= user_frag_size) 1193 | revents |= POLLOUT; 1194 | if (stream[REC] && 1195 | pa_stream_readable_size(stream[REC]) >= user_frag_size) 1196 | revents |= POLLIN; 1197 | 1198 | *(unsigned *)rarg = revents; 1199 | return 0; 1200 | } 1201 | 1202 | static ssize_t padsp_mmap(enum ossp_opcode opcode, 1203 | void *carg, void *din, size_t din_sz, 1204 | void *rarg, void *dout, size_t *dout_szp, int tfd) 1205 | { 1206 | struct ossp_dsp_mmap_arg *arg = carg; 1207 | int dir = arg->dir; 1208 | 1209 | assert(!mmap_map[dir]); 1210 | 1211 | kill_streams(); 1212 | 1213 | /* arg->size is rounded up to the nearest page boundary. 1214 | * There is no way to tell what the actual requested value is 1215 | * but assume that it was the reported buffer space if it 1216 | * falls into the same page aligned range. 1217 | */ 1218 | mmap_raw_size = arg->size; 1219 | if (user_max_length && user_max_length < mmap_raw_size && 1220 | round_up(mmap_raw_size, page_size) == 1221 | round_up(user_max_length, page_size)) { 1222 | info("MMAP adjusting raw_size %zu -> %zu", 1223 | mmap_raw_size, user_max_length); 1224 | mmap_raw_size = user_max_length; 1225 | } 1226 | 1227 | dbg0("MMAP server-addr=%p sz=%zu", ossp_mmap_addr[dir], mmap_raw_size); 1228 | 1229 | mmap_map[dir] = ossp_mmap_addr[dir]; 1230 | 1231 | /* if mmapped, only mmapped streams are enabled */ 1232 | stream_enabled[PLAY] = !!mmap_map[PLAY]; 1233 | stream_enabled[REC] = !!mmap_map[REC]; 1234 | 1235 | return 0; 1236 | } 1237 | 1238 | static ssize_t padsp_munmap(enum ossp_opcode opcode, 1239 | void *carg, void *din, size_t din_sz, 1240 | void *rarg, void *dout, size_t *dout_szp, int tfd) 1241 | { 1242 | int dir = *(int *)carg; 1243 | 1244 | assert(mmap_map[dir]); 1245 | kill_streams(); 1246 | mmap_map[dir] = NULL; 1247 | return 0; 1248 | } 1249 | 1250 | static ssize_t padsp_flush(enum ossp_opcode opcode, 1251 | void *carg, void *din, size_t din_sz, 1252 | void *rarg, void *dout, size_t *dout_szp, int tfd) 1253 | { 1254 | flush_streams(opcode == OSSP_DSP_SYNC); 1255 | return 0; 1256 | } 1257 | 1258 | static ssize_t padsp_post(enum ossp_opcode opcode, 1259 | void *carg, void *din, size_t din_sz, 1260 | void *rarg, void *dout, size_t *dout_szp, int tfd) 1261 | { 1262 | return trigger_streams(1, -1); 1263 | } 1264 | 1265 | static ssize_t padsp_get_param(enum ossp_opcode opcode, 1266 | void *carg, void *din, size_t din_sz, 1267 | void *rarg, void *dout, size_t *dout_szp, 1268 | int tfd) 1269 | { 1270 | int v = 0; 1271 | 1272 | switch (opcode) { 1273 | case OSSP_DSP_GET_RATE: 1274 | v = sample_spec.rate; 1275 | break; 1276 | 1277 | case OSSP_DSP_GET_CHANNELS: 1278 | v = sample_spec.channels; 1279 | break; 1280 | 1281 | case OSSP_DSP_GET_FORMAT: 1282 | v = fmt_pa_to_oss(sample_spec.format); 1283 | break; 1284 | 1285 | case OSSP_DSP_GET_BLKSIZE: 1286 | if (prepare_streams() < 0) 1287 | return -EIO; 1288 | v = user_frag_size; 1289 | break; 1290 | 1291 | case OSSP_DSP_GET_FORMATS: 1292 | v = AFMT_U8 | AFMT_A_LAW | AFMT_MU_LAW | AFMT_S16_LE | 1293 | AFMT_S16_BE | AFMT_FLOAT | AFMT_S32_LE | AFMT_S32_BE; 1294 | break; 1295 | 1296 | case OSSP_DSP_GET_TRIGGER: 1297 | if (!stream_corked[PLAY]) 1298 | v |= PCM_ENABLE_OUTPUT; 1299 | if (!stream_corked[REC]) 1300 | v |= PCM_ENABLE_INPUT; 1301 | break; 1302 | 1303 | default: 1304 | assert(0); 1305 | } 1306 | 1307 | *(int *)rarg = v; 1308 | 1309 | return 0; 1310 | } 1311 | 1312 | static ssize_t padsp_set_param(enum ossp_opcode opcode, 1313 | void *carg, void *din, size_t din_sz, 1314 | void *rarg, void *dout, size_t *dout_szp, 1315 | int tfd) 1316 | { 1317 | pa_sample_spec new_spec = sample_spec; 1318 | int v = *(int *)carg; 1319 | 1320 | /* kill the streams before changing parameters */ 1321 | kill_streams(); 1322 | 1323 | switch (opcode) { 1324 | case OSSP_DSP_SET_RATE: 1325 | new_spec.rate = v; 1326 | if (pa_sample_spec_valid(&new_spec)) 1327 | sample_spec = new_spec; 1328 | v = sample_spec.rate; 1329 | break; 1330 | 1331 | case OSSP_DSP_SET_CHANNELS: 1332 | new_spec.channels = v; 1333 | if (pa_sample_spec_valid(&new_spec)) 1334 | sample_spec = new_spec; 1335 | v = sample_spec.channels; 1336 | break; 1337 | 1338 | case OSSP_DSP_SET_FORMAT: 1339 | new_spec.format = fmt_oss_to_pa(v); 1340 | if (pa_sample_spec_valid(&new_spec)) 1341 | sample_spec = new_spec; 1342 | v = fmt_pa_to_oss(sample_spec.format); 1343 | break; 1344 | 1345 | case OSSP_DSP_SET_SUBDIVISION: 1346 | if (!v) { 1347 | v = user_subdivision ?: 1; 1348 | break; 1349 | } 1350 | user_frag_size= 0; 1351 | user_subdivision = v; 1352 | break; 1353 | 1354 | case OSSP_DSP_SET_FRAGMENT: 1355 | user_subdivision = 0; 1356 | user_frag_size = 1 << (v & 0xffff); 1357 | user_max_frags = (v >> 16) & 0xffff; 1358 | if (user_frag_size < 4) 1359 | user_frag_size = 4; 1360 | if (user_max_frags < 2) 1361 | user_max_frags = 2; 1362 | break; 1363 | default: 1364 | assert(0); 1365 | } 1366 | 1367 | if (rarg) 1368 | *(int *)rarg = v; 1369 | return 0; 1370 | } 1371 | 1372 | static ssize_t padsp_set_trigger(enum ossp_opcode opcode, 1373 | void *carg, void *din, size_t din_sz, 1374 | void *rarg, void *dout, size_t *dout_szp, 1375 | int fd) 1376 | { 1377 | int enable = *(int *)carg; 1378 | 1379 | return trigger_streams(enable & PCM_ENABLE_OUTPUT, 1380 | enable & PCM_ENABLE_INPUT); 1381 | } 1382 | 1383 | static ssize_t padsp_get_space(enum ossp_opcode opcode, 1384 | void *carg, void *din, size_t din_sz, 1385 | void *rarg, void *dout, size_t *dout_szp, int tfd) 1386 | { 1387 | int dir = (opcode == OSSP_DSP_GET_OSPACE) ? PLAY : REC; 1388 | struct audio_buf_info info = { }; 1389 | int rc; 1390 | 1391 | rc = prepare_streams(); 1392 | if (rc) 1393 | return -EIO; 1394 | 1395 | if (mmapped()) { 1396 | info.fragments = mmap_raw_size / user_frag_size; 1397 | info.fragstotal = info.fragments; 1398 | info.fragsize = user_frag_size; 1399 | info.bytes = mmap_raw_size; 1400 | } else { 1401 | size_t space; 1402 | 1403 | if (dir == PLAY) 1404 | space = pa_stream_writable_size(stream[PLAY]); 1405 | else 1406 | space = pa_stream_readable_size(stream[REC]); 1407 | 1408 | space = round_down(space, user_frag_size); 1409 | space = min(space, user_frag_size * user_max_frags); 1410 | 1411 | info.fragments = space / user_frag_size; 1412 | info.fragstotal = user_max_frags; 1413 | info.fragsize = user_frag_size; 1414 | info.bytes = space; 1415 | } 1416 | 1417 | *(struct audio_buf_info *)rarg = info; 1418 | return 0; 1419 | } 1420 | 1421 | static ssize_t padsp_get_ptr(enum ossp_opcode opcode, 1422 | void *carg, void *din, size_t din_sz, 1423 | void *rarg, void *dout, size_t *dout_szp, int tfd) 1424 | { 1425 | int dir = (opcode == OSSP_DSP_GET_OPTR) ? PLAY : REC; 1426 | struct count_info info = { }; 1427 | 1428 | if (prepare_streams() < 0 || !stream[dir]) 1429 | return -EIO; 1430 | 1431 | if (mmap_map[dir]) { 1432 | /* mmap operation in progress, report mmap buffer parameters */ 1433 | if (dir == PLAY) 1434 | mmap_fill_pstg(); 1435 | else 1436 | mmap_consume_rstg(); 1437 | 1438 | info.bytes = mmap_idx[dir]; 1439 | info.blocks = (mmap_idx[dir] - mmap_last_idx[dir]) / frame_size; 1440 | info.ptr = mmap_idx[dir] % mmap_size; 1441 | 1442 | mmap_last_idx[dir] = mmap_idx[dir]; 1443 | } else { 1444 | /* simulate pointers using timestamps */ 1445 | double bpus = (double)sample_bps / 1000000; 1446 | size_t bytes, delta_bytes; 1447 | pa_usec_t usec, delta; 1448 | 1449 | if (pa_stream_get_time(stream[dir], &usec) < 0) { 1450 | warn_pa("pa_stream_get_time() failed"); 1451 | return -EIO; 1452 | } 1453 | 1454 | delta = usec - stream_ptr_timestamp[dir]; 1455 | stream_ptr_timestamp[dir] = usec; 1456 | bytes = bpus * usec; 1457 | delta_bytes = bpus * delta; 1458 | 1459 | info.bytes = bytes & INT_MAX; 1460 | info.blocks = (delta_bytes + frame_size - 1) / frame_size; 1461 | info.ptr = bytes % user_max_length; 1462 | } 1463 | 1464 | *(struct count_info *)rarg = info; 1465 | return 0; 1466 | } 1467 | 1468 | static ssize_t padsp_get_odelay(enum ossp_opcode opcode, 1469 | void *carg, void *din, size_t din_sz, 1470 | void *rarg, void *dout, size_t *dout_szp, 1471 | int fd) 1472 | { 1473 | double bpus = (double)sample_bps / 1000000; 1474 | pa_usec_t usec; 1475 | 1476 | if (prepare_streams() < 0 || !stream[PLAY]) 1477 | return -EIO; 1478 | 1479 | if (pa_stream_get_latency(stream[PLAY], &usec, NULL) < 0) { 1480 | warn_pa("pa_stream_get_latency() failed"); 1481 | return -EIO; 1482 | } 1483 | 1484 | *(int *)rarg = bpus * usec; 1485 | return 0; 1486 | } 1487 | 1488 | static ossp_action_fn_t action_fn_tbl[OSSP_NR_OPCODES] = { 1489 | [OSSP_MIXER] = padsp_mixer, 1490 | [OSSP_DSP_OPEN] = padsp_open, 1491 | [OSSP_DSP_READ] = padsp_read, 1492 | [OSSP_DSP_WRITE] = padsp_write, 1493 | [OSSP_DSP_POLL] = padsp_poll, 1494 | [OSSP_DSP_MMAP] = padsp_mmap, 1495 | [OSSP_DSP_MUNMAP] = padsp_munmap, 1496 | [OSSP_DSP_RESET] = padsp_flush, 1497 | [OSSP_DSP_SYNC] = padsp_flush, 1498 | [OSSP_DSP_POST] = padsp_post, 1499 | [OSSP_DSP_GET_RATE] = padsp_get_param, 1500 | [OSSP_DSP_GET_CHANNELS] = padsp_get_param, 1501 | [OSSP_DSP_GET_FORMAT] = padsp_get_param, 1502 | [OSSP_DSP_GET_BLKSIZE] = padsp_get_param, 1503 | [OSSP_DSP_GET_FORMATS] = padsp_get_param, 1504 | [OSSP_DSP_SET_RATE] = padsp_set_param, 1505 | [OSSP_DSP_SET_CHANNELS] = padsp_set_param, 1506 | [OSSP_DSP_SET_FORMAT] = padsp_set_param, 1507 | [OSSP_DSP_SET_SUBDIVISION] = padsp_set_param, 1508 | [OSSP_DSP_SET_FRAGMENT] = padsp_set_param, 1509 | [OSSP_DSP_GET_TRIGGER] = padsp_get_param, 1510 | [OSSP_DSP_SET_TRIGGER] = padsp_set_trigger, 1511 | [OSSP_DSP_GET_OSPACE] = padsp_get_space, 1512 | [OSSP_DSP_GET_ISPACE] = padsp_get_space, 1513 | [OSSP_DSP_GET_OPTR] = padsp_get_ptr, 1514 | [OSSP_DSP_GET_IPTR] = padsp_get_ptr, 1515 | [OSSP_DSP_GET_ODELAY] = padsp_get_odelay, 1516 | }; 1517 | 1518 | static int action_pre(void) 1519 | { 1520 | pa_threaded_mainloop_lock(mainloop); 1521 | if (fail_code) { 1522 | pa_threaded_mainloop_unlock(mainloop); 1523 | return fail_code; 1524 | } 1525 | return 0; 1526 | } 1527 | 1528 | static void action_post(void) 1529 | { 1530 | pa_threaded_mainloop_unlock(mainloop); 1531 | } 1532 | 1533 | int main(int argc, char **argv) 1534 | { 1535 | int rc; 1536 | 1537 | ossp_slave_init("ossp-padsp", argc, argv); 1538 | 1539 | page_size = sysconf(_SC_PAGE_SIZE); 1540 | 1541 | mainloop = pa_threaded_mainloop_new(); 1542 | if (!mainloop) { 1543 | err("failed to allocate mainloop"); 1544 | return 1; 1545 | } 1546 | mainloop_api = pa_threaded_mainloop_get_api(mainloop); 1547 | 1548 | if (pa_threaded_mainloop_start(mainloop)) { 1549 | err("pa_mainloop_start() failed"); 1550 | return 1; 1551 | } 1552 | 1553 | /* Okay, now we're open for business */ 1554 | rc = 0; 1555 | do { 1556 | rc = ossp_slave_process_command(ossp_cmd_fd, action_fn_tbl, 1557 | action_pre, action_post); 1558 | } while (rc > 0 && !fail_code); 1559 | if (rc) 1560 | fail_code = rc; 1561 | 1562 | pa_threaded_mainloop_lock(mainloop); 1563 | 1564 | kill_streams(); 1565 | if (context) { 1566 | pa_context_disconnect(context); 1567 | pa_context_unref(context); 1568 | } 1569 | 1570 | pa_threaded_mainloop_unlock(mainloop); 1571 | 1572 | pa_threaded_mainloop_stop(mainloop); 1573 | pa_threaded_mainloop_free(mainloop); 1574 | 1575 | return fail_code ? 1 : 0; 1576 | } 1577 | -------------------------------------------------------------------------------- /ossp-slave.c: -------------------------------------------------------------------------------- 1 | /* 2 | * ossp-slave - OSS Proxy: Common codes for slaves 3 | * 4 | * This file is released under the GPLv2. 5 | */ 6 | 7 | #define _GNU_SOURCE 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include "ossp-slave.h" 20 | 21 | static const char *usage = 22 | "usage: ossp-SLAVE [options]\n" 23 | "\n" 24 | "proxies commands from osspd to pulseaudio\n" 25 | "\n" 26 | "options:\n" 27 | " -u UID uid to use\n" 28 | " -g GID gid to use\n" 29 | " -c CMD_FD fd to receive commands from osspd\n" 30 | " -n NOTIFY_FD fd to send async notifications to osspd\n" 31 | " -m MMAP_FD fd to use for mmap\n" 32 | " -s MMAP_SIZE mmap size\n" 33 | " -l LOG_LEVEL set log level\n" 34 | " -t enable log timestamps\n"; 35 | 36 | char ossp_user_name[OSSP_USER_NAME_LEN]; 37 | int ossp_cmd_fd = -1, ossp_notify_fd = -1; 38 | void *ossp_mmap_addr[2]; 39 | struct ossp_transfer *ossp_mmap_transfer; 40 | 41 | void ossp_slave_init(const char *slave_name, int argc, char **argv) 42 | { 43 | int have_uid = 0, have_gid = 0; 44 | uid_t uid; 45 | gid_t gid; 46 | int mmap_fd = -1; 47 | size_t mmap_size = 0; 48 | int opt; 49 | struct passwd *pw, pw_buf; 50 | struct sigaction sa; 51 | char pw_sbuf[sysconf(_SC_GETPW_R_SIZE_MAX)]; 52 | 53 | while ((opt = getopt(argc, argv, "u:g:c:n:m:o:s:l:t")) != -1) { 54 | switch (opt) { 55 | case 'u': 56 | have_uid = 1; 57 | uid = strtol(optarg, NULL, 0); 58 | break; 59 | case 'g': 60 | have_gid = 1; 61 | gid = strtol(optarg, NULL, 0); 62 | break; 63 | case 'c': 64 | ossp_cmd_fd = strtol(optarg, NULL, 0); 65 | break; 66 | case 'n': 67 | ossp_notify_fd = strtol(optarg, NULL, 0); 68 | break; 69 | case 'm': 70 | mmap_fd = strtol(optarg, NULL, 0); 71 | break; 72 | case 's': 73 | mmap_size = strtoul(optarg, NULL, 0); 74 | break; 75 | case 'l': 76 | ossp_log_level = strtol(optarg, NULL, 0); 77 | break; 78 | case 't': 79 | ossp_log_timestamp = 1; 80 | break; 81 | } 82 | } 83 | 84 | if (!have_uid || !have_gid || ossp_cmd_fd < 0 || ossp_notify_fd < 0) { 85 | fputs(usage, stderr); 86 | _exit(1); 87 | } 88 | 89 | snprintf(ossp_user_name, sizeof(ossp_user_name), "uid%d", uid); 90 | if (getpwuid_r(uid, &pw_buf, pw_sbuf, sizeof(pw_sbuf), &pw) == 0) 91 | snprintf(ossp_user_name, sizeof(ossp_user_name), "%s", 92 | pw->pw_name); 93 | 94 | snprintf(ossp_log_name, sizeof(ossp_log_name), "%s[%s:%d]", 95 | slave_name, ossp_user_name, getpid()); 96 | 97 | if (mmap_fd >= 0) { 98 | void *p; 99 | 100 | if (!mmap_size) { 101 | fputs(usage, stderr); 102 | _exit(1); 103 | } 104 | 105 | p = mmap(NULL, mmap_size + 2 * sizeof(struct ossp_transfer), 106 | PROT_READ | PROT_WRITE, MAP_SHARED, mmap_fd, 0); 107 | if (p == MAP_FAILED) 108 | fatal_e(-errno, "mmap failed"); 109 | 110 | ossp_mmap_addr[PLAY] = p; 111 | ossp_mmap_addr[REC] = p + mmap_size / 2; 112 | ossp_mmap_transfer = p + mmap_size; 113 | close(mmap_fd); 114 | } 115 | 116 | /* mmap done, drop privileges */ 117 | if (setresgid(gid, gid, gid) || setresuid(uid, uid, uid)) 118 | fatal_e(-errno, "failed to drop privileges"); 119 | 120 | /* block SIGPIPE */ 121 | memset(&sa, 0, sizeof(sa)); 122 | sa.sa_handler = SIG_IGN; 123 | if (sigaction(SIGPIPE, &sa, NULL)) 124 | fatal_e(-errno, "failed to ignore SIGPIPE"); 125 | } 126 | 127 | int ossp_slave_process_command(int cmd_fd, 128 | ossp_action_fn_t const *action_fn_tbl, 129 | int (*action_pre_fn)(void), 130 | void (*action_post_fn)(void)) 131 | { 132 | static struct sized_buf carg_sbuf = { }, rarg_sbuf = { }; 133 | static struct sized_buf din_sbuf = { }, dout_sbuf = { }; 134 | struct ossp_cmd cmd; 135 | int fd = -1; 136 | char cmsg_buf[CMSG_SPACE(sizeof(fd))]; 137 | struct iovec iov = { &cmd, sizeof(cmd) }; 138 | struct msghdr msg = { .msg_iov = &iov, .msg_iovlen = 1, 139 | .msg_control = cmsg_buf, 140 | .msg_controllen = sizeof(cmsg_buf) }; 141 | struct cmsghdr *cmsg; 142 | size_t carg_size, din_size, rarg_size, dout_size; 143 | char *carg = NULL, *din = NULL, *rarg = NULL, *dout = NULL; 144 | struct ossp_reply reply = { .magic = OSSP_REPLY_MAGIC }; 145 | ssize_t ret; 146 | 147 | ret = recvmsg(cmd_fd, &msg, 0); 148 | if (ret == 0) 149 | return 0; 150 | if (ret < 0) { 151 | ret = -errno; 152 | err_e(ret, "failed to read command channel"); 153 | return ret; 154 | } 155 | 156 | if (ret != sizeof(cmd)) { 157 | err("command struct size mismatch (%zu, should be %zu)", 158 | ret, sizeof(cmd)); 159 | return -EINVAL; 160 | } 161 | 162 | if (cmd.magic != OSSP_CMD_MAGIC) { 163 | err("illegal command magic 0x%x", cmd.magic); 164 | return -EINVAL; 165 | } 166 | 167 | for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; 168 | cmsg = CMSG_NXTHDR(&msg, cmsg)) { 169 | if (cmsg->cmsg_level == SOL_SOCKET && 170 | cmsg->cmsg_type == SCM_RIGHTS) 171 | fd = *(int *)CMSG_DATA(cmsg); 172 | else { 173 | err("unknown cmsg %d:%d received (opcode %d)", 174 | cmsg->cmsg_level, cmsg->cmsg_type, cmd.opcode); 175 | return -EINVAL; 176 | } 177 | } 178 | 179 | if (cmd.opcode >= OSSP_NR_OPCODES) { 180 | err("unknown opcode %d", cmd.opcode); 181 | return -EINVAL; 182 | } 183 | 184 | carg_size = ossp_arg_sizes[cmd.opcode].carg_size; 185 | din_size = cmd.din_size; 186 | rarg_size = ossp_arg_sizes[cmd.opcode].rarg_size; 187 | dout_size = cmd.dout_size; 188 | 189 | if ((fd >= 0) != ossp_arg_sizes[cmd.opcode].has_fd) { 190 | err("fd=%d unexpected for opcode %d", fd, cmd.opcode); 191 | return -EINVAL; 192 | } 193 | 194 | if (ensure_sbuf_size(&carg_sbuf, carg_size) || 195 | ensure_sbuf_size(&din_sbuf, din_size) || 196 | ensure_sbuf_size(&rarg_sbuf, rarg_size) || 197 | ensure_sbuf_size(&dout_sbuf, dout_size)) { 198 | err("failed to allocate command buffers"); 199 | return -ENOMEM; 200 | } 201 | 202 | if (carg_size) { 203 | carg = carg_sbuf.buf; 204 | ret = read_fill(cmd_fd, carg, carg_size); 205 | if (ret < 0) 206 | return ret; 207 | } 208 | if (din_size) { 209 | din = din_sbuf.buf; 210 | ret = read_fill(cmd_fd, din, din_size); 211 | if (ret < 0) 212 | return ret; 213 | } 214 | if (rarg_size) 215 | rarg = rarg_sbuf.buf; 216 | if (dout_size) 217 | dout = dout_sbuf.buf; 218 | 219 | ret = -EINVAL; 220 | if (action_fn_tbl[cmd.opcode]) { 221 | ret = action_pre_fn(); 222 | if (ret == 0) { 223 | ret = action_fn_tbl[cmd.opcode](cmd.opcode, carg, 224 | din, din_size, rarg, 225 | dout, &dout_size, fd); 226 | action_post_fn(); 227 | } 228 | } 229 | 230 | reply.result = ret; 231 | if (ret >= 0) 232 | reply.dout_size = dout_size; 233 | else { 234 | rarg_size = 0; 235 | dout_size = 0; 236 | } 237 | 238 | if (write_fill(cmd_fd, &reply, sizeof(reply)) < 0 || 239 | write_fill(cmd_fd, rarg, rarg_size) < 0 || 240 | write_fill(cmd_fd, dout, dout_size) < 0) 241 | return -EIO; 242 | 243 | return 1; 244 | } 245 | -------------------------------------------------------------------------------- /ossp-slave.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ossp-slave - OSS Proxy: Common codes for slaves 3 | * 4 | * This file is released under the GPLv2. 5 | */ 6 | 7 | #ifndef _OSSP_SLAVE_H 8 | #define _OSSP_SLAVE_H 9 | 10 | #include "ossp.h" 11 | #include "ossp-util.h" 12 | 13 | #define OSSP_USER_NAME_LEN 128 14 | 15 | extern char ossp_user_name[OSSP_USER_NAME_LEN]; 16 | extern int ossp_cmd_fd, ossp_notify_fd; 17 | extern void *ossp_mmap_addr[2]; 18 | extern struct ossp_transfer *ossp_mmap_transfer; 19 | 20 | void ossp_slave_init(const char *slave_name, int argc, char **argv); 21 | int ossp_slave_process_command(int cmd_fd, 22 | ossp_action_fn_t const *action_fn_tbl, 23 | int (*action_pre_fn)(void), 24 | void (*action_post_fn)(void)); 25 | 26 | #endif /* _OSSP_SLAVE_H */ 27 | -------------------------------------------------------------------------------- /ossp-util.c: -------------------------------------------------------------------------------- 1 | /* 2 | * ossp-util - OSS Proxy: Common utilities 3 | * 4 | * This file is released under the GPLv2. 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include "ossp-util.h" 18 | 19 | #define BIT(nr) (1UL << (nr)) 20 | #define BIT_MASK(nr) (1UL << ((nr) % BITS_PER_LONG)) 21 | #define BIT_WORD(nr) ((nr) / BITS_PER_LONG) 22 | #define BITS_TO_LONGS(nr) DIV_ROUND_UP(nr, BITS_PER_BYTE * sizeof(long)) 23 | #define BITOP_WORD(nr) ((nr) / BITS_PER_LONG) 24 | 25 | char ossp_log_name[OSSP_LOG_NAME_LEN]; 26 | int ossp_log_level = OSSP_LOG_DFL; 27 | int ossp_log_timestamp; 28 | 29 | static const char *severity_strs[] = { 30 | [OSSP_LOG_CRIT] = "CRIT", 31 | [OSSP_LOG_ERR] = " ERR", 32 | [OSSP_LOG_WARN] = "WARN", 33 | [OSSP_LOG_INFO] = NULL, 34 | [OSSP_LOG_DBG0] = "DBG0", 35 | [OSSP_LOG_DBG1] = "DBG1", 36 | }; 37 | 38 | static int severity_map[] = { 39 | [OSSP_LOG_CRIT] = LOG_ERR, 40 | [OSSP_LOG_ERR] = LOG_ERR, 41 | [OSSP_LOG_WARN] = LOG_WARNING, 42 | [OSSP_LOG_INFO] = LOG_INFO, 43 | [OSSP_LOG_DBG0] = LOG_DEBUG, 44 | [OSSP_LOG_DBG1] = LOG_DEBUG, 45 | }; 46 | 47 | void log_msg(int severity, const char *fmt, ...) 48 | { 49 | static int syslog_opened = 0; 50 | char buf[1024]; 51 | size_t len = sizeof(buf), off = 0; 52 | va_list ap; 53 | 54 | if (severity > abs(ossp_log_level)) 55 | return; 56 | 57 | if (ossp_log_level < 0 && !syslog_opened) 58 | openlog(ossp_log_name, 0, LOG_DAEMON); 59 | 60 | assert(severity >= 0 && severity < ARRAY_SIZE(severity_strs)); 61 | 62 | if (ossp_log_timestamp) { 63 | static uint64_t start; 64 | uint64_t now; 65 | struct timeval tv; 66 | gettimeofday(&tv, NULL); 67 | now = tv.tv_sec * 1000 + tv.tv_usec / 1000; 68 | if (!start) 69 | start = now; 70 | 71 | off += snprintf(buf + off, len - off, "<%08"PRIu64"> ", 72 | now - start); 73 | } 74 | 75 | if (ossp_log_level > 0) { 76 | char sev_buf[16] = ""; 77 | if (severity_strs[severity]) 78 | snprintf(sev_buf, sizeof(sev_buf), " %s", 79 | severity_strs[severity]); 80 | off += snprintf(buf + off, len - off, "%s%s: ", 81 | ossp_log_name, sev_buf); 82 | } else if (severity_strs[severity]) 83 | off += snprintf(buf + off, len - off, "%s ", 84 | severity_strs[severity]); 85 | 86 | va_start(ap, fmt); 87 | off += vsnprintf(buf + off, len - off, fmt, ap); 88 | va_end(ap); 89 | 90 | off += snprintf(buf + off, len - off, "\n"); 91 | 92 | if (ossp_log_level > 0) 93 | fputs(buf, stderr); 94 | else 95 | syslog(severity_map[severity], "%s", buf); 96 | } 97 | 98 | int read_fill(int fd, void *buf, size_t size) 99 | { 100 | while (size) { 101 | ssize_t ret; 102 | int rc; 103 | 104 | ret = read(fd, buf, size); 105 | if (ret <= 0) { 106 | if (ret == 0) 107 | rc = -EIO; 108 | else 109 | rc = -errno; 110 | err_e(rc, "failed to read_fill %zu bytes from fd %d", 111 | size, fd); 112 | return rc; 113 | } 114 | buf += ret; 115 | size -= ret; 116 | } 117 | return 0; 118 | } 119 | 120 | int write_fill(int fd, const void *buf, size_t size) 121 | { 122 | while (size) { 123 | ssize_t ret; 124 | int rc; 125 | 126 | ret = write(fd, buf, size); 127 | if (ret <= 0) { 128 | if (ret == 0) 129 | rc = -EIO; 130 | else 131 | rc = -errno; 132 | err_e(rc, "failed to write_fill %zu bytes to fd %d", 133 | size, fd); 134 | return rc; 135 | } 136 | buf += ret; 137 | size -= ret; 138 | } 139 | return 0; 140 | } 141 | 142 | void ring_fill(struct ring_buf *ring, const void *buf, size_t size) 143 | { 144 | size_t tail; 145 | 146 | assert(ring_space(ring) >= size); 147 | 148 | tail = (ring->head + ring->size - ring->bytes) % ring->size; 149 | 150 | if (ring->head >= tail) { 151 | size_t todo = min(size, ring->size - ring->head); 152 | 153 | memcpy(ring->buf + ring->head, buf, todo); 154 | ring->head = (ring->head + todo) % ring->size; 155 | ring->bytes += todo; 156 | buf += todo; 157 | size -= todo; 158 | } 159 | 160 | assert(ring->size - ring->head >= size); 161 | memcpy(ring->buf + ring->head, buf, size); 162 | ring->head += size; 163 | ring->bytes += size; 164 | } 165 | 166 | void *ring_data(struct ring_buf *ring, size_t *sizep) 167 | { 168 | size_t tail; 169 | 170 | if (!ring->bytes) 171 | return NULL; 172 | 173 | tail = (ring->head + ring->size - ring->bytes) % ring->size; 174 | 175 | *sizep = min(ring->bytes, ring->size - tail); 176 | return ring->buf + tail; 177 | } 178 | 179 | int ring_resize(struct ring_buf *ring, size_t new_size) 180 | { 181 | struct ring_buf new_ring = { .size = new_size }; 182 | void *p; 183 | size_t size; 184 | 185 | if (ring_bytes(ring) > new_size) 186 | return -ENOSPC; 187 | 188 | new_ring.buf = calloc(1, new_size); 189 | if (new_size && !new_ring.buf) 190 | return -ENOMEM; 191 | 192 | while ((p = ring_data(ring, &size))) { 193 | ring_fill(&new_ring, p, size); 194 | ring_consume(ring, size); 195 | } 196 | 197 | free(ring->buf); 198 | *ring = new_ring; 199 | return 0; 200 | } 201 | 202 | int ensure_sbuf_size(struct sized_buf *sbuf, size_t size) 203 | { 204 | char *new_buf; 205 | 206 | if (sbuf->size >= size) 207 | return 0; 208 | 209 | new_buf = realloc(sbuf->buf, size); 210 | if (size && !new_buf) 211 | return -ENOMEM; 212 | 213 | sbuf->buf = new_buf; 214 | sbuf->size = size; 215 | return 0; 216 | } 217 | 218 | static unsigned long __ffs(unsigned long word) 219 | { 220 | int num = 0; 221 | 222 | if (BITS_PER_LONG == 64) { 223 | if ((word & 0xffffffff) == 0) { 224 | num += 32; 225 | word >>= 32; 226 | } 227 | } 228 | 229 | if ((word & 0xffff) == 0) { 230 | num += 16; 231 | word >>= 16; 232 | } 233 | if ((word & 0xff) == 0) { 234 | num += 8; 235 | word >>= 8; 236 | } 237 | if ((word & 0xf) == 0) { 238 | num += 4; 239 | word >>= 4; 240 | } 241 | if ((word & 0x3) == 0) { 242 | num += 2; 243 | word >>= 2; 244 | } 245 | if ((word & 0x1) == 0) 246 | num += 1; 247 | return num; 248 | } 249 | 250 | #define ffz(x) __ffs(~(x)) 251 | 252 | unsigned long find_next_zero_bit(const unsigned long *addr, unsigned long size, 253 | unsigned long offset) 254 | { 255 | const unsigned long *p = addr + BITOP_WORD(offset); 256 | unsigned long result = offset & ~(BITS_PER_LONG-1); 257 | unsigned long tmp; 258 | 259 | if (offset >= size) 260 | return size; 261 | size -= result; 262 | offset %= BITS_PER_LONG; 263 | if (offset) { 264 | tmp = *(p++); 265 | tmp |= ~0UL >> (BITS_PER_LONG - offset); 266 | if (size < BITS_PER_LONG) 267 | goto found_first; 268 | if (~tmp) 269 | goto found_middle; 270 | size -= BITS_PER_LONG; 271 | result += BITS_PER_LONG; 272 | } 273 | while (size & ~(BITS_PER_LONG-1)) { 274 | if (~(tmp = *(p++))) 275 | goto found_middle; 276 | result += BITS_PER_LONG; 277 | size -= BITS_PER_LONG; 278 | } 279 | if (!size) 280 | return result; 281 | tmp = *p; 282 | 283 | found_first: 284 | tmp |= ~0UL << size; 285 | if (tmp == ~0UL) /* Are any bits zero? */ 286 | return result + size; /* Nope. */ 287 | found_middle: 288 | return result + ffz(tmp); 289 | } 290 | 291 | void __set_bit(int nr, volatile unsigned long *addr) 292 | { 293 | unsigned long mask = BIT_MASK(nr); 294 | unsigned long *p = ((unsigned long *)addr) + BIT_WORD(nr); 295 | 296 | *p |= mask; 297 | } 298 | 299 | void __clear_bit(int nr, volatile unsigned long *addr) 300 | { 301 | unsigned long mask = BIT_MASK(nr); 302 | unsigned long *p = ((unsigned long *)addr) + BIT_WORD(nr); 303 | 304 | *p &= ~mask; 305 | } 306 | 307 | int get_proc_self_info(pid_t pid, pid_t *ppid_r, 308 | char *cmd_buf, size_t cmd_buf_sz) 309 | 310 | { 311 | char path[64], buf[4096]; 312 | int fd = -1; 313 | char *cmd_start, *cmd_end, *ppid_start, *end; 314 | ssize_t ret; 315 | pid_t ppid; 316 | int i, rc; 317 | 318 | snprintf(path, sizeof(path), "/proc/%ld/stat", (long)pid); 319 | fd = open(path, O_RDONLY); 320 | if (fd < 0) { 321 | rc = -errno; 322 | goto out; 323 | } 324 | 325 | ret = read(fd, buf, sizeof(buf)); 326 | if (ret < 0) 327 | goto out; 328 | if (ret == sizeof(buf)) { 329 | rc = -EOVERFLOW; 330 | goto out; 331 | } 332 | buf[ret] = '\0'; 333 | 334 | rc = -EINVAL; 335 | cmd_start = strchr(buf, '('); 336 | cmd_end = strrchr(buf, ')'); 337 | if (!cmd_start || !cmd_end) 338 | goto out; 339 | cmd_start++; 340 | 341 | ppid_start = cmd_end; 342 | for (i = 0; i < 3; i++) { 343 | ppid_start = strchr(ppid_start, ' '); 344 | if (!ppid_start) 345 | goto out; 346 | ppid_start++; 347 | } 348 | 349 | ppid = strtoul(ppid_start, &end, 10); 350 | if (end == ppid_start || *end != ' ') 351 | goto out; 352 | 353 | if (ppid_r) 354 | *ppid_r = ppid; 355 | if (cmd_buf) { 356 | size_t len = min_t(size_t, cmd_end - cmd_start, cmd_buf_sz - 1); 357 | memcpy(cmd_buf, cmd_start, len); 358 | cmd_buf[len] = '\0'; 359 | } 360 | 361 | rc = 0; 362 | out: 363 | close(fd); 364 | 365 | return rc; 366 | } 367 | -------------------------------------------------------------------------------- /ossp-util.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ossp-util - OSS Proxy: Common utilities 3 | * 4 | * This file is released under the GPLv2. 5 | */ 6 | 7 | #ifndef _OSSP_UTIL_H 8 | #define _OSSP_UTIL_H 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include "ossp.h" 17 | 18 | #define OSSP_LOG_NAME_LEN 128 19 | 20 | enum { 21 | OSSP_LOG_CRIT = 1, 22 | OSSP_LOG_ERR, 23 | OSSP_LOG_WARN, 24 | OSSP_LOG_INFO, 25 | OSSP_LOG_DFL = OSSP_LOG_INFO, /* default log level */ 26 | OSSP_LOG_DBG0, 27 | OSSP_LOG_DBG1, 28 | OSSP_LOG_MAX = OSSP_LOG_DBG1, 29 | }; 30 | 31 | extern char ossp_log_name[OSSP_LOG_NAME_LEN]; 32 | extern int ossp_log_level; 33 | extern int ossp_log_timestamp; 34 | 35 | #define BITS_PER_BYTE 8 36 | #define BITS_PER_LONG (BITS_PER_BYTE * sizeof(long)) 37 | #define DIV_ROUND_UP(n,d) (((n) + (d) - 1) / (d)) 38 | #define BITS_TO_LONGS(nr) DIV_ROUND_UP(nr, BITS_PER_BYTE * sizeof(long)) 39 | 40 | /* ARRAY_SIZE and min/max macros stolen from linux/kernel.h */ 41 | #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) 42 | 43 | #define min(x, y) ({ \ 44 | typeof(x) _min1 = (x); \ 45 | typeof(y) _min2 = (y); \ 46 | (void) (&_min1 == &_min2); \ 47 | _min1 < _min2 ? _min1 : _min2; }) 48 | 49 | #define max(x, y) ({ \ 50 | typeof(x) _max1 = (x); \ 51 | typeof(y) _max2 = (y); \ 52 | (void) (&_max1 == &_max2); \ 53 | _max1 > _max2 ? _max1 : _max2; }) 54 | 55 | #define min_t(type, x, y) ({ \ 56 | type __min1 = (x); \ 57 | type __min2 = (y); \ 58 | __min1 < __min2 ? __min1: __min2; }) 59 | 60 | #define max_t(type, x, y) ({ \ 61 | type __max1 = (x); \ 62 | type __max2 = (y); \ 63 | __max1 > __max2 ? __max1: __max2; }) 64 | 65 | void log_msg(int severity, const char *fmt, ...) 66 | __attribute__ ((format (printf, 2, 3))); 67 | 68 | #define fatal(fmt, args...) do { \ 69 | log_msg(OSSP_LOG_CRIT, fmt , ##args); \ 70 | _exit(1); \ 71 | } while (0) 72 | #define err(fmt, args...) log_msg(OSSP_LOG_ERR, fmt , ##args) 73 | #define warn(fmt, args...) log_msg(OSSP_LOG_WARN, fmt , ##args) 74 | #define info(fmt, args...) log_msg(OSSP_LOG_INFO, fmt , ##args) 75 | #define dbg0(fmt, args...) log_msg(OSSP_LOG_DBG0, fmt , ##args) 76 | #define dbg1(fmt, args...) log_msg(OSSP_LOG_DBG1, fmt , ##args) 77 | 78 | #define fatal_e(e, fmt, args...) \ 79 | fatal(fmt" (%s)" , ##args, strerror(-(e))) 80 | #define err_e(e, fmt, args...) \ 81 | err(fmt" (%s)" , ##args, strerror(-(e))) 82 | #define warn_e(e, fmt, args...) \ 83 | warn(fmt" (%s)" , ##args, strerror(-(e))) 84 | #define info_e(e, fmt, args...) \ 85 | info(fmt" (%s)" , ##args, strerror(-(e))) 86 | #define dbg0_e(e, fmt, args...) \ 87 | dbg0(fmt" (%s)" , ##args, strerror(-(e))) 88 | #define dbg1_e(e, fmt, args...) \ 89 | dbg1(fmt" (%s)" , ##args, strerror(-(e))) 90 | 91 | struct ring_buf { 92 | char *buf; 93 | size_t size; 94 | size_t head; 95 | size_t bytes; 96 | }; 97 | 98 | static inline size_t ring_size(struct ring_buf *ring) 99 | { 100 | return ring->size; 101 | } 102 | 103 | static inline size_t ring_bytes(struct ring_buf *ring) 104 | { 105 | return ring->bytes; 106 | } 107 | 108 | static inline size_t ring_space(struct ring_buf *ring) 109 | { 110 | return ring->size - ring->bytes; 111 | } 112 | 113 | static inline void ring_consume(struct ring_buf *ring, size_t size) 114 | { 115 | assert(ring->bytes >= size); 116 | ring->bytes -= size; 117 | } 118 | 119 | static inline void ring_manual_init(struct ring_buf *ring, void *buf, 120 | size_t size, size_t head, size_t bytes) 121 | { 122 | ring->buf = buf; 123 | ring->size = size; 124 | ring->head = head; 125 | ring->bytes = bytes; 126 | } 127 | 128 | void ring_fill(struct ring_buf *ring, const void *buf, size_t size); 129 | void *ring_data(struct ring_buf *ring, size_t *sizep); 130 | int ring_resize(struct ring_buf *ring, size_t new_size); 131 | 132 | struct sized_buf { 133 | char *buf; 134 | size_t size; 135 | }; 136 | 137 | int ensure_sbuf_size(struct sized_buf *sbuf, size_t size); 138 | 139 | int read_fill(int fd, void *buf, size_t size); 140 | int write_fill(int fd, const void *buf, size_t size); 141 | 142 | /* 143 | * Bitops lifted from linux asm-generic implementation. 144 | */ 145 | unsigned long find_next_zero_bit(const unsigned long *addr, unsigned 146 | long size, unsigned long offset); 147 | #define find_first_zero_bit(addr, size) find_next_zero_bit((addr), (size), 0) 148 | extern void __set_bit(int nr, volatile unsigned long *addr); 149 | extern void __clear_bit(int nr, volatile unsigned long *addr); 150 | 151 | typedef ssize_t (*ossp_action_fn_t)(enum ossp_opcode opcode, 152 | void *carg, void *din, size_t din_sz, 153 | void *rarg, void *dout, size_t *dout_szp, 154 | int fd); 155 | 156 | int get_proc_self_info(pid_t tid, pid_t *pgrp, 157 | char *cmd_buf, size_t cmd_buf_sz); 158 | 159 | /* 160 | * Doubly linked list handling code shamelessly stolen from the Linux 161 | * kernel 2.6.26 include/linux/list.h. 162 | */ 163 | 164 | /** 165 | * container_of - cast a member of a structure out to the containing structure 166 | * @ptr: the pointer to the member. 167 | * @type: the type of the container struct this is embedded in. 168 | * @member: the name of the member within the struct. 169 | * 170 | */ 171 | #define container_of(ptr, type, member) ({ \ 172 | const typeof( ((type *)0)->member ) *__mptr = (ptr); \ 173 | (type *)( (char *)__mptr - offsetof(type,member) );}) 174 | 175 | #define LIST_POISON1 ((void *) 0x00100100) 176 | #define LIST_POISON2 ((void *) 0x00200200) 177 | 178 | /* 179 | * Simple doubly linked list implementation. 180 | * 181 | * Some of the internal functions ("__xxx") are useful when 182 | * manipulating whole lists rather than single entries, as 183 | * sometimes we already know the next/prev entries and we can 184 | * generate better code by using them directly rather than 185 | * using the generic single-entry routines. 186 | */ 187 | 188 | struct list_head { 189 | struct list_head *next, *prev; 190 | }; 191 | 192 | #define LIST_HEAD_INIT(name) { &(name), &(name) } 193 | 194 | #define LIST_HEAD(name) \ 195 | struct list_head name = LIST_HEAD_INIT(name) 196 | 197 | static inline void INIT_LIST_HEAD(struct list_head *list) 198 | { 199 | list->next = list; 200 | list->prev = list; 201 | } 202 | 203 | /* 204 | * Insert a new entry between two known consecutive entries. 205 | * 206 | * This is only for internal list manipulation where we know 207 | * the prev/next entries already! 208 | */ 209 | static inline void __list_add(struct list_head *new, 210 | struct list_head *prev, 211 | struct list_head *next) 212 | { 213 | next->prev = new; 214 | new->next = next; 215 | new->prev = prev; 216 | prev->next = new; 217 | } 218 | 219 | /** 220 | * list_add - add a new entry 221 | * @new: new entry to be added 222 | * @head: list head to add it after 223 | * 224 | * Insert a new entry after the specified head. 225 | * This is good for implementing stacks. 226 | */ 227 | static inline void list_add(struct list_head *new, struct list_head *head) 228 | { 229 | __list_add(new, head, head->next); 230 | } 231 | 232 | /** 233 | * list_add_tail - add a new entry 234 | * @new: new entry to be added 235 | * @head: list head to add it before 236 | * 237 | * Insert a new entry before the specified head. 238 | * This is useful for implementing queues. 239 | */ 240 | static inline void list_add_tail(struct list_head *new, struct list_head *head) 241 | { 242 | __list_add(new, head->prev, head); 243 | } 244 | 245 | /* 246 | * Delete a list entry by making the prev/next entries 247 | * point to each other. 248 | * 249 | * This is only for internal list manipulation where we know 250 | * the prev/next entries already! 251 | */ 252 | static inline void __list_del(struct list_head * prev, struct list_head * next) 253 | { 254 | next->prev = prev; 255 | prev->next = next; 256 | } 257 | 258 | /** 259 | * list_del - deletes entry from list. 260 | * @entry: the element to delete from the list. 261 | * Note: list_empty() on entry does not return true after this, the entry is 262 | * in an undefined state. 263 | */ 264 | static inline void list_del(struct list_head *entry) 265 | { 266 | __list_del(entry->prev, entry->next); 267 | entry->next = LIST_POISON1; 268 | entry->prev = LIST_POISON2; 269 | } 270 | 271 | /** 272 | * list_replace - replace old entry by new one 273 | * @old : the element to be replaced 274 | * @new : the new element to insert 275 | * 276 | * If @old was empty, it will be overwritten. 277 | */ 278 | static inline void list_replace(struct list_head *old, 279 | struct list_head *new) 280 | { 281 | new->next = old->next; 282 | new->next->prev = new; 283 | new->prev = old->prev; 284 | new->prev->next = new; 285 | } 286 | 287 | static inline void list_replace_init(struct list_head *old, 288 | struct list_head *new) 289 | { 290 | list_replace(old, new); 291 | INIT_LIST_HEAD(old); 292 | } 293 | 294 | /** 295 | * list_del_init - deletes entry from list and reinitialize it. 296 | * @entry: the element to delete from the list. 297 | */ 298 | static inline void list_del_init(struct list_head *entry) 299 | { 300 | __list_del(entry->prev, entry->next); 301 | INIT_LIST_HEAD(entry); 302 | } 303 | 304 | /** 305 | * list_move - delete from one list and add as another's head 306 | * @list: the entry to move 307 | * @head: the head that will precede our entry 308 | */ 309 | static inline void list_move(struct list_head *list, struct list_head *head) 310 | { 311 | __list_del(list->prev, list->next); 312 | list_add(list, head); 313 | } 314 | 315 | /** 316 | * list_move_tail - delete from one list and add as another's tail 317 | * @list: the entry to move 318 | * @head: the head that will follow our entry 319 | */ 320 | static inline void list_move_tail(struct list_head *list, 321 | struct list_head *head) 322 | { 323 | __list_del(list->prev, list->next); 324 | list_add_tail(list, head); 325 | } 326 | 327 | /** 328 | * list_is_last - tests whether @list is the last entry in list @head 329 | * @list: the entry to test 330 | * @head: the head of the list 331 | */ 332 | static inline int list_is_last(const struct list_head *list, 333 | const struct list_head *head) 334 | { 335 | return list->next == head; 336 | } 337 | 338 | /** 339 | * list_empty - tests whether a list is empty 340 | * @head: the list to test. 341 | */ 342 | static inline int list_empty(const struct list_head *head) 343 | { 344 | return head->next == head; 345 | } 346 | 347 | /** 348 | * list_empty_careful - tests whether a list is empty and not being modified 349 | * @head: the list to test 350 | * 351 | * Description: 352 | * tests whether a list is empty _and_ checks that no other CPU might be 353 | * in the process of modifying either member (next or prev) 354 | * 355 | * NOTE: using list_empty_careful() without synchronization 356 | * can only be safe if the only activity that can happen 357 | * to the list entry is list_del_init(). Eg. it cannot be used 358 | * if another CPU could re-list_add() it. 359 | */ 360 | static inline int list_empty_careful(const struct list_head *head) 361 | { 362 | struct list_head *next = head->next; 363 | return (next == head) && (next == head->prev); 364 | } 365 | 366 | /** 367 | * list_is_singular - tests whether a list has just one entry. 368 | * @head: the list to test. 369 | */ 370 | static inline int list_is_singular(const struct list_head *head) 371 | { 372 | return !list_empty(head) && (head->next == head->prev); 373 | } 374 | 375 | static inline void __list_splice(const struct list_head *list, 376 | struct list_head *head) 377 | { 378 | struct list_head *first = list->next; 379 | struct list_head *last = list->prev; 380 | struct list_head *at = head->next; 381 | 382 | first->prev = head; 383 | head->next = first; 384 | 385 | last->next = at; 386 | at->prev = last; 387 | } 388 | 389 | /** 390 | * list_splice - join two lists 391 | * @list: the new list to add. 392 | * @head: the place to add it in the first list. 393 | */ 394 | static inline void list_splice(const struct list_head *list, 395 | struct list_head *head) 396 | { 397 | if (!list_empty(list)) 398 | __list_splice(list, head); 399 | } 400 | 401 | /** 402 | * list_splice_init - join two lists and reinitialise the emptied list. 403 | * @list: the new list to add. 404 | * @head: the place to add it in the first list. 405 | * 406 | * The list at @list is reinitialised 407 | */ 408 | static inline void list_splice_init(struct list_head *list, 409 | struct list_head *head) 410 | { 411 | if (!list_empty(list)) { 412 | __list_splice(list, head); 413 | INIT_LIST_HEAD(list); 414 | } 415 | } 416 | 417 | /** 418 | * list_entry - get the struct for this entry 419 | * @ptr: the &struct list_head pointer. 420 | * @type: the type of the struct this is embedded in. 421 | * @member: the name of the list_struct within the struct. 422 | */ 423 | #define list_entry(ptr, type, member) \ 424 | container_of(ptr, type, member) 425 | 426 | /** 427 | * list_first_entry - get the first element from a list 428 | * @ptr: the list head to take the element from. 429 | * @type: the type of the struct this is embedded in. 430 | * @member: the name of the list_struct within the struct. 431 | * 432 | * Note, that list is expected to be not empty. 433 | */ 434 | #define list_first_entry(ptr, type, member) \ 435 | list_entry((ptr)->next, type, member) 436 | 437 | /** 438 | * list_for_each - iterate over a list 439 | * @pos: the &struct list_head to use as a loop cursor. 440 | * @head: the head for your list. 441 | */ 442 | #define list_for_each(pos, head) \ 443 | for (pos = (head)->next; pos != (head); pos = pos->next) 444 | 445 | /** 446 | * list_for_each_prev - iterate over a list backwards 447 | * @pos: the &struct list_head to use as a loop cursor. 448 | * @head: the head for your list. 449 | */ 450 | #define list_for_each_prev(pos, head) \ 451 | for (pos = (head)->prev; pos != (head); pos = pos->prev) 452 | 453 | /** 454 | * list_for_each_safe - iterate over a list safe against removal of list entry 455 | * @pos: the &struct list_head to use as a loop cursor. 456 | * @n: another &struct list_head to use as temporary storage 457 | * @head: the head for your list. 458 | */ 459 | #define list_for_each_safe(pos, n, head) \ 460 | for (pos = (head)->next, n = pos->next; pos != (head); \ 461 | pos = n, n = pos->next) 462 | 463 | /** 464 | * list_for_each_prev_safe - iterate over a list backwards safe against removal of list entry 465 | * @pos: the &struct list_head to use as a loop cursor. 466 | * @n: another &struct list_head to use as temporary storage 467 | * @head: the head for your list. 468 | */ 469 | #define list_for_each_prev_safe(pos, n, head) \ 470 | for (pos = (head)->prev, n = pos->prev; \ 471 | pos != (head); pos = n, n = pos->prev) 472 | 473 | /** 474 | * list_for_each_entry - iterate over list of given type 475 | * @pos: the type * to use as a loop cursor. 476 | * @head: the head for your list. 477 | * @member: the name of the list_struct within the struct. 478 | */ 479 | #define list_for_each_entry(pos, head, member) \ 480 | for (pos = list_entry((head)->next, typeof(*pos), member); \ 481 | &pos->member != (head); \ 482 | pos = list_entry(pos->member.next, typeof(*pos), member)) 483 | 484 | /** 485 | * list_for_each_entry_reverse - iterate backwards over list of given type. 486 | * @pos: the type * to use as a loop cursor. 487 | * @head: the head for your list. 488 | * @member: the name of the list_struct within the struct. 489 | */ 490 | #define list_for_each_entry_reverse(pos, head, member) \ 491 | for (pos = list_entry((head)->prev, typeof(*pos), member); \ 492 | &pos->member != (head); \ 493 | pos = list_entry(pos->member.prev, typeof(*pos), member)) 494 | 495 | /** 496 | * list_prepare_entry - prepare a pos entry for use in list_for_each_entry_continue() 497 | * @pos: the type * to use as a start point 498 | * @head: the head of the list 499 | * @member: the name of the list_struct within the struct. 500 | * 501 | * Prepares a pos entry for use as a start point in list_for_each_entry_continue(). 502 | */ 503 | #define list_prepare_entry(pos, head, member) \ 504 | ((pos) ? : list_entry(head, typeof(*pos), member)) 505 | 506 | /** 507 | * list_for_each_entry_continue - continue iteration over list of given type 508 | * @pos: the type * to use as a loop cursor. 509 | * @head: the head for your list. 510 | * @member: the name of the list_struct within the struct. 511 | * 512 | * Continue to iterate over list of given type, continuing after 513 | * the current position. 514 | */ 515 | #define list_for_each_entry_continue(pos, head, member) \ 516 | for (pos = list_entry(pos->member.next, typeof(*pos), member); \ 517 | &pos->member != (head); \ 518 | pos = list_entry(pos->member.next, typeof(*pos), member)) 519 | 520 | /** 521 | * list_for_each_entry_continue_reverse - iterate backwards from the given point 522 | * @pos: the type * to use as a loop cursor. 523 | * @head: the head for your list. 524 | * @member: the name of the list_struct within the struct. 525 | * 526 | * Start to iterate over list of given type backwards, continuing after 527 | * the current position. 528 | */ 529 | #define list_for_each_entry_continue_reverse(pos, head, member) \ 530 | for (pos = list_entry(pos->member.prev, typeof(*pos), member); \ 531 | &pos->member != (head); \ 532 | pos = list_entry(pos->member.prev, typeof(*pos), member)) 533 | 534 | /** 535 | * list_for_each_entry_from - iterate over list of given type from the current point 536 | * @pos: the type * to use as a loop cursor. 537 | * @head: the head for your list. 538 | * @member: the name of the list_struct within the struct. 539 | * 540 | * Iterate over list of given type, continuing from current position. 541 | */ 542 | #define list_for_each_entry_from(pos, head, member) \ 543 | for (; &pos->member != (head); \ 544 | pos = list_entry(pos->member.next, typeof(*pos), member)) 545 | 546 | /** 547 | * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry 548 | * @pos: the type * to use as a loop cursor. 549 | * @n: another type * to use as temporary storage 550 | * @head: the head for your list. 551 | * @member: the name of the list_struct within the struct. 552 | */ 553 | #define list_for_each_entry_safe(pos, n, head, member) \ 554 | for (pos = list_entry((head)->next, typeof(*pos), member), \ 555 | n = list_entry(pos->member.next, typeof(*pos), member); \ 556 | &pos->member != (head); \ 557 | pos = n, n = list_entry(n->member.next, typeof(*n), member)) 558 | 559 | /** 560 | * list_for_each_entry_safe_continue 561 | * @pos: the type * to use as a loop cursor. 562 | * @n: another type * to use as temporary storage 563 | * @head: the head for your list. 564 | * @member: the name of the list_struct within the struct. 565 | * 566 | * Iterate over list of given type, continuing after current point, 567 | * safe against removal of list entry. 568 | */ 569 | #define list_for_each_entry_safe_continue(pos, n, head, member) \ 570 | for (pos = list_entry(pos->member.next, typeof(*pos), member), \ 571 | n = list_entry(pos->member.next, typeof(*pos), member); \ 572 | &pos->member != (head); \ 573 | pos = n, n = list_entry(n->member.next, typeof(*n), member)) 574 | 575 | /** 576 | * list_for_each_entry_safe_from 577 | * @pos: the type * to use as a loop cursor. 578 | * @n: another type * to use as temporary storage 579 | * @head: the head for your list. 580 | * @member: the name of the list_struct within the struct. 581 | * 582 | * Iterate over list of given type from current point, safe against 583 | * removal of list entry. 584 | */ 585 | #define list_for_each_entry_safe_from(pos, n, head, member) \ 586 | for (n = list_entry(pos->member.next, typeof(*pos), member); \ 587 | &pos->member != (head); \ 588 | pos = n, n = list_entry(n->member.next, typeof(*n), member)) 589 | 590 | /** 591 | * list_for_each_entry_safe_reverse 592 | * @pos: the type * to use as a loop cursor. 593 | * @n: another type * to use as temporary storage 594 | * @head: the head for your list. 595 | * @member: the name of the list_struct within the struct. 596 | * 597 | * Iterate backwards over list of given type, safe against removal 598 | * of list entry. 599 | */ 600 | #define list_for_each_entry_safe_reverse(pos, n, head, member) \ 601 | for (pos = list_entry((head)->prev, typeof(*pos), member), \ 602 | n = list_entry(pos->member.prev, typeof(*pos), member); \ 603 | &pos->member != (head); \ 604 | pos = n, n = list_entry(n->member.prev, typeof(*n), member)) 605 | 606 | #endif /*_OSSP_UTIL_H*/ 607 | -------------------------------------------------------------------------------- /ossp.c: -------------------------------------------------------------------------------- 1 | /* 2 | * ossp - OSS Proxy: emulate OSS device using CUSE 3 | * 4 | * This file is released under the GPLv2. 5 | */ 6 | 7 | #include "ossp.h" 8 | 9 | const struct ossp_arg_size ossp_arg_sizes[OSSP_NR_OPCODES] = { 10 | [OSSP_MIXER] = { sizeof(struct ossp_mixer_arg), 11 | sizeof(struct ossp_mixer_arg), 0 }, 12 | 13 | [OSSP_DSP_OPEN] = { sizeof(struct ossp_dsp_open_arg), 0, 0 }, 14 | [OSSP_DSP_READ] = { sizeof(struct ossp_dsp_rw_arg), 0, 0 }, 15 | [OSSP_DSP_WRITE] = { sizeof(struct ossp_dsp_rw_arg), 0, 0 }, 16 | [OSSP_DSP_POLL] = { sizeof(int), sizeof(unsigned), 0 }, 17 | [OSSP_DSP_MMAP] = { sizeof(struct ossp_dsp_mmap_arg), 0, 0 }, 18 | [OSSP_DSP_MUNMAP] = { sizeof(int), 0, 0 }, 19 | 20 | [OSSP_DSP_RESET] = { 0, 0, 0 }, 21 | [OSSP_DSP_SYNC] = { 0, 0, 0 }, 22 | [OSSP_DSP_POST] = { 0, 0, 0 }, 23 | [OSSP_DSP_GET_RATE] = { 0, sizeof(int), 0 }, 24 | [OSSP_DSP_GET_CHANNELS] = { 0, sizeof(int), 0 }, 25 | [OSSP_DSP_GET_FORMAT] = { 0, sizeof(int), 0 }, 26 | [OSSP_DSP_GET_BLKSIZE] = { 0, sizeof(int), 0 }, 27 | [OSSP_DSP_GET_FORMATS] = { 0, sizeof(int), 0 }, 28 | [OSSP_DSP_SET_RATE] = { sizeof(int), sizeof(int), 0 }, 29 | [OSSP_DSP_SET_CHANNELS] = { sizeof(int), sizeof(int), 0 }, 30 | [OSSP_DSP_SET_FORMAT] = { sizeof(int), sizeof(int), 0 }, 31 | [OSSP_DSP_SET_SUBDIVISION] = { sizeof(int), sizeof(int), 0 }, 32 | [OSSP_DSP_SET_FRAGMENT] = { sizeof(int), 0, 0 }, 33 | [OSSP_DSP_GET_TRIGGER] = { 0, sizeof(int), 0 }, 34 | [OSSP_DSP_SET_TRIGGER] = { sizeof(int), 0, 0 }, 35 | [OSSP_DSP_GET_OSPACE] = { 0, sizeof(struct audio_buf_info), 0 }, 36 | [OSSP_DSP_GET_ISPACE] = { 0, sizeof(struct audio_buf_info), 0 }, 37 | [OSSP_DSP_GET_OPTR] = { 0, sizeof(struct count_info), 0 }, 38 | [OSSP_DSP_GET_IPTR] = { 0, sizeof(struct count_info), 0 }, 39 | [OSSP_DSP_GET_ODELAY] = { 0, sizeof(int), 0 }, 40 | }; 41 | 42 | const char *ossp_cmd_str[OSSP_NR_OPCODES] = { 43 | [OSSP_MIXER] = "MIXER", 44 | 45 | [OSSP_DSP_OPEN] = "OPEN", 46 | [OSSP_DSP_READ] = "READ", 47 | [OSSP_DSP_WRITE] = "WRITE", 48 | [OSSP_DSP_POLL] = "POLL", 49 | [OSSP_DSP_MMAP] = "MMAP", 50 | [OSSP_DSP_MUNMAP] = "MUNMAP", 51 | 52 | [OSSP_DSP_RESET] = "RESET", 53 | [OSSP_DSP_SYNC] = "SYNC", 54 | [OSSP_DSP_POST] = "POST", 55 | 56 | [OSSP_DSP_GET_RATE] = "GET_RATE", 57 | [OSSP_DSP_GET_CHANNELS] = "GET_CHANNELS", 58 | [OSSP_DSP_GET_FORMAT] = "GET_FORMAT", 59 | [OSSP_DSP_GET_BLKSIZE] = "GET_BLKSIZE", 60 | [OSSP_DSP_GET_FORMATS] = "GET_FORMATS", 61 | [OSSP_DSP_SET_RATE] = "SET_RATE", 62 | [OSSP_DSP_SET_CHANNELS] = "SET_CHANNELS", 63 | [OSSP_DSP_SET_FORMAT] = "SET_FORMAT", 64 | [OSSP_DSP_SET_SUBDIVISION] = "SET_BUSDIVISION", 65 | 66 | [OSSP_DSP_SET_FRAGMENT] = "SET_FRAGMENT", 67 | [OSSP_DSP_GET_TRIGGER] = "GET_TRIGGER", 68 | [OSSP_DSP_SET_TRIGGER] = "SET_TRIGGER", 69 | [OSSP_DSP_GET_OSPACE] = "GET_OSPACE", 70 | [OSSP_DSP_GET_ISPACE] = "GET_ISPACE", 71 | [OSSP_DSP_GET_OPTR] = "GET_OPTR", 72 | [OSSP_DSP_GET_IPTR] = "GET_IPTR", 73 | [OSSP_DSP_GET_ODELAY] = "GET_ODELAY", 74 | }; 75 | 76 | const char *ossp_notify_str[OSSP_NR_NOTIFY_OPCODES] = { 77 | [OSSP_NOTIFY_POLL] = "POLL", 78 | [OSSP_NOTIFY_OBITUARY] = "OBITUARY", 79 | [OSSP_NOTIFY_VOLCHG] = "VOLCHG", 80 | }; 81 | -------------------------------------------------------------------------------- /ossp.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ossp - OSS Proxy: emulate OSS device using CUSE 3 | * 4 | * This file is released under the GPLv2. 5 | */ 6 | 7 | #ifndef _OSSP_H 8 | #define _OSSP_H 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #define OSSP_CMD_MAGIC 0xdeadbeef 16 | #define OSSP_REPLY_MAGIC 0xbeefdead 17 | #define OSSP_NOTIFY_MAGIC 0xbebebebe 18 | 19 | #define PLAY 0 20 | #define REC 1 21 | #define LEFT 0 22 | #define RIGHT 1 23 | 24 | enum ossp_opcode { 25 | OSSP_MIXER, 26 | 27 | OSSP_DSP_OPEN, 28 | OSSP_DSP_READ, 29 | OSSP_DSP_WRITE, 30 | OSSP_DSP_POLL, 31 | OSSP_DSP_MMAP, 32 | OSSP_DSP_MUNMAP, 33 | 34 | OSSP_DSP_RESET, 35 | OSSP_DSP_SYNC, 36 | OSSP_DSP_POST, 37 | 38 | OSSP_DSP_GET_RATE, 39 | OSSP_DSP_GET_CHANNELS, 40 | OSSP_DSP_GET_FORMAT, 41 | OSSP_DSP_GET_BLKSIZE, 42 | OSSP_DSP_GET_FORMATS, 43 | OSSP_DSP_SET_RATE, 44 | OSSP_DSP_SET_CHANNELS, 45 | OSSP_DSP_SET_FORMAT, 46 | OSSP_DSP_SET_SUBDIVISION, 47 | 48 | OSSP_DSP_SET_FRAGMENT, 49 | OSSP_DSP_GET_TRIGGER, 50 | OSSP_DSP_SET_TRIGGER, 51 | OSSP_DSP_GET_OSPACE, 52 | OSSP_DSP_GET_ISPACE, 53 | OSSP_DSP_GET_OPTR, 54 | OSSP_DSP_GET_IPTR, 55 | OSSP_DSP_GET_ODELAY, 56 | 57 | OSSP_NR_OPCODES, 58 | }; 59 | 60 | enum ossp_notify_opcode { 61 | OSSP_NOTIFY_POLL, 62 | OSSP_NOTIFY_OBITUARY, 63 | OSSP_NOTIFY_VOLCHG, 64 | OSSP_NOTIFY_FILL, 65 | OSSP_NOTIFY_STORE, 66 | 67 | OSSP_NR_NOTIFY_OPCODES, 68 | }; 69 | 70 | struct ossp_transfer { 71 | sem_t sem; 72 | size_t pos; 73 | size_t bytes; 74 | }; 75 | 76 | struct ossp_mixer_arg { 77 | int vol[2][2]; 78 | }; 79 | 80 | struct ossp_dsp_open_arg { 81 | int flags; 82 | pid_t opener_pid; 83 | }; 84 | 85 | struct ossp_dsp_rw_arg { 86 | unsigned nonblock:1; 87 | }; 88 | 89 | struct ossp_dsp_mmap_arg { 90 | int dir; 91 | size_t size; 92 | }; 93 | 94 | struct ossp_cmd { 95 | unsigned magic; 96 | enum ossp_opcode opcode; 97 | size_t din_size; 98 | size_t dout_size; 99 | }; 100 | 101 | struct ossp_reply { 102 | unsigned magic; 103 | int result; 104 | size_t dout_size; /* <= cmd.data_in_size */ 105 | }; 106 | 107 | struct ossp_notify { 108 | unsigned magic; 109 | enum ossp_notify_opcode opcode; 110 | }; 111 | 112 | struct ossp_arg_size { 113 | ssize_t carg_size; 114 | ssize_t rarg_size; 115 | unsigned has_fd:1; 116 | }; 117 | 118 | extern const struct ossp_arg_size ossp_arg_sizes[OSSP_NR_OPCODES]; 119 | extern const char *ossp_cmd_str[OSSP_NR_OPCODES]; 120 | extern const char *ossp_notify_str[OSSP_NR_NOTIFY_OPCODES]; 121 | 122 | #endif /* _OSSP_H */ 123 | -------------------------------------------------------------------------------- /osspd.c: -------------------------------------------------------------------------------- 1 | /* 2 | * osspd - OSS Proxy Daemon: emulate OSS device using CUSE 3 | * 4 | * This file is released under the GPLv2. 5 | */ 6 | 7 | #define FUSE_USE_VERSION 35 8 | #define _GNU_SOURCE 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include "ossp.h" 33 | #include "ossp-util.h" 34 | 35 | #define DFL_MIXER_NAME "mixer" 36 | #define DFL_DSP_NAME "dsp" 37 | #define DFL_ADSP_NAME "adsp" 38 | #define STRFMT "S[%u/%d]" 39 | #define STRID(os) os->id, os->pid 40 | 41 | #define dbg1_os(os, fmt, args...) dbg1(STRFMT" "fmt, STRID(os) , ##args) 42 | #define dbg0_os(os, fmt, args...) dbg0(STRFMT" "fmt, STRID(os) , ##args) 43 | #define warn_os(os, fmt, args...) warn(STRFMT" "fmt, STRID(os) , ##args) 44 | #define err_os(os, fmt, args...) err(STRFMT" "fmt, STRID(os) , ##args) 45 | #define warn_ose(os, err, fmt, args...) \ 46 | warn_e(err, STRFMT" "fmt, STRID(os) , ##args) 47 | #define err_ose(os, err, fmt, args...) \ 48 | err_e(err, STRFMT" "fmt, STRID(os) , ##args) 49 | 50 | enum { 51 | SNDRV_OSS_VERSION = ((3<<16)|(8<<8)|(1<<4)|(0)), /* 3.8.1a */ 52 | DFL_MIXER_MAJOR = 14, 53 | DFL_MIXER_MINOR = 0, 54 | DFL_DSP_MAJOR = 14, 55 | DFL_DSP_MINOR = 3, 56 | DFL_ADSP_MAJOR = 14, 57 | DFL_ADSP_MINOR = 12, 58 | DFL_MAX_STREAMS = 128, 59 | MIXER_PUT_DELAY = 600, /* 10 mins */ 60 | /* DSPS_MMAP_SIZE / 2 must be multiple of SHMLBA */ 61 | DSPS_MMAP_SIZE = 2 * (512 << 10), /* 512k for each dir */ 62 | }; 63 | 64 | struct ossp_uid_cnt { 65 | struct list_head link; 66 | uid_t uid; 67 | unsigned nr_os; 68 | }; 69 | 70 | struct ossp_mixer { 71 | pid_t pgrp; 72 | struct list_head link; 73 | struct list_head delayed_put_link; 74 | unsigned refcnt; 75 | /* the following two fields are protected by mixer_mutex */ 76 | int vol[2][2]; 77 | int modify_counter; 78 | time_t put_expires; 79 | }; 80 | 81 | struct ossp_mixer_cmd { 82 | struct ossp_mixer *mixer; 83 | struct ossp_mixer_arg set; 84 | int out_dir; 85 | int rvol; 86 | }; 87 | 88 | #define for_each_vol(i, j) \ 89 | for (i = 0, j = 0; i < 2; j += i << 1, j++, i = j >> 1, j &= 1) 90 | 91 | struct ossp_stream { 92 | unsigned id; /* stream ID */ 93 | struct list_head link; 94 | struct list_head pgrp_link; 95 | struct list_head notify_link; 96 | unsigned refcnt; 97 | pthread_mutex_t cmd_mutex; 98 | pthread_mutex_t mmap_mutex; 99 | struct fuse_pollhandle *ph; 100 | 101 | /* stream owner info */ 102 | pid_t pid; 103 | pid_t pgrp; 104 | uid_t uid; 105 | gid_t gid; 106 | 107 | /* slave info */ 108 | pid_t slave_pid; 109 | int cmd_fd; 110 | int notify_tx; 111 | int notify_rx; 112 | 113 | /* the following dead flag is set asynchronously, keep it separate. */ 114 | int dead; 115 | 116 | /* stream mixer state, protected by mixer_mutex */ 117 | int mixer_pending; 118 | int vol[2][2]; 119 | int vol_set[2][2]; 120 | 121 | int mmap_fd; 122 | size_t mmap_size; 123 | void *mmap; 124 | void *mmap_addr[2]; 125 | struct ossp_transfer *mmap_transfer; 126 | 127 | struct ossp_uid_cnt *ucnt; 128 | struct fuse_session *se; /* associated fuse session */ 129 | struct ossp_mixer *mixer; 130 | }; 131 | 132 | struct ossp_dsp_stream { 133 | struct ossp_stream os; 134 | unsigned rw; 135 | unsigned mmapped; 136 | int nonblock; 137 | }; 138 | 139 | #define os_to_dsps(_os) container_of(_os, struct ossp_dsp_stream, os) 140 | 141 | static unsigned max_streams; 142 | static unsigned umax_streams; 143 | static unsigned hashtbl_size; 144 | static char dsp_slave_path[PATH_MAX]; 145 | 146 | static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 147 | static pthread_mutex_t mixer_mutex = PTHREAD_MUTEX_INITIALIZER; 148 | static unsigned long *os_id_bitmap; 149 | static unsigned nr_mixers; 150 | static struct list_head *mixer_tbl; /* indexed by PGRP */ 151 | static struct list_head *os_tbl; /* indexed by ID */ 152 | static struct list_head *os_pgrp_tbl; /* indexed by PGRP */ 153 | static struct list_head *os_notify_tbl; /* indexed by notify fd */ 154 | static LIST_HEAD(uid_cnt_list); 155 | static int notify_epfd; /* epoll used to monitor notify fds */ 156 | static pthread_t notify_poller_thread; 157 | static pthread_t slave_reaper_thread; 158 | static pthread_t mixer_delayed_put_thread; 159 | static pthread_t cuse_mixer_thread; 160 | static pthread_t cuse_adsp_thread; 161 | static pthread_cond_t notify_poller_kill_wait = PTHREAD_COND_INITIALIZER; 162 | static pthread_cond_t slave_reaper_wait = PTHREAD_COND_INITIALIZER; 163 | static LIST_HEAD(slave_corpse_list); 164 | static LIST_HEAD(mixer_delayed_put_head); /* delayed reference */ 165 | static pthread_cond_t mixer_delayed_put_cond = PTHREAD_COND_INITIALIZER; 166 | 167 | static int init_wait_fd = -1; 168 | static int exit_on_idle; 169 | static struct fuse_session *mixer_se; 170 | static struct fuse_session *dsp_se; 171 | static struct fuse_session *adsp_se; 172 | 173 | static void put_os(struct ossp_stream *os); 174 | 175 | 176 | /*************************************************************************** 177 | * Accessors 178 | */ 179 | 180 | static struct list_head *mixer_tbl_head(pid_t pid) 181 | { 182 | return &mixer_tbl[pid % hashtbl_size]; 183 | } 184 | 185 | static struct list_head *os_tbl_head(uint64_t id) 186 | { 187 | return &os_tbl[id % hashtbl_size]; 188 | } 189 | 190 | static struct list_head *os_pgrp_tbl_head(pid_t pgrp) 191 | { 192 | return &os_pgrp_tbl[pgrp % hashtbl_size]; 193 | } 194 | 195 | static struct list_head *os_notify_tbl_head(int notify_rx) 196 | { 197 | return &os_notify_tbl[notify_rx % hashtbl_size]; 198 | } 199 | 200 | static struct ossp_mixer *find_mixer_locked(pid_t pgrp) 201 | { 202 | struct ossp_mixer *mixer; 203 | 204 | list_for_each_entry(mixer, mixer_tbl_head(pgrp), link) 205 | if (mixer->pgrp == pgrp) 206 | return mixer; 207 | return NULL; 208 | } 209 | 210 | static struct ossp_mixer *find_mixer(pid_t pgrp) 211 | { 212 | struct ossp_mixer *mixer; 213 | 214 | pthread_mutex_lock(&mutex); 215 | mixer = find_mixer_locked(pgrp); 216 | pthread_mutex_unlock(&mutex); 217 | return mixer; 218 | } 219 | 220 | static struct ossp_stream *find_os(unsigned id) 221 | { 222 | struct ossp_stream *os, *found = NULL; 223 | 224 | pthread_mutex_lock(&mutex); 225 | list_for_each_entry(os, os_tbl_head(id), link) 226 | if (os->id == id) { 227 | found = os; 228 | break; 229 | } 230 | pthread_mutex_unlock(&mutex); 231 | return found; 232 | } 233 | 234 | static struct ossp_stream *find_os_by_notify_rx(int notify_rx) 235 | { 236 | struct ossp_stream *os, *found = NULL; 237 | 238 | pthread_mutex_lock(&mutex); 239 | list_for_each_entry(os, os_notify_tbl_head(notify_rx), notify_link) 240 | if (os->notify_rx == notify_rx) { 241 | found = os; 242 | break; 243 | } 244 | pthread_mutex_unlock(&mutex); 245 | return found; 246 | } 247 | 248 | 249 | /*************************************************************************** 250 | * Command and ioctl helpers 251 | */ 252 | 253 | static ssize_t exec_cmd_intern(struct ossp_stream *os, enum ossp_opcode opcode, 254 | const void *carg, size_t carg_size, const void *din, size_t din_size, 255 | void *rarg, size_t rarg_size, void *dout, size_t *dout_sizep, int fd) 256 | { 257 | size_t dout_size = dout_sizep ? *dout_sizep : 0; 258 | struct ossp_cmd cmd = { .magic = OSSP_CMD_MAGIC, .opcode = opcode, 259 | .din_size = din_size, 260 | .dout_size = dout_size }; 261 | struct iovec iov = { &cmd, sizeof(cmd) }; 262 | struct msghdr msg = { .msg_iov = &iov, .msg_iovlen = 1 }; 263 | struct ossp_reply reply = { }; 264 | char cmsg_buf[CMSG_SPACE(sizeof(fd))]; 265 | char reason[512]; 266 | int rc; 267 | 268 | if (os->dead) 269 | return -EIO; 270 | 271 | dbg1_os(os, "%s carg=%zu din=%zu rarg=%zu dout=%zu", 272 | ossp_cmd_str[opcode], carg_size, din_size, rarg_size, 273 | dout_size); 274 | 275 | if (fd >= 0) { 276 | struct cmsghdr *cmsg; 277 | 278 | msg.msg_control = cmsg_buf; 279 | msg.msg_controllen = sizeof(cmsg_buf); 280 | cmsg = CMSG_FIRSTHDR(&msg); 281 | cmsg->cmsg_level = SOL_SOCKET; 282 | cmsg->cmsg_type = SCM_RIGHTS; 283 | cmsg->cmsg_len = CMSG_LEN(sizeof(fd)); 284 | *(int *)CMSG_DATA(cmsg) = fd; 285 | msg.msg_controllen = cmsg->cmsg_len; 286 | } 287 | 288 | if (sendmsg(os->cmd_fd, &msg, 0) <= 0) { 289 | rc = -errno; 290 | snprintf(reason, sizeof(reason), "command sendmsg failed: %s", 291 | strerror(-rc)); 292 | goto fail; 293 | } 294 | 295 | if ((rc = write_fill(os->cmd_fd, carg, carg_size)) < 0 || 296 | (rc = write_fill(os->cmd_fd, din, din_size)) < 0) { 297 | snprintf(reason, sizeof(reason), 298 | "can't tranfer command argument and/or data: %s", 299 | strerror(-rc)); 300 | goto fail; 301 | } 302 | if ((rc = read_fill(os->cmd_fd, &reply, sizeof(reply))) < 0) { 303 | snprintf(reason, sizeof(reason), "can't read reply: %s", 304 | strerror(-rc)); 305 | goto fail; 306 | } 307 | 308 | if (reply.magic != OSSP_REPLY_MAGIC) { 309 | snprintf(reason, sizeof(reason), 310 | "reply magic mismatch %x != %x", 311 | reply.magic, OSSP_REPLY_MAGIC); 312 | rc = -EINVAL; 313 | goto fail; 314 | } 315 | 316 | if (reply.result < 0) 317 | goto out_unlock; 318 | 319 | if (reply.dout_size > dout_size) { 320 | snprintf(reason, sizeof(reason), 321 | "data out size overflow %zu > %zu", 322 | reply.dout_size, dout_size); 323 | rc = -EINVAL; 324 | goto fail; 325 | } 326 | 327 | dout_size = reply.dout_size; 328 | if (dout_sizep) 329 | *dout_sizep = dout_size; 330 | 331 | if ((rc = read_fill(os->cmd_fd, rarg, rarg_size)) < 0 || 332 | (rc = read_fill(os->cmd_fd, dout, dout_size)) < 0) { 333 | snprintf(reason, sizeof(reason), "can't read data out: %s", 334 | strerror(-rc)); 335 | goto fail; 336 | } 337 | 338 | out_unlock: 339 | dbg1_os(os, " completed, result=%d dout=%zu", 340 | reply.result, dout_size); 341 | return reply.result; 342 | 343 | fail: 344 | warn_os(os, "communication with slave failed (%s)", reason); 345 | os->dead = 1; 346 | return rc; 347 | } 348 | 349 | static ssize_t exec_cmd(struct ossp_stream *os, enum ossp_opcode opcode, 350 | const void *carg, size_t carg_size, const void *din, size_t din_size, 351 | void *rarg, size_t rarg_size, void *dout, size_t *dout_sizep, int fd) 352 | { 353 | int is_mixer; 354 | int i, j; 355 | ssize_t ret, mret; 356 | 357 | /* mixer command is handled exlicitly below */ 358 | is_mixer = opcode == OSSP_MIXER; 359 | if (is_mixer) { 360 | ret = -pthread_mutex_trylock(&os->cmd_mutex); 361 | if (ret) 362 | return ret; 363 | } else { 364 | pthread_mutex_lock(&os->cmd_mutex); 365 | 366 | ret = exec_cmd_intern(os, opcode, carg, carg_size, 367 | din, din_size, rarg, rarg_size, 368 | dout, dout_sizep, fd); 369 | } 370 | 371 | /* lazy mixer handling */ 372 | pthread_mutex_lock(&mixer_mutex); 373 | 374 | if (os->mixer_pending) { 375 | struct ossp_mixer_arg marg; 376 | repeat_mixer: 377 | /* we have mixer command pending */ 378 | memcpy(marg.vol, os->vol_set, sizeof(os->vol_set)); 379 | memset(os->vol_set, -1, sizeof(os->vol_set)); 380 | 381 | pthread_mutex_unlock(&mixer_mutex); 382 | mret = exec_cmd_intern(os, OSSP_MIXER, &marg, sizeof(marg), 383 | NULL, 0, &marg, sizeof(marg), NULL, NULL, 384 | -1); 385 | pthread_mutex_lock(&mixer_mutex); 386 | 387 | /* was there mixer set request while executing mixer command? */ 388 | for_each_vol(i, j) 389 | if (os->vol_set[i][j] >= 0) 390 | goto repeat_mixer; 391 | 392 | /* update internal mixer state */ 393 | if (mret == 0) { 394 | for_each_vol(i, j) { 395 | if (marg.vol[i][j] >= 0) { 396 | if (os->vol[i][j] != marg.vol[i][j]) 397 | os->mixer->modify_counter++; 398 | os->vol[i][j] = marg.vol[i][j]; 399 | } 400 | } 401 | } 402 | os->mixer_pending = 0; 403 | } 404 | 405 | pthread_mutex_unlock(&os->cmd_mutex); 406 | 407 | /* 408 | * mixer mutex must be released after cmd_mutex so that 409 | * exec_mixer_cmd() can guarantee that mixer_pending flags 410 | * will be handled immediately or when the currently 411 | * in-progress command completes. 412 | */ 413 | pthread_mutex_unlock(&mixer_mutex); 414 | 415 | return is_mixer ? mret : ret; 416 | } 417 | 418 | static ssize_t exec_simple_cmd(struct ossp_stream *os, 419 | enum ossp_opcode opcode, void *carg, void *rarg) 420 | { 421 | return exec_cmd(os, opcode, 422 | carg, ossp_arg_sizes[opcode].carg_size, NULL, 0, 423 | rarg, ossp_arg_sizes[opcode].rarg_size, NULL, NULL, -1); 424 | } 425 | 426 | static int ioctl_prep_uarg(fuse_req_t req, void *in, size_t in_sz, void *out, 427 | size_t out_sz, void *uarg, const void *in_buf, 428 | size_t in_bufsz, size_t out_bufsz) 429 | { 430 | struct iovec in_iov = { }, out_iov = { }; 431 | int retry = 0; 432 | 433 | if (in) { 434 | if (!in_bufsz) { 435 | in_iov.iov_base = uarg; 436 | in_iov.iov_len = in_sz; 437 | retry = 1; 438 | } else { 439 | assert(in_bufsz == in_sz); 440 | memcpy(in, in_buf, in_sz); 441 | } 442 | } 443 | 444 | if (out) { 445 | if (!out_bufsz) { 446 | out_iov.iov_base = uarg; 447 | out_iov.iov_len = out_sz; 448 | retry = 1; 449 | } else 450 | assert(out_bufsz == out_sz); 451 | } 452 | 453 | if (retry) 454 | fuse_reply_ioctl_retry(req, &in_iov, 1, &out_iov, 1); 455 | 456 | return retry; 457 | } 458 | 459 | #define PREP_UARG(inp, outp) do { \ 460 | if (ioctl_prep_uarg(req, (inp), sizeof(*(inp)), \ 461 | (outp), sizeof(*(outp)), uarg, \ 462 | in_buf, in_bufsz, out_bufsz)) \ 463 | return; \ 464 | } while (0) 465 | 466 | /*************************************************************************** 467 | * Mixer implementation 468 | */ 469 | 470 | static void put_mixer_real(struct ossp_mixer *mixer) 471 | { 472 | if (!--mixer->refcnt) { 473 | dbg0("DESTROY mixer(%d)", mixer->pgrp); 474 | list_del_init(&mixer->link); 475 | list_del_init(&mixer->delayed_put_link); 476 | free(mixer); 477 | nr_mixers--; 478 | 479 | /* 480 | * If exit_on_idle, mixer for pgrp0 is touched during 481 | * init and each stream has mixer attached. As mixers 482 | * are destroyed after they have been idle for 483 | * MIXER_PUT_DELAY seconds, we can use it for idle 484 | * detection. Note that this might race with 485 | * concurrent open. The race is inherent. 486 | */ 487 | if (exit_on_idle && !nr_mixers) { 488 | info("idle, exiting"); 489 | exit(0); 490 | } 491 | } 492 | } 493 | 494 | static struct ossp_mixer *get_mixer(pid_t pgrp) 495 | { 496 | struct ossp_mixer *mixer; 497 | 498 | pthread_mutex_lock(&mutex); 499 | 500 | /* is there a matching one? */ 501 | mixer = find_mixer_locked(pgrp); 502 | if (mixer) { 503 | if (list_empty(&mixer->delayed_put_link)) 504 | mixer->refcnt++; 505 | else 506 | list_del_init(&mixer->delayed_put_link); 507 | goto out_unlock; 508 | } 509 | 510 | /* reap delayed put list if there are too many mixers */ 511 | while (nr_mixers > 2 * max_streams && 512 | !list_empty(&mixer_delayed_put_head)) { 513 | struct ossp_mixer *mixer = 514 | list_first_entry(&mixer_delayed_put_head, 515 | struct ossp_mixer, delayed_put_link); 516 | 517 | assert(mixer->refcnt == 1); 518 | put_mixer_real(mixer); 519 | } 520 | 521 | /* create a new one */ 522 | mixer = calloc(1, sizeof(*mixer)); 523 | if (!mixer) { 524 | warn("failed to allocate mixer for %d", pgrp); 525 | mixer = NULL; 526 | goto out_unlock; 527 | } 528 | 529 | mixer->pgrp = pgrp; 530 | INIT_LIST_HEAD(&mixer->link); 531 | INIT_LIST_HEAD(&mixer->delayed_put_link); 532 | mixer->refcnt = 1; 533 | memset(mixer->vol, -1, sizeof(mixer->vol)); 534 | 535 | list_add(&mixer->link, mixer_tbl_head(pgrp)); 536 | nr_mixers++; 537 | dbg0("CREATE mixer(%d)", pgrp); 538 | 539 | out_unlock: 540 | pthread_mutex_unlock(&mutex); 541 | return mixer; 542 | } 543 | 544 | static void put_mixer(struct ossp_mixer *mixer) 545 | { 546 | pthread_mutex_lock(&mutex); 547 | 548 | if (mixer) { 549 | if (mixer->refcnt == 1) { 550 | struct timespec ts; 551 | 552 | clock_gettime(CLOCK_REALTIME, &ts); 553 | mixer->put_expires = ts.tv_sec + MIXER_PUT_DELAY; 554 | list_add_tail(&mixer->delayed_put_link, 555 | &mixer_delayed_put_head); 556 | pthread_cond_signal(&mixer_delayed_put_cond); 557 | } else 558 | put_mixer_real(mixer); 559 | } 560 | 561 | pthread_mutex_unlock(&mutex); 562 | } 563 | 564 | static void *mixer_delayed_put_worker(void *arg) 565 | { 566 | struct ossp_mixer *mixer; 567 | struct timespec ts; 568 | time_t now; 569 | 570 | pthread_mutex_lock(&mutex); 571 | again: 572 | clock_gettime(CLOCK_REALTIME, &ts); 573 | now = ts.tv_sec; 574 | 575 | mixer = NULL; 576 | while (!list_empty(&mixer_delayed_put_head)) { 577 | mixer = list_first_entry(&mixer_delayed_put_head, 578 | struct ossp_mixer, delayed_put_link); 579 | 580 | if (now <= mixer->put_expires) 581 | break; 582 | 583 | assert(mixer->refcnt == 1); 584 | put_mixer_real(mixer); 585 | mixer = NULL; 586 | } 587 | 588 | if (mixer) { 589 | ts.tv_sec = mixer->put_expires + 1; 590 | pthread_cond_timedwait(&mixer_delayed_put_cond, &mutex, &ts); 591 | } else 592 | pthread_cond_wait(&mixer_delayed_put_cond, &mutex); 593 | 594 | goto again; 595 | } 596 | 597 | static void init_mixer_cmd(struct ossp_mixer_cmd *mxcmd, 598 | struct ossp_mixer *mixer) 599 | { 600 | memset(mxcmd, 0, sizeof(*mxcmd)); 601 | memset(&mxcmd->set.vol, -1, sizeof(mxcmd->set.vol)); 602 | mxcmd->mixer = mixer; 603 | mxcmd->out_dir = -1; 604 | } 605 | 606 | static int exec_mixer_cmd(struct ossp_mixer_cmd *mxcmd, struct ossp_stream *os) 607 | { 608 | int i, j, rc; 609 | 610 | /* 611 | * Set pending flags before trying to execute mixer command. 612 | * Combined with lock release order in exec_cmd(), this 613 | * guarantees that the mixer command will be executed 614 | * immediately or when the current command completes. 615 | */ 616 | pthread_mutex_lock(&mixer_mutex); 617 | os->mixer_pending = 1; 618 | for_each_vol(i, j) 619 | if (mxcmd->set.vol[i][j] >= 0) 620 | os->vol_set[i][j] = mxcmd->set.vol[i][j]; 621 | pthread_mutex_unlock(&mixer_mutex); 622 | 623 | rc = exec_simple_cmd(os, OSSP_MIXER, NULL, NULL); 624 | if (rc >= 0) { 625 | dbg0_os(os, "volume set=%d/%d:%d/%d get=%d/%d:%d/%d", 626 | mxcmd->set.vol[PLAY][LEFT], mxcmd->set.vol[PLAY][RIGHT], 627 | mxcmd->set.vol[REC][LEFT], mxcmd->set.vol[REC][RIGHT], 628 | os->vol[PLAY][LEFT], os->vol[PLAY][RIGHT], 629 | os->vol[REC][LEFT], os->vol[REC][RIGHT]); 630 | } else if (rc != -EBUSY) 631 | warn_ose(os, rc, "mixer command failed"); 632 | 633 | return rc; 634 | } 635 | 636 | static void finish_mixer_cmd(struct ossp_mixer_cmd *mxcmd) 637 | { 638 | struct ossp_mixer *mixer = mxcmd->mixer; 639 | struct ossp_stream *os; 640 | int dir = mxcmd->out_dir; 641 | int vol[2][2] = { }; 642 | int cnt[2][2] = { }; 643 | int i, j; 644 | 645 | pthread_mutex_lock(&mixer_mutex); 646 | 647 | /* get volume of all streams attached to this mixer */ 648 | pthread_mutex_lock(&mutex); 649 | list_for_each_entry(os, os_pgrp_tbl_head(mixer->pgrp), pgrp_link) { 650 | if (os->pgrp != mixer->pgrp) 651 | continue; 652 | for_each_vol(i, j) { 653 | if (os->vol[i][j] < 0) 654 | continue; 655 | vol[i][j] += os->vol[i][j]; 656 | cnt[i][j]++; 657 | } 658 | } 659 | pthread_mutex_unlock(&mutex); 660 | 661 | /* calculate the summary volume values */ 662 | for_each_vol(i, j) { 663 | if (mxcmd->set.vol[i][j] >= 0) 664 | vol[i][j] = mxcmd->set.vol[i][j]; 665 | else if (cnt[i][j]) 666 | vol[i][j] = vol[i][j] / cnt[i][j]; 667 | else if (mixer->vol[i][j] >= 0) 668 | vol[i][j] = mixer->vol[i][j]; 669 | else 670 | vol[i][j] = 100; 671 | 672 | vol[i][j] = min(max(0, vol[i][j]), 100); 673 | } 674 | 675 | if (dir >= 0) 676 | mxcmd->rvol = vol[dir][LEFT] | (vol[dir][RIGHT] << 8); 677 | 678 | pthread_mutex_unlock(&mixer_mutex); 679 | } 680 | 681 | static void mixer_simple_ioctl(fuse_req_t req, struct ossp_mixer *mixer, 682 | unsigned cmd, void *uarg, const void *in_buf, 683 | size_t in_bufsz, size_t out_bufsz, 684 | int *not_minep) 685 | { 686 | const char *id = "OSS Proxy", *name = "Mixer"; 687 | int i; 688 | 689 | switch (cmd) { 690 | case SOUND_MIXER_INFO: { 691 | struct mixer_info info = { }; 692 | 693 | PREP_UARG(NULL, &info); 694 | strncpy(info.id, id, sizeof(info.id) - 1); 695 | strncpy(info.name, name, sizeof(info.name) - 1); 696 | info.modify_counter = mixer->modify_counter; 697 | fuse_reply_ioctl(req, 0, &info, sizeof(info)); 698 | break; 699 | } 700 | 701 | case SOUND_OLD_MIXER_INFO: { 702 | struct _old_mixer_info info = { }; 703 | 704 | PREP_UARG(NULL, &info); 705 | strncpy(info.id, id, sizeof(info.id) - 1); 706 | strncpy(info.name, name, sizeof(info.name) - 1); 707 | fuse_reply_ioctl(req, 0, &info, sizeof(info)); 708 | break; 709 | } 710 | 711 | case OSS_GETVERSION: 712 | i = SNDRV_OSS_VERSION; 713 | goto puti; 714 | case SOUND_MIXER_READ_DEVMASK: 715 | case SOUND_MIXER_READ_STEREODEVS: 716 | i = SOUND_MASK_PCM | SOUND_MASK_IGAIN; 717 | goto puti; 718 | case SOUND_MIXER_READ_CAPS: 719 | i = SOUND_CAP_EXCL_INPUT; 720 | goto puti; 721 | case SOUND_MIXER_READ_RECMASK: 722 | case SOUND_MIXER_READ_RECSRC: 723 | i = SOUND_MASK_IGAIN; 724 | goto puti; 725 | puti: 726 | PREP_UARG(NULL, &i); 727 | fuse_reply_ioctl(req, 0, &i, sizeof(i)); 728 | break; 729 | 730 | case SOUND_MIXER_WRITE_RECSRC: 731 | fuse_reply_ioctl(req, 0, NULL, 0); 732 | break; 733 | 734 | default: 735 | *not_minep = 1; 736 | } 737 | } 738 | 739 | static void mixer_do_ioctl(fuse_req_t req, struct ossp_mixer *mixer, 740 | unsigned cmd, void *uarg, const void *in_buf, 741 | size_t in_bufsz, size_t out_bufsz) 742 | { 743 | struct ossp_mixer_cmd mxcmd; 744 | struct ossp_stream *os, **osa; 745 | int not_mine = 0; 746 | int slot = cmd & 0xff, dir; 747 | int nr_os; 748 | int i, rc; 749 | 750 | mixer_simple_ioctl(req, mixer, cmd, uarg, in_buf, in_bufsz, out_bufsz, 751 | ¬_mine); 752 | if (!not_mine) 753 | return; 754 | 755 | rc = -ENXIO; 756 | if (!(cmd & (SIOC_IN | SIOC_OUT))) 757 | goto err; 758 | 759 | /* 760 | * Okay, it's not one of the easy ones. Build mxcmd for 761 | * actual volume control. 762 | */ 763 | if (cmd & SIOC_IN) 764 | PREP_UARG(&i, &i); 765 | else 766 | PREP_UARG(NULL, &i); 767 | 768 | switch (slot) { 769 | case SOUND_MIXER_PCM: 770 | dir = PLAY; 771 | break; 772 | case SOUND_MIXER_IGAIN: 773 | dir = REC; 774 | break; 775 | default: 776 | i = 0; 777 | fuse_reply_ioctl(req, 0, &i, sizeof(i)); 778 | return; 779 | } 780 | 781 | init_mixer_cmd(&mxcmd, mixer); 782 | 783 | if (cmd & SIOC_IN) { 784 | unsigned l, r; 785 | 786 | rc = -EINVAL; 787 | l = i & 0xff; 788 | r = (i >> 8) & 0xff; 789 | if (l > 100 || r > 100) 790 | goto err; 791 | 792 | mixer->vol[dir][LEFT] = mxcmd.set.vol[dir][LEFT] = l; 793 | mixer->vol[dir][RIGHT] = mxcmd.set.vol[dir][RIGHT] = r; 794 | } 795 | mxcmd.out_dir = dir; 796 | 797 | /* 798 | * Apply volume conrol 799 | */ 800 | /* acquire target streams */ 801 | pthread_mutex_lock(&mutex); 802 | osa = calloc(max_streams, sizeof(osa[0])); 803 | if (!osa) { 804 | pthread_mutex_unlock(&mutex); 805 | rc = -ENOMEM; 806 | goto err; 807 | } 808 | 809 | nr_os = 0; 810 | list_for_each_entry(os, os_pgrp_tbl_head(mixer->pgrp), pgrp_link) { 811 | if (os->pgrp == mixer->pgrp) { 812 | osa[nr_os++] = os; 813 | os->refcnt++; 814 | } 815 | } 816 | 817 | pthread_mutex_unlock(&mutex); 818 | 819 | /* execute mxcmd for each stream and put it */ 820 | for (i = 0; i < nr_os; i++) { 821 | exec_mixer_cmd(&mxcmd, osa[i]); 822 | put_os(osa[i]); 823 | } 824 | 825 | finish_mixer_cmd(&mxcmd); 826 | free(osa); 827 | 828 | if (out_bufsz) 829 | fuse_reply_ioctl(req, 0, &mxcmd.rvol, sizeof(mxcmd.rvol)); 830 | else 831 | fuse_reply_ioctl(req, 0, NULL, 0); 832 | 833 | return; 834 | 835 | err: 836 | fuse_reply_err(req, -rc); 837 | } 838 | 839 | static void mixer_open(fuse_req_t req, struct fuse_file_info *fi) 840 | { 841 | pid_t pid = fuse_req_ctx(req)->pid, pgrp; 842 | struct ossp_mixer *mixer; 843 | int rc; 844 | 845 | rc = get_proc_self_info(pid, &pgrp, NULL, 0); 846 | if (rc) { 847 | err_e(rc, "get_proc_self_info(%d) failed", pid); 848 | fuse_reply_err(req, -rc); 849 | return; 850 | } 851 | 852 | mixer = get_mixer(pgrp); 853 | fi->fh = pgrp; 854 | 855 | if (mixer) 856 | fuse_reply_open(req, fi); 857 | else 858 | fuse_reply_err(req, ENOMEM); 859 | } 860 | 861 | static void mixer_ioctl(fuse_req_t req, int signed_cmd, void *uarg, 862 | struct fuse_file_info *fi, unsigned int flags, 863 | const void *in_buf, size_t in_bufsz, size_t out_bufsz) 864 | { 865 | struct ossp_mixer *mixer; 866 | 867 | mixer = find_mixer(fi->fh); 868 | if (!mixer) { 869 | fuse_reply_err(req, EBADF); 870 | return; 871 | } 872 | 873 | mixer_do_ioctl(req, mixer, signed_cmd, uarg, in_buf, in_bufsz, 874 | out_bufsz); 875 | } 876 | 877 | static void mixer_release(fuse_req_t req, struct fuse_file_info *fi) 878 | { 879 | struct ossp_mixer *mixer; 880 | 881 | mixer = find_mixer(fi->fh); 882 | if (mixer) { 883 | put_mixer(mixer); 884 | fuse_reply_err(req, 0); 885 | } else 886 | fuse_reply_err(req, EBADF); 887 | } 888 | 889 | 890 | /*************************************************************************** 891 | * Stream implementation 892 | */ 893 | 894 | static int os_create_shared_memory(struct ossp_stream *os, size_t mmap_size) 895 | { 896 | int rc = 0; 897 | 898 | os->mmap_fd = -1; 899 | os->mmap_size = 0; 900 | 901 | #ifdef OSSP_MMAP 902 | if (mmap_size) { 903 | char shmname[32]; 904 | int fd; 905 | void *p; 906 | 907 | sprintf(shmname, "/ossp.%i", getpid()); 908 | fd = shm_open(shmname, O_RDWR | O_CREAT | O_EXCL | O_TRUNC, 909 | 0600); 910 | 911 | if (fd == -1) { 912 | rc = -errno; 913 | warn_ose(os, rc, "failed to open shared memory"); 914 | return rc; 915 | } 916 | rc = shm_unlink(shmname); 917 | if (rc == -1) { 918 | rc = -errno; 919 | close(fd); 920 | warn_ose(os, rc, "failed to unlink shared memory"); 921 | return rc; 922 | } 923 | rc = ftruncate(fd, mmap_size + 2*sizeof(struct ossp_transfer)); 924 | if (rc == -1) { 925 | rc = -errno; 926 | close(fd); 927 | warn_ose(os, rc, "failed to set shared memory size"); 928 | return rc; 929 | } 930 | rc = fcntl(fd, F_SETFD, 0); /* reset cloexec */ 931 | if (rc == -1) { 932 | rc = -errno; 933 | close(fd); 934 | warn_ose(os, rc, "failed to reset close-on-exec for shared memory"); 935 | return rc; 936 | } 937 | p = mmap(NULL, mmap_size + 2 * sizeof(struct ossp_transfer), 938 | PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 939 | if (p == MAP_FAILED) { 940 | rc = -errno; 941 | close(fd); 942 | warn_ose(os, rc, "failed to mmap shared memory"); 943 | return rc; 944 | } 945 | os->mmap_addr[PLAY] = p; 946 | os->mmap_addr[REC] = p + mmap_size / 2; 947 | os->mmap_transfer = p + mmap_size; 948 | 949 | if (sem_init(&os->mmap_transfer[PLAY].sem, 1, 0) == -1 || 950 | sem_init(&os->mmap_transfer[REC].sem, 1, 0) == -1) { 951 | rc = -errno; 952 | close(fd); 953 | munmap(p, mmap_size + 2 * sizeof(struct ossp_transfer)); 954 | warn_ose(os, rc, "failed to init shared semaphore"); 955 | return rc; 956 | } 957 | 958 | os->mmap = p; 959 | os->mmap_size = mmap_size; 960 | os->mmap_fd = fd; 961 | } 962 | #endif 963 | 964 | return rc; 965 | } 966 | 967 | static int alloc_os(size_t stream_size, size_t mmap_size, pid_t pid, uid_t pgrp, 968 | uid_t uid, gid_t gid, int cmd_sock, 969 | const int *notify, struct fuse_session *se, 970 | struct ossp_stream **osp) 971 | { 972 | struct ossp_uid_cnt *tmp_ucnt, *ucnt = NULL; 973 | struct ossp_stream *os; 974 | int rc; 975 | 976 | assert(stream_size >= sizeof(struct ossp_stream)); 977 | os = calloc(1, stream_size); 978 | if (!os) 979 | return -ENOMEM; 980 | 981 | INIT_LIST_HEAD(&os->link); 982 | INIT_LIST_HEAD(&os->pgrp_link); 983 | INIT_LIST_HEAD(&os->notify_link); 984 | os->refcnt = 1; 985 | 986 | rc = -pthread_mutex_init(&os->cmd_mutex, NULL); 987 | if (rc) 988 | goto err_free; 989 | 990 | rc = -pthread_mutex_init(&os->mmap_mutex, NULL); 991 | if (rc) 992 | goto err_destroy_cmd_mutex; 993 | 994 | pthread_mutex_lock(&mutex); 995 | 996 | list_for_each_entry(tmp_ucnt, &uid_cnt_list, link) 997 | if (tmp_ucnt->uid == uid) { 998 | ucnt = tmp_ucnt; 999 | break; 1000 | } 1001 | if (!ucnt) { 1002 | rc = -ENOMEM; 1003 | ucnt = calloc(1, sizeof(*ucnt)); 1004 | if (!ucnt) 1005 | goto err_unlock; 1006 | ucnt->uid = uid; 1007 | list_add(&ucnt->link, &uid_cnt_list); 1008 | } 1009 | 1010 | rc = -EBUSY; 1011 | if (ucnt->nr_os + 1 > umax_streams) 1012 | goto err_unlock; 1013 | 1014 | /* everything looks fine, allocate id and init stream */ 1015 | rc = -EBUSY; 1016 | os->id = find_next_zero_bit(os_id_bitmap, max_streams, 0); 1017 | if (os->id >= max_streams) 1018 | goto err_unlock; 1019 | __set_bit(os->id, os_id_bitmap); 1020 | 1021 | os->cmd_fd = cmd_sock; 1022 | os->notify_tx = notify[1]; 1023 | os->notify_rx = notify[0]; 1024 | os->pid = pid; 1025 | os->pgrp = pgrp; 1026 | os->uid = uid; 1027 | os->gid = gid; 1028 | os->ucnt = ucnt; 1029 | os->se = se; 1030 | 1031 | rc = os_create_shared_memory(os, mmap_size); 1032 | if (rc) 1033 | goto err_unlock; 1034 | 1035 | memset(os->vol, -1, sizeof(os->vol)); 1036 | memset(os->vol_set, -1, sizeof(os->vol)); 1037 | 1038 | list_add(&os->link, os_tbl_head(os->id)); 1039 | list_add(&os->pgrp_link, os_pgrp_tbl_head(os->pgrp)); 1040 | 1041 | ucnt->nr_os++; 1042 | *osp = os; 1043 | pthread_mutex_unlock(&mutex); 1044 | return 0; 1045 | 1046 | err_unlock: 1047 | pthread_mutex_unlock(&mutex); 1048 | pthread_mutex_destroy(&os->mmap_mutex); 1049 | err_destroy_cmd_mutex: 1050 | pthread_mutex_destroy(&os->cmd_mutex); 1051 | err_free: 1052 | free(os); 1053 | return rc; 1054 | } 1055 | 1056 | static void shutdown_notification(struct ossp_stream *os) 1057 | { 1058 | struct ossp_notify obituary = { .magic = OSSP_NOTIFY_MAGIC, 1059 | .opcode = OSSP_NOTIFY_OBITUARY }; 1060 | ssize_t ret; 1061 | 1062 | /* 1063 | * Shutdown notification for this stream. We politely ask 1064 | * notify_poller to shut the receive side down to avoid racing 1065 | * with it. 1066 | */ 1067 | while (os->notify_rx >= 0) { 1068 | ret = write(os->notify_tx, &obituary, sizeof(obituary)); 1069 | if (ret <= 0) { 1070 | if (ret == 0) 1071 | warn_os(os, "unexpected EOF on notify_tx"); 1072 | else if (errno != EPIPE) 1073 | warn_ose(os, -errno, 1074 | "unexpected error on notify_tx"); 1075 | close(os->notify_rx); 1076 | os->notify_rx = -1; 1077 | break; 1078 | } 1079 | 1080 | if (ret != sizeof(obituary)) 1081 | warn_os(os, "short transfer on notify_tx"); 1082 | pthread_cond_wait(¬ify_poller_kill_wait, &mutex); 1083 | } 1084 | } 1085 | 1086 | static void put_os(struct ossp_stream *os) 1087 | { 1088 | if (!os) 1089 | return; 1090 | 1091 | pthread_mutex_lock(&mutex); 1092 | 1093 | assert(os->refcnt); 1094 | if (--os->refcnt) { 1095 | pthread_mutex_unlock(&mutex); 1096 | return; 1097 | } 1098 | 1099 | os->dead = 1; 1100 | shutdown_notification(os); 1101 | 1102 | dbg0_os(os, "DESTROY"); 1103 | 1104 | list_del_init(&os->link); 1105 | list_del_init(&os->pgrp_link); 1106 | list_del_init(&os->notify_link); 1107 | os->ucnt->nr_os--; 1108 | 1109 | pthread_mutex_unlock(&mutex); 1110 | 1111 | close(os->cmd_fd); 1112 | close(os->notify_tx); 1113 | put_mixer(os->mixer); 1114 | pthread_mutex_destroy(&os->cmd_mutex); 1115 | pthread_mutex_destroy(&os->mmap_mutex); 1116 | 1117 | pthread_mutex_lock(&mutex); 1118 | dbg1_os(os, "stream dead, requesting reaping"); 1119 | list_add_tail(&os->link, &slave_corpse_list); 1120 | pthread_cond_signal(&slave_reaper_wait); 1121 | pthread_mutex_unlock(&mutex); 1122 | } 1123 | 1124 | static void set_extra_env(const pid_t pid) 1125 | { 1126 | const char *target_vars[] = { 1127 | "DISPLAY=", // Display manager 1128 | "PULSE_", // PulseAudio 1129 | "XDG_RUNTIME_DIR=" // Audio servers 1130 | }; 1131 | 1132 | char *line = NULL; 1133 | FILE *file; 1134 | size_t size; 1135 | ssize_t len; 1136 | 1137 | char path[32]; 1138 | snprintf(path, sizeof(path), "/proc/%d/environ", pid); 1139 | 1140 | file = fopen(path, "r"); 1141 | if (!file) 1142 | return; 1143 | 1144 | while ((len = getdelim(&line, &size, '\0', file)) != -1) { 1145 | for (uint8_t i = 0; i < ARRAY_SIZE(target_vars); ++i) { 1146 | char *sign; 1147 | 1148 | if (strncmp(line, target_vars[i], strlen(target_vars[i])) != 0) 1149 | continue; 1150 | 1151 | if ((sign = strchr(line, '='))) { 1152 | *sign = '\0'; 1153 | setenv(line, sign + 1, 1); 1154 | } 1155 | } 1156 | } 1157 | 1158 | free(line); 1159 | fclose(file); 1160 | } 1161 | 1162 | static int create_os(const char *slave_path, 1163 | size_t stream_size, size_t mmap_size, 1164 | pid_t pid, pid_t pgrp, uid_t uid, gid_t gid, 1165 | struct fuse_session *se, struct ossp_stream **osp) 1166 | { 1167 | static pthread_mutex_t create_mutex = PTHREAD_MUTEX_INITIALIZER; 1168 | int cmd_sock[2] = { -1, -1 }; 1169 | int notify_sock[2] = { -1, -1 }; 1170 | struct ossp_stream *os = NULL; 1171 | struct epoll_event ev = { }; 1172 | int i, rc; 1173 | 1174 | /* 1175 | * Only one thread can be creating a stream. This is to avoid 1176 | * leaking unwanted fds into slaves. 1177 | */ 1178 | pthread_mutex_lock(&create_mutex); 1179 | 1180 | /* prepare communication channels */ 1181 | if (socketpair(AF_UNIX, SOCK_STREAM, 0, cmd_sock) || 1182 | socketpair(AF_UNIX, SOCK_STREAM, 0, notify_sock)) { 1183 | rc = -errno; 1184 | warn_e(rc, "failed to create slave command channel"); 1185 | goto close_all; 1186 | } 1187 | 1188 | if (fcntl(notify_sock[0], F_SETFL, O_NONBLOCK) < 0) { 1189 | rc = -errno; 1190 | warn_e(rc, "failed to set NONBLOCK on notify sock"); 1191 | goto close_all; 1192 | } 1193 | 1194 | /* 1195 | * Alloc stream which will be responsible for all server side 1196 | * resources from now on. 1197 | */ 1198 | rc = alloc_os(stream_size, mmap_size, pid, pgrp, uid, gid, cmd_sock[0], 1199 | notify_sock, se, &os); 1200 | if (rc) { 1201 | warn_e(rc, "failed to allocate stream for %d", pid); 1202 | goto close_all; 1203 | } 1204 | 1205 | rc = -ENOMEM; 1206 | os->mixer = get_mixer(pgrp); 1207 | if (!os->mixer) 1208 | goto put_os; 1209 | 1210 | /* 1211 | * Register notification. If successful, notify_poller has 1212 | * custody of notify_rx fd. 1213 | */ 1214 | pthread_mutex_lock(&mutex); 1215 | list_add(&os->notify_link, os_notify_tbl_head(os->notify_rx)); 1216 | pthread_mutex_unlock(&mutex); 1217 | 1218 | ev.events = EPOLLIN; 1219 | ev.data.fd = notify_sock[0]; 1220 | if (epoll_ctl(notify_epfd, EPOLL_CTL_ADD, notify_sock[0], &ev)) { 1221 | /* 1222 | * Without poller watching this notify sock, poller 1223 | * shutdown sequence in shutdown_notification() can't 1224 | * be used. Kill notification rx manually. 1225 | */ 1226 | rc = -errno; 1227 | warn_ose(os, rc, "failed to add notify epoll"); 1228 | close(os->notify_rx); 1229 | os->notify_rx = -1; 1230 | goto put_os; 1231 | } 1232 | 1233 | /* start slave */ 1234 | os->slave_pid = fork(); 1235 | if (os->slave_pid < 0) { 1236 | rc = -errno; 1237 | warn_ose(os, rc, "failed to fork slave"); 1238 | goto put_os; 1239 | } 1240 | 1241 | if (os->slave_pid == 0) { 1242 | /* child */ 1243 | char id_str[2][16], fd_str[3][16]; 1244 | char mmap_size_str[32]; 1245 | char log_str[16], slave_path_copy[PATH_MAX]; 1246 | char *argv[] = { slave_path_copy, "-u", id_str[0], 1247 | "-g", id_str[1], "-c", fd_str[0], 1248 | "-n", fd_str[1], "-m", fd_str[2], 1249 | "-s", mmap_size_str, 1250 | "-l", log_str, NULL, NULL }; 1251 | struct passwd *pwd; 1252 | 1253 | /* drop stuff we don't need */ 1254 | if (close(cmd_sock[0]) || close(notify_sock[0])) 1255 | fatal_e(-errno, "failed to close server pipe fds"); 1256 | 1257 | clearenv(); 1258 | pwd = getpwuid(os->uid); 1259 | if (pwd) { 1260 | setenv("LOGNAME", pwd->pw_name, 1); 1261 | setenv("USER", pwd->pw_name, 1); 1262 | setenv("HOME", pwd->pw_dir, 1); 1263 | } 1264 | /* Set extra environment variables from the caller */ 1265 | set_extra_env(pid); 1266 | 1267 | /* prep and exec */ 1268 | slave_path_copy[sizeof(slave_path_copy) - 1] = '\0'; 1269 | strncpy(slave_path_copy, slave_path, sizeof(slave_path_copy) - 1); 1270 | if (slave_path_copy[sizeof(slave_path_copy) - 1] != '\0') { 1271 | rc = -errno; 1272 | err_ose(os, rc, "slave path too long"); 1273 | goto child_fail; 1274 | } 1275 | 1276 | snprintf(id_str[0], sizeof(id_str[0]), "%d", os->uid); 1277 | snprintf(id_str[1], sizeof(id_str[0]), "%d", os->gid); 1278 | snprintf(fd_str[0], sizeof(fd_str[0]), "%d", cmd_sock[1]); 1279 | snprintf(fd_str[1], sizeof(fd_str[1]), "%d", notify_sock[1]); 1280 | snprintf(fd_str[2], sizeof(fd_str[2]), "%d", os->mmap_fd); 1281 | snprintf(mmap_size_str, sizeof(mmap_size_str), "0x%zx", 1282 | os->mmap_size); 1283 | snprintf(log_str, sizeof(log_str), "%d", ossp_log_level); 1284 | if (ossp_log_timestamp) 1285 | argv[ARRAY_SIZE(argv) - 2] = "-t"; 1286 | 1287 | execv(slave_path, argv); 1288 | rc = -errno; 1289 | err_ose(os, rc, "execv failed for <%d>", pid); 1290 | child_fail: 1291 | _exit(1); 1292 | } 1293 | 1294 | /* turn on CLOEXEC on all server side fds */ 1295 | if (fcntl(os->cmd_fd, F_SETFD, FD_CLOEXEC) < 0 || 1296 | fcntl(os->notify_tx, F_SETFD, FD_CLOEXEC) < 0 || 1297 | fcntl(os->notify_rx, F_SETFD, FD_CLOEXEC) < 0) { 1298 | rc = -errno; 1299 | err_ose(os, rc, "failed to set CLOEXEC on server side fds"); 1300 | goto put_os; 1301 | } 1302 | 1303 | dbg0_os(os, "CREATE slave=%d %s", os->slave_pid, slave_path); 1304 | dbg0_os(os, " client=%d cmd=%d:%d notify=%d:%d mmap=%d:%zu", 1305 | pid, cmd_sock[0], cmd_sock[1], notify_sock[0], notify_sock[1], 1306 | os->mmap_fd, os->mmap_size); 1307 | 1308 | close(os->mmap_fd); 1309 | os->mmap_fd = -1; 1310 | 1311 | *osp = os; 1312 | rc = 0; 1313 | goto close_client_fds; 1314 | 1315 | put_os: 1316 | put_os(os); 1317 | close_client_fds: 1318 | close(cmd_sock[1]); 1319 | pthread_mutex_unlock(&create_mutex); 1320 | return rc; 1321 | 1322 | close_all: 1323 | for (i = 0; i < 2; i++) { 1324 | close(cmd_sock[i]); 1325 | close(notify_sock[i]); 1326 | } 1327 | pthread_mutex_unlock(&create_mutex); 1328 | return rc; 1329 | } 1330 | 1331 | static void dsp_open_common(fuse_req_t req, struct fuse_file_info *fi, 1332 | struct fuse_session *se) 1333 | { 1334 | const struct fuse_ctx *fuse_ctx = fuse_req_ctx(req); 1335 | struct ossp_dsp_open_arg arg = { }; 1336 | struct ossp_stream *os = NULL; 1337 | struct ossp_mixer *mixer; 1338 | struct ossp_dsp_stream *dsps; 1339 | struct ossp_mixer_cmd mxcmd; 1340 | pid_t pgrp; 1341 | ssize_t ret; 1342 | 1343 | ret = get_proc_self_info(fuse_ctx->pid, &pgrp, NULL, 0); 1344 | if (ret) { 1345 | err_e(ret, "get_proc_self_info(%d) failed", fuse_ctx->pid); 1346 | goto err; 1347 | } 1348 | 1349 | ret = create_os(dsp_slave_path, sizeof(*dsps), DSPS_MMAP_SIZE, 1350 | fuse_ctx->pid, pgrp, fuse_ctx->uid, fuse_ctx->gid, 1351 | se, &os); 1352 | if (ret) 1353 | goto err; 1354 | dsps = os_to_dsps(os); 1355 | mixer = os->mixer; 1356 | 1357 | switch (fi->flags & O_ACCMODE) { 1358 | case O_WRONLY: 1359 | dsps->rw |= 1 << PLAY; 1360 | break; 1361 | case O_RDONLY: 1362 | dsps->rw |= 1 << REC; 1363 | break; 1364 | case O_RDWR: 1365 | dsps->rw |= (1 << PLAY) | (1 << REC); 1366 | break; 1367 | default: 1368 | assert(0); 1369 | } 1370 | 1371 | arg.flags = fi->flags; 1372 | arg.opener_pid = os->pid; 1373 | ret = exec_simple_cmd(&dsps->os, OSSP_DSP_OPEN, &arg, NULL); 1374 | if (ret < 0) { 1375 | put_os(os); 1376 | goto err; 1377 | } 1378 | 1379 | memcpy(os->vol, mixer->vol, sizeof(os->vol)); 1380 | if (os->vol[PLAY][0] >= 0 || os->vol[REC][0] >= 0) { 1381 | init_mixer_cmd(&mxcmd, mixer); 1382 | memcpy(mxcmd.set.vol, os->vol, sizeof(os->vol)); 1383 | exec_mixer_cmd(&mxcmd, os); 1384 | finish_mixer_cmd(&mxcmd); 1385 | } 1386 | 1387 | fi->direct_io = 1; 1388 | fi->nonseekable = 1; 1389 | fi->fh = os->id; 1390 | 1391 | fuse_reply_open(req, fi); 1392 | return; 1393 | 1394 | err: 1395 | fuse_reply_err(req, -ret); 1396 | } 1397 | 1398 | static void dsp_open(fuse_req_t req, struct fuse_file_info *fi) 1399 | { 1400 | dsp_open_common(req, fi, dsp_se); 1401 | } 1402 | 1403 | static void adsp_open(fuse_req_t req, struct fuse_file_info *fi) 1404 | { 1405 | dsp_open_common(req, fi, adsp_se); 1406 | } 1407 | 1408 | static void dsp_release(fuse_req_t req, struct fuse_file_info *fi) 1409 | { 1410 | struct ossp_stream *os; 1411 | 1412 | os = find_os(fi->fh); 1413 | if (os) { 1414 | put_os(os); 1415 | fuse_reply_err(req, 0); 1416 | } else 1417 | fuse_reply_err(req, EBADF); 1418 | } 1419 | 1420 | static void dsp_read(fuse_req_t req, size_t size, off_t off, 1421 | struct fuse_file_info *fi) 1422 | { 1423 | struct ossp_dsp_rw_arg arg = { }; 1424 | struct ossp_stream *os; 1425 | struct ossp_dsp_stream *dsps; 1426 | void *buf = NULL; 1427 | ssize_t ret; 1428 | 1429 | ret = -EBADF; 1430 | os = find_os(fi->fh); 1431 | if (!os) 1432 | goto out; 1433 | dsps = os_to_dsps(os); 1434 | 1435 | ret = -EINVAL; 1436 | if (!(dsps->rw & (1 << REC))) 1437 | goto out; 1438 | 1439 | ret = -ENXIO; 1440 | if (dsps->mmapped) 1441 | goto out; 1442 | 1443 | ret = -ENOMEM; 1444 | buf = malloc(size); 1445 | if (!buf) 1446 | goto out; 1447 | 1448 | arg.nonblock = (fi->flags & O_NONBLOCK) || dsps->nonblock; 1449 | 1450 | ret = exec_cmd(os, OSSP_DSP_READ, &arg, sizeof(arg), 1451 | NULL, 0, NULL, 0, buf, &size, -1); 1452 | out: 1453 | if (ret >= 0) 1454 | fuse_reply_buf(req, buf, size); 1455 | else 1456 | fuse_reply_err(req, -ret); 1457 | 1458 | free(buf); 1459 | } 1460 | 1461 | static void dsp_write(fuse_req_t req, const char *buf, size_t size, off_t off, 1462 | struct fuse_file_info *fi) 1463 | { 1464 | struct ossp_dsp_rw_arg arg = { }; 1465 | struct ossp_stream *os; 1466 | struct ossp_dsp_stream *dsps; 1467 | ssize_t ret; 1468 | 1469 | ret = -EBADF; 1470 | os = find_os(fi->fh); 1471 | if (!os) 1472 | goto out; 1473 | dsps = os_to_dsps(os); 1474 | 1475 | ret = -EINVAL; 1476 | if (!(dsps->rw & (1 << PLAY))) 1477 | goto out; 1478 | 1479 | ret = -ENXIO; 1480 | if (dsps->mmapped) 1481 | goto out; 1482 | 1483 | arg.nonblock = (fi->flags & O_NONBLOCK) || dsps->nonblock; 1484 | 1485 | ret = exec_cmd(os, OSSP_DSP_WRITE, &arg, sizeof(arg), 1486 | buf, size, NULL, 0, NULL, NULL, -1); 1487 | out: 1488 | if (ret >= 0) 1489 | fuse_reply_write(req, ret); 1490 | else 1491 | fuse_reply_err(req, -ret); 1492 | } 1493 | 1494 | static void dsp_poll(fuse_req_t req, struct fuse_file_info *fi, 1495 | struct fuse_pollhandle *ph) 1496 | { 1497 | int notify = ph != NULL; 1498 | unsigned revents = 0; 1499 | struct ossp_stream *os; 1500 | ssize_t ret; 1501 | 1502 | ret = -EBADF; 1503 | os = find_os(fi->fh); 1504 | if (!os) 1505 | goto out; 1506 | 1507 | if (ph) { 1508 | pthread_mutex_lock(&mutex); 1509 | if (os->ph) 1510 | fuse_pollhandle_destroy(os->ph); 1511 | os->ph = ph; 1512 | pthread_mutex_unlock(&mutex); 1513 | } 1514 | 1515 | ret = exec_simple_cmd(os, OSSP_DSP_POLL, ¬ify, &revents); 1516 | out: 1517 | if (ret >= 0) 1518 | fuse_reply_poll(req, revents); 1519 | else 1520 | fuse_reply_err(req, -ret); 1521 | } 1522 | 1523 | static void dsp_ioctl(fuse_req_t req, int signed_cmd, void *uarg, 1524 | struct fuse_file_info *fi, unsigned int flags, 1525 | const void *in_buf, size_t in_bufsz, size_t out_bufsz) 1526 | { 1527 | /* some ioctl constants are long and has the highest bit set */ 1528 | unsigned cmd = signed_cmd; 1529 | struct ossp_stream *os; 1530 | struct ossp_dsp_stream *dsps; 1531 | enum ossp_opcode op; 1532 | ssize_t ret; 1533 | int i; 1534 | 1535 | ret = -EBADF; 1536 | os = find_os(fi->fh); 1537 | if (!os) 1538 | goto err; 1539 | dsps = os_to_dsps(os); 1540 | 1541 | /* mixer commands are allowed on DSP devices */ 1542 | if (((cmd >> 8) & 0xff) == 'M') { 1543 | mixer_do_ioctl(req, os->mixer, cmd, uarg, in_buf, in_bufsz, 1544 | out_bufsz); 1545 | return; 1546 | } 1547 | 1548 | /* and the rest */ 1549 | switch (cmd) { 1550 | case OSS_GETVERSION: 1551 | i = SNDRV_OSS_VERSION; 1552 | PREP_UARG(NULL, &i); 1553 | fuse_reply_ioctl(req, 0, &i, sizeof(i)); 1554 | break; 1555 | 1556 | case SNDCTL_DSP_GETCAPS: 1557 | i = DSP_CAP_DUPLEX | DSP_CAP_REALTIME | DSP_CAP_TRIGGER | 1558 | #ifdef OSSP_MMAP 1559 | DSP_CAP_MMAP | 1560 | #endif 1561 | DSP_CAP_MULTI; 1562 | PREP_UARG(NULL, &i); 1563 | fuse_reply_ioctl(req, 0, &i, sizeof(i)); 1564 | break; 1565 | 1566 | case SNDCTL_DSP_NONBLOCK: 1567 | dsps->nonblock = 1; 1568 | ret = 0; 1569 | fuse_reply_ioctl(req, 0, NULL, 0); 1570 | break; 1571 | 1572 | case SNDCTL_DSP_RESET: op = OSSP_DSP_RESET; goto nd; 1573 | case SNDCTL_DSP_SYNC: op = OSSP_DSP_SYNC; goto nd; 1574 | case SNDCTL_DSP_POST: op = OSSP_DSP_POST; goto nd; 1575 | nd: 1576 | ret = exec_simple_cmd(&dsps->os, op, NULL, NULL); 1577 | if (ret) 1578 | goto err; 1579 | fuse_reply_ioctl(req, 0, NULL, 0); 1580 | break; 1581 | 1582 | case SOUND_PCM_READ_RATE: op = OSSP_DSP_GET_RATE; goto ri; 1583 | case SOUND_PCM_READ_BITS: op = OSSP_DSP_GET_FORMAT; goto ri; 1584 | case SOUND_PCM_READ_CHANNELS: op = OSSP_DSP_GET_CHANNELS; goto ri; 1585 | case SNDCTL_DSP_GETBLKSIZE: op = OSSP_DSP_GET_BLKSIZE; goto ri; 1586 | case SNDCTL_DSP_GETFMTS: op = OSSP_DSP_GET_FORMATS; goto ri; 1587 | case SNDCTL_DSP_GETTRIGGER: op = OSSP_DSP_GET_TRIGGER; goto ri; 1588 | ri: 1589 | PREP_UARG(NULL, &i); 1590 | ret = exec_simple_cmd(&dsps->os, op, NULL, &i); 1591 | if (ret) 1592 | goto err; 1593 | fuse_reply_ioctl(req, 0, &i, sizeof(i)); 1594 | break; 1595 | 1596 | case SNDCTL_DSP_SPEED: op = OSSP_DSP_SET_RATE; goto wi; 1597 | case SNDCTL_DSP_SETFMT: op = OSSP_DSP_SET_FORMAT; goto wi; 1598 | case SNDCTL_DSP_CHANNELS: op = OSSP_DSP_SET_CHANNELS; goto wi; 1599 | case SNDCTL_DSP_SUBDIVIDE: op = OSSP_DSP_SET_SUBDIVISION; goto wi; 1600 | wi: 1601 | PREP_UARG(&i, &i); 1602 | ret = exec_simple_cmd(&dsps->os, op, &i, &i); 1603 | if (ret) 1604 | goto err; 1605 | fuse_reply_ioctl(req, 0, &i, sizeof(i)); 1606 | break; 1607 | 1608 | case SNDCTL_DSP_STEREO: 1609 | PREP_UARG(NULL, &i); 1610 | i = 2; 1611 | ret = exec_simple_cmd(&dsps->os, OSSP_DSP_SET_CHANNELS, &i, &i); 1612 | i--; 1613 | if (ret) 1614 | goto err; 1615 | fuse_reply_ioctl(req, 0, &i, sizeof(i)); 1616 | break; 1617 | 1618 | case SNDCTL_DSP_SETFRAGMENT: 1619 | PREP_UARG(&i, NULL); 1620 | ret = exec_simple_cmd(&dsps->os, 1621 | OSSP_DSP_SET_FRAGMENT, &i, NULL); 1622 | if (ret) 1623 | goto err; 1624 | fuse_reply_ioctl(req, 0, NULL, 0); 1625 | break; 1626 | 1627 | case SNDCTL_DSP_SETTRIGGER: 1628 | PREP_UARG(&i, NULL); 1629 | ret = exec_simple_cmd(&dsps->os, 1630 | OSSP_DSP_SET_TRIGGER, &i, NULL); 1631 | if (ret) 1632 | goto err; 1633 | fuse_reply_ioctl(req, 0, NULL, 0); 1634 | break; 1635 | 1636 | case SNDCTL_DSP_GETOSPACE: 1637 | case SNDCTL_DSP_GETISPACE: { 1638 | struct audio_buf_info info; 1639 | 1640 | ret = -EINVAL; 1641 | if (cmd == SNDCTL_DSP_GETOSPACE) { 1642 | if (!(dsps->rw & (1 << PLAY))) 1643 | goto err; 1644 | op = OSSP_DSP_GET_OSPACE; 1645 | } else { 1646 | if (!(dsps->rw & (1 << REC))) 1647 | goto err; 1648 | op = OSSP_DSP_GET_ISPACE; 1649 | } 1650 | 1651 | PREP_UARG(NULL, &info); 1652 | ret = exec_simple_cmd(&dsps->os, op, NULL, &info); 1653 | if (ret) 1654 | goto err; 1655 | fuse_reply_ioctl(req, 0, &info, sizeof(info)); 1656 | break; 1657 | } 1658 | 1659 | case SNDCTL_DSP_GETOPTR: 1660 | case SNDCTL_DSP_GETIPTR: { 1661 | struct count_info info; 1662 | 1663 | op = cmd == SNDCTL_DSP_GETOPTR ? OSSP_DSP_GET_OPTR 1664 | : OSSP_DSP_GET_IPTR; 1665 | PREP_UARG(NULL, &info); 1666 | ret = exec_simple_cmd(&dsps->os, op, NULL, &info); 1667 | if (ret) 1668 | goto err; 1669 | fuse_reply_ioctl(req, 0, &info, sizeof(info)); 1670 | break; 1671 | } 1672 | 1673 | case SNDCTL_DSP_GETODELAY: 1674 | PREP_UARG(NULL, &i); 1675 | i = 0; 1676 | ret = exec_simple_cmd(&dsps->os, OSSP_DSP_GET_ODELAY, NULL, &i); 1677 | fuse_reply_ioctl(req, ret, &i, sizeof(i)); /* always copy out result, 0 on err */ 1678 | break; 1679 | 1680 | case SOUND_PCM_WRITE_FILTER: 1681 | case SOUND_PCM_READ_FILTER: 1682 | ret = -EIO; 1683 | goto err; 1684 | 1685 | case SNDCTL_DSP_MAPINBUF: 1686 | case SNDCTL_DSP_MAPOUTBUF: 1687 | ret = -EINVAL; 1688 | goto err; 1689 | 1690 | case SNDCTL_DSP_SETSYNCRO: 1691 | case SNDCTL_DSP_SETDUPLEX: 1692 | case SNDCTL_DSP_PROFILE: 1693 | fuse_reply_ioctl(req, 0, NULL, 0); 1694 | break; 1695 | 1696 | default: 1697 | warn_os(os, "unknown ioctl 0x%x", cmd); 1698 | ret = -EINVAL; 1699 | goto err; 1700 | } 1701 | return; 1702 | 1703 | err: 1704 | fuse_reply_err(req, -ret); 1705 | } 1706 | 1707 | #ifdef OSSP_MMAP 1708 | static int dsp_mmap_dir(int prot) 1709 | { 1710 | if (!(prot & PROT_WRITE)) 1711 | return REC; 1712 | return PLAY; 1713 | } 1714 | 1715 | static int dsp_dir_to_mapid(struct ossp_stream *os, int dir) 1716 | { 1717 | return os->id * 2 + dir + 1; 1718 | } 1719 | 1720 | static int dsp_mapid_to_dir(int map_id) 1721 | { 1722 | return (map_id - 1) & 1; 1723 | } 1724 | 1725 | static void dsp_mmap(fuse_req_t req, uint64_t addr, size_t len, int prot, 1726 | int flags, off_t offset, struct fuse_file_info *fi) 1727 | { 1728 | int dir = dsp_mmap_dir(prot); 1729 | struct ossp_dsp_mmap_arg arg = { }; 1730 | struct ossp_stream *os; 1731 | struct ossp_dsp_stream *dsps; 1732 | ssize_t ret; 1733 | 1734 | os = find_os(fi->fh); 1735 | if (!os) { 1736 | fuse_reply_err(req, EBADF); 1737 | return; 1738 | } 1739 | dsps = os_to_dsps(os); 1740 | 1741 | if (len > os->mmap_size / 2) { 1742 | fuse_reply_err(req, EINVAL); 1743 | return; 1744 | } 1745 | 1746 | pthread_mutex_lock(&os->mmap_mutex); 1747 | 1748 | ret = -EBUSY; 1749 | if (dsps->mmapped & (1 << dir)) 1750 | goto out_unlock; 1751 | 1752 | arg.dir = dir; 1753 | arg.size = len; 1754 | 1755 | ret = exec_simple_cmd(os, OSSP_DSP_MMAP, &arg, NULL); 1756 | if (ret == 0) 1757 | dsps->mmapped |= 1 << dir; 1758 | 1759 | out_unlock: 1760 | pthread_mutex_unlock(&os->mmap_mutex); 1761 | 1762 | if (ret == 0) 1763 | fuse_reply_mmap(req, dsp_dir_to_mapid(os, dir), os->mmap_size / 2); 1764 | else 1765 | fuse_reply_err(req, -ret); 1766 | } 1767 | 1768 | static void dsp_munmap(fuse_req_t req, uint64_t map_id, size_t length, 1769 | struct fuse_file_info *fi) 1770 | { 1771 | struct ossp_stream *os; 1772 | struct ossp_dsp_stream *dsps; 1773 | int dir, rc; 1774 | 1775 | os = find_os(fi->fh); 1776 | if (!os) 1777 | goto out; 1778 | dsps = os_to_dsps(os); 1779 | 1780 | pthread_mutex_lock(&os->mmap_mutex); 1781 | 1782 | dir = dsp_mapid_to_dir(map_id); 1783 | if (dsp_dir_to_mapid(os, dir) != map_id || 1784 | (dir != PLAY && dir != REC) || length != os->mmap_size / 2) { 1785 | warn_os(os, "invalid munmap request map_id=%llu len=%zu mmapped=0x%x", 1786 | (unsigned long long) map_id, length, dsps->mmapped); 1787 | goto out_unlock; 1788 | } 1789 | 1790 | rc = exec_simple_cmd(os, OSSP_DSP_MUNMAP, &dir, NULL); 1791 | if (rc) 1792 | warn_ose(os, rc, "MUNMAP failed for dir=%d", dir); 1793 | 1794 | dsps->mmapped &= ~(1 << dir); 1795 | 1796 | out_unlock: 1797 | pthread_mutex_unlock(&os->mmap_mutex); 1798 | out: 1799 | fuse_reply_err(req, -rc); 1800 | } 1801 | 1802 | static void fill_mmap_buffer(struct ossp_stream *os) 1803 | { 1804 | int rc; 1805 | struct fuse_chan *ch = fuse_session_next_chan(os->se, NULL); 1806 | unsigned mapid = dsp_dir_to_mapid(os, PLAY); 1807 | size_t pos = os->mmap_transfer[PLAY].pos; 1808 | size_t bytes = os->mmap_transfer[PLAY].bytes; 1809 | 1810 | dbg1_os(os, "fill mmap buffer pos=%zu bytes=%zu", pos, bytes); 1811 | 1812 | rc = fuse_lowlevel_notify_retrieve(ch, mapid, bytes, pos, os); 1813 | if (rc) 1814 | sem_post(&os->mmap_transfer[PLAY].sem); 1815 | } 1816 | 1817 | static void dsp_retrieve_reply(fuse_req_t req, void *cookie, uint64_t map_id, 1818 | off_t offset, struct fuse_bufvec *bufv) 1819 | { 1820 | struct ossp_stream *os = cookie; 1821 | size_t size = fuse_buf_size(bufv); 1822 | struct fuse_bufvec dest = FUSE_BUFVEC_INIT(size); 1823 | ssize_t res; 1824 | 1825 | if (dsp_mapid_to_dir(map_id) != PLAY || 1826 | dsp_dir_to_mapid(os, PLAY) != map_id || 1827 | offset != os->mmap_transfer[PLAY].pos || 1828 | size > os->mmap_transfer[PLAY].bytes) { 1829 | warn_os(os, "invalid retrieve reply map_id=%llu offset=%llu, size=%zu", 1830 | (unsigned long long) map_id, 1831 | (unsigned long long) offset, size); 1832 | sem_post(&os->mmap_transfer[PLAY].sem); 1833 | return; 1834 | } 1835 | 1836 | dest.buf[0].mem = os->mmap_addr[PLAY] + offset; 1837 | 1838 | res = fuse_buf_copy(&dest, bufv, 0); 1839 | if (res < 0) 1840 | warn_ose(os, res, "dsp_retrieve_reply: buffer copy"); 1841 | else if ((size_t) res < size) { 1842 | warn_os(os, "dsp_retrieve_reply: short buffer copy"); 1843 | } 1844 | 1845 | if (size < os->mmap_transfer[PLAY].bytes) { 1846 | os->mmap_transfer[PLAY].bytes -= size; 1847 | os->mmap_transfer[PLAY].pos += size; 1848 | 1849 | fill_mmap_buffer(os); 1850 | } else { 1851 | sem_post(&os->mmap_transfer[PLAY].sem); 1852 | } 1853 | } 1854 | 1855 | static void store_mmap_buffer(struct ossp_stream *os) 1856 | { 1857 | int rc; 1858 | struct fuse_chan *ch = fuse_session_next_chan(os->se, NULL); 1859 | unsigned mapid = dsp_dir_to_mapid(os, REC); 1860 | size_t pos = os->mmap_transfer[REC].pos; 1861 | size_t bytes = os->mmap_transfer[REC].bytes; 1862 | struct fuse_bufvec bufv = FUSE_BUFVEC_INIT(bytes); 1863 | 1864 | dbg1_os(os, "store mmap buffer pos=%zu bytes=%zu", pos, bytes); 1865 | 1866 | bufv.buf[0].mem = os->mmap_addr[REC] + pos; 1867 | 1868 | rc = fuse_lowlevel_notify_store(ch, mapid, pos, &bufv, 0); 1869 | if (rc < 0) 1870 | warn_ose(os, rc, "store_mmap_buffer: failure"); 1871 | 1872 | sem_post(&os->mmap_transfer[REC].sem); 1873 | } 1874 | #endif 1875 | 1876 | 1877 | /*************************************************************************** 1878 | * Notify poller 1879 | */ 1880 | 1881 | static void *notify_poller(void *arg) 1882 | { 1883 | struct epoll_event events[1024]; 1884 | int i, nfds; 1885 | 1886 | repeat: 1887 | nfds = epoll_wait(notify_epfd, events, ARRAY_SIZE(events), -1); 1888 | for (i = 0; i < nfds; i++) { 1889 | int do_notify = 0; 1890 | struct ossp_stream *os; 1891 | struct ossp_notify notify; 1892 | ssize_t ret; 1893 | 1894 | os = find_os_by_notify_rx(events[i].data.fd); 1895 | if (!os) { 1896 | err("can't find stream for notify_rx fd %d", 1897 | events[i].data.fd); 1898 | epoll_ctl(notify_epfd, EPOLL_CTL_DEL, events[i].data.fd, 1899 | NULL); 1900 | /* we don't know what's going on, don't close the fd */ 1901 | continue; 1902 | } 1903 | 1904 | while ((ret = read(os->notify_rx, 1905 | ¬ify, sizeof(notify))) > 0) { 1906 | if (os->dead) 1907 | continue; 1908 | if (ret != sizeof(notify)) { 1909 | warn_os(os, "short read on notify_rx (%zu, " 1910 | "expected %zu), killing the stream", 1911 | ret, sizeof(notify)); 1912 | os->dead = 1; 1913 | break; 1914 | } 1915 | if (notify.magic != OSSP_NOTIFY_MAGIC) { 1916 | warn_os(os, "invalid magic on notification, " 1917 | "killing the stream"); 1918 | os->dead = 1; 1919 | break; 1920 | } 1921 | 1922 | if (notify.opcode >= OSSP_NR_NOTIFY_OPCODES) 1923 | goto unknown; 1924 | 1925 | dbg1_os(os, "NOTIFY %s", ossp_notify_str[notify.opcode]); 1926 | 1927 | switch (notify.opcode) { 1928 | case OSSP_NOTIFY_POLL: 1929 | do_notify = 1; 1930 | break; 1931 | case OSSP_NOTIFY_OBITUARY: 1932 | os->dead = 1; 1933 | break; 1934 | case OSSP_NOTIFY_VOLCHG: 1935 | pthread_mutex_lock(&mixer_mutex); 1936 | os->mixer->modify_counter++; 1937 | pthread_mutex_unlock(&mixer_mutex); 1938 | break; 1939 | #ifdef OSSP_MMAP 1940 | case OSSP_NOTIFY_FILL: 1941 | fill_mmap_buffer(os); 1942 | break; 1943 | case OSSP_NOTIFY_STORE: 1944 | store_mmap_buffer(os); 1945 | break; 1946 | #endif 1947 | default: 1948 | unknown: 1949 | warn_os(os, "unknown notification %d", 1950 | notify.opcode); 1951 | } 1952 | } 1953 | if (ret == 0) 1954 | os->dead = 1; 1955 | else if (ret < 0 && errno != EAGAIN) { 1956 | warn_ose(os, -errno, "read fail on notify fd"); 1957 | os->dead = 1; 1958 | } 1959 | 1960 | if (!do_notify && !os->dead) 1961 | continue; 1962 | 1963 | pthread_mutex_lock(&mutex); 1964 | 1965 | if (os->ph) { 1966 | fuse_lowlevel_notify_poll(os->ph); 1967 | fuse_pollhandle_destroy(os->ph); 1968 | os->ph = NULL; 1969 | } 1970 | 1971 | if (os->dead) { 1972 | dbg0_os(os, "removing %d from notify poll list", 1973 | os->notify_rx); 1974 | epoll_ctl(notify_epfd, EPOLL_CTL_DEL, os->notify_rx, 1975 | NULL); 1976 | close(os->notify_rx); 1977 | os->notify_rx = -1; 1978 | pthread_cond_broadcast(¬ify_poller_kill_wait); 1979 | } 1980 | 1981 | pthread_mutex_unlock(&mutex); 1982 | } 1983 | goto repeat; 1984 | 1985 | /* not reachable */ 1986 | return NULL; 1987 | } 1988 | 1989 | 1990 | /*************************************************************************** 1991 | * Slave corpse reaper 1992 | */ 1993 | 1994 | static void *slave_reaper(void *arg) 1995 | { 1996 | struct ossp_stream *os; 1997 | int status; 1998 | pid_t pid; 1999 | 2000 | pthread_mutex_lock(&mutex); 2001 | repeat: 2002 | while (list_empty(&slave_corpse_list)) 2003 | pthread_cond_wait(&slave_reaper_wait, &mutex); 2004 | 2005 | os = list_first_entry(&slave_corpse_list, struct ossp_stream, link); 2006 | list_del_init(&os->link); 2007 | 2008 | pthread_mutex_unlock(&mutex); 2009 | 2010 | do { 2011 | pid = waitpid(os->slave_pid, &status, 0); 2012 | } while (pid < 0 && errno == EINTR); 2013 | 2014 | if (pid < 0) { 2015 | if (errno == ECHILD) 2016 | warn_ose(os, -errno, "slave %d already gone?", 2017 | os->slave_pid); 2018 | else 2019 | fatal_e(-errno, "waitpid(%d) failed", os->slave_pid); 2020 | } 2021 | 2022 | pthread_mutex_lock(&mutex); 2023 | 2024 | dbg1_os(os, "slave %d reaped", os->slave_pid); 2025 | __clear_bit(os->id, os_id_bitmap); 2026 | if (os->mmap_size) { 2027 | sem_destroy(&os->mmap_transfer[PLAY].sem); 2028 | sem_destroy(&os->mmap_transfer[REC].sem); 2029 | 2030 | munmap(os->mmap, os->mmap_size + 2 * sizeof(struct ossp_transfer)); 2031 | close(os->mmap_fd); 2032 | } 2033 | 2034 | free(os); 2035 | 2036 | goto repeat; 2037 | 2038 | /* not reachable */ 2039 | return NULL; 2040 | } 2041 | 2042 | 2043 | /*************************************************************************** 2044 | * Stuff to bind and start everything 2045 | */ 2046 | 2047 | static void ossp_daemonize(void) 2048 | { 2049 | int fd, pfd[2]; 2050 | pid_t pid; 2051 | ssize_t ret; 2052 | int err; 2053 | 2054 | fd = open("/dev/null", O_RDWR); 2055 | if (fd >= 0) { 2056 | dup2(fd, 0); 2057 | dup2(fd, 1); 2058 | dup2(fd, 2); 2059 | if (fd > 2) 2060 | close(fd); 2061 | } 2062 | 2063 | if (pipe(pfd)) 2064 | fatal_e(-errno, "failed to create pipe for init wait"); 2065 | 2066 | if (fcntl(pfd[0], F_SETFD, FD_CLOEXEC) < 0 || 2067 | fcntl(pfd[1], F_SETFD, FD_CLOEXEC) < 0) 2068 | fatal_e(-errno, "failed to set CLOEXEC on init wait pipe"); 2069 | 2070 | pid = fork(); 2071 | if (pid < 0) 2072 | fatal_e(-errno, "failed to fork for daemon"); 2073 | 2074 | if (pid == 0) { 2075 | close(pfd[0]); 2076 | init_wait_fd = pfd[1]; 2077 | 2078 | /* be evil, my child */ 2079 | chdir("/"); 2080 | setsid(); 2081 | return; 2082 | } 2083 | 2084 | /* wait for init completion and pass over success indication */ 2085 | close(pfd[1]); 2086 | 2087 | do { 2088 | ret = read(pfd[0], &err, sizeof(err)); 2089 | } while (ret < 0 && errno == EINTR); 2090 | 2091 | if (ret == sizeof(err) && err == 0) 2092 | exit(0); 2093 | 2094 | fatal("daemon init failed ret=%zd err=%d", ret, err); 2095 | exit(1); 2096 | } 2097 | 2098 | static void ossp_init_done(void *userdata) 2099 | { 2100 | /* init complete, notify parent if it's waiting */ 2101 | if (init_wait_fd >= 0) { 2102 | ssize_t ret; 2103 | int err = 0; 2104 | 2105 | ret = write(init_wait_fd, &err, sizeof(err)); 2106 | if (ret != sizeof(err)) 2107 | fatal_e(-errno, "failed to notify init completion, " 2108 | "ret=%zd", ret); 2109 | close(init_wait_fd); 2110 | init_wait_fd = -1; 2111 | } 2112 | } 2113 | 2114 | static const struct cuse_lowlevel_ops mixer_ops = { 2115 | .open = mixer_open, 2116 | .release = mixer_release, 2117 | .ioctl = mixer_ioctl, 2118 | }; 2119 | 2120 | static const struct cuse_lowlevel_ops dsp_ops = { 2121 | .init_done = ossp_init_done, 2122 | .open = dsp_open, 2123 | .release = dsp_release, 2124 | .read = dsp_read, 2125 | .write = dsp_write, 2126 | .poll = dsp_poll, 2127 | .ioctl = dsp_ioctl, 2128 | #ifdef OSSP_MMAP 2129 | .mmap = dsp_mmap, 2130 | .munmap = dsp_munmap, 2131 | .retrieve_reply = dsp_retrieve_reply, 2132 | #endif 2133 | }; 2134 | 2135 | static const struct cuse_lowlevel_ops adsp_ops = { 2136 | .open = adsp_open, 2137 | .release = dsp_release, 2138 | .read = dsp_read, 2139 | .write = dsp_write, 2140 | .poll = dsp_poll, 2141 | .ioctl = dsp_ioctl, 2142 | #ifdef OSSP_MMAP 2143 | .mmap = dsp_mmap, 2144 | .munmap = dsp_munmap, 2145 | .retrieve_reply = dsp_retrieve_reply, 2146 | #endif 2147 | }; 2148 | 2149 | static const char *usage = 2150 | "usage: osspd [options]\n" 2151 | "\n" 2152 | "options:\n" 2153 | " --help print this help message\n" 2154 | " --dsp=NAME DSP device name (default dsp)\n" 2155 | " --dsp-maj=MAJ DSP device major number (default 14)\n" 2156 | " --dsp-min=MIN DSP device minor number (default 3)\n" 2157 | " --adsp=NAME Aux DSP device name (default adsp, blank to disable)\n" 2158 | " --adsp-maj=MAJ Aux DSP device major number (default 14)\n" 2159 | " --adsp-min=MIN Aux DSP device minor number (default 12)\n" 2160 | " --mixer=NAME mixer device name (default mixer, blank to disable)\n" 2161 | " --mixer-maj=MAJ mixer device major number (default 14)\n" 2162 | " --mixer-min=MIN mixer device minor number (default 0)\n" 2163 | " --max=MAX maximum number of open streams (default 256)\n" 2164 | " --umax=MAX maximum number of open streams per UID (default --max)\n" 2165 | " --exit-on-idle exit if idle\n" 2166 | " --dsp-slave=PATH DSP slave (default ossp-padsp in the same dir)\n" 2167 | " --log=LEVEL log level (0..6)\n" 2168 | " --timestamp timestamp log messages\n" 2169 | " -v increase verbosity, can be specified multiple times\n" 2170 | " -f Run in foreground (don't daemonize)\n" 2171 | "\n"; 2172 | 2173 | struct ossp_param { 2174 | char *dsp_name; 2175 | unsigned dsp_major; 2176 | unsigned dsp_minor; 2177 | char *adsp_name; 2178 | unsigned adsp_major; 2179 | unsigned adsp_minor; 2180 | char *mixer_name; 2181 | unsigned mixer_major; 2182 | unsigned mixer_minor; 2183 | unsigned max_streams; 2184 | unsigned umax_streams; 2185 | char *dsp_slave_path; 2186 | unsigned log_level; 2187 | int exit_on_idle; 2188 | int timestamp; 2189 | int fg; 2190 | int help; 2191 | }; 2192 | 2193 | #define OSSP_OPT(t, p) { t, offsetof(struct ossp_param, p), 1 } 2194 | 2195 | static const struct fuse_opt ossp_opts[] = { 2196 | OSSP_OPT("--dsp=%s", dsp_name), 2197 | OSSP_OPT("--dsp-maj=%u", dsp_major), 2198 | OSSP_OPT("--dsp-min=%u", dsp_minor), 2199 | OSSP_OPT("--adsp=%s", adsp_name), 2200 | OSSP_OPT("--adsp-maj=%u", adsp_major), 2201 | OSSP_OPT("--adsp-min=%u", adsp_minor), 2202 | OSSP_OPT("--mixer=%s", mixer_name), 2203 | OSSP_OPT("--mixer-maj=%u", mixer_major), 2204 | OSSP_OPT("--mixer-min=%u", mixer_minor), 2205 | OSSP_OPT("--max=%u", max_streams), 2206 | OSSP_OPT("--umax=%u", umax_streams), 2207 | OSSP_OPT("--exit-on-idle", exit_on_idle), 2208 | OSSP_OPT("--dsp-slave=%s", dsp_slave_path), 2209 | OSSP_OPT("--timestamp", timestamp), 2210 | OSSP_OPT("--log=%u", log_level), 2211 | OSSP_OPT("-f", fg), 2212 | FUSE_OPT_KEY("-h", 0), 2213 | FUSE_OPT_KEY("--help", 0), 2214 | FUSE_OPT_KEY("-v", 1), 2215 | FUSE_OPT_END 2216 | }; 2217 | 2218 | static struct fuse_session *setup_ossp_cuse(const struct cuse_lowlevel_ops *ops, 2219 | const char *name, int major, 2220 | int minor, int argc, char **argv) 2221 | { 2222 | char name_buf[128]; 2223 | const char *bufp = name_buf; 2224 | struct cuse_info ci = { .dev_major = major, .dev_minor = minor, 2225 | .dev_info_argc = 1, .dev_info_argv = &bufp, 2226 | .flags = CUSE_UNRESTRICTED_IOCTL }; 2227 | struct fuse_session *se; 2228 | int fd; 2229 | int multithreaded; 2230 | 2231 | snprintf(name_buf, sizeof(name_buf), "DEVNAME=%s", name); 2232 | 2233 | se = cuse_lowlevel_setup(argc, argv, &ci, ops, &multithreaded, NULL); 2234 | if (!se) { 2235 | err("failed to setup %s CUSE", name); 2236 | return NULL; 2237 | } 2238 | 2239 | fd = fuse_session_fd(se); 2240 | if (fcntl(fd, F_SETFD, FD_CLOEXEC) < 0) { 2241 | err_e(-errno, "failed to set CLOEXEC on %s CUSE fd", name); 2242 | cuse_lowlevel_teardown(se); 2243 | return NULL; 2244 | } 2245 | 2246 | return se; 2247 | } 2248 | 2249 | static void *cuse_worker(void *arg) 2250 | { 2251 | struct fuse_session *se = arg; 2252 | int rc; 2253 | 2254 | rc = fuse_session_loop_mt(se, NULL); 2255 | cuse_lowlevel_teardown(se); 2256 | 2257 | return (void *)(unsigned long)rc; 2258 | } 2259 | 2260 | static int process_arg(void *data, const char *arg, int key, 2261 | struct fuse_args *outargs) 2262 | { 2263 | struct ossp_param *param = data; 2264 | 2265 | switch (key) { 2266 | case 0: 2267 | fputs(usage, stderr); 2268 | param->help = 1; 2269 | return 0; 2270 | case 1: 2271 | param->log_level++; 2272 | return 0; 2273 | } 2274 | return 1; 2275 | } 2276 | 2277 | int main(int argc, char **argv) 2278 | { 2279 | static struct ossp_param param = { 2280 | .dsp_name = DFL_DSP_NAME, 2281 | .dsp_major = DFL_DSP_MAJOR, .dsp_minor = DFL_DSP_MINOR, 2282 | .adsp_name = DFL_ADSP_NAME, 2283 | .adsp_major = DFL_ADSP_MAJOR, .adsp_minor = DFL_ADSP_MINOR, 2284 | .mixer_name = DFL_MIXER_NAME, 2285 | .mixer_major = DFL_MIXER_MAJOR, .mixer_minor = DFL_MIXER_MINOR, 2286 | .max_streams = DFL_MAX_STREAMS, 2287 | }; 2288 | struct fuse_args args = FUSE_ARGS_INIT(argc, argv); 2289 | char path_buf[PATH_MAX], *dir; 2290 | char adsp_buf[64] = "", mixer_buf[64] = ""; 2291 | struct sigaction sa; 2292 | struct stat stat_buf; 2293 | ssize_t ret; 2294 | unsigned u; 2295 | 2296 | snprintf(ossp_log_name, sizeof(ossp_log_name), "osspd"); 2297 | param.log_level = ossp_log_level; 2298 | 2299 | if (fuse_opt_parse(&args, ¶m, ossp_opts, process_arg)) 2300 | fatal("failed to parse arguments"); 2301 | 2302 | if (param.help) 2303 | return 0; 2304 | 2305 | max_streams = param.max_streams; 2306 | hashtbl_size = max_streams / 2 + 13; 2307 | 2308 | umax_streams = max_streams; 2309 | if (param.umax_streams) 2310 | umax_streams = param.umax_streams; 2311 | if (param.log_level > OSSP_LOG_MAX) 2312 | param.log_level = OSSP_LOG_MAX; 2313 | if (!param.fg) 2314 | param.log_level = -param.log_level; 2315 | ossp_log_level = param.log_level; 2316 | ossp_log_timestamp = param.timestamp; 2317 | 2318 | if (!param.fg) 2319 | ossp_daemonize(); 2320 | 2321 | /* daemonization already handled, prevent forking inside FUSE */ 2322 | fuse_opt_add_arg(&args, "-f"); 2323 | 2324 | info("OSS Proxy v%s", OSSP_VERSION); 2325 | 2326 | /* ignore stupid SIGPIPEs */ 2327 | memset(&sa, 0, sizeof(sa)); 2328 | sa.sa_handler = SIG_IGN; 2329 | if (sigaction(SIGPIPE, &sa, NULL)) 2330 | fatal_e(-errno, "failed to ignore SIGPIPE"); 2331 | 2332 | /* determine slave path and check for availability */ 2333 | ret = readlink("/proc/self/exe", path_buf, PATH_MAX - 1); 2334 | if (ret < 0) 2335 | fatal_e(-errno, "failed to determine executable path"); 2336 | path_buf[ret] = '\0'; 2337 | dir = dirname(path_buf); 2338 | 2339 | if (param.dsp_slave_path) { 2340 | strncpy(dsp_slave_path, param.dsp_slave_path, PATH_MAX - 1); 2341 | dsp_slave_path[PATH_MAX - 1] = '\0'; 2342 | } else { 2343 | ret = snprintf(dsp_slave_path, PATH_MAX, "%s/%s", 2344 | dir, "ossp-padsp"); 2345 | if (ret >= PATH_MAX) 2346 | fatal("dsp slave pathname too long"); 2347 | } 2348 | 2349 | if (stat(dsp_slave_path, &stat_buf)) 2350 | fatal_e(-errno, "failed to stat %s", dsp_slave_path); 2351 | if (!S_ISREG(stat_buf.st_mode) || !(stat_buf.st_mode & 0444)) 2352 | fatal("%s is not executable", dsp_slave_path); 2353 | 2354 | /* allocate tables */ 2355 | os_id_bitmap = calloc(BITS_TO_LONGS(max_streams), sizeof(long)); 2356 | mixer_tbl = calloc(hashtbl_size, sizeof(mixer_tbl[0])); 2357 | os_tbl = calloc(hashtbl_size, sizeof(os_tbl[0])); 2358 | os_pgrp_tbl = calloc(hashtbl_size, sizeof(os_pgrp_tbl[0])); 2359 | os_notify_tbl = calloc(hashtbl_size, sizeof(os_notify_tbl[0])); 2360 | if (!os_id_bitmap || !mixer_tbl || !os_tbl || !os_pgrp_tbl || 2361 | !os_notify_tbl) 2362 | fatal("failed to allocate stream hash tables"); 2363 | for (u = 0; u < hashtbl_size; u++) { 2364 | INIT_LIST_HEAD(&mixer_tbl[u]); 2365 | INIT_LIST_HEAD(&os_tbl[u]); 2366 | INIT_LIST_HEAD(&os_pgrp_tbl[u]); 2367 | INIT_LIST_HEAD(&os_notify_tbl[u]); 2368 | } 2369 | __set_bit(0, os_id_bitmap); /* don't use id 0 */ 2370 | 2371 | /* create mixer delayed reference worker */ 2372 | ret = -pthread_create(&mixer_delayed_put_thread, NULL, 2373 | mixer_delayed_put_worker, NULL); 2374 | if (ret) 2375 | fatal_e(ret, "failed to create mixer delayed put worker"); 2376 | 2377 | /* if exit_on_idle, touch mixer for pgrp0 */ 2378 | exit_on_idle = param.exit_on_idle; 2379 | if (exit_on_idle) { 2380 | struct ossp_mixer *mixer; 2381 | 2382 | mixer = get_mixer(0); 2383 | if (!mixer) 2384 | fatal("failed to touch idle mixer"); 2385 | put_mixer(mixer); 2386 | } 2387 | 2388 | /* create notify epoll and kick off watcher thread */ 2389 | notify_epfd = epoll_create(max_streams); 2390 | if (notify_epfd < 0) 2391 | fatal_e(-errno, "failed to create notify epoll"); 2392 | if (fcntl(notify_epfd, F_SETFD, FD_CLOEXEC) < 0) 2393 | fatal_e(-errno, "failed to set CLOEXEC on notify epfd"); 2394 | 2395 | ret = -pthread_create(¬ify_poller_thread, NULL, notify_poller, NULL); 2396 | if (ret) 2397 | fatal_e(ret, "failed to create notify poller thread"); 2398 | 2399 | /* create reaper for slave corpses */ 2400 | ret = -pthread_create(&slave_reaper_thread, NULL, slave_reaper, NULL); 2401 | if (ret) 2402 | fatal_e(ret, "failed to create slave reaper thread"); 2403 | 2404 | /* we're set, let's setup fuse structures */ 2405 | if (strlen(param.mixer_name)) 2406 | mixer_se = setup_ossp_cuse(&mixer_ops, param.mixer_name, 2407 | param.mixer_major, param.mixer_minor, 2408 | args.argc, args.argv); 2409 | if (strlen(param.adsp_name)) 2410 | adsp_se = setup_ossp_cuse(&adsp_ops, param.adsp_name, 2411 | param.adsp_major, param.adsp_minor, 2412 | args.argc, args.argv); 2413 | 2414 | dsp_se = setup_ossp_cuse(&dsp_ops, param.dsp_name, 2415 | param.dsp_major, param.dsp_minor, 2416 | args.argc, args.argv); 2417 | if (!dsp_se) 2418 | fatal("can't create dsp, giving up"); 2419 | 2420 | if (mixer_se) 2421 | snprintf(mixer_buf, sizeof(mixer_buf), ", %s (%d:%d)", 2422 | param.mixer_name, param.mixer_major, param.mixer_minor); 2423 | if (adsp_se) 2424 | snprintf(adsp_buf, sizeof(adsp_buf), ", %s (%d:%d)", 2425 | param.adsp_name, param.adsp_major, param.adsp_minor); 2426 | 2427 | info("Creating %s (%d:%d)%s%s", param.dsp_name, param.dsp_major, 2428 | param.dsp_minor, adsp_buf, mixer_buf); 2429 | 2430 | /* start threads for mixer and adsp */ 2431 | if (mixer_se) { 2432 | ret = -pthread_create(&cuse_mixer_thread, NULL, 2433 | cuse_worker, mixer_se); 2434 | if (ret) 2435 | err_e(ret, "failed to create mixer worker"); 2436 | } 2437 | if (adsp_se) { 2438 | ret = -pthread_create(&cuse_adsp_thread, NULL, 2439 | cuse_worker, adsp_se); 2440 | if (ret) 2441 | err_e(ret, "failed to create adsp worker"); 2442 | } 2443 | 2444 | /* run CUSE for /dev/dsp in the main thread */ 2445 | ret = (ssize_t)cuse_worker(dsp_se); 2446 | if (ret < 0) 2447 | fatal("dsp worker failed"); 2448 | return 0; 2449 | } 2450 | -------------------------------------------------------------------------------- /osstest.c: -------------------------------------------------------------------------------- 1 | /* Simple oss testsuite 2 | * 3 | * This file is released under the GPLv2. 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #define MIXERDEV "/dev/mixer" 21 | #define DSPDEV "/dev/dsp" 22 | 23 | /* Test macros */ 24 | 25 | static int errors, success; 26 | static int report_success = 1; 27 | 28 | #define ok(a, b, c...) do { \ 29 | if (!(a)) { \ 30 | fprintf(stderr, "%s@%d test failed (%s): " b "\n", __func__, __LINE__, #a, ##c); \ 31 | ++errors; \ 32 | } else { \ 33 | if (report_success) \ 34 | printf("%s@%d test succeeded (%s)\n", __func__, __LINE__, #a); \ 35 | ++success; \ 36 | } } while (0) 37 | 38 | static int mixerfd, dspfd; 39 | 40 | static int reopen(int blocking) 41 | { 42 | close(dspfd); 43 | if (!blocking) 44 | blocking = O_NDELAY; 45 | else 46 | blocking = 0; 47 | dspfd = open(DSPDEV, O_RDWR|blocking); 48 | return dspfd; 49 | } 50 | 51 | static void test_ro(int fd) 52 | { 53 | int ret; 54 | char buf[1024]; 55 | struct audio_buf_info abi; 56 | memset(buf, 0, sizeof(buf)); 57 | 58 | ret = read(fd, buf, sizeof(buf)); 59 | ok(ret >= 0, "%s", strerror(errno)); 60 | 61 | ret = write(fd, buf, sizeof(buf)); 62 | ok(ret < 0, "read %d bytes", ret); 63 | 64 | ret = ioctl(fd, SNDCTL_DSP_GETISPACE, &abi); 65 | ok(ret >= 0, "%s", strerror(errno)); 66 | 67 | ret = ioctl(fd, SNDCTL_DSP_GETOSPACE, &abi); 68 | ok(ret < 0, "%s", strerror(errno)); 69 | if (ret < 0) 70 | ok(errno == EINVAL, "Invalid errno: %s", strerror(errno)); 71 | } 72 | 73 | static void test_wo(int fd) 74 | { 75 | int ret; 76 | char buf[1024]; 77 | struct audio_buf_info abi; 78 | memset(buf, 0, sizeof(buf)); 79 | 80 | ret = read(fd, buf, sizeof(buf)); 81 | ok(ret < 0, "read %d bytes", ret); 82 | 83 | ret = write(fd, buf, sizeof(buf)); 84 | ok(ret >= 0, "%s", strerror(errno)); 85 | 86 | ret = ioctl(fd, SNDCTL_DSP_GETISPACE, &abi); 87 | ok(ret < 0, "%s", strerror(errno)); 88 | if (ret < 0) 89 | ok(errno == EINVAL, "Invalid errno: %s", strerror(errno)); 90 | 91 | ret = ioctl(fd, SNDCTL_DSP_GETOSPACE, &abi); 92 | ok(ret >= 0, "%s", strerror(errno)); 93 | } 94 | 95 | static void test_rw(int fd) 96 | { 97 | int ret; 98 | char buf[1024]; 99 | struct audio_buf_info abi; 100 | memset(buf, 0, sizeof(buf)); 101 | 102 | ret = read(fd, buf, sizeof(buf)); 103 | ok(ret >= 0, "%s", strerror(errno)); 104 | 105 | ret = write(fd, buf, sizeof(buf)); 106 | ok(ret >= 0, "%s", strerror(errno)); 107 | 108 | ret = ioctl(fd, SNDCTL_DSP_GETISPACE, &abi); 109 | ok(ret >= 0, "%s", strerror(errno)); 110 | 111 | ret = ioctl(fd, SNDCTL_DSP_GETOSPACE, &abi); 112 | ok(ret >= 0, "%s", strerror(errno)); 113 | } 114 | 115 | static void test_open(void) 116 | { 117 | int ro_fd, rw_fd, wo_fd; 118 | 119 | mixerfd = open(MIXERDEV, O_RDONLY|O_NDELAY); 120 | ok(mixerfd >= 0, "%s", strerror(errno)); 121 | 122 | 123 | /* In order to make this work it has to be serialized 124 | * alsa's kernel emulation can only have device open once 125 | * so do some specific smokescreen tests here 126 | * and then open dsp for testing 127 | */ 128 | ro_fd = open(DSPDEV, O_RDONLY); 129 | ok(ro_fd >= 0, "%s", strerror(errno)); 130 | 131 | if (ro_fd >= 0) 132 | test_ro(ro_fd); 133 | 134 | close(ro_fd); 135 | 136 | wo_fd = open(DSPDEV, O_WRONLY); 137 | ok(wo_fd >= 0, "%s", strerror(errno)); 138 | 139 | if (wo_fd >= 0) 140 | test_wo(wo_fd); 141 | 142 | close(wo_fd); 143 | 144 | rw_fd = open(DSPDEV, O_RDWR); 145 | ok(rw_fd >= 0, "%s", strerror(errno)); 146 | 147 | if (rw_fd >= 0) 148 | test_rw(rw_fd); 149 | 150 | dspfd = rw_fd; 151 | } 152 | 153 | static void test_mixer(void) 154 | { 155 | int ret; 156 | struct mixer_info info; 157 | memset(&info, 0, sizeof(info)); 158 | 159 | ret = ioctl(mixerfd, SOUND_MIXER_INFO, &info); 160 | ok(ret >= 0, "%s", strerror(errno)); 161 | if (ret >= 0) { 162 | printf("Mixer id: %s\n", info.id); 163 | printf("Name: %s\n", info.name); 164 | } 165 | } 166 | 167 | static void test_trigger(int fd) 168 | { 169 | int ret, i; 170 | 171 | ret = ioctl(fd, SNDCTL_DSP_GETTRIGGER, &i); 172 | ok(ret == 0, "Returned error %s", strerror(errno)); 173 | ok(i == (PCM_ENABLE_INPUT|PCM_ENABLE_OUTPUT), "i is set to %d", i); 174 | 175 | i = 0; 176 | ret = ioctl(fd, SNDCTL_DSP_SETTRIGGER, &i); 177 | ok(ret == 0, "Returned error %s", strerror(errno)); 178 | ok(i == 0, "Wrong i returned"); 179 | 180 | i = PCM_ENABLE_INPUT|PCM_ENABLE_OUTPUT; 181 | ret = ioctl(fd, SNDCTL_DSP_SETTRIGGER, &i); 182 | ok(ret == 0, "Returned error %s", strerror(errno)); 183 | ok(i == (PCM_ENABLE_INPUT|PCM_ENABLE_OUTPUT), "i has value %d", i); 184 | 185 | ret = ioctl(fd, SNDCTL_DSP_POST, NULL); 186 | ok(ret == 0, "Returned error %s", strerror(errno)); 187 | } 188 | 189 | static void test_mmap(int fd) 190 | { 191 | char *area; 192 | int ret; 193 | char buf[24]; 194 | 195 | area = mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); 196 | ok(area != MAP_FAILED, "Failed to map: %s\n", strerror(errno)); 197 | 198 | if (area == MAP_FAILED) 199 | return; 200 | 201 | ret = write(fd, &buf, sizeof(buf)); 202 | ok(ret == -1, "write after mmap returned %i\n", ret); 203 | if (ret == -1) 204 | ok(errno == ENXIO, "Error returned is %s\n", strerror(errno)); 205 | 206 | munmap(area, 8192); 207 | } 208 | 209 | static void test_notify(int fd) 210 | { 211 | struct audio_buf_info bi; 212 | char *bytes = NULL; 213 | int ret, written; 214 | struct pollfd pfd = { fd, POLLOUT }; 215 | int rounds = 20; 216 | 217 | ioctl(fd, SNDCTL_DSP_GETOSPACE, &bi); 218 | 219 | bytes = calloc(1, bi.fragsize); 220 | written = 0; 221 | ok(0, "Fragsize: %i, bytes: %i\n", bi.fragsize, bi.bytes); 222 | while (written + bi.fragsize - 1 < bi.bytes) 223 | { 224 | ret = write(fd, bytes, bi.fragsize); 225 | ok(ret == bi.fragsize, "Returned: %i instead of %i\n", 226 | ret, bi.fragsize); 227 | if (ret > 0) 228 | written += ret; 229 | }; 230 | 231 | while (rounds--) 232 | { 233 | ret = poll(&pfd, 1, -1); 234 | ok(ret > 0, "Poll returned %i\n", ret); 235 | if (ret < 0) 236 | break; 237 | ret = write(fd, bytes, bi.fragsize); 238 | if (ret < 0) ret = -errno; 239 | ok(ret == bi.fragsize, "Returned: %i instead of %i\n", 240 | ret, bi.fragsize); 241 | } 242 | } 243 | 244 | int main() 245 | { 246 | test_open(); 247 | if (mixerfd >= 0) 248 | test_mixer(); 249 | 250 | if (reopen(1) >= 0) 251 | test_trigger(dspfd); 252 | 253 | if (reopen(0) >= 0) 254 | test_notify(dspfd); 255 | 256 | if (reopen(1) >= 0) 257 | test_mmap(dspfd); 258 | 259 | close(mixerfd); 260 | close(dspfd); 261 | printf("Tests: %d errors %d success\n", errors, success); 262 | return errors > 127 ? 127 : errors; 263 | } 264 | 265 | --------------------------------------------------------------------------------