diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:28:17 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:28:17 +0000 |
commit | 7a46c07230b8d8108c0e8e80df4522d0ac116538 (patch) | |
tree | d483300dab478b994fe199a5d19d18d74153718a /spa/plugins/support | |
parent | Initial commit. (diff) | |
download | pipewire-7a46c07230b8d8108c0e8e80df4522d0ac116538.tar.xz pipewire-7a46c07230b8d8108c0e8e80df4522d0ac116538.zip |
Adding upstream version 0.3.65.upstream/0.3.65upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | spa/plugins/support/cpu-arm.c | 137 | ||||
-rw-r--r-- | spa/plugins/support/cpu-x86.c | 204 | ||||
-rw-r--r-- | spa/plugins/support/cpu.c | 313 | ||||
-rw-r--r-- | spa/plugins/support/dbus.c | 592 | ||||
-rw-r--r-- | spa/plugins/support/evl-plugin.c | 47 | ||||
-rw-r--r-- | spa/plugins/support/evl-system.c | 460 | ||||
-rw-r--r-- | spa/plugins/support/journal.c | 341 | ||||
-rw-r--r-- | spa/plugins/support/log-patterns.c | 108 | ||||
-rw-r--r-- | spa/plugins/support/log-patterns.h | 13 | ||||
-rw-r--r-- | spa/plugins/support/logger.c | 415 | ||||
-rw-r--r-- | spa/plugins/support/loop.c | 1024 | ||||
-rw-r--r-- | spa/plugins/support/meson.build | 70 | ||||
-rw-r--r-- | spa/plugins/support/node-driver.c | 492 | ||||
-rw-r--r-- | spa/plugins/support/null-audio-sink.c | 1001 | ||||
-rw-r--r-- | spa/plugins/support/plugin.c | 67 | ||||
-rw-r--r-- | spa/plugins/support/system.c | 384 |
16 files changed, 5668 insertions, 0 deletions
diff --git a/spa/plugins/support/cpu-arm.c b/spa/plugins/support/cpu-arm.c new file mode 100644 index 0000000..6cd68d8 --- /dev/null +++ b/spa/plugins/support/cpu-arm.c @@ -0,0 +1,137 @@ +/* Spa + * + * Copyright © 2019 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include <spa/utils/string.h> + +#define MAX_BUFFER 4096 + +static char *get_cpuinfo_line(char *cpuinfo, const char *tag) +{ + char *line, *end, *colon; + + if (!(line = strstr(cpuinfo, tag))) + return NULL; + + if (!(end = strchr(line, '\n'))) + return NULL; + + if (!(colon = strchr(line, ':'))) + return NULL; + + if (++colon >= end) + return NULL; + + return strndup(colon, end - colon); +} + +static int +arm_init(struct impl *impl) +{ + uint32_t flags = 0; + char *cpuinfo, *line, buffer[MAX_BUFFER]; + int arch; + + if (!(cpuinfo = spa_cpu_read_file("/proc/cpuinfo", buffer, sizeof(buffer)))) { + spa_log_warn(impl->log, "%p: Can't read cpuinfo", impl); + return 1; + } + + if ((line = get_cpuinfo_line(cpuinfo, "CPU architecture"))) { + arch = strtoul(line, NULL, 0); + if (arch >= 6) + flags |= SPA_CPU_FLAG_ARMV6; + if (arch >= 8) + flags |= SPA_CPU_FLAG_ARMV8; + + free(line); + } + + if ((line = get_cpuinfo_line(cpuinfo, "Features"))) { + char *state = NULL; + char *current = strtok_r(line, " ", &state); + + do { +#if defined (__aarch64__) + if (spa_streq(current, "asimd")) + flags |= SPA_CPU_FLAG_NEON; + else if (spa_streq(current, "fp")) + flags |= SPA_CPU_FLAG_VFPV3 | SPA_CPU_FLAG_VFP; +#else + if (spa_streq(current, "vfp")) + flags |= SPA_CPU_FLAG_VFP; + else if (spa_streq(current, "neon")) + flags |= SPA_CPU_FLAG_NEON; + else if (spa_streq(current, "vfpv3")) + flags |= SPA_CPU_FLAG_VFPV3; +#endif + } while ((current = strtok_r(NULL, " ", &state))); + + free(line); + } + + impl->flags = flags; + + return 0; +} + + +static int arm_zero_denormals(void *object, bool enable) +{ +#if defined(__aarch64__) + uint64_t cw; + if (enable) + __asm__ __volatile__( + "mrs %0, fpcr \n" + "orr %0, %0, #0x1000000 \n" + "msr fpcr, %0 \n" + "isb \n" + : "=r"(cw)::"memory"); + else + __asm__ __volatile__( + "mrs %0, fpcr \n" + "and %0, %0, #~0x1000000 \n" + "msr fpcr, %0 \n" + "isb \n" + : "=r"(cw)::"memory"); +#elif (defined(__VFP_FP__) && !defined(__SOFTFP__)) + uint32_t cw; + if (enable) + __asm__ __volatile__( + "vmrs %0, fpscr \n" + "orr %0, %0, #0x1000000 \n" + "vmsr fpscr, %0 \n" + : "=r"(cw)::"memory"); + else + __asm__ __volatile__( + "vmrs %0, fpscr \n" + "and %0, %0, #~0x1000000 \n" + "vmsr fpscr, %0 \n" + : "=r"(cw)::"memory"); +#endif + return 0; +} diff --git a/spa/plugins/support/cpu-x86.c b/spa/plugins/support/cpu-x86.c new file mode 100644 index 0000000..722f7c9 --- /dev/null +++ b/spa/plugins/support/cpu-x86.c @@ -0,0 +1,204 @@ +/* Spa + * + * Copyright © 2018 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <cpuid.h> + +static int +x86_init(struct impl *impl) +{ + uint32_t flags; + + unsigned int vendor; + unsigned int model, family; + unsigned int max_level, ext_level, has_osxsave; + unsigned int eax, ebx, ecx, edx; + + + max_level = __get_cpuid_max(0, &vendor); + if (max_level < 1) + return 0; + + __cpuid(1, eax, ebx, ecx, edx); + + model = (eax >> 4) & 0x0f; + family = (eax >> 8) & 0x0f; + + if (vendor == signature_INTEL_ebx || + vendor == signature_AMD_ebx) { + unsigned int extended_model, extended_family; + + extended_model = (eax >> 12) & 0xf0; + extended_family = (eax >> 20) & 0xff; + if (family == 0x0f) { + family += extended_family; + model += extended_model; + } else if (family == 0x06) + model += extended_model; + } + (void)model; + + flags = 0; + if (ecx & bit_SSE3) + flags |= SPA_CPU_FLAG_SSE3; + if (ecx & bit_SSSE3) + flags |= SPA_CPU_FLAG_SSSE3; + if (ecx & bit_SSE4_1) + flags |= SPA_CPU_FLAG_SSE41; + if (ecx & bit_SSE4_2) + flags |= SPA_CPU_FLAG_SSE42; + if (ecx & bit_AVX) + flags |= SPA_CPU_FLAG_AVX; + has_osxsave = ecx & bit_OSXSAVE; + if (ecx & bit_FMA) + flags |= SPA_CPU_FLAG_FMA3; + + if (edx & bit_CMOV) + flags |= SPA_CPU_FLAG_CMOV; + if (edx & bit_MMX) + flags |= SPA_CPU_FLAG_MMX; + if (edx & bit_MMXEXT) + flags |= SPA_CPU_FLAG_MMXEXT; + if (edx & bit_SSE) + flags |= SPA_CPU_FLAG_SSE; + if (edx & bit_SSE2) + flags |= SPA_CPU_FLAG_SSE2; + + + if (max_level >= 7) { + __cpuid_count(7, 0, eax, ebx, ecx, edx); + + if (ebx & bit_BMI) + flags |= SPA_CPU_FLAG_BMI1; + if (ebx & bit_AVX2) + flags |= SPA_CPU_FLAG_AVX2; + if (ebx & bit_BMI2) + flags |= SPA_CPU_FLAG_BMI2; +#define AVX512_BITS (bit_AVX512F | bit_AVX512DQ | bit_AVX512CD | bit_AVX512BW | bit_AVX512VL) + if ((ebx & AVX512_BITS) == AVX512_BITS) + flags |= SPA_CPU_FLAG_AVX512; + } + + /* Check cpuid level of extended features. */ + __cpuid (0x80000000, ext_level, ebx, ecx, edx); + + if (ext_level >= 0x80000001) { + __cpuid (0x80000001, eax, ebx, ecx, edx); + + if (edx & bit_3DNOW) + flags |= SPA_CPU_FLAG_3DNOW; + if (edx & bit_3DNOWP) + flags |= SPA_CPU_FLAG_3DNOWEXT; + if (edx & bit_MMX) + flags |= SPA_CPU_FLAG_MMX; + if (edx & bit_MMXEXT) + flags |= SPA_CPU_FLAG_MMXEXT; + if (ecx & bit_FMA4) + flags |= SPA_CPU_FLAG_FMA4; + if (ecx & bit_XOP) + flags |= SPA_CPU_FLAG_XOP; + } + + /* Get XCR_XFEATURE_ENABLED_MASK register with xgetbv. */ +#define XCR_XFEATURE_ENABLED_MASK 0x0 +#define XSTATE_FP 0x1 +#define XSTATE_SSE 0x2 +#define XSTATE_YMM 0x4 +#define XSTATE_OPMASK 0x20 +#define XSTATE_ZMM 0x40 +#define XSTATE_HI_ZMM 0x80 + +#define XCR_AVX_ENABLED_MASK \ + (XSTATE_SSE | XSTATE_YMM) +#define XCR_AVX512F_ENABLED_MASK \ + (XSTATE_SSE | XSTATE_YMM | XSTATE_OPMASK | XSTATE_ZMM | XSTATE_HI_ZMM) + + if (has_osxsave) + asm (".byte 0x0f; .byte 0x01; .byte 0xd0" + : "=a" (eax), "=d" (edx) + : "c" (XCR_XFEATURE_ENABLED_MASK)); + else + eax = 0; + + /* Check if AVX registers are supported. */ + if ((eax & XCR_AVX_ENABLED_MASK) != XCR_AVX_ENABLED_MASK) { + flags &= ~(SPA_CPU_FLAG_AVX | + SPA_CPU_FLAG_AVX2 | + SPA_CPU_FLAG_FMA3 | + SPA_CPU_FLAG_FMA4 | + SPA_CPU_FLAG_XOP); + } + + /* Check if AVX512F registers are supported. */ + if ((eax & XCR_AVX512F_ENABLED_MASK) != XCR_AVX512F_ENABLED_MASK) { + flags &= ~SPA_CPU_FLAG_AVX512; + } + + if (flags & SPA_CPU_FLAG_AVX512) + impl->max_align = 64; + else if (flags & (SPA_CPU_FLAG_AVX2 | + SPA_CPU_FLAG_AVX | + SPA_CPU_FLAG_XOP | + SPA_CPU_FLAG_FMA4 | + SPA_CPU_FLAG_FMA3)) + impl->max_align = 32; + else if (flags & (SPA_CPU_FLAG_AESNI | + SPA_CPU_FLAG_SSE42 | + SPA_CPU_FLAG_SSE41 | + SPA_CPU_FLAG_SSSE3 | + SPA_CPU_FLAG_SSE3 | + SPA_CPU_FLAG_SSE2 | + SPA_CPU_FLAG_SSE)) + impl->max_align = 16; + else + impl->max_align = 8; + + impl->flags = flags; + + return 0; +} + +#if defined(HAVE_SSE) +#include <xmmintrin.h> +#endif + +static int x86_zero_denormals(void *object, bool enable) +{ +#if defined(HAVE_SSE) + struct impl *impl = object; + if (impl->flags & SPA_CPU_FLAG_SSE) { + unsigned int mxcsr; + mxcsr = _mm_getcsr(); + if (enable) + mxcsr |= 0x8040; + else + mxcsr &= ~0x8040; + _mm_setcsr(mxcsr); + spa_log_debug(impl->log, "%p: zero-denormals:%s", + impl, enable ? "on" : "off"); + } + return 0; +#else + return -ENOTSUP; +#endif +} diff --git a/spa/plugins/support/cpu.c b/spa/plugins/support/cpu.c new file mode 100644 index 0000000..fc13c16 --- /dev/null +++ b/spa/plugins/support/cpu.c @@ -0,0 +1,313 @@ +/* Spa + * + * Copyright © 2018 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <stddef.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <stdio.h> +#include <sched.h> +#include <fcntl.h> + +#if defined(__FreeBSD__) || defined(__MidnightBSD__) +#include <sys/sysctl.h> +#endif + +#include <spa/support/log.h> +#include <spa/support/cpu.h> +#include <spa/support/plugin.h> +#include <spa/utils/type.h> +#include <spa/utils/hook.h> +#include <spa/utils/names.h> +#include <spa/utils/string.h> + +static struct spa_log_topic log_topic = SPA_LOG_TOPIC(0, "spa.cpu"); +#undef SPA_LOG_TOPIC_DEFAULT +#define SPA_LOG_TOPIC_DEFAULT &log_topic + +struct impl { + struct spa_handle handle; + struct spa_cpu cpu; + + struct spa_log *log; + + uint32_t flags; + uint32_t force; + uint32_t count; + uint32_t max_align; + uint32_t vm_type; +}; + +char *spa_cpu_read_file(const char *name, char *buffer, size_t len) +{ + int n, fd; + + if ((fd = open(name, O_RDONLY | O_CLOEXEC, 0)) < 0) + return NULL; + + if ((n = read(fd, buffer, len-1)) < 0) { + close(fd); + return NULL; + } + buffer[n] = '\0'; + close(fd); + return buffer; +} + +# if defined (__i386__) || defined (__x86_64__) +#include "cpu-x86.c" +#define init(t) x86_init(t) +#define impl_cpu_zero_denormals x86_zero_denormals +# elif defined (__arm__) || defined (__aarch64__) +#include "cpu-arm.c" +#define init(t) arm_init(t) +#define impl_cpu_zero_denormals arm_zero_denormals +# else +#define init(t) +#define impl_cpu_zero_denormals NULL +#endif + +static uint32_t +impl_cpu_get_flags(void *object) +{ + struct impl *impl = object; + if (impl->force != SPA_CPU_FORCE_AUTODETECT) + return impl->force; + return impl->flags; +} + +static int +impl_cpu_force_flags(void *object, uint32_t flags) +{ + struct impl *impl = object; + impl->force = flags; + return 0; +} + +#ifndef __FreeBSD__ +static uint32_t get_count(struct impl *this) +{ + cpu_set_t cpuset; + CPU_ZERO(&cpuset); + if (sched_getaffinity(0, sizeof(cpuset), &cpuset) == 0) + return CPU_COUNT(&cpuset); + return 1; +} +#else +static uint32_t get_count(struct impl *this) +{ + static const int mib[] = {CTL_HW, HW_NCPU}; + int r; + size_t rSize = sizeof(r); + if(-1 == sysctl(mib, 2, &r, &rSize, 0, 0)) + return 1; + return r; +} +#endif + +static uint32_t +impl_cpu_get_count(void *object) +{ + struct impl *impl = object; + return impl->count; +} + +static uint32_t +impl_cpu_get_max_align(void *object) +{ + struct impl *impl = object; + return impl->max_align; +} + +static uint32_t +impl_cpu_get_vm_type(void *object) +{ + struct impl *impl = object; + + if (impl->vm_type != 0) + return impl->vm_type; + +#if defined(__i386__) || defined(__x86_64__) || defined(__arm__) || defined(__aarch64__) + static const char *const dmi_vendors[] = { + "/sys/class/dmi/id/product_name", /* Test this before sys_vendor to detect KVM over QEMU */ + "/sys/class/dmi/id/sys_vendor", + "/sys/class/dmi/id/board_vendor", + "/sys/class/dmi/id/bios_vendor" + }; + static const struct { + const char *vendor; + int id; + } dmi_vendor_table[] = { + { "KVM", SPA_CPU_VM_KVM }, + { "QEMU", SPA_CPU_VM_QEMU }, + { "VMware", SPA_CPU_VM_VMWARE }, /* https://kb.vmware.com/s/article/1009458 */ + { "VMW", SPA_CPU_VM_VMWARE }, + { "innotek GmbH", SPA_CPU_VM_ORACLE }, + { "Oracle Corporation", SPA_CPU_VM_ORACLE }, + { "Xen", SPA_CPU_VM_XEN }, + { "Bochs", SPA_CPU_VM_BOCHS }, + { "Parallels", SPA_CPU_VM_PARALLELS }, + /* https://wiki.freebsd.org/bhyve */ + { "BHYVE", SPA_CPU_VM_BHYVE }, + }; + + SPA_FOR_EACH_ELEMENT_VAR(dmi_vendors, dv) { + char buffer[256], *s; + + if ((s = spa_cpu_read_file(*dv, buffer, sizeof(buffer))) == NULL) + continue; + + SPA_FOR_EACH_ELEMENT_VAR(dmi_vendor_table, t) { + if (spa_strstartswith(s, t->vendor)) { + spa_log_debug(impl->log, "Virtualization %s found in DMI (%s)", + s, *dv); + impl->vm_type = t->id; + goto done; + } + } + } +done: +#endif + return impl->vm_type; +} + +static const struct spa_cpu_methods impl_cpu = { + SPA_VERSION_CPU_METHODS, + .get_flags = impl_cpu_get_flags, + .force_flags = impl_cpu_force_flags, + .get_count = impl_cpu_get_count, + .get_max_align = impl_cpu_get_max_align, + .get_vm_type = impl_cpu_get_vm_type, + .zero_denormals = impl_cpu_zero_denormals, +}; + +static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) +{ + struct impl *this; + + spa_return_val_if_fail(handle != NULL, -EINVAL); + spa_return_val_if_fail(interface != NULL, -EINVAL); + + this = (struct impl *) handle; + + if (spa_streq(type, SPA_TYPE_INTERFACE_CPU)) + *interface = &this->cpu; + else + return -ENOENT; + + return 0; +} + +static int impl_clear(struct spa_handle *handle) +{ + return 0; +} + +static size_t +impl_get_size(const struct spa_handle_factory *factory, + const struct spa_dict *params) +{ + return sizeof(struct impl); +} + +static int +impl_init(const struct spa_handle_factory *factory, + struct spa_handle *handle, + const struct spa_dict *info, + const struct spa_support *support, + uint32_t n_support) +{ + struct impl *this; + const char *str; + + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(handle != NULL, -EINVAL); + + handle->get_interface = impl_get_interface; + handle->clear = impl_clear; + + this = (struct impl *) handle; + + this->cpu.iface = SPA_INTERFACE_INIT( + SPA_TYPE_INTERFACE_CPU, + SPA_VERSION_CPU, + &impl_cpu, this); + + this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); + spa_log_topic_init(this->log, &log_topic); + + this->flags = 0; + this->force = SPA_CPU_FORCE_AUTODETECT; + this->max_align = 16; + this->count = get_count(this); + init(this); + + if (info) { + if ((str = spa_dict_lookup(info, SPA_KEY_CPU_FORCE)) != NULL) + this->flags = atoi(str); + if ((str = spa_dict_lookup(info, SPA_KEY_CPU_VM_TYPE)) != NULL) + this->vm_type = atoi(str); + if ((str = spa_dict_lookup(info, SPA_KEY_CPU_ZERO_DENORMALS)) != NULL) + spa_cpu_zero_denormals(&this->cpu, spa_atob(str)); + } + + spa_log_debug(this->log, "%p: count:%d align:%d flags:%08x", + this, this->count, this->max_align, this->flags); + + return 0; +} + +static const struct spa_interface_info impl_interfaces[] = { + {SPA_TYPE_INTERFACE_CPU,}, +}; + +static int +impl_enum_interface_info(const struct spa_handle_factory *factory, + const struct spa_interface_info **info, + uint32_t *index) +{ + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(info != NULL, -EINVAL); + spa_return_val_if_fail(index != NULL, -EINVAL); + + switch (*index) { + case 0: + *info = &impl_interfaces[*index]; + break; + default: + return 0; + } + (*index)++; + + return 1; +} + +const struct spa_handle_factory spa_support_cpu_factory = { + SPA_VERSION_HANDLE_FACTORY, + SPA_NAME_SUPPORT_CPU, + NULL, + impl_get_size, + impl_init, + impl_enum_interface_info, +}; diff --git a/spa/plugins/support/dbus.c b/spa/plugins/support/dbus.c new file mode 100644 index 0000000..a6b5e58 --- /dev/null +++ b/spa/plugins/support/dbus.c @@ -0,0 +1,592 @@ +/* PipeWire + * + * Copyright © 2018 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <string.h> +#include <stdio.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> + +#include <dbus/dbus.h> + +#include <spa/utils/result.h> +#include <spa/utils/type.h> +#include <spa/utils/names.h> +#include <spa/utils/string.h> +#include <spa/support/log.h> +#include <spa/support/plugin.h> +#include <spa/support/dbus.h> + +static struct spa_log_topic log_topic = SPA_LOG_TOPIC(0, "spa.dbus"); +#undef SPA_LOG_TOPIC_DEFAULT +#define SPA_LOG_TOPIC_DEFAULT &log_topic + +struct impl { + struct spa_handle handle; + struct spa_dbus dbus; + + struct spa_log *log; + struct spa_loop_utils *utils; + + struct spa_list connection_list; +}; + +struct source_data { + struct spa_list link; + struct spa_source *source; + struct connection *conn; +}; + +#define connection_emit(c,m,v,...) spa_hook_list_call(&c->listener_list, struct spa_dbus_connection_events, m, v, ##__VA_ARGS__) +#define connection_emit_destroy(c) connection_emit(c, destroy, 0) +#define connection_emit_disconnected(c) connection_emit(c, disconnected, 0) + +struct connection { + struct spa_list link; + + struct spa_dbus_connection this; + struct impl *impl; + enum spa_dbus_type type; + DBusConnection *conn; + struct spa_source *dispatch_event; + struct spa_list source_list; + + struct spa_hook_list listener_list; +}; + +static void source_data_free(void *data) +{ + struct source_data *d = data; + struct connection *conn = d->conn; + struct impl *impl = conn->impl; + + spa_list_remove(&d->link); + spa_loop_utils_destroy_source(impl->utils, d->source); + free(d); +} + +static void dispatch_cb(void *userdata) +{ + struct connection *conn = userdata; + struct impl *impl = conn->impl; + + if (dbus_connection_dispatch(conn->conn) == DBUS_DISPATCH_COMPLETE) + spa_loop_utils_enable_idle(impl->utils, conn->dispatch_event, false); +} + +static void dispatch_status(DBusConnection *conn, DBusDispatchStatus status, void *userdata) +{ + struct connection *c = userdata; + struct impl *impl = c->impl; + + spa_loop_utils_enable_idle(impl->utils, c->dispatch_event, + status == DBUS_DISPATCH_COMPLETE ? false : true); +} + +static inline uint32_t dbus_to_io(DBusWatch *watch) +{ + uint32_t mask; + unsigned int flags; + + /* no watch flags for disabled watches */ + if (!dbus_watch_get_enabled(watch)) + return 0; + + flags = dbus_watch_get_flags(watch); + mask = SPA_IO_HUP | SPA_IO_ERR; + + if (flags & DBUS_WATCH_READABLE) + mask |= SPA_IO_IN; + if (flags & DBUS_WATCH_WRITABLE) + mask |= SPA_IO_OUT; + + return mask; +} + +static inline unsigned int io_to_dbus(uint32_t mask) +{ + unsigned int flags = 0; + + if (mask & SPA_IO_IN) + flags |= DBUS_WATCH_READABLE; + if (mask & SPA_IO_OUT) + flags |= DBUS_WATCH_WRITABLE; + if (mask & SPA_IO_HUP) + flags |= DBUS_WATCH_HANGUP; + if (mask & SPA_IO_ERR) + flags |= DBUS_WATCH_ERROR; + return flags; +} + +static void +handle_io_event(void *userdata, int fd, uint32_t mask) +{ + DBusWatch *watch = userdata; + + if (!dbus_watch_get_enabled(watch)) { + fprintf(stderr, "Asked to handle disabled watch: %p %i", (void *) watch, fd); + return; + } + dbus_watch_handle(watch, io_to_dbus(mask)); +} + +static dbus_bool_t add_watch(DBusWatch *watch, void *userdata) +{ + struct connection *conn = userdata; + struct impl *impl = conn->impl; + struct source_data *data; + + spa_log_debug(impl->log, "add watch %p %d", watch, dbus_watch_get_unix_fd(watch)); + + data = calloc(1, sizeof(struct source_data)); + data->conn = conn; + /* we dup because dbus tends to add the same fd multiple times and our epoll + * implementation does not like that */ + data->source = spa_loop_utils_add_io(impl->utils, + dup(dbus_watch_get_unix_fd(watch)), + dbus_to_io(watch), true, handle_io_event, watch); + spa_list_append(&conn->source_list, &data->link); + + dbus_watch_set_data(watch, data, source_data_free); + return TRUE; +} + +static void remove_watch(DBusWatch *watch, void *userdata) +{ + struct connection *conn = userdata; + struct impl *impl = conn->impl; + spa_log_debug(impl->log, "remove watch %p", watch); + dbus_watch_set_data(watch, NULL, NULL); +} + +static void toggle_watch(DBusWatch *watch, void *userdata) +{ + struct connection *conn = userdata; + struct impl *impl = conn->impl; + struct source_data *data; + + spa_log_debug(impl->log, "toggle watch %p", watch); + + if ((data = dbus_watch_get_data(watch)) == NULL) + return; + + spa_loop_utils_update_io(impl->utils, data->source, dbus_to_io(watch)); +} + +static void +handle_timer_event(void *userdata, uint64_t expirations) +{ + DBusTimeout *timeout = userdata; + uint64_t t; + struct timespec ts; + struct source_data *data; + struct connection *conn; + struct impl *impl; + + if ((data = dbus_timeout_get_data(timeout)) == NULL) + return; + + conn = data->conn; + impl = conn->impl; + + spa_log_debug(impl->log, "timeout %p conn:%p impl:%p", timeout, conn, impl); + + if (dbus_timeout_get_enabled(timeout)) { + t = dbus_timeout_get_interval(timeout) * SPA_NSEC_PER_MSEC; + ts.tv_sec = t / SPA_NSEC_PER_SEC; + ts.tv_nsec = t % SPA_NSEC_PER_SEC; + spa_loop_utils_update_timer(impl->utils, + data->source, &ts, NULL, false); + dbus_timeout_handle(timeout); + } +} + +static dbus_bool_t add_timeout(DBusTimeout *timeout, void *userdata) +{ + struct connection *conn = userdata; + struct impl *impl = conn->impl; + struct timespec ts; + struct source_data *data; + uint64_t t; + + if (!dbus_timeout_get_enabled(timeout)) + return FALSE; + + spa_log_debug(impl->log, "add timeout %p conn:%p impl:%p", timeout, conn, impl); + + data = calloc(1, sizeof(struct source_data)); + data->conn = conn; + data->source = spa_loop_utils_add_timer(impl->utils, handle_timer_event, timeout); + spa_list_append(&conn->source_list, &data->link); + + dbus_timeout_set_data(timeout, data, source_data_free); + + t = dbus_timeout_get_interval(timeout) * SPA_NSEC_PER_MSEC; + ts.tv_sec = t / SPA_NSEC_PER_SEC; + ts.tv_nsec = t % SPA_NSEC_PER_SEC; + spa_loop_utils_update_timer(impl->utils, data->source, &ts, NULL, false); + + return TRUE; +} + +static void remove_timeout(DBusTimeout *timeout, void *userdata) +{ + struct connection *conn = userdata; + struct impl *impl = conn->impl; + spa_log_debug(impl->log, "remove timeout %p conn:%p impl:%p", timeout, conn, impl); + dbus_timeout_set_data(timeout, NULL, NULL); +} + +static void toggle_timeout(DBusTimeout *timeout, void *userdata) +{ + struct connection *conn = userdata; + struct impl *impl = conn->impl; + struct source_data *data; + struct timespec ts, *tsp; + + if ((data = dbus_timeout_get_data(timeout)) == NULL) + return; + + spa_log_debug(impl->log, "toggle timeout %p conn:%p impl:%p", timeout, conn, impl); + + if (dbus_timeout_get_enabled(timeout)) { + uint64_t t = dbus_timeout_get_interval(timeout) * SPA_NSEC_PER_MSEC; + ts.tv_sec = t / SPA_NSEC_PER_SEC; + ts.tv_nsec = t % SPA_NSEC_PER_SEC; + tsp = &ts; + } else { + tsp = NULL; + } + spa_loop_utils_update_timer(impl->utils, data->source, tsp, NULL, false); +} + +static void wakeup_main(void *userdata) +{ + struct connection *this = userdata; + struct impl *impl = this->impl; + + spa_loop_utils_enable_idle(impl->utils, this->dispatch_event, true); +} + +static void connection_close(struct connection *this); + +static DBusHandlerResult filter_message (DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + struct connection *this = user_data; + struct impl *impl = this->impl; + + if (dbus_message_is_signal(message, DBUS_INTERFACE_LOCAL, "Disconnected")) { + spa_log_debug(impl->log, "dbus connection %p disconnected", this); + connection_close(this); + connection_emit_disconnected(this); + } + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static const char *type_to_string(enum spa_dbus_type type) { + switch (type) { + case SPA_DBUS_TYPE_SESSION: return "session"; + case SPA_DBUS_TYPE_SYSTEM: return "system"; + case SPA_DBUS_TYPE_STARTER: return "starter"; + default: return "unknown"; + } +} + +static void * +impl_connection_get(struct spa_dbus_connection *conn) +{ + struct connection *this = SPA_CONTAINER_OF(conn, struct connection, this); + struct impl *impl = this->impl; + DBusError error; + + if (this->conn != NULL) + return this->conn; + + dbus_error_init(&error); + + this->conn = dbus_bus_get_private((DBusBusType)this->type, &error); + if (this->conn == NULL) + goto error; + + dbus_connection_set_exit_on_disconnect(this->conn, false); + if (!dbus_connection_add_filter(this->conn, filter_message, this, NULL)) + goto error_filter; + + dbus_connection_set_dispatch_status_function(this->conn, dispatch_status, this, NULL); + dbus_connection_set_watch_functions(this->conn, add_watch, remove_watch, toggle_watch, this, + NULL); + dbus_connection_set_timeout_functions(this->conn, add_timeout, remove_timeout, + toggle_timeout, this, NULL); + dbus_connection_set_wakeup_main_function(this->conn, wakeup_main, this, NULL); + + return this->conn; + +error: + spa_log_error(impl->log, "Failed to connect to %s bus: %s", type_to_string(this->type), error.message); + dbus_error_free(&error); + errno = ECONNREFUSED; + return NULL; +error_filter: + spa_log_error(impl->log, "Failed to create filter"); + dbus_connection_close(this->conn); + dbus_connection_unref(this->conn); + this->conn = NULL; + errno = ENOMEM; + return NULL; +} + + +static void connection_close(struct connection *this) +{ + if (this->conn) { + dbus_connection_remove_filter(this->conn, filter_message, this); + dbus_connection_close(this->conn); + + /* Someone may still hold a ref to the handle from get(), so the + * unref below may not be the final one. For that case, reset + * all callbacks we defined to be sure they are not called. */ + dbus_connection_set_dispatch_status_function(this->conn, NULL, NULL, NULL); + dbus_connection_set_watch_functions(this->conn, NULL, NULL, NULL, NULL, NULL); + dbus_connection_set_timeout_functions(this->conn, NULL, NULL, NULL, NULL, NULL); + dbus_connection_set_wakeup_main_function(this->conn, NULL, NULL, NULL); + + dbus_connection_unref(this->conn); + } + this->conn = NULL; +} + +static void connection_free(struct connection *conn) +{ + struct impl *impl = conn->impl; + struct source_data *data; + + spa_list_remove(&conn->link); + + connection_close(conn); + + spa_list_consume(data, &conn->source_list, link) + source_data_free(data); + + spa_loop_utils_destroy_source(impl->utils, conn->dispatch_event); + + spa_hook_list_clean(&conn->listener_list); + + free(conn); +} + +static void +impl_connection_destroy(struct spa_dbus_connection *conn) +{ + struct connection *this = SPA_CONTAINER_OF(conn, struct connection, this); + struct impl *impl = this->impl; + + connection_emit_destroy(this); + + spa_log_debug(impl->log, "destroy conn %p", this); + connection_free(this); +} + +static void +impl_connection_add_listener(struct spa_dbus_connection *conn, + struct spa_hook *listener, + const struct spa_dbus_connection_events *events, + void *data) +{ + struct connection *this = SPA_CONTAINER_OF(conn, struct connection, this); + spa_hook_list_append(&this->listener_list, listener, events, data); +} + +static const struct spa_dbus_connection impl_connection = { + SPA_VERSION_DBUS_CONNECTION, + impl_connection_get, + impl_connection_destroy, + impl_connection_add_listener, +}; + +static struct spa_dbus_connection * +impl_get_connection(void *object, + enum spa_dbus_type type) +{ + struct impl *impl = object; + struct connection *conn; + int res; + + conn = calloc(1, sizeof(struct connection)); + conn->this = impl_connection; + conn->impl = impl; + conn->type = type; + conn->dispatch_event = spa_loop_utils_add_idle(impl->utils, + false, dispatch_cb, conn); + if (conn->dispatch_event == NULL) + goto no_event; + + spa_list_init(&conn->source_list); + spa_hook_list_init(&conn->listener_list); + + spa_list_append(&impl->connection_list, &conn->link); + + spa_log_debug(impl->log, "new conn %p", conn); + + return &conn->this; + +no_event: + res = -errno; + spa_log_error(impl->log, "Failed to create idle event: %m"); + free(conn); + errno = -res; + return NULL; +} + +static const struct spa_dbus_methods impl_dbus = { + SPA_VERSION_DBUS_METHODS, + .get_connection = impl_get_connection, +}; + +static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) +{ + struct impl *this; + + spa_return_val_if_fail(handle != NULL, -EINVAL); + spa_return_val_if_fail(interface != NULL, -EINVAL); + + this = (struct impl *) handle; + + if (spa_streq(type, SPA_TYPE_INTERFACE_DBus)) + *interface = &this->dbus; + else + return -ENOENT; + + return 0; +} + +static int impl_clear(struct spa_handle *handle) +{ + struct impl *impl = (struct impl *) handle; + struct connection *conn; + + spa_return_val_if_fail(handle != NULL, -EINVAL); + + spa_list_consume(conn, &impl->connection_list, link) + connection_free(conn); + return 0; +} + +static size_t +impl_get_size(const struct spa_handle_factory *factory, + const struct spa_dict *params) +{ + return sizeof(struct impl); +} + +static int +impl_init(const struct spa_handle_factory *factory, + struct spa_handle *handle, + const struct spa_dict *info, + const struct spa_support *support, + uint32_t n_support) +{ + struct impl *this; + + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(handle != NULL, -EINVAL); + + handle->get_interface = impl_get_interface; + handle->clear = impl_clear; + + this = (struct impl *) handle; + spa_list_init(&this->connection_list); + + this->dbus.iface = SPA_INTERFACE_INIT( + SPA_TYPE_INTERFACE_DBus, + SPA_VERSION_DBUS, + &impl_dbus, this); + + this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); + spa_log_topic_init(this->log, &log_topic); + + this->utils = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_LoopUtils); + + if (this->utils == NULL) { + spa_log_error(this->log, "a LoopUtils is needed"); + return -EINVAL; + } + + spa_log_debug(this->log, "%p: initialized", this); + + return 0; +} + +static const struct spa_interface_info impl_interfaces[] = { + {SPA_TYPE_INTERFACE_DBus,}, +}; + +static int +impl_enum_interface_info(const struct spa_handle_factory *factory, + const struct spa_interface_info **info, + uint32_t *index) +{ + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(info != NULL, -EINVAL); + spa_return_val_if_fail(index != NULL, -EINVAL); + + switch (*index) { + case 0: + *info = &impl_interfaces[*index]; + break; + default: + return 0; + } + (*index)++; + + return 1; +} + +static const struct spa_handle_factory dbus_factory = { + SPA_VERSION_HANDLE_FACTORY, + SPA_NAME_SUPPORT_DBUS, + NULL, + impl_get_size, + impl_init, + impl_enum_interface_info, +}; + +SPA_EXPORT +int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index) +{ + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(index != NULL, -EINVAL); + + switch (*index) { + case 0: + *factory = &dbus_factory; + break; + default: + return 0; + } + (*index)++; + return 1; +} diff --git a/spa/plugins/support/evl-plugin.c b/spa/plugins/support/evl-plugin.c new file mode 100644 index 0000000..e140cf7 --- /dev/null +++ b/spa/plugins/support/evl-plugin.c @@ -0,0 +1,47 @@ +/* Spa Support plugin + * + * Copyright © 2019 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <errno.h> +#include <stdio.h> + +#include <spa/support/plugin.h> + +extern const struct spa_handle_factory spa_support_evl_system_factory; + +SPA_EXPORT +int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index) +{ + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(index != NULL, -EINVAL); + + switch (*index) { + case 0: + *factory = &spa_support_evl_system_factory; + break; + default: + return 0; + } + (*index)++; + return 1; +} diff --git a/spa/plugins/support/evl-system.c b/spa/plugins/support/evl-system.c new file mode 100644 index 0000000..58130a0 --- /dev/null +++ b/spa/plugins/support/evl-system.c @@ -0,0 +1,460 @@ +/* Spa + * + * Copyright © 2019 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <unistd.h> +#include <errno.h> +#include <sys/types.h> +#include <signal.h> +#include <stdlib.h> +#include <stdio.h> +#include <fcntl.h> +#include <sys/eventfd.h> +#include <sys/signalfd.h> + +#include <evl/evl.h> +#include <evl/timer.h> + +#include <spa/support/log.h> +#include <spa/support/system.h> +#include <spa/support/plugin.h> +#include <spa/utils/type.h> +#include <spa/utils/result.h> +#include <spa/utils/string.h> + +#define NAME "evl-system" + +#define MAX_POLL 512 + +struct poll_entry { + int pfd; + int fd; + uint32_t events; + void *data; +}; + +struct impl { + struct spa_handle handle; + struct spa_system system; + + struct spa_log *log; + + struct poll_entry entries[MAX_POLL]; + uint32_t n_entries; + + uint32_t n_xbuf; + int attached; + int pid; +}; + +static ssize_t impl_read(void *object, int fd, void *buf, size_t count) +{ + return oob_read(fd, buf, count); +} + +static ssize_t impl_write(void *object, int fd, const void *buf, size_t count) +{ + return oob_write(fd, buf, count); +} + +static int impl_ioctl(void *object, int fd, unsigned long request, ...) +{ + int res; + va_list ap; + long arg; + + va_start(ap, request); + arg = va_arg(ap, long); + res = oob_ioctl(fd, request, arg); + va_end(ap); + + return res; +} + +static int impl_close(void *object, int fd) +{ + return close(fd); +} + +static inline int clock_id_to_evl(int clockid) +{ + switch(clockid) { + case CLOCK_MONOTONIC: + return EVL_CLOCK_MONOTONIC; + case CLOCK_REALTIME: + return EVL_CLOCK_REALTIME; + default: + return -clockid; + } +} + +/* clock */ +static int impl_clock_gettime(void *object, + int clockid, struct timespec *value) +{ + return evl_read_clock(clock_id_to_evl(clockid), value); +} + +static int impl_clock_getres(void *object, + int clockid, struct timespec *res) +{ + return evl_get_clock_resolution(clock_id_to_evl(clockid), res); +} + +/* poll */ +static int impl_pollfd_create(void *object, int flags) +{ + int retval; + retval = evl_new_poll(); + return retval; +} + +static inline struct poll_entry *find_entry(struct impl *impl, int pfd, int fd) +{ + uint32_t i; + for (i = 0; i < impl->n_entries; i++) { + struct poll_entry *e = &impl->entries[i]; + if (e->pfd == pfd && e->fd == fd) + return e; + } + return NULL; +} + +static int impl_pollfd_add(void *object, int pfd, int fd, uint32_t events, void *data) +{ + struct impl *impl = object; + struct poll_entry *e; + + if (impl->n_entries == MAX_POLL) + return -ENOSPC; + + e = &impl->entries[impl->n_entries++]; + e->pfd = pfd; + e->fd = fd; + e->events = events; + e->data = data; + return evl_add_pollfd(pfd, fd, e->events); +} + +static int impl_pollfd_mod(void *object, int pfd, int fd, uint32_t events, void *data) +{ + struct impl *impl = object; + struct poll_entry *e; + + e = find_entry(impl, pfd, fd); + if (e == NULL) + return -ENOENT; + + e->events = events; + e->data = data; + return evl_mod_pollfd(pfd, fd, e->events); +} + +static int impl_pollfd_del(void *object, int pfd, int fd) +{ + struct impl *impl = object; + struct poll_entry *e; + + e = find_entry(impl, pfd, fd); + if (e == NULL) + return -ENOENT; + + e->pfd = -1; + e->fd = -1; + return evl_del_pollfd(pfd, fd); +} + +static int impl_pollfd_wait(void *object, int pfd, + struct spa_poll_event *ev, int n_ev, int timeout) +{ + struct impl *impl = object; + struct evl_poll_event pollset[n_ev]; + struct timespec tv; + int i, j, res; + + if (impl->attached == 0) { + res = evl_attach_self("evl-thread-%d-%p", impl->pid, impl); + if (res < 0) + return res; + impl->attached = res; + } + + if (timeout == -1) { + tv.tv_sec = 0; + tv.tv_nsec = 0; + } else { + tv.tv_sec = timeout / SPA_MSEC_PER_SEC; + tv.tv_nsec = (timeout % SPA_MSEC_PER_SEC) * SPA_NSEC_PER_MSEC; + } + res = evl_timedpoll(pfd, pollset, n_ev, &tv); + if (SPA_UNLIKELY(res < 0)) + return res; + + for (i = 0, j = 0; i < res; i++) { + struct poll_entry *e; + + e = find_entry(impl, pfd, pollset[i].fd); + if (e == NULL) + continue; + + ev[j].events = pollset[i].events; + ev[j].data = e->data; + j++; + } + return j; +} + +/* timers */ +static int impl_timerfd_create(void *object, int clockid, int flags) +{ + int cid; + + switch (clockid) { + case CLOCK_MONOTONIC: + cid = EVL_CLOCK_MONOTONIC; + break; + default: + return -ENOTSUP; + } + return evl_new_timer(cid); +} + +static int impl_timerfd_settime(void *object, + int fd, int flags, + const struct itimerspec *new_value, + struct itimerspec *old_value) +{ + struct itimerspec val = *new_value; + + if (!(flags & SPA_FD_TIMER_ABSTIME)) { + struct timespec now; + + evl_read_clock(EVL_CLOCK_MONOTONIC, &now); + val.it_value.tv_sec += now.tv_sec; + val.it_value.tv_nsec += now.tv_nsec; + if (val.it_value.tv_nsec >= 1000000000) { + val.it_value.tv_sec++; + val.it_value.tv_nsec -= 1000000000; + } + } + return evl_set_timer(fd, &val, old_value); +} + +static int impl_timerfd_gettime(void *object, + int fd, struct itimerspec *curr_value) +{ + return evl_get_timer(fd, curr_value); + +} +static int impl_timerfd_read(void *object, int fd, uint64_t *expirations) +{ + uint32_t ticks; + if (oob_read(fd, &ticks, sizeof(ticks)) != sizeof(ticks)) + return -errno; + *expirations = ticks; + return 0; +} + +/* events */ +static int impl_eventfd_create(void *object, int flags) +{ + struct impl *impl = object; + int res; + + res = evl_new_xbuf(1024, 1024, "xbuf-%d-%p-%d", impl->pid, impl, impl->n_xbuf); + if (res < 0) + return res; + + impl->n_xbuf++; + + if (flags & SPA_FD_NONBLOCK) + fcntl(res, F_SETFL, fcntl(res, F_GETFL) | O_NONBLOCK); + + return res; +} + +static int impl_eventfd_write(void *object, int fd, uint64_t count) +{ + if (write(fd, &count, sizeof(uint64_t)) != sizeof(uint64_t)) + return -errno; + return 0; +} + +static int impl_eventfd_read(void *object, int fd, uint64_t *count) +{ + if (oob_read(fd, count, sizeof(uint64_t)) != sizeof(uint64_t)) + return -errno; + return 0; +} + +/* signals */ +static int impl_signalfd_create(void *object, int signal, int flags) +{ + sigset_t mask; + int res, fl = 0; + + if (flags & SPA_FD_CLOEXEC) + fl |= SFD_CLOEXEC; + if (flags & SPA_FD_NONBLOCK) + fl |= SFD_NONBLOCK; + + sigemptyset(&mask); + sigaddset(&mask, signal); + res = signalfd(-1, &mask, fl); + sigprocmask(SIG_BLOCK, &mask, NULL); + + return res; +} + +static int impl_signalfd_read(void *object, int fd, int *signal) +{ + struct signalfd_siginfo signal_info; + int len; + + len = read(fd, &signal_info, sizeof signal_info); + if (!(len == -1 && errno == EAGAIN) && len != sizeof signal_info) + return -errno; + + *signal = signal_info.ssi_signo; + + return 0; +} + +static const struct spa_system_methods impl_system = { + SPA_VERSION_SYSTEM_METHODS, + .read = impl_read, + .write = impl_write, + .ioctl = impl_ioctl, + .close = impl_close, + .clock_gettime = impl_clock_gettime, + .clock_getres = impl_clock_getres, + .pollfd_create = impl_pollfd_create, + .pollfd_add = impl_pollfd_add, + .pollfd_mod = impl_pollfd_mod, + .pollfd_del = impl_pollfd_del, + .pollfd_wait = impl_pollfd_wait, + .timerfd_create = impl_timerfd_create, + .timerfd_settime = impl_timerfd_settime, + .timerfd_gettime = impl_timerfd_gettime, + .timerfd_read = impl_timerfd_read, + .eventfd_create = impl_eventfd_create, + .eventfd_write = impl_eventfd_write, + .eventfd_read = impl_eventfd_read, + .signalfd_create = impl_signalfd_create, + .signalfd_read = impl_signalfd_read, +}; + +static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) +{ + struct impl *impl; + + spa_return_val_if_fail(handle != NULL, -EINVAL); + spa_return_val_if_fail(interface != NULL, -EINVAL); + + impl = (struct impl *) handle; + + if (spa_streq(type, SPA_TYPE_INTERFACE_System)) + *interface = &impl->system; + else + return -ENOENT; + + return 0; +} + +static int impl_clear(struct spa_handle *handle) +{ + spa_return_val_if_fail(handle != NULL, -EINVAL); + return 0; +} + +static size_t +impl_get_size(const struct spa_handle_factory *factory, + const struct spa_dict *params) +{ + return sizeof(struct impl); +} + +static int +impl_init(const struct spa_handle_factory *factory, + struct spa_handle *handle, + const struct spa_dict *info, + const struct spa_support *support, + uint32_t n_support) +{ + struct impl *impl; + int res; + + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(handle != NULL, -EINVAL); + + handle->get_interface = impl_get_interface; + handle->clear = impl_clear; + + impl = (struct impl *) handle; + impl->system.iface = SPA_INTERFACE_INIT( + SPA_TYPE_INTERFACE_System, + SPA_VERSION_SYSTEM, + &impl_system, impl); + + impl->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); + + impl->pid = getpid(); + + if ((res = evl_attach_self("evl-system-%d-%p", impl->pid, impl)) < 0) { + spa_log_error(impl->log, NAME " %p: init failed: %s", impl, spa_strerror(res)); + return res; + } + + spa_log_debug(impl->log, NAME " %p: initialized", impl); + + return 0; +} + +static const struct spa_interface_info impl_interfaces[] = { + {SPA_TYPE_INTERFACE_System,}, +}; + +static int +impl_enum_interface_info(const struct spa_handle_factory *factory, + const struct spa_interface_info **info, + uint32_t *index) +{ + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(info != NULL, -EINVAL); + spa_return_val_if_fail(index != NULL, -EINVAL); + + if (*index >= SPA_N_ELEMENTS(impl_interfaces)) + return 0; + + *info = &impl_interfaces[(*index)++]; + return 1; +} + +const struct spa_handle_factory spa_support_evl_system_factory = { + SPA_VERSION_HANDLE_FACTORY, + SPA_NAME_SUPPORT_SYSTEM, + NULL, + impl_get_size, + impl_init, + impl_enum_interface_info +}; diff --git a/spa/plugins/support/journal.c b/spa/plugins/support/journal.c new file mode 100644 index 0000000..e0e58eb --- /dev/null +++ b/spa/plugins/support/journal.c @@ -0,0 +1,341 @@ +/* Spa + * + * Copyright © 2020 Sergey Bugaev + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <stddef.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <stdio.h> +#include <limits.h> +#include <syslog.h> +#include <sys/stat.h> + +#include <spa/support/log.h> +#include <spa/support/plugin.h> +#include <spa/utils/type.h> +#include <spa/utils/names.h> +#include <spa/utils/string.h> + +#include <systemd/sd-journal.h> + +#include "log-patterns.h" + +#define NAME "journal" + +#define DEFAULT_LOG_LEVEL SPA_LOG_LEVEL_INFO + +struct impl { + struct spa_handle handle; + struct spa_log log; + + /* if non-null, we'll additionally forward all logging to there */ + struct spa_log *chain_log; + + struct spa_list patterns; +}; + +static SPA_PRINTF_FUNC(7,0) void +impl_log_logtv(void *object, + enum spa_log_level level, + const struct spa_log_topic *topic, + const char *file, + int line, + const char *func, + const char *fmt, + va_list args) +{ + struct impl *impl = object; + char line_buffer[32]; + char file_buffer[strlen("CODE_FILE=") + strlen(file) + 1]; + char message_buffer[LINE_MAX]; + int priority; + size_t sz = 0; + + if (impl->chain_log != NULL) { + va_list args_copy; + va_copy(args_copy, args); + spa_log_logtv(impl->chain_log, + level, topic, + file, line, func, fmt, args_copy); + va_end(args_copy); + } + + /* convert SPA log level to syslog priority */ + switch (level) { + case SPA_LOG_LEVEL_ERROR: + priority = LOG_ERR; + break; + case SPA_LOG_LEVEL_WARN: + priority = LOG_WARNING; + break; + case SPA_LOG_LEVEL_INFO: + priority = LOG_INFO; + break; + case SPA_LOG_LEVEL_DEBUG: + case SPA_LOG_LEVEL_TRACE: + default: + priority = LOG_DEBUG; + break; + } + + if (topic) + sz = spa_scnprintf(message_buffer, sizeof(message_buffer), + "%s: ", topic->topic); + + /* we'll be using the low-level journal API, which expects us to provide + * the location explicitly. line and file are to be passed as preformatted + * entries, whereas the function name is passed as-is, and converted into + * a field inside sd_journal_send_with_location(). */ + snprintf(line_buffer, sizeof(line_buffer), "CODE_LINE=%d", line); + snprintf(file_buffer, sizeof(file_buffer), "CODE_FILE=%s", file); + vsnprintf(message_buffer + sz, sizeof(message_buffer) - sz, fmt, args); + + sd_journal_send_with_location(file_buffer, line_buffer, func, + "MESSAGE=%s", message_buffer, + "PRIORITY=%i", priority, + NULL); +} + +static SPA_PRINTF_FUNC(6,7) void +impl_log_log(void *object, + enum spa_log_level level, + const char *file, + int line, + const char *func, + const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + impl_log_logtv(object, level, NULL, file, line, func, fmt, args); + va_end(args); +} + +static SPA_PRINTF_FUNC(6,0) void +impl_log_logv(void *object, + enum spa_log_level level, + const char *file, + int line, + const char *func, + const char *fmt, + va_list args) +{ + impl_log_logtv(object, level, NULL, file, line, func, fmt, args); +} + +static SPA_PRINTF_FUNC(7,8) void +impl_log_logt(void *object, + enum spa_log_level level, + const struct spa_log_topic *topic, + const char *file, + int line, + const char *func, + const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + impl_log_logtv(object, level, topic, file, line, func, fmt, args); + va_end(args); +} + +static void +impl_log_topic_init(void *object, struct spa_log_topic *t) +{ + struct impl *impl = object; + enum spa_log_level level = impl->log.level; + + support_log_topic_init(&impl->patterns, level, t); +} + +static const struct spa_log_methods impl_log = { + SPA_VERSION_LOG_METHODS, + .log = impl_log_log, + .logv = impl_log_logv, + .logt = impl_log_logt, + .logtv = impl_log_logtv, + .topic_init = impl_log_topic_init, +}; + +static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) +{ + struct impl *this; + + spa_return_val_if_fail(handle != NULL, -EINVAL); + spa_return_val_if_fail(interface != NULL, -EINVAL); + + this = (struct impl *) handle; + + if (spa_streq(type, SPA_TYPE_INTERFACE_Log)) + *interface = &this->log; + else + return -ENOENT; + + return 0; +} + +static int impl_clear(struct spa_handle *handle) +{ + struct impl *this; + + spa_return_val_if_fail(handle != NULL, -EINVAL); + + this = (struct impl *) handle; + support_log_free_patterns(&this->patterns); + + return 0; +} + +static size_t +impl_get_size(const struct spa_handle_factory *factory, + const struct spa_dict *params) +{ + return sizeof(struct impl); +} + +/** Determine if our stderr goes straight to the journal */ +static int +stderr_is_connected_to_journal(void) +{ + const char *journal_stream; + unsigned long long journal_device, journal_inode; + struct stat stderr_stat; + + /* when a service's stderr is connected to the journal, systemd sets + * JOURNAL_STREAM in the environment of that service to device:inode + * of its stderr. if the variable is not set, clearly our stderr is + * not connected to the journal */ + journal_stream = getenv("JOURNAL_STREAM"); + if (journal_stream == NULL) + return 0; + + /* if it *is* set, that doesn't immediately mean that *our* stderr + * is (still) connected to the journal. to know for sure, we have to + * compare our actual stderr to the stream systemd has created for + * the service we're a part of */ + + if (sscanf(journal_stream, "%llu:%llu", &journal_device, &journal_inode) != 2) + return 0; + + if (fstat(STDERR_FILENO, &stderr_stat) < 0) + return 0; + + return stderr_stat.st_dev == journal_device && stderr_stat.st_ino == journal_inode; +} + +static int +impl_init(const struct spa_handle_factory *factory, + struct spa_handle *handle, + const struct spa_dict *info, + const struct spa_support *support, + uint32_t n_support) +{ + struct impl *impl; + const char *str; + + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(handle != NULL, -EINVAL); + + handle->get_interface = impl_get_interface; + handle->clear = impl_clear; + + impl = (struct impl *) handle; + + impl->log.iface = SPA_INTERFACE_INIT( + SPA_TYPE_INTERFACE_Log, + SPA_VERSION_LOG, + &impl_log, impl); + impl->log.level = DEFAULT_LOG_LEVEL; + + spa_list_init(&impl->patterns); + + if (info) { + if ((str = spa_dict_lookup(info, SPA_KEY_LOG_LEVEL)) != NULL) + impl->log.level = atoi(str); + if ((str = spa_dict_lookup(info, SPA_KEY_LOG_PATTERNS)) != NULL) + support_log_parse_patterns(&impl->patterns, str); + } + + /* if our stderr goes to the journal, there's no point in logging both + * via the native journal API and by printing to stderr, that would just + * result in message duplication */ + if (stderr_is_connected_to_journal()) + impl->chain_log = NULL; + else + impl->chain_log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); + + spa_log_debug(&impl->log, NAME " %p: initialized", impl); + + return 0; +} + +static const struct spa_interface_info impl_interfaces[] = { + {SPA_TYPE_INTERFACE_Log,}, +}; + +static int +impl_enum_interface_info(const struct spa_handle_factory *factory, + const struct spa_interface_info **info, + uint32_t *index) +{ + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(info != NULL, -EINVAL); + spa_return_val_if_fail(index != NULL, -EINVAL); + + switch (*index) { + case 0: + *info = &impl_interfaces[*index]; + break; + default: + return 0; + } + (*index)++; + + return 1; +} + +static const struct spa_handle_factory journal_factory = { + SPA_VERSION_HANDLE_FACTORY, + .name = SPA_NAME_SUPPORT_LOG, + .info = NULL, + .get_size = impl_get_size, + .init = impl_init, + .enum_interface_info = impl_enum_interface_info, +}; + + +SPA_EXPORT +int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index) +{ + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(index != NULL, -EINVAL); + + switch (*index) { + case 0: + *factory = &journal_factory; + break; + default: + return 0; + } + (*index)++; + return 1; +} diff --git a/spa/plugins/support/log-patterns.c b/spa/plugins/support/log-patterns.c new file mode 100644 index 0000000..4a35b1e --- /dev/null +++ b/spa/plugins/support/log-patterns.c @@ -0,0 +1,108 @@ +/* Spa + * + * Copyright © 2021 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <errno.h> +#include <fnmatch.h> + +#include <spa/support/log.h> +#include <spa/utils/list.h> +#include <spa/utils/json.h> + +#include "log-patterns.h" + +struct support_log_pattern { + struct spa_list link; + enum spa_log_level level; + char pattern[]; +}; + +void +support_log_topic_init(struct spa_list *patterns, enum spa_log_level default_level, + struct spa_log_topic *t) +{ + enum spa_log_level level = default_level; + const char *topic = t->topic; + struct support_log_pattern *pattern; + + spa_list_for_each(pattern, patterns, link) { + if (fnmatch(pattern->pattern, topic, 0) != 0) + continue; + level = pattern->level; + t->has_custom_level = true; + } + + t->level = level; +} + +int +support_log_parse_patterns(struct spa_list *patterns, const char *jsonstr) +{ + struct spa_json iter, array, elem; + int res = 0; + + spa_json_init(&iter, jsonstr, strlen(jsonstr)); + + if (spa_json_enter_array(&iter, &array) < 0) + return -EINVAL; + + while (spa_json_enter_object(&array, &elem) > 0) { + char pattern[512] = {0}; + + while (spa_json_get_string(&elem, pattern, sizeof(pattern)) > 0) { + struct support_log_pattern *p; + const char *val; + int len; + int lvl; + + if ((len = spa_json_next(&elem, &val)) <= 0) + break; + + if (!spa_json_is_int(val, len)) + break; + + if ((res = spa_json_parse_int(val, len, &lvl)) < 0) + break; + + SPA_CLAMP(lvl, SPA_LOG_LEVEL_NONE, SPA_LOG_LEVEL_TRACE); + + p = calloc(1, sizeof(*p) + strlen(pattern) + 1); + p->level = lvl; + strcpy(p->pattern, pattern); + spa_list_append(patterns, &p->link); + } + } + + return res; +} + +void +support_log_free_patterns(struct spa_list *patterns) +{ + struct support_log_pattern *p; + + spa_list_consume(p, patterns, link) { + spa_list_remove(&p->link); + free(p); + } +} diff --git a/spa/plugins/support/log-patterns.h b/spa/plugins/support/log-patterns.h new file mode 100644 index 0000000..a10b445 --- /dev/null +++ b/spa/plugins/support/log-patterns.h @@ -0,0 +1,13 @@ +#ifndef LOG_PATTERNS_H +#define LOG_PATTERNS_H + +#include <spa/support/log.h> + +struct spa_list; + +void support_log_topic_init(struct spa_list *patterns, enum spa_log_level default_level, + struct spa_log_topic *t); +int support_log_parse_patterns(struct spa_list *patterns, const char *jsonstr); +void support_log_free_patterns(struct spa_list *patterns); + +#endif /* LOG_PATTERNS_H */ diff --git a/spa/plugins/support/logger.c b/spa/plugins/support/logger.c new file mode 100644 index 0000000..615a49b --- /dev/null +++ b/spa/plugins/support/logger.c @@ -0,0 +1,415 @@ +/* Spa + * + * Copyright © 2018 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <stddef.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <stdio.h> +#include <time.h> +#include <fnmatch.h> + +#include <spa/support/log.h> +#include <spa/support/loop.h> +#include <spa/support/system.h> +#include <spa/support/plugin.h> +#include <spa/utils/ringbuffer.h> +#include <spa/utils/type.h> +#include <spa/utils/names.h> +#include <spa/utils/string.h> +#include <spa/utils/ansi.h> + +#include "log-patterns.h" + +#if defined(__FreeBSD__) || defined(__MidnightBSD__) +#define CLOCK_MONOTONIC_RAW CLOCK_MONOTONIC +#endif + +#define NAME "logger" + +#define DEFAULT_LOG_LEVEL SPA_LOG_LEVEL_INFO + +#define TRACE_BUFFER (16*1024) + +struct impl { + struct spa_handle handle; + struct spa_log log; + + FILE *file; + bool close_file; + + struct spa_system *system; + struct spa_source source; + struct spa_ringbuffer trace_rb; + uint8_t trace_data[TRACE_BUFFER]; + + unsigned int have_source:1; + unsigned int colors:1; + unsigned int timestamp:1; + unsigned int line:1; + + struct spa_list patterns; +}; + +static SPA_PRINTF_FUNC(7,0) void +impl_log_logtv(void *object, + enum spa_log_level level, + const struct spa_log_topic *topic, + const char *file, + int line, + const char *func, + const char *fmt, + va_list args) +{ +#define RESERVED_LENGTH 24 + + struct impl *impl = object; + char timestamp[15] = {0}; + char topicstr[32] = {0}; + char filename[64] = {0}; + char location[1000 + RESERVED_LENGTH], *p, *s; + static const char * const levels[] = { "-", "E", "W", "I", "D", "T", "*T*" }; + const char *prefix = "", *suffix = ""; + int size, len; + bool do_trace; + + if ((do_trace = (level == SPA_LOG_LEVEL_TRACE && impl->have_source))) + level++; + + if (impl->colors) { + if (level <= SPA_LOG_LEVEL_ERROR) + prefix = SPA_ANSI_BOLD_RED; + else if (level <= SPA_LOG_LEVEL_WARN) + prefix = SPA_ANSI_BOLD_YELLOW; + else if (level <= SPA_LOG_LEVEL_INFO) + prefix = SPA_ANSI_BOLD_GREEN; + if (prefix[0]) + suffix = SPA_ANSI_RESET; + } + + p = location; + len = sizeof(location) - RESERVED_LENGTH; + + if (impl->timestamp) { + struct timespec now; + clock_gettime(CLOCK_MONOTONIC_RAW, &now); + spa_scnprintf(timestamp, sizeof(timestamp), "[%05lu.%06lu]", + (now.tv_sec & 0x1FFFFFFF) % 100000, now.tv_nsec / 1000); + } + + if (topic && topic->topic) + spa_scnprintf(topicstr, sizeof(topicstr), " %-12s | ", topic->topic); + + + if (impl->line && line != 0) { + s = strrchr(file, '/'); + spa_scnprintf(filename, sizeof(filename), "[%16.16s:%5i %s()]", + s ? s + 1 : file, line, func); + } + + size = spa_scnprintf(p, len, "%s[%s]%s%s%s ", prefix, levels[level], + timestamp, topicstr, filename); + /* + * it is assumed that at this point `size` <= `len`, + * which is reasonable as long as file names and function names + * don't become very long + */ + size += spa_vscnprintf(p + size, len - size, fmt, args); + + /* + * `RESERVED_LENGTH` bytes are reserved for printing the suffix + * (at the moment it's "... (truncated)\x1B[0m\n" at its longest - 21 bytes), + * its length must be less than `RESERVED_LENGTH` (including the null byte), + * otherwise a stack buffer overrun could ensue + */ + + /* if the message could not fit entirely... */ + if (size >= len - 1) { + size = len - 1; /* index of the null byte */ + len = sizeof(location); + size += spa_scnprintf(p + size, len - size, "... (truncated)"); + } + else { + len = sizeof(location); + } + + size += spa_scnprintf(p + size, len - size, "%s\n", suffix); + + if (SPA_UNLIKELY(do_trace)) { + uint32_t index; + + spa_ringbuffer_get_write_index(&impl->trace_rb, &index); + spa_ringbuffer_write_data(&impl->trace_rb, impl->trace_data, TRACE_BUFFER, + index & (TRACE_BUFFER - 1), location, size); + spa_ringbuffer_write_update(&impl->trace_rb, index + size); + + if (spa_system_eventfd_write(impl->system, impl->source.fd, 1) < 0) + fprintf(impl->file, "error signaling eventfd: %s\n", strerror(errno)); + } else + fputs(location, impl->file); + +#undef RESERVED_LENGTH +} + +static SPA_PRINTF_FUNC(6,0) void +impl_log_logv(void *object, + enum spa_log_level level, + const char *file, + int line, + const char *func, + const char *fmt, + va_list args) +{ + impl_log_logtv(object, level, NULL, file, line, func, fmt, args); +} + +static SPA_PRINTF_FUNC(7,8) void +impl_log_logt(void *object, + enum spa_log_level level, + const struct spa_log_topic *topic, + const char *file, + int line, + const char *func, + const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + impl_log_logtv(object, level, topic, file, line, func, fmt, args); + va_end(args); +} + +static SPA_PRINTF_FUNC(6,7) void +impl_log_log(void *object, + enum spa_log_level level, + const char *file, + int line, + const char *func, + const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + impl_log_logtv(object, level, NULL, file, line, func, fmt, args); + va_end(args); +} + +static void on_trace_event(struct spa_source *source) +{ + struct impl *impl = source->data; + int32_t avail; + uint32_t index; + uint64_t count; + + if (spa_system_eventfd_read(impl->system, source->fd, &count) < 0) + fprintf(impl->file, "failed to read event fd: %s", strerror(errno)); + + while ((avail = spa_ringbuffer_get_read_index(&impl->trace_rb, &index)) > 0) { + int32_t offset, first; + + if (avail > TRACE_BUFFER) { + index += avail - TRACE_BUFFER; + avail = TRACE_BUFFER; + } + offset = index & (TRACE_BUFFER - 1); + first = SPA_MIN(avail, TRACE_BUFFER - offset); + + fwrite(impl->trace_data + offset, first, 1, impl->file); + if (SPA_UNLIKELY(avail > first)) { + fwrite(impl->trace_data, avail - first, 1, impl->file); + } + spa_ringbuffer_read_update(&impl->trace_rb, index + avail); + } +} + +static void +impl_log_topic_init(void *object, struct spa_log_topic *t) +{ + struct impl *impl = object; + enum spa_log_level level = impl->log.level; + + support_log_topic_init(&impl->patterns, level, t); +} + +static const struct spa_log_methods impl_log = { + SPA_VERSION_LOG_METHODS, + .log = impl_log_log, + .logv = impl_log_logv, + .logt = impl_log_logt, + .logtv = impl_log_logtv, + .topic_init = impl_log_topic_init, +}; + +static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) +{ + struct impl *this; + + spa_return_val_if_fail(handle != NULL, -EINVAL); + spa_return_val_if_fail(interface != NULL, -EINVAL); + + this = (struct impl *) handle; + + if (spa_streq(type, SPA_TYPE_INTERFACE_Log)) + *interface = &this->log; + else + return -ENOENT; + + return 0; +} + +static int impl_clear(struct spa_handle *handle) +{ + struct impl *this; + + spa_return_val_if_fail(handle != NULL, -EINVAL); + + this = (struct impl *) handle; + + support_log_free_patterns(&this->patterns); + + if (this->close_file && this->file != NULL) + fclose(this->file); + + if (this->have_source) { + spa_loop_remove_source(this->source.loop, &this->source); + spa_system_close(this->system, this->source.fd); + this->have_source = false; + } + return 0; +} + +static size_t +impl_get_size(const struct spa_handle_factory *factory, + const struct spa_dict *params) +{ + return sizeof(struct impl); +} + +static int +impl_init(const struct spa_handle_factory *factory, + struct spa_handle *handle, + const struct spa_dict *info, + const struct spa_support *support, + uint32_t n_support) +{ + struct impl *this; + struct spa_loop *loop = NULL; + const char *str; + + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(handle != NULL, -EINVAL); + + handle->get_interface = impl_get_interface; + handle->clear = impl_clear; + + this = (struct impl *) handle; + + this->log.iface = SPA_INTERFACE_INIT( + SPA_TYPE_INTERFACE_Log, + SPA_VERSION_LOG, + &impl_log, this); + this->log.level = DEFAULT_LOG_LEVEL; + + loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Loop); + this->system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_System); + spa_list_init(&this->patterns); + + if (loop != NULL && this->system != NULL) { + this->source.func = on_trace_event; + this->source.data = this; + this->source.fd = spa_system_eventfd_create(this->system, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); + this->source.mask = SPA_IO_IN; + this->source.rmask = 0; + + if (this->source.fd < 0) { + fprintf(stderr, "Warning: failed to create eventfd: %m"); + } else { + spa_loop_add_source(loop, &this->source); + this->have_source = true; + } + } + if (info) { + if ((str = spa_dict_lookup(info, SPA_KEY_LOG_TIMESTAMP)) != NULL) + this->timestamp = spa_atob(str); + if ((str = spa_dict_lookup(info, SPA_KEY_LOG_LINE)) != NULL) + this->line = spa_atob(str); + if ((str = spa_dict_lookup(info, SPA_KEY_LOG_COLORS)) != NULL) + this->colors = spa_atob(str); + if ((str = spa_dict_lookup(info, SPA_KEY_LOG_LEVEL)) != NULL) + this->log.level = atoi(str); + if ((str = spa_dict_lookup(info, SPA_KEY_LOG_FILE)) != NULL) { + this->file = fopen(str, "we"); + if (this->file == NULL) + fprintf(stderr, "Warning: failed to open file %s: (%m)", str); + else + this->close_file = true; + } + if ((str = spa_dict_lookup(info, SPA_KEY_LOG_PATTERNS)) != NULL) + support_log_parse_patterns(&this->patterns, str); + } + if (this->file == NULL) + this->file = stderr; + if (!isatty(fileno(this->file))) + this->colors = false; + + spa_ringbuffer_init(&this->trace_rb); + + spa_log_debug(&this->log, NAME " %p: initialized", this); + + setlinebuf(this->file); + + return 0; +} + +static const struct spa_interface_info impl_interfaces[] = { + {SPA_TYPE_INTERFACE_Log,}, +}; + +static int +impl_enum_interface_info(const struct spa_handle_factory *factory, + const struct spa_interface_info **info, + uint32_t *index) +{ + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(info != NULL, -EINVAL); + spa_return_val_if_fail(index != NULL, -EINVAL); + + switch (*index) { + case 0: + *info = &impl_interfaces[*index]; + break; + default: + return 0; + } + (*index)++; + + return 1; +} + +const struct spa_handle_factory spa_support_logger_factory = { + SPA_VERSION_HANDLE_FACTORY, + .name = SPA_NAME_SUPPORT_LOG, + .info = NULL, + .get_size = impl_get_size, + .init = impl_init, + .enum_interface_info = impl_enum_interface_info, +}; diff --git a/spa/plugins/support/loop.c b/spa/plugins/support/loop.c new file mode 100644 index 0000000..3c3e020 --- /dev/null +++ b/spa/plugins/support/loop.c @@ -0,0 +1,1024 @@ +/* Spa + * + * Copyright © 2018 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <unistd.h> +#include <errno.h> +#include <sys/types.h> +#include <signal.h> +#include <stdlib.h> +#include <stdio.h> +#include <pthread.h> + +#include <spa/support/loop.h> +#include <spa/support/system.h> +#include <spa/support/log.h> +#include <spa/support/plugin.h> +#include <spa/utils/list.h> +#include <spa/utils/names.h> +#include <spa/utils/result.h> +#include <spa/utils/type.h> +#include <spa/utils/ringbuffer.h> +#include <spa/utils/string.h> + +static struct spa_log_topic log_topic = SPA_LOG_TOPIC(0, "spa.loop"); +#undef SPA_LOG_TOPIC_DEFAULT +#define SPA_LOG_TOPIC_DEFAULT &log_topic + +#define MAX_ALIGN 8 +#define ITEM_ALIGN 8 +#define DATAS_SIZE (4096*8) +#define MAX_EP 32 + +/** \cond */ + +struct invoke_item { + size_t item_size; + spa_invoke_func_t func; + uint32_t seq; + void *data; + size_t size; + bool block; + void *user_data; + int res; +}; + +static int loop_signal_event(void *object, struct spa_source *source); + +struct impl { + struct spa_handle handle; + struct spa_loop loop; + struct spa_loop_control control; + struct spa_loop_utils utils; + + struct spa_log *log; + struct spa_system *system; + + struct spa_list source_list; + struct spa_list destroy_list; + struct spa_hook_list hooks_list; + + int poll_fd; + pthread_t thread; + int enter_count; + + struct spa_source *wakeup; + int ack_fd; + + struct spa_ringbuffer buffer; + uint8_t *buffer_data; + uint8_t buffer_mem[DATAS_SIZE + MAX_ALIGN]; + + uint32_t flush_count; + unsigned int polling:1; +}; + +struct source_impl { + struct spa_source source; + + struct impl *impl; + struct spa_list link; + + union { + spa_source_io_func_t io; + spa_source_idle_func_t idle; + spa_source_event_func_t event; + spa_source_timer_func_t timer; + spa_source_signal_func_t signal; + } func; + + struct spa_source *fallback; + + bool close; + bool enabled; +}; +/** \endcond */ + +static int loop_add_source(void *object, struct spa_source *source) +{ + struct impl *impl = object; + source->loop = &impl->loop; + source->priv = NULL; + source->rmask = 0; + return spa_system_pollfd_add(impl->system, impl->poll_fd, source->fd, source->mask, source); +} + +static int loop_update_source(void *object, struct spa_source *source) +{ + struct impl *impl = object; + + spa_assert(source->loop == &impl->loop); + + return spa_system_pollfd_mod(impl->system, impl->poll_fd, source->fd, source->mask, source); +} + +static void detach_source(struct spa_source *source) +{ + struct spa_poll_event *e; + + source->loop = NULL; + source->rmask = 0; + + if ((e = source->priv)) { + /* active in an iteration of the loop, remove it from there */ + e->data = NULL; + source->priv = NULL; + } +} + +static int remove_from_poll(struct impl *impl, struct spa_source *source) +{ + spa_assert(source->loop == &impl->loop); + + return spa_system_pollfd_del(impl->system, impl->poll_fd, source->fd); +} + +static int loop_remove_source(void *object, struct spa_source *source) +{ + struct impl *impl = object; + spa_assert(!impl->polling); + + int res = remove_from_poll(impl, source); + detach_source(source); + + return res; +} + +static void flush_items(struct impl *impl) +{ + uint32_t index, flush_count; + int32_t avail; + int res; + + flush_count = ++impl->flush_count; + avail = spa_ringbuffer_get_read_index(&impl->buffer, &index); + while (avail > 0) { + struct invoke_item *item; + bool block; + spa_invoke_func_t func; + + item = SPA_PTROFF(impl->buffer_data, index & (DATAS_SIZE - 1), struct invoke_item); + block = item->block; + func = item->func; + + spa_log_trace_fp(impl->log, "%p: flush item %p", impl, item); + /* first we remove the function from the item so that recursive + * calls don't call the callback again. We can't update the + * read index before we call the function because then the item + * might get overwritten. */ + item->func = NULL; + if (func) + item->res = func(&impl->loop, true, item->seq, item->data, + item->size, item->user_data); + + /* if this function did a recursive invoke, it now flushed the + * ringbuffer and we can exit */ + if (flush_count != impl->flush_count) + break; + + index += item->item_size; + avail -= item->item_size; + spa_ringbuffer_read_update(&impl->buffer, index); + + if (block) { + if ((res = spa_system_eventfd_write(impl->system, impl->ack_fd, 1)) < 0) + spa_log_warn(impl->log, "%p: failed to write event fd:%d: %s", + impl, impl->ack_fd, spa_strerror(res)); + } + } +} + +static int +loop_invoke_inthread(struct impl *impl, + spa_invoke_func_t func, + uint32_t seq, + const void *data, + size_t size, + bool block, + void *user_data) +{ + /* we should probably have a second ringbuffer for the in-thread pending + * callbacks. A recursive callback when flushing will insert itself + * before this one. */ + flush_items(impl); + return func ? func(&impl->loop, true, seq, data, size, user_data) : 0; +} + +static int +loop_invoke(void *object, + spa_invoke_func_t func, + uint32_t seq, + const void *data, + size_t size, + bool block, + void *user_data) +{ + struct impl *impl = object; + struct invoke_item *item; + int res; + int32_t filled; + uint32_t avail, idx, offset, l0; + + /* the ringbuffer can only be written to from one thread, if we are + * in the same thread as the loop, don't write into the ringbuffer + * but try to emit the calback right away after flushing what we have */ + if (impl->thread == 0 || pthread_equal(impl->thread, pthread_self())) + return loop_invoke_inthread(impl, func, seq, data, size, block, user_data); + + filled = spa_ringbuffer_get_write_index(&impl->buffer, &idx); + if (filled < 0 || filled > DATAS_SIZE) { + spa_log_warn(impl->log, "%p: queue xrun %d", impl, filled); + return -EPIPE; + } + avail = DATAS_SIZE - filled; + if (avail < sizeof(struct invoke_item)) { + spa_log_warn(impl->log, "%p: queue full %d", impl, avail); + return -EPIPE; + } + offset = idx & (DATAS_SIZE - 1); + + /* l0 is remaining size in ringbuffer, this should always be larger than + * invoke_item, see below */ + l0 = DATAS_SIZE - offset; + + item = SPA_PTROFF(impl->buffer_data, offset, struct invoke_item); + item->func = func; + item->seq = seq; + item->size = size; + item->block = block; + item->user_data = user_data; + item->res = 0; + item->item_size = SPA_ROUND_UP_N(sizeof(struct invoke_item) + size, ITEM_ALIGN); + + spa_log_trace_fp(impl->log, "%p: add item %p filled:%d", impl, item, filled); + + if (l0 >= item->item_size) { + /* item + size fit in current ringbuffer idx */ + item->data = SPA_PTROFF(item, sizeof(struct invoke_item), void); + if (l0 < sizeof(struct invoke_item) + item->item_size) { + /* not enough space for next invoke_item, fill up till the end + * so that the next item will be at the start */ + item->item_size = l0; + } + } else { + /* item does not fit, place the invoke_item at idx and start the + * data at the start of the ringbuffer */ + item->data = impl->buffer_data; + item->item_size = SPA_ROUND_UP_N(l0 + size, ITEM_ALIGN); + } + if (avail < item->item_size) { + spa_log_warn(impl->log, "%p: queue full %d, need %zd", impl, avail, + item->item_size); + return -EPIPE; + } + if (data && size > 0) + memcpy(item->data, data, size); + + spa_ringbuffer_write_update(&impl->buffer, idx + item->item_size); + + loop_signal_event(impl, impl->wakeup); + + if (block) { + uint64_t count = 1; + + spa_loop_control_hook_before(&impl->hooks_list); + + if ((res = spa_system_eventfd_read(impl->system, impl->ack_fd, &count)) < 0) + spa_log_warn(impl->log, "%p: failed to read event fd:%d: %s", + impl, impl->ack_fd, spa_strerror(res)); + + spa_loop_control_hook_after(&impl->hooks_list); + + res = item->res; + } + else { + if (seq != SPA_ID_INVALID) + res = SPA_RESULT_RETURN_ASYNC(seq); + else + res = 0; + } + return res; +} + +static void wakeup_func(void *data, uint64_t count) +{ + struct impl *impl = data; + flush_items(impl); +} + +static int loop_get_fd(void *object) +{ + struct impl *impl = object; + return impl->poll_fd; +} + +static void +loop_add_hook(void *object, + struct spa_hook *hook, + const struct spa_loop_control_hooks *hooks, + void *data) +{ + struct impl *impl = object; + spa_hook_list_append(&impl->hooks_list, hook, hooks, data); +} + +static void loop_enter(void *object) +{ + struct impl *impl = object; + pthread_t thread_id = pthread_self(); + + if (impl->enter_count == 0) { + spa_return_if_fail(impl->thread == 0); + impl->thread = thread_id; + impl->enter_count = 1; + } else { + spa_return_if_fail(impl->enter_count > 0); + spa_return_if_fail(impl->thread == thread_id); + impl->enter_count++; + } + spa_log_trace(impl->log, "%p: enter %lu", impl, impl->thread); +} + +static void loop_leave(void *object) +{ + struct impl *impl = object; + pthread_t thread_id = pthread_self(); + + spa_return_if_fail(impl->enter_count > 0); + spa_return_if_fail(impl->thread == thread_id); + + spa_log_trace(impl->log, "%p: leave %lu", impl, impl->thread); + + if (--impl->enter_count == 0) { + impl->thread = 0; + flush_items(impl); + impl->polling = false; + } +} + +static inline void free_source(struct source_impl *s) +{ + detach_source(&s->source); + free(s); +} + +static inline void process_destroy(struct impl *impl) +{ + struct source_impl *source, *tmp; + + spa_list_for_each_safe(source, tmp, &impl->destroy_list, link) + free_source(source); + + spa_list_init(&impl->destroy_list); +} + +struct cancellation_handler_data { + struct spa_poll_event *ep; + int ep_count; +}; + +static void cancellation_handler(void *closure) +{ + const struct cancellation_handler_data *data = closure; + + for (int i = 0; i < data->ep_count; i++) { + struct spa_source *s = data->ep[i].data; + if (SPA_LIKELY(s)) { + s->rmask = 0; + s->priv = NULL; + } + } +} + +static int loop_iterate(void *object, int timeout) +{ + struct impl *impl = object; + struct spa_poll_event ep[MAX_EP], *e; + int i, nfds; + + impl->polling = true; + spa_loop_control_hook_before(&impl->hooks_list); + + nfds = spa_system_pollfd_wait(impl->system, impl->poll_fd, ep, SPA_N_ELEMENTS(ep), timeout); + + spa_loop_control_hook_after(&impl->hooks_list); + impl->polling = false; + + struct cancellation_handler_data cdata = { ep, nfds }; + pthread_cleanup_push(cancellation_handler, &cdata); + + /* first we set all the rmasks, then call the callbacks. The reason is that + * some callback might also want to look at other sources it manages and + * can then reset the rmask to suppress the callback */ + for (i = 0; i < nfds; i++) { + struct spa_source *s = ep[i].data; + + spa_assert(s->loop == &impl->loop); + + s->rmask = ep[i].events; + /* already active in another iteration of the loop, + * remove it from that iteration */ + if (SPA_UNLIKELY(e = s->priv)) + e->data = NULL; + s->priv = &ep[i]; + } + + if (SPA_UNLIKELY(!spa_list_is_empty(&impl->destroy_list))) + process_destroy(impl); + + for (i = 0; i < nfds; i++) { + struct spa_source *s = ep[i].data; + if (SPA_LIKELY(s && s->rmask)) + s->func(s); + } + + pthread_cleanup_pop(true); + + return nfds; +} + +static void source_io_func(struct spa_source *source) +{ + struct source_impl *s = SPA_CONTAINER_OF(source, struct source_impl, source); + spa_log_trace_fp(s->impl->log, "%p: io %08x", s, source->rmask); + s->func.io(source->data, source->fd, source->rmask); +} + +static struct spa_source *loop_add_io(void *object, + int fd, + uint32_t mask, + bool close, spa_source_io_func_t func, void *data) +{ + struct impl *impl = object; + struct source_impl *source; + int res; + + source = calloc(1, sizeof(struct source_impl)); + if (source == NULL) + goto error_exit; + + source->source.func = source_io_func; + source->source.data = data; + source->source.fd = fd; + source->source.mask = mask; + source->impl = impl; + source->close = close; + source->func.io = func; + + if ((res = loop_add_source(impl, &source->source)) < 0) { + if (res != -EPERM) + goto error_exit_free; + + /* file fds (stdin/stdout/...) give EPERM in epoll. Those fds always + * return from epoll with the mask set, so we can handle this with + * an idle source */ + source->source.rmask = mask; + source->fallback = spa_loop_utils_add_idle(&impl->utils, + mask & (SPA_IO_IN | SPA_IO_OUT) ? true : false, + (spa_source_idle_func_t) source_io_func, source); + spa_log_trace(impl->log, "%p: adding fallback %p", impl, + source->fallback); + } + + spa_list_insert(&impl->source_list, &source->link); + + return &source->source; + +error_exit_free: + free(source); + errno = -res; +error_exit: + return NULL; +} + +static int loop_update_io(void *object, struct spa_source *source, uint32_t mask) +{ + struct impl *impl = object; + struct source_impl *s = SPA_CONTAINER_OF(source, struct source_impl, source); + int res; + + spa_assert(s->impl == object); + spa_assert(source->func == source_io_func); + + spa_log_trace(impl->log, "%p: update %08x -> %08x", s, source->mask, mask); + source->mask = mask; + + if (s->fallback) + res = spa_loop_utils_enable_idle(&impl->utils, s->fallback, + mask & (SPA_IO_IN | SPA_IO_OUT) ? true : false); + else + res = loop_update_source(object, source); + return res; +} + +static void source_idle_func(struct spa_source *source) +{ + struct source_impl *s = SPA_CONTAINER_OF(source, struct source_impl, source); + s->func.idle(source->data); +} + +static int loop_enable_idle(void *object, struct spa_source *source, bool enabled) +{ + struct source_impl *s = SPA_CONTAINER_OF(source, struct source_impl, source); + int res = 0; + + spa_assert(s->impl == object); + spa_assert(source->func == source_idle_func); + + if (enabled && !s->enabled) { + if ((res = spa_system_eventfd_write(s->impl->system, source->fd, 1)) < 0) + spa_log_warn(s->impl->log, "%p: failed to write idle fd:%d: %s", + source, source->fd, spa_strerror(res)); + } else if (!enabled && s->enabled) { + uint64_t count; + if ((res = spa_system_eventfd_read(s->impl->system, source->fd, &count)) < 0) + spa_log_warn(s->impl->log, "%p: failed to read idle fd:%d: %s", + source, source->fd, spa_strerror(res)); + } + s->enabled = enabled; + return res; +} + +static struct spa_source *loop_add_idle(void *object, + bool enabled, spa_source_idle_func_t func, void *data) +{ + struct impl *impl = object; + struct source_impl *source; + int res; + + source = calloc(1, sizeof(struct source_impl)); + if (source == NULL) + goto error_exit; + + if ((res = spa_system_eventfd_create(impl->system, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK)) < 0) + goto error_exit_free; + + source->source.func = source_idle_func; + source->source.data = data; + source->source.fd = res; + source->impl = impl; + source->close = true; + source->source.mask = SPA_IO_IN; + source->func.idle = func; + + if ((res = loop_add_source(impl, &source->source)) < 0) + goto error_exit_close; + + spa_list_insert(&impl->source_list, &source->link); + + if (enabled) + loop_enable_idle(impl, &source->source, true); + + return &source->source; + +error_exit_close: + spa_system_close(impl->system, source->source.fd); +error_exit_free: + free(source); + errno = -res; +error_exit: + return NULL; +} + +static void source_event_func(struct spa_source *source) +{ + struct source_impl *s = SPA_CONTAINER_OF(source, struct source_impl, source); + uint64_t count = 0; + int res; + + if ((res = spa_system_eventfd_read(s->impl->system, source->fd, &count)) < 0) { + if (res != -EAGAIN) + spa_log_warn(s->impl->log, "%p: failed to read event fd:%d: %s", + source, source->fd, spa_strerror(res)); + return; + } + s->func.event(source->data, count); +} + +static struct spa_source *loop_add_event(void *object, + spa_source_event_func_t func, void *data) +{ + struct impl *impl = object; + struct source_impl *source; + int res; + + source = calloc(1, sizeof(struct source_impl)); + if (source == NULL) + goto error_exit; + + if ((res = spa_system_eventfd_create(impl->system, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK)) < 0) + goto error_exit_free; + + source->source.func = source_event_func; + source->source.data = data; + source->source.fd = res; + source->source.mask = SPA_IO_IN; + source->impl = impl; + source->close = true; + source->func.event = func; + + if ((res = loop_add_source(impl, &source->source)) < 0) + goto error_exit_close; + + spa_list_insert(&impl->source_list, &source->link); + + return &source->source; + +error_exit_close: + spa_system_close(impl->system, source->source.fd); +error_exit_free: + free(source); + errno = -res; +error_exit: + return NULL; +} + +static int loop_signal_event(void *object, struct spa_source *source) +{ + struct source_impl *s = SPA_CONTAINER_OF(source, struct source_impl, source); + int res; + + spa_assert(s->impl == object); + spa_assert(source->func == source_event_func); + + if (SPA_UNLIKELY((res = spa_system_eventfd_write(s->impl->system, source->fd, 1)) < 0)) + spa_log_warn(s->impl->log, "%p: failed to write event fd:%d: %s", + source, source->fd, spa_strerror(res)); + return res; +} + +static void source_timer_func(struct spa_source *source) +{ + struct source_impl *s = SPA_CONTAINER_OF(source, struct source_impl, source); + uint64_t expirations = 0; + int res; + + if (SPA_UNLIKELY((res = spa_system_timerfd_read(s->impl->system, + source->fd, &expirations)) < 0)) { + if (res != -EAGAIN) + spa_log_warn(s->impl->log, "%p: failed to read timer fd:%d: %s", + source, source->fd, spa_strerror(res)); + return; + } + s->func.timer(source->data, expirations); +} + +static struct spa_source *loop_add_timer(void *object, + spa_source_timer_func_t func, void *data) +{ + struct impl *impl = object; + struct source_impl *source; + int res; + + source = calloc(1, sizeof(struct source_impl)); + if (source == NULL) + goto error_exit; + + if ((res = spa_system_timerfd_create(impl->system, CLOCK_MONOTONIC, + SPA_FD_CLOEXEC | SPA_FD_NONBLOCK)) < 0) + goto error_exit_free; + + source->source.func = source_timer_func; + source->source.data = data; + source->source.fd = res; + source->source.mask = SPA_IO_IN; + source->impl = impl; + source->close = true; + source->func.timer = func; + + if ((res = loop_add_source(impl, &source->source)) < 0) + goto error_exit_close; + + spa_list_insert(&impl->source_list, &source->link); + + return &source->source; + +error_exit_close: + spa_system_close(impl->system, source->source.fd); +error_exit_free: + free(source); + errno = -res; +error_exit: + return NULL; +} + +static int +loop_update_timer(void *object, struct spa_source *source, + struct timespec *value, struct timespec *interval, bool absolute) +{ + struct source_impl *s = SPA_CONTAINER_OF(source, struct source_impl, source); + struct itimerspec its; + int flags = 0, res; + + spa_assert(s->impl == object); + spa_assert(source->func == source_timer_func); + + spa_zero(its); + if (SPA_LIKELY(value)) { + its.it_value = *value; + } else if (interval) { + its.it_value = *interval; + absolute = true; + } + if (SPA_UNLIKELY(interval)) + its.it_interval = *interval; + if (SPA_LIKELY(absolute)) + flags |= SPA_FD_TIMER_ABSTIME; + + if (SPA_UNLIKELY((res = spa_system_timerfd_settime(s->impl->system, source->fd, flags, &its, NULL)) < 0)) + return res; + + return 0; +} + +static void source_signal_func(struct spa_source *source) +{ + struct source_impl *s = SPA_CONTAINER_OF(source, struct source_impl, source); + int res, signal_number = 0; + + if ((res = spa_system_signalfd_read(s->impl->system, source->fd, &signal_number)) < 0) { + if (res != -EAGAIN) + spa_log_warn(s->impl->log, "%p: failed to read signal fd:%d: %s", + source, source->fd, spa_strerror(res)); + return; + } + s->func.signal(source->data, signal_number); +} + +static struct spa_source *loop_add_signal(void *object, + int signal_number, + spa_source_signal_func_t func, void *data) +{ + struct impl *impl = object; + struct source_impl *source; + int res; + + source = calloc(1, sizeof(struct source_impl)); + if (source == NULL) + goto error_exit; + + if ((res = spa_system_signalfd_create(impl->system, + signal_number, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK)) < 0) + goto error_exit_free; + + source->source.func = source_signal_func; + source->source.data = data; + source->source.fd = res; + source->source.mask = SPA_IO_IN; + source->impl = impl; + source->close = true; + source->func.signal = func; + + if ((res = loop_add_source(impl, &source->source)) < 0) + goto error_exit_close; + + spa_list_insert(&impl->source_list, &source->link); + + return &source->source; + +error_exit_close: + spa_system_close(impl->system, source->source.fd); +error_exit_free: + free(source); + errno = -res; +error_exit: + return NULL; +} + +static void loop_destroy_source(void *object, struct spa_source *source) +{ + struct source_impl *s = SPA_CONTAINER_OF(source, struct source_impl, source); + + spa_assert(s->impl == object); + + spa_log_trace(s->impl->log, "%p ", s); + + spa_list_remove(&s->link); + + if (s->fallback) + loop_destroy_source(s->impl, s->fallback); + else + remove_from_poll(s->impl, source); + + if (source->fd != -1 && s->close) { + spa_system_close(s->impl->system, source->fd); + source->fd = -1; + } + + if (!s->impl->polling) + free_source(s); + else + spa_list_insert(&s->impl->destroy_list, &s->link); +} + +static const struct spa_loop_methods impl_loop = { + SPA_VERSION_LOOP_METHODS, + .add_source = loop_add_source, + .update_source = loop_update_source, + .remove_source = loop_remove_source, + .invoke = loop_invoke, +}; + +static const struct spa_loop_control_methods impl_loop_control = { + SPA_VERSION_LOOP_CONTROL_METHODS, + .get_fd = loop_get_fd, + .add_hook = loop_add_hook, + .enter = loop_enter, + .leave = loop_leave, + .iterate = loop_iterate, +}; + +static const struct spa_loop_utils_methods impl_loop_utils = { + SPA_VERSION_LOOP_UTILS_METHODS, + .add_io = loop_add_io, + .update_io = loop_update_io, + .add_idle = loop_add_idle, + .enable_idle = loop_enable_idle, + .add_event = loop_add_event, + .signal_event = loop_signal_event, + .add_timer = loop_add_timer, + .update_timer = loop_update_timer, + .add_signal = loop_add_signal, + .destroy_source = loop_destroy_source, +}; + +static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) +{ + struct impl *impl; + + spa_return_val_if_fail(handle != NULL, -EINVAL); + spa_return_val_if_fail(interface != NULL, -EINVAL); + + impl = (struct impl *) handle; + + if (spa_streq(type, SPA_TYPE_INTERFACE_Loop)) + *interface = &impl->loop; + else if (spa_streq(type, SPA_TYPE_INTERFACE_LoopControl)) + *interface = &impl->control; + else if (spa_streq(type, SPA_TYPE_INTERFACE_LoopUtils)) + *interface = &impl->utils; + else + return -ENOENT; + + return 0; +} + +static int impl_clear(struct spa_handle *handle) +{ + struct impl *impl; + struct source_impl *source; + + spa_return_val_if_fail(handle != NULL, -EINVAL); + + impl = (struct impl *) handle; + + if (impl->enter_count != 0 || impl->polling) + spa_log_warn(impl->log, "%p: loop is entered %d times polling:%d", + impl, impl->enter_count, impl->polling); + + spa_list_consume(source, &impl->source_list, link) + loop_destroy_source(impl, &source->source); + + spa_system_close(impl->system, impl->ack_fd); + spa_system_close(impl->system, impl->poll_fd); + + return 0; +} + +static size_t +impl_get_size(const struct spa_handle_factory *factory, + const struct spa_dict *params) +{ + return sizeof(struct impl); +} + +static int +impl_init(const struct spa_handle_factory *factory, + struct spa_handle *handle, + const struct spa_dict *info, + const struct spa_support *support, + uint32_t n_support) +{ + struct impl *impl; + int res; + + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(handle != NULL, -EINVAL); + + handle->get_interface = impl_get_interface; + handle->clear = impl_clear; + + impl = (struct impl *) handle; + impl->loop.iface = SPA_INTERFACE_INIT( + SPA_TYPE_INTERFACE_Loop, + SPA_VERSION_LOOP, + &impl_loop, impl); + impl->control.iface = SPA_INTERFACE_INIT( + SPA_TYPE_INTERFACE_LoopControl, + SPA_VERSION_LOOP_CONTROL, + &impl_loop_control, impl); + impl->utils.iface = SPA_INTERFACE_INIT( + SPA_TYPE_INTERFACE_LoopUtils, + SPA_VERSION_LOOP_UTILS, + &impl_loop_utils, impl); + + impl->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); + spa_log_topic_init(impl->log, &log_topic); + impl->system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_System); + + if (impl->system == NULL) { + spa_log_error(impl->log, "%p: a System is needed", impl); + res = -EINVAL; + goto error_exit; + } + + if ((res = spa_system_pollfd_create(impl->system, SPA_FD_CLOEXEC)) < 0) { + spa_log_error(impl->log, "%p: can't create pollfd: %s", + impl, spa_strerror(res)); + goto error_exit; + } + impl->poll_fd = res; + + spa_list_init(&impl->source_list); + spa_list_init(&impl->destroy_list); + spa_hook_list_init(&impl->hooks_list); + + impl->buffer_data = SPA_PTR_ALIGN(impl->buffer_mem, MAX_ALIGN, uint8_t); + spa_ringbuffer_init(&impl->buffer); + + impl->wakeup = loop_add_event(impl, wakeup_func, impl); + if (impl->wakeup == NULL) { + res = -errno; + spa_log_error(impl->log, "%p: can't create wakeup event: %m", impl); + goto error_exit_free_poll; + } + if ((res = spa_system_eventfd_create(impl->system, + SPA_FD_EVENT_SEMAPHORE | SPA_FD_CLOEXEC)) < 0) { + spa_log_error(impl->log, "%p: can't create ack event: %s", + impl, spa_strerror(res)); + goto error_exit_free_wakeup; + } + impl->ack_fd = res; + + spa_log_debug(impl->log, "%p: initialized", impl); + + return 0; + +error_exit_free_wakeup: + loop_destroy_source(impl, impl->wakeup); +error_exit_free_poll: + spa_system_close(impl->system, impl->poll_fd); +error_exit: + return res; +} + +static const struct spa_interface_info impl_interfaces[] = { + {SPA_TYPE_INTERFACE_Loop,}, + {SPA_TYPE_INTERFACE_LoopControl,}, + {SPA_TYPE_INTERFACE_LoopUtils,}, +}; + +static int +impl_enum_interface_info(const struct spa_handle_factory *factory, + const struct spa_interface_info **info, + uint32_t *index) +{ + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(info != NULL, -EINVAL); + spa_return_val_if_fail(index != NULL, -EINVAL); + + if (*index >= SPA_N_ELEMENTS(impl_interfaces)) + return 0; + + *info = &impl_interfaces[(*index)++]; + return 1; +} + +const struct spa_handle_factory spa_support_loop_factory = { + SPA_VERSION_HANDLE_FACTORY, + SPA_NAME_SUPPORT_LOOP, + NULL, + impl_get_size, + impl_init, + impl_enum_interface_info +}; diff --git a/spa/plugins/support/meson.build b/spa/plugins/support/meson.build new file mode 100644 index 0000000..1672d38 --- /dev/null +++ b/spa/plugins/support/meson.build @@ -0,0 +1,70 @@ +spa_support_sources = [ + 'cpu.c', + 'logger.c', + 'log-patterns.c', + 'loop.c', + 'node-driver.c', + 'null-audio-sink.c', + 'plugin.c', + 'system.c' +] + +simd_cargs = [] + +if have_sse + simd_cargs += [sse_args, '-DHAVE_SSE'] +endif + +spa_support_lib = shared_library('spa-support', + spa_support_sources, + c_args : [ simd_cargs ], + dependencies : [ spa_dep, pthread_lib, epoll_shim_dep ], + install : true, + install_dir : spa_plugindir / 'support') +spa_support_dep = declare_dependency(link_with: spa_support_lib) + +if get_option('evl').allowed() + evl_inc = include_directories('/usr/evl/include') + evl_lib = cc.find_library('evl', + dirs: ['/usr/evl/lib/'], + required: get_option('evl')) + + spa_evl_sources = ['evl-system.c', 'evl-plugin.c'] + + spa_evl_lib = shared_library('spa-evl', + spa_evl_sources, + include_directories : [ evl_inc], + dependencies : [ spa_dep, pthread_lib, evl_lib ], + install : true, + install_dir : spa_plugindir / 'support') +endif + +if dbus_dep.found() + spa_dbus_sources = ['dbus.c'] + + spa_dbus_lib = shared_library('spa-dbus', + spa_dbus_sources, + dependencies : [ spa_dep, dbus_dep ], + install : true, + install_dir : spa_plugindir / 'support') + spa_dbus_dep = declare_dependency(link_with: spa_dbus_lib) +else + spa_dbus_dep = declare_dependency() +endif + + +if systemd_dep.found() + spa_journal_sources = [ + 'journal.c', + 'log-patterns.c', + ] + + spa_journal_lib = shared_library('spa-journal', + spa_journal_sources, + dependencies : [ spa_dep, systemd_dep ], + install : true, + install_dir : spa_plugindir / 'support') + spa_journal_dep = declare_dependency(link_with: spa_journal_lib) +else + spa_journal_dep = declare_dependency() +endif diff --git a/spa/plugins/support/node-driver.c b/spa/plugins/support/node-driver.c new file mode 100644 index 0000000..9701a47 --- /dev/null +++ b/spa/plugins/support/node-driver.c @@ -0,0 +1,492 @@ +/* Spa + * + * Copyright © 2020 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <errno.h> +#include <stddef.h> +#include <unistd.h> +#include <string.h> +#include <stdio.h> + +#include <spa/support/plugin.h> +#include <spa/support/log.h> +#include <spa/support/loop.h> +#include <spa/utils/names.h> +#include <spa/utils/result.h> +#include <spa/utils/string.h> +#include <spa/node/node.h> +#include <spa/node/keys.h> +#include <spa/node/io.h> +#include <spa/node/utils.h> +#include <spa/param/param.h> + +#define NAME "driver" + +#define DEFAULT_FREEWHEEL false +#define DEFAULT_CLOCK_NAME "clock.system.monotonic" + +struct props { + bool freewheel; + char clock_name[64]; +}; + +struct impl { + struct spa_handle handle; + struct spa_node node; + + struct props props; + + struct spa_log *log; + struct spa_loop *data_loop; + struct spa_system *data_system; + + uint64_t info_all; + struct spa_node_info info; + struct spa_param_info params[1]; + + struct spa_hook_list hooks; + struct spa_callbacks callbacks; + + struct spa_io_position *position; + struct spa_io_clock *clock; + + struct spa_source timer_source; + struct itimerspec timerspec; + + bool started; + bool following; + uint64_t next_time; +}; + +static void reset_props(struct props *props) +{ + props->freewheel = DEFAULT_FREEWHEEL; + spa_scnprintf(props->clock_name, sizeof(props->clock_name), + "%s", DEFAULT_CLOCK_NAME); +} + +static void set_timeout(struct impl *this, uint64_t next_time) +{ + spa_log_trace(this->log, "set timeout %"PRIu64, next_time); + this->timerspec.it_value.tv_sec = next_time / SPA_NSEC_PER_SEC; + this->timerspec.it_value.tv_nsec = next_time % SPA_NSEC_PER_SEC; + spa_system_timerfd_settime(this->data_system, + this->timer_source.fd, SPA_FD_TIMER_ABSTIME, &this->timerspec, NULL); +} + +static int set_timers(struct impl *this) +{ + struct timespec now; + int res; + + if ((res = spa_system_clock_gettime(this->data_system, CLOCK_MONOTONIC, &now)) < 0) + return res; + this->next_time = SPA_TIMESPEC_TO_NSEC(&now); + + if (this->following) { + set_timeout(this, 0); + } else { + set_timeout(this, this->next_time); + } + return 0; +} + +static inline bool is_following(struct impl *this) +{ + return this->position && this->clock && this->position->clock.id != this->clock->id; +} + +static int do_reassign_follower(struct spa_loop *loop, + bool async, + uint32_t seq, + const void *data, + size_t size, + void *user_data) +{ + struct impl *this = user_data; + set_timers(this); + return 0; +} + +static int reassign_follower(struct impl *this) +{ + bool following; + + if (this->clock) + SPA_FLAG_UPDATE(this->clock->flags, + SPA_IO_CLOCK_FLAG_FREEWHEEL, this->props.freewheel); + + if (!this->started) + return 0; + + following = is_following(this); + if (following != this->following) { + spa_log_debug(this->log, NAME" %p: reassign follower %d->%d", this, this->following, following); + this->following = following; + spa_loop_invoke(this->data_loop, do_reassign_follower, 0, NULL, 0, true, this); + } + return 0; +} + +static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) +{ + struct impl *this = object; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + switch (id) { + case SPA_IO_Clock: + if (size > 0 && size < sizeof(struct spa_io_clock)) + return -EINVAL; + this->clock = data; + if (this->clock) + spa_scnprintf(this->clock->name, sizeof(this->clock->name), + "%s", this->props.clock_name); + break; + case SPA_IO_Position: + if (size > 0 && size < sizeof(struct spa_io_position)) + return -EINVAL; + this->position = data; + break; + default: + return -ENOENT; + } + reassign_follower(this); + + return 0; +} + +static void on_timeout(struct spa_source *source) +{ + struct impl *this = source->data; + uint64_t expirations, nsec, duration; + uint32_t rate; + int res; + + spa_log_trace(this->log, "timeout"); + + if ((res = spa_system_timerfd_read(this->data_system, + this->timer_source.fd, &expirations)) < 0) { + if (res != EAGAIN) + spa_log_error(this->log, NAME " %p: timerfd error: %s", + this, spa_strerror(res)); + return; + } + + nsec = this->next_time; + + if (SPA_LIKELY(this->position)) { + duration = this->position->clock.duration; + rate = this->position->clock.rate.denom; + } else { + duration = 1024; + rate = 48000; + } + + this->next_time = nsec + duration * SPA_NSEC_PER_SEC / rate; + + if (SPA_LIKELY(this->clock)) { + this->clock->nsec = nsec; + this->clock->position += duration; + this->clock->duration = duration; + this->clock->delay = 0; + this->clock->rate_diff = 1.0; + this->clock->next_nsec = this->next_time; + } + + spa_node_call_ready(&this->callbacks, + SPA_STATUS_HAVE_DATA | SPA_STATUS_NEED_DATA); + + set_timeout(this, this->next_time); +} + +static int do_start(struct impl *this) +{ + if (this->started) + return 0; + + this->following = is_following(this); + set_timers(this); + this->started = true; + return 0; +} + +static int do_stop(struct impl *this) +{ + if (!this->started) + return 0; + this->started = false; + set_timeout(this, 0); + return 0; +} + +static int impl_node_send_command(void *object, const struct spa_command *command) +{ + struct impl *this = object; + + spa_return_val_if_fail(this != NULL, -EINVAL); + spa_return_val_if_fail(command != NULL, -EINVAL); + + + switch (SPA_NODE_COMMAND_ID(command)) { + case SPA_NODE_COMMAND_Start: + do_start(this); + break; + case SPA_NODE_COMMAND_Suspend: + case SPA_NODE_COMMAND_Pause: + do_stop(this); + break; + default: + return -ENOTSUP; + } + return 0; +} + +static const struct spa_dict_item node_info_items[] = { + { SPA_KEY_NODE_DRIVER, "true" }, +}; + +static void emit_node_info(struct impl *this, bool full) +{ + uint64_t old = full ? this->info.change_mask : 0; + if (full) + this->info.change_mask = this->info_all; + if (this->info.change_mask) { + this->info.props = &SPA_DICT_INIT_ARRAY(node_info_items); + spa_node_emit_info(&this->hooks, &this->info); + this->info.change_mask = old; + } +} + +static int impl_node_add_listener(void *object, + struct spa_hook *listener, + const struct spa_node_events *events, + void *data) +{ + struct impl *this = object; + struct spa_hook_list save; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + spa_hook_list_isolate(&this->hooks, &save, listener, events, data); + + emit_node_info(this, true); + + spa_hook_list_join(&this->hooks, &save); + + return 0; +} + +static int +impl_node_set_callbacks(void *object, + const struct spa_node_callbacks *callbacks, + void *data) +{ + struct impl *this = object; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + this->callbacks = SPA_CALLBACKS_INIT(callbacks, data); + + return 0; +} + +static int impl_node_process(void *object) +{ + struct impl *this = object; + struct timespec now; + + spa_return_val_if_fail(this != NULL, -EINVAL); + spa_log_trace(this->log, "process %d", this->props.freewheel); + + if (this->props.freewheel) { + clock_gettime(CLOCK_MONOTONIC, &now); + this->next_time = SPA_TIMESPEC_TO_NSEC(&now); + set_timeout(this, this->next_time); + } + return SPA_STATUS_HAVE_DATA | SPA_STATUS_NEED_DATA; +} + +static const struct spa_node_methods impl_node = { + SPA_VERSION_NODE_METHODS, + .add_listener = impl_node_add_listener, + .set_callbacks = impl_node_set_callbacks, + .set_io = impl_node_set_io, + .send_command = impl_node_send_command, + .process = impl_node_process, +}; + +static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) +{ + struct impl *this; + + spa_return_val_if_fail(handle != NULL, -EINVAL); + spa_return_val_if_fail(interface != NULL, -EINVAL); + + this = (struct impl *) handle; + + if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) + *interface = &this->node; + else + return -ENOENT; + + return 0; +} + +static int do_remove_timer(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + struct impl *this = user_data; + spa_loop_remove_source(this->data_loop, &this->timer_source); + return 0; +} + +static int impl_clear(struct spa_handle *handle) +{ + struct impl *this; + + spa_return_val_if_fail(handle != NULL, -EINVAL); + + this = (struct impl *) handle; + + spa_loop_invoke(this->data_loop, do_remove_timer, 0, NULL, 0, true, this); + spa_system_close(this->data_system, this->timer_source.fd); + + return 0; +} + +static size_t +impl_get_size(const struct spa_handle_factory *factory, + const struct spa_dict *params) +{ + return sizeof(struct impl); +} + +static int +impl_init(const struct spa_handle_factory *factory, + struct spa_handle *handle, + const struct spa_dict *info, + const struct spa_support *support, + uint32_t n_support) +{ + struct impl *this; + uint32_t i; + + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(handle != NULL, -EINVAL); + + handle->get_interface = impl_get_interface; + handle->clear = impl_clear; + + this = (struct impl *) handle; + + this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); + this->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); + this->data_system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataSystem); + + if (this->data_loop == NULL) { + spa_log_error(this->log, "a data_loop is needed"); + return -EINVAL; + } + if (this->data_system == NULL) { + spa_log_error(this->log, "a data_system is needed"); + return -EINVAL; + } + + spa_hook_list_init(&this->hooks); + + this->node.iface = SPA_INTERFACE_INIT( + SPA_TYPE_INTERFACE_Node, + SPA_VERSION_NODE, + &impl_node, this); + + this->info_all = SPA_NODE_CHANGE_MASK_FLAGS | + SPA_NODE_CHANGE_MASK_PROPS | + SPA_NODE_CHANGE_MASK_PARAMS; + this->info = SPA_NODE_INFO_INIT(); + this->info.max_input_ports = 0; + this->info.max_output_ports = 0; + this->info.flags = SPA_NODE_FLAG_RT; + this->params[0] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); + this->info.params = this->params; + this->info.n_params = 0; + + this->timer_source.func = on_timeout; + this->timer_source.data = this; + this->timer_source.fd = spa_system_timerfd_create(this->data_system, CLOCK_MONOTONIC, + SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); + this->timer_source.mask = SPA_IO_IN; + this->timer_source.rmask = 0; + this->timerspec.it_value.tv_sec = 0; + this->timerspec.it_value.tv_nsec = 0; + this->timerspec.it_interval.tv_sec = 0; + this->timerspec.it_interval.tv_nsec = 0; + + reset_props(&this->props); + + for (i = 0; info && i < info->n_items; i++) { + const char *k = info->items[i].key; + const char *s = info->items[i].value; + if (spa_streq(k, "node.freewheel")) { + this->props.freewheel = spa_atob(s); + } else if (spa_streq(k, "clock.name")) { + spa_scnprintf(this->props.clock_name, + sizeof(this->props.clock_name), "%s", s); + } + } + spa_loop_add_source(this->data_loop, &this->timer_source); + + return 0; +} + +static const struct spa_interface_info impl_interfaces[] = { + {SPA_TYPE_INTERFACE_Node,}, +}; + +static int +impl_enum_interface_info(const struct spa_handle_factory *factory, + const struct spa_interface_info **info, + uint32_t *index) +{ + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(info != NULL, -EINVAL); + spa_return_val_if_fail(index != NULL, -EINVAL); + + switch (*index) { + case 0: + *info = &impl_interfaces[*index]; + break; + default: + return 0; + } + (*index)++; + return 1; +} + +const struct spa_handle_factory spa_support_node_driver_factory = { + SPA_VERSION_HANDLE_FACTORY, + SPA_NAME_SUPPORT_NODE_DRIVER, + NULL, + impl_get_size, + impl_init, + impl_enum_interface_info, +}; diff --git a/spa/plugins/support/null-audio-sink.c b/spa/plugins/support/null-audio-sink.c new file mode 100644 index 0000000..e5b9f04 --- /dev/null +++ b/spa/plugins/support/null-audio-sink.c @@ -0,0 +1,1001 @@ +/* Spa + * + * Copyright © 2020 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <errno.h> +#include <stddef.h> +#include <unistd.h> +#include <string.h> +#include <stdio.h> + +#include <spa/support/plugin.h> +#include <spa/support/log.h> +#include <spa/support/system.h> +#include <spa/support/loop.h> +#include <spa/utils/list.h> +#include <spa/utils/keys.h> +#include <spa/utils/json.h> +#include <spa/utils/result.h> +#include <spa/utils/string.h> +#include <spa/node/node.h> +#include <spa/node/utils.h> +#include <spa/node/io.h> +#include <spa/node/keys.h> +#include <spa/param/audio/format-utils.h> +#include <spa/debug/types.h> +#include <spa/debug/mem.h> +#include <spa/param/audio/type-info.h> +#include <spa/param/param.h> +#include <spa/pod/filter.h> +#include <spa/control/control.h> + +#define NAME "null-audio-sink" + +#define DEFAULT_CLOCK_NAME "clock.system.monotonic" + +struct props { + uint32_t channels; + uint32_t rate; + uint32_t n_pos; + uint32_t pos[SPA_AUDIO_MAX_CHANNELS]; + char clock_name[64]; + unsigned int debug:1; +}; + +static void reset_props(struct props *props) +{ + props->channels = 0; + props->rate = 0; + props->n_pos = 0; + strncpy(props->clock_name, DEFAULT_CLOCK_NAME, sizeof(props->clock_name)); + props->debug = false; +} + +#define DEFAULT_CHANNELS 2 +#define DEFAULT_RATE 48000 + +#define MAX_BUFFERS 16 +#define MAX_PORTS 1 + +struct buffer { + uint32_t id; +#define BUFFER_FLAG_OUT (1<<0) + uint32_t flags; + struct spa_buffer *outbuf; +}; + +struct impl; + +struct port { + uint64_t info_all; + struct spa_port_info info; + struct spa_param_info params[5]; + + struct spa_io_buffers *io; + + bool have_format; + struct spa_audio_info current_format; + uint32_t blocks; + size_t bpf; + + struct buffer buffers[MAX_BUFFERS]; + uint32_t n_buffers; +}; + +struct impl { + struct spa_handle handle; + struct spa_node node; + + struct spa_log *log; + struct spa_loop *data_loop; + struct spa_system *data_system; + + uint32_t quantum_limit; + + struct props props; + + uint64_t info_all; + struct spa_node_info info; + struct spa_param_info params[2]; + struct spa_io_clock *clock; + struct spa_io_position *position; + + struct spa_hook_list hooks; + struct spa_callbacks callbacks; + + struct port port; + + unsigned int started:1; + unsigned int following:1; + struct spa_source timer_source; + struct itimerspec timerspec; + uint64_t next_time; +}; + +#define CHECK_PORT(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) < MAX_PORTS) + +static int impl_node_enum_params(void *object, int seq, + uint32_t id, uint32_t start, uint32_t num, + const struct spa_pod *filter) +{ + struct impl *this = object; + struct spa_pod *param; + struct spa_pod_builder b = { 0 }; + uint8_t buffer[1024]; + struct spa_result_node_params result; + uint32_t count = 0; + + spa_return_val_if_fail(this != NULL, -EINVAL); + spa_return_val_if_fail(num != 0, -EINVAL); + + result.id = id; + result.next = start; + next: + result.index = result.next++; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + switch (id) { + case SPA_PARAM_IO: + { + switch (result.index) { + case 0: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamIO, id, + SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Clock), + SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_clock))); + break; + case 1: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamIO, id, + SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Position), + SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_position))); + break; + default: + return 0; + } + break; + } + default: + return -ENOENT; + } + + if (spa_pod_filter(&b, &result.param, param, filter) < 0) + goto next; + + spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); + + if (++count != num) + goto next; + + return 0; +} + +static void set_timeout(struct impl *this, uint64_t next_time) +{ + spa_log_trace(this->log, "set timeout %"PRIu64, next_time); + this->timerspec.it_value.tv_sec = next_time / SPA_NSEC_PER_SEC; + this->timerspec.it_value.tv_nsec = next_time % SPA_NSEC_PER_SEC; + spa_system_timerfd_settime(this->data_system, + this->timer_source.fd, SPA_FD_TIMER_ABSTIME, &this->timerspec, NULL); +} + +static int set_timers(struct impl *this) +{ + struct timespec now; + int res; + + if ((res = spa_system_clock_gettime(this->data_system, CLOCK_MONOTONIC, &now)) < 0) + return res; + this->next_time = SPA_TIMESPEC_TO_NSEC(&now); + + if (this->following) { + set_timeout(this, 0); + } else { + set_timeout(this, this->next_time); + } + return 0; +} + +static inline bool is_following(struct impl *this) +{ + return this->position && this->clock && this->position->clock.id != this->clock->id; +} + +static int do_reassign_follower(struct spa_loop *loop, + bool async, + uint32_t seq, + const void *data, + size_t size, + void *user_data) +{ + struct impl *this = user_data; + set_timers(this); + return 0; +} + +static int reassign_follower(struct impl *this) +{ + bool following; + + if (!this->started) + return 0; + + following = is_following(this); + if (following != this->following) { + spa_log_debug(this->log, NAME" %p: reassign follower %d->%d", this, this->following, following); + this->following = following; + spa_loop_invoke(this->data_loop, do_reassign_follower, 0, NULL, 0, true, this); + } + return 0; +} + +static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) +{ + struct impl *this = object; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + switch (id) { + case SPA_IO_Clock: + if (size > 0 && size < sizeof(struct spa_io_clock)) + return -EINVAL; + this->clock = data; + if (this->clock != NULL) { + spa_scnprintf(this->clock->name, + sizeof(this->clock->name), + "%s", this->props.clock_name); + } + break; + case SPA_IO_Position: + this->position = data; + break; + default: + return -ENOENT; + } + reassign_follower(this); + + return 0; +} + +static void on_timeout(struct spa_source *source) +{ + struct impl *this = source->data; + uint64_t expirations, nsec, duration = 10; + uint32_t rate; + int res; + + spa_log_trace(this->log, "timeout"); + + if ((res = spa_system_timerfd_read(this->data_system, + this->timer_source.fd, &expirations)) < 0) { + if (res != EAGAIN) + spa_log_error(this->log, NAME " %p: timerfd error: %s", + this, spa_strerror(res)); + return; + } + + nsec = this->next_time; + + if (SPA_LIKELY(this->position)) { + duration = this->position->clock.duration; + rate = this->position->clock.rate.denom; + } else { + duration = 1024; + rate = 48000; + } + + this->next_time = nsec + duration * SPA_NSEC_PER_SEC / rate; + + if (SPA_LIKELY(this->clock)) { + this->clock->nsec = nsec; + this->clock->position += duration; + this->clock->duration = duration; + this->clock->delay = 0; + this->clock->rate_diff = 1.0; + this->clock->next_nsec = this->next_time; + } + + spa_node_call_ready(&this->callbacks, SPA_STATUS_NEED_DATA); + + set_timeout(this, this->next_time); +} + +static int do_start(struct impl *this) +{ + if (this->started) + return 0; + + this->following = is_following(this); + set_timers(this); + this->started = true; + return 0; +} + +static int do_stop(struct impl *this) +{ + if (!this->started) + return 0; + this->started = false; + set_timeout(this, 0); + return 0; +} + +static int impl_node_send_command(void *object, const struct spa_command *command) +{ + struct impl *this = object; + struct port *port; + + spa_return_val_if_fail(this != NULL, -EINVAL); + spa_return_val_if_fail(command != NULL, -EINVAL); + + port = &this->port; + + switch (SPA_NODE_COMMAND_ID(command)) { + case SPA_NODE_COMMAND_Start: + { + if (!port->have_format) + return -EIO; + if (port->n_buffers == 0) + return -EIO; + + do_start(this); + break; + } + case SPA_NODE_COMMAND_Suspend: + case SPA_NODE_COMMAND_Pause: + do_stop(this); + break; + + default: + return -ENOTSUP; + } + return 0; +} + +static const struct spa_dict_item node_info_items[] = { + { SPA_KEY_NODE_DRIVER, "true" }, +}; + +static void emit_node_info(struct impl *this, bool full) +{ + uint64_t old = full ? this->info.change_mask : 0; + if (full) + this->info.change_mask = this->info_all; + if (this->info.change_mask) { + this->info.props = &SPA_DICT_INIT_ARRAY(node_info_items); + spa_node_emit_info(&this->hooks, &this->info); + this->info.change_mask = old; + } +} + +static void emit_port_info(struct impl *this, struct port *port, bool full) +{ + uint64_t old = full ? port->info.change_mask : 0; + if (full) + port->info.change_mask = port->info_all; + if (port->info.change_mask) { + spa_node_emit_port_info(&this->hooks, + SPA_DIRECTION_INPUT, 0, &port->info); + port->info.change_mask = old; + } +} + +static int +impl_node_add_listener(void *object, + struct spa_hook *listener, + const struct spa_node_events *events, + void *data) +{ + struct impl *this = object; + struct spa_hook_list save; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + spa_hook_list_isolate(&this->hooks, &save, listener, events, data); + + emit_node_info(this, true); + emit_port_info(this, &this->port, true); + + spa_hook_list_join(&this->hooks, &save); + + return 0; +} + +static int +impl_node_set_callbacks(void *object, + const struct spa_node_callbacks *callbacks, + void *data) +{ + struct impl *this = object; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + this->callbacks = SPA_CALLBACKS_INIT(callbacks, data); + + return 0; +} + +static int +port_enum_formats(struct impl *this, + enum spa_direction direction, uint32_t port_id, + uint32_t index, + struct spa_pod **param, + struct spa_pod_builder *builder) +{ + struct spa_pod_frame f[1]; + + switch (index) { + case 0: + spa_pod_builder_push_object(builder, &f[0], + SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); + spa_pod_builder_add(builder, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), + SPA_FORMAT_AUDIO_format, SPA_POD_CHOICE_ENUM_Id(3, + SPA_AUDIO_FORMAT_F32P, + SPA_AUDIO_FORMAT_F32P, + SPA_AUDIO_FORMAT_F32), + 0); + + if (this->props.rate != 0) { + spa_pod_builder_add(builder, + SPA_FORMAT_AUDIO_rate, SPA_POD_Int(this->props.rate), + 0); + } else { + spa_pod_builder_add(builder, + SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_RANGE_Int(DEFAULT_RATE, 1, INT32_MAX), + 0); + } + if (this->props.channels != 0) { + spa_pod_builder_add(builder, + SPA_FORMAT_AUDIO_channels, SPA_POD_Int(this->props.channels), + 0); + } else { + spa_pod_builder_add(builder, + SPA_FORMAT_AUDIO_channels, SPA_POD_CHOICE_RANGE_Int(DEFAULT_CHANNELS, 1, INT32_MAX), + 0); + } + if (this->props.n_pos != 0) { + spa_pod_builder_prop(builder, SPA_FORMAT_AUDIO_position, 0); + spa_pod_builder_array(builder, sizeof(uint32_t), SPA_TYPE_Id, + this->props.n_pos, this->props.pos); + } + *param = spa_pod_builder_pop(builder, &f[0]); + break; + default: + return 0; + } + return 1; +} + +static int +impl_node_port_enum_params(void *object, int seq, + enum spa_direction direction, uint32_t port_id, + uint32_t id, uint32_t start, uint32_t num, + const struct spa_pod *filter) +{ + struct impl *this = object; + struct port *port; + struct spa_pod_builder b = { 0 }; + uint8_t buffer[1024]; + struct spa_pod *param; + struct spa_result_node_params result; + uint32_t count = 0; + int res; + + spa_return_val_if_fail(this != NULL, -EINVAL); + spa_return_val_if_fail(num != 0, -EINVAL); + + spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); + + port = &this->port; + + result.id = id; + result.next = start; + next: + result.index = result.next++; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + switch (id) { + case SPA_PARAM_EnumFormat: + if ((res = port_enum_formats(this, direction, port_id, + result.index, ¶m, &b)) <= 0) + return res; + break; + + case SPA_PARAM_Format: + if (!port->have_format) + return -EIO; + if (result.index > 0) + return 0; + + param = spa_format_audio_raw_build(&b, id, &port->current_format.info.raw); + break; + + case SPA_PARAM_Buffers: + if (!port->have_format) + return -EIO; + if (result.index > 0) + return 0; + + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamBuffers, id, + SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(1, 1, MAX_BUFFERS), + SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(port->blocks), + SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( + this->quantum_limit * port->bpf, + 16 * port->bpf, + INT32_MAX), + SPA_PARAM_BUFFERS_stride, SPA_POD_Int(port->bpf)); + break; + case SPA_PARAM_IO: + switch (result.index) { + case 0: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamIO, id, + SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), + SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); + break; + default: + return 0; + } + break; + default: + return -ENOENT; + } + + if (spa_pod_filter(&b, &result.param, param, filter) < 0) + goto next; + + spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); + + if (++count != num) + goto next; + + return 0; +} + +static int clear_buffers(struct impl *this, struct port *port) +{ + if (port->n_buffers > 0) { + spa_log_info(this->log, NAME " %p: clear buffers", this); + port->n_buffers = 0; + this->started = false; + } + return 0; +} + +static int +port_set_format(struct impl *this, + enum spa_direction direction, + uint32_t port_id, + uint32_t flags, + const struct spa_pod *format) +{ + int res; + struct port *port = &this->port; + + if (format == NULL) { + port->have_format = false; + clear_buffers(this, port); + } else { + struct spa_audio_info info = { 0 }; + + if ((res = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) + return res; + + if (info.media_type != SPA_MEDIA_TYPE_audio || + info.media_subtype != SPA_MEDIA_SUBTYPE_raw) + return -EINVAL; + + if (spa_format_audio_raw_parse(format, &info.info.raw) < 0) + return -EINVAL; + + if (info.info.raw.rate == 0 || + info.info.raw.channels == 0 || + info.info.raw.channels > SPA_AUDIO_MAX_CHANNELS) + return -EINVAL; + + if (info.info.raw.format == SPA_AUDIO_FORMAT_F32) { + port->bpf = 4 * info.info.raw.channels; + port->blocks = 1; + } else if (info.info.raw.format == SPA_AUDIO_FORMAT_F32P) { + port->bpf = 4; + port->blocks = info.info.raw.channels; + } else + return -EINVAL; + + port->current_format = info; + port->have_format = true; + } + + port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; + if (port->have_format) { + port->info.change_mask |= SPA_PORT_CHANGE_MASK_RATE; + port->info.rate = SPA_FRACTION(1, port->current_format.info.raw.rate); + port->params[1] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); + port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); + } else { + port->params[1] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); + port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); + } + emit_port_info(this, port, false); + + return 0; +} + +static int +impl_node_port_set_param(void *object, + enum spa_direction direction, uint32_t port_id, + uint32_t id, uint32_t flags, + const struct spa_pod *param) +{ + struct impl *this = object; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); + + switch (id) { + case SPA_PARAM_Format: + return port_set_format(this, direction, port_id, flags, param); + default: + return -ENOENT; + } + return 0; +} + +static int +impl_node_port_use_buffers(void *object, + enum spa_direction direction, + uint32_t port_id, + uint32_t flags, + struct spa_buffer **buffers, + uint32_t n_buffers) +{ + struct impl *this = object; + struct port *port; + uint32_t i; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); + + port = &this->port; + + clear_buffers(this, port); + + if (n_buffers > 0 && !port->have_format) + return -EIO; + if (n_buffers > MAX_BUFFERS) + return -ENOSPC; + + for (i = 0; i < n_buffers; i++) { + struct buffer *b; + struct spa_data *d = buffers[i]->datas; + + b = &port->buffers[i]; + b->id = i; + b->flags = 0; + b->outbuf = buffers[i]; + + if (d[0].data == NULL) { + spa_log_error(this->log, NAME " %p: invalid memory on buffer %p", this, + buffers[i]); + return -EINVAL; + } + } + port->n_buffers = n_buffers; + + return 0; +} + +static int +impl_node_port_set_io(void *object, + enum spa_direction direction, + uint32_t port_id, + uint32_t id, + void *data, size_t size) +{ + struct impl *this = object; + struct port *port; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); + + port = &this->port; + + switch (id) { + case SPA_IO_Buffers: + port->io = data; + break; + default: + return -ENOENT; + } + return 0; +} + +static int impl_node_process(void *object) +{ + struct impl *this = object; + struct port *port; + struct spa_io_buffers *io; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + port = &this->port; + if ((io = port->io) == NULL) + return -EIO; + + if (io->status != SPA_STATUS_HAVE_DATA) + return io->status; + if (io->buffer_id >= port->n_buffers) { + io->status = -EINVAL; + return io->status; + } + if (this->props.debug) { + struct buffer *b; + uint32_t i; + + b = &port->buffers[io->buffer_id]; + for (i = 0; i < b->outbuf->n_datas; i++) { + uint32_t offs, size; + struct spa_data *d = b->outbuf->datas; + + offs = SPA_MIN(d->chunk->offset, d->maxsize); + size = SPA_MIN(d->maxsize - offs, d->chunk->size); + spa_debug_mem(i, SPA_PTROFF(d[i].data, offs, void), SPA_MIN(16u, size));; + } + } + io->status = SPA_STATUS_OK; + return SPA_STATUS_HAVE_DATA; +} + +static const struct spa_node_methods impl_node = { + SPA_VERSION_NODE_METHODS, + .add_listener = impl_node_add_listener, + .set_callbacks = impl_node_set_callbacks, + .enum_params = impl_node_enum_params, + .set_io = impl_node_set_io, + .send_command = impl_node_send_command, + .port_enum_params = impl_node_port_enum_params, + .port_set_param = impl_node_port_set_param, + .port_use_buffers = impl_node_port_use_buffers, + .port_set_io = impl_node_port_set_io, + .process = impl_node_process, +}; + +static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) +{ + struct impl *this; + + spa_return_val_if_fail(handle != NULL, -EINVAL); + spa_return_val_if_fail(interface != NULL, -EINVAL); + + this = (struct impl *) handle; + + if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) + *interface = &this->node; + else + return -ENOENT; + + return 0; +} + +static int do_remove_timer(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + struct impl *this = user_data; + spa_loop_remove_source(this->data_loop, &this->timer_source); + return 0; +} + +static int impl_clear(struct spa_handle *handle) +{ + struct impl *this; + + spa_return_val_if_fail(handle != NULL, -EINVAL); + + this = (struct impl *) handle; + + spa_loop_invoke(this->data_loop, do_remove_timer, 0, NULL, 0, true, this); + spa_system_close(this->data_system, this->timer_source.fd); + + return 0; +} + +static size_t +impl_get_size(const struct spa_handle_factory *factory, + const struct spa_dict *params) +{ + return sizeof(struct impl); +} + +static uint32_t channel_from_name(const char *name) +{ + int i; + for (i = 0; spa_type_audio_channel[i].name; i++) { + if (spa_streq(name, spa_debug_type_short_name(spa_type_audio_channel[i].name))) + return spa_type_audio_channel[i].type; + } + return SPA_AUDIO_CHANNEL_UNKNOWN; +} + +static inline void parse_position(struct impl *this, const char *val, size_t len) +{ + struct spa_json it[2]; + char v[256]; + + spa_json_init(&it[0], val, len); + if (spa_json_enter_array(&it[0], &it[1]) <= 0) + spa_json_init(&it[1], val, len); + + this->props.n_pos = 0; + while (spa_json_get_string(&it[1], v, sizeof(v)) > 0 && + this->props.n_pos < SPA_AUDIO_MAX_CHANNELS) { + this->props.pos[this->props.n_pos++] = channel_from_name(v); + } +} + +static int +impl_init(const struct spa_handle_factory *factory, + struct spa_handle *handle, + const struct spa_dict *info, + const struct spa_support *support, + uint32_t n_support) +{ + struct impl *this; + struct port *port; + uint32_t i; + + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(handle != NULL, -EINVAL); + + handle->get_interface = impl_get_interface; + handle->clear = impl_clear; + + this = (struct impl *) handle; + + this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); + this->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); + this->data_system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataSystem); + + if (this->data_loop == NULL) { + spa_log_error(this->log, "a data_loop is needed"); + return -EINVAL; + } + if (this->data_system == NULL) { + spa_log_error(this->log, "a data_system is needed"); + return -EINVAL; + } + spa_hook_list_init(&this->hooks); + + this->node.iface = SPA_INTERFACE_INIT( + SPA_TYPE_INTERFACE_Node, + SPA_VERSION_NODE, + &impl_node, this); + + this->info_all |= SPA_NODE_CHANGE_MASK_FLAGS | + SPA_NODE_CHANGE_MASK_PROPS | + SPA_NODE_CHANGE_MASK_PARAMS; + this->info = SPA_NODE_INFO_INIT(); + this->info.max_input_ports = 1; + this->info.flags = SPA_NODE_FLAG_RT; + this->params[0] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); + this->info.params = this->params; + this->info.n_params = 1; + reset_props(&this->props); + + port = &this->port; + port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | + SPA_PORT_CHANGE_MASK_PARAMS; + port->info = SPA_PORT_INFO_INIT(); + port->info.flags = SPA_PORT_FLAG_NO_REF | + SPA_PORT_FLAG_LIVE; + port->params[0] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); + port->params[1] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); + port->params[2] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); + port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); + port->info.params = port->params; + port->info.n_params = 4; + + this->timer_source.func = on_timeout; + this->timer_source.data = this; + this->timer_source.fd = spa_system_timerfd_create(this->data_system, CLOCK_MONOTONIC, + SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); + this->timer_source.mask = SPA_IO_IN; + this->timer_source.rmask = 0; + this->timerspec.it_value.tv_sec = 0; + this->timerspec.it_value.tv_nsec = 0; + this->timerspec.it_interval.tv_sec = 0; + this->timerspec.it_interval.tv_nsec = 0; + + spa_loop_add_source(this->data_loop, &this->timer_source); + + for (i = 0; info && i < info->n_items; i++) { + const char *k = info->items[i].key; + const char *s = info->items[i].value; + if (spa_streq(k, "clock.quantum-limit")) { + spa_atou32(s, &this->quantum_limit, 0); + } else if (spa_streq(k, SPA_KEY_AUDIO_CHANNELS)) { + this->props.channels = atoi(s); + } else if (spa_streq(k, SPA_KEY_AUDIO_RATE)) { + this->props.rate = atoi(s); + } else if (spa_streq(k, SPA_KEY_AUDIO_POSITION)) { + parse_position(this, s, strlen(s)); + } else if (spa_streq(k, "clock.name")) { + spa_scnprintf(this->props.clock_name, + sizeof(this->props.clock_name), + "%s", s); + } + } + if (this->props.n_pos > 0) + this->props.channels = this->props.n_pos; + + spa_log_info(this->log, NAME " %p: initialized", this); + + return 0; +} + +static const struct spa_interface_info impl_interfaces[] = { + {SPA_TYPE_INTERFACE_Node,}, +}; + +static int +impl_enum_interface_info(const struct spa_handle_factory *factory, + const struct spa_interface_info **info, + uint32_t *index) +{ + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(info != NULL, -EINVAL); + spa_return_val_if_fail(index != NULL, -EINVAL); + + switch (*index) { + case 0: + *info = &impl_interfaces[*index]; + break; + default: + return 0; + } + (*index)++; + + return 1; +} + +static const struct spa_dict_item info_items[] = { + { SPA_KEY_FACTORY_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, + { SPA_KEY_FACTORY_DESCRIPTION, "Consume audio samples" }, +}; + +static const struct spa_dict info = SPA_DICT_INIT_ARRAY(info_items); + +const struct spa_handle_factory spa_support_null_audio_sink_factory = { + SPA_VERSION_HANDLE_FACTORY, + "support.null-audio-sink", + &info, + impl_get_size, + impl_init, + impl_enum_interface_info, +}; diff --git a/spa/plugins/support/plugin.c b/spa/plugins/support/plugin.c new file mode 100644 index 0000000..29bd8a9 --- /dev/null +++ b/spa/plugins/support/plugin.c @@ -0,0 +1,67 @@ +/* Spa Support plugin + * + * Copyright © 2018 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <errno.h> +#include <stdio.h> + +#include <spa/support/plugin.h> + +extern const struct spa_handle_factory spa_support_logger_factory; +extern const struct spa_handle_factory spa_support_system_factory; +extern const struct spa_handle_factory spa_support_cpu_factory; +extern const struct spa_handle_factory spa_support_loop_factory; +extern const struct spa_handle_factory spa_support_node_driver_factory; +extern const struct spa_handle_factory spa_support_null_audio_sink_factory; + +SPA_EXPORT +int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index) +{ + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(index != NULL, -EINVAL); + + switch (*index) { + case 0: + *factory = &spa_support_logger_factory; + break; + case 1: + *factory = &spa_support_system_factory; + break; + case 2: + *factory = &spa_support_cpu_factory; + break; + case 3: + *factory = &spa_support_loop_factory; + break; + case 4: + *factory = &spa_support_node_driver_factory; + break; + case 5: + *factory = &spa_support_null_audio_sink_factory; + break; + default: + return 0; + } + (*index)++; + return 1; +} diff --git a/spa/plugins/support/system.c b/spa/plugins/support/system.c new file mode 100644 index 0000000..e7efec9 --- /dev/null +++ b/spa/plugins/support/system.c @@ -0,0 +1,384 @@ +/* Spa + * + * Copyright © 2019 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include <unistd.h> +#include <errno.h> +#include <sys/types.h> +#include <signal.h> +#include <stdlib.h> +#include <stdio.h> +#include <sys/epoll.h> +#include <sys/ioctl.h> +#include <sys/timerfd.h> +#include <sys/eventfd.h> +#include <sys/signalfd.h> + +#include <spa/support/log.h> +#include <spa/support/system.h> +#include <spa/support/plugin.h> +#include <spa/utils/type.h> +#include <spa/utils/names.h> +#include <spa/utils/string.h> + +static struct spa_log_topic log_topic = SPA_LOG_TOPIC(0, "spa.system"); +#undef SPA_LOG_TOPIC_DEFAULT +#define SPA_LOG_TOPIC_DEFAULT &log_topic + +#ifndef TFD_TIMER_CANCEL_ON_SET +# define TFD_TIMER_CANCEL_ON_SET (1 << 1) +#endif + +struct impl { + struct spa_handle handle; + struct spa_system system; + struct spa_log *log; +}; + +static ssize_t impl_read(void *object, int fd, void *buf, size_t count) +{ + ssize_t res = read(fd, buf, count); + return res < 0 ? -errno : res; +} + +static ssize_t impl_write(void *object, int fd, const void *buf, size_t count) +{ + ssize_t res = write(fd, buf, count); + return res < 0 ? -errno : res; +} + +static int impl_ioctl(void *object, int fd, unsigned long request, ...) +{ + int res; + va_list ap; + long arg; + + va_start(ap, request); + arg = va_arg(ap, long); + res = ioctl(fd, request, arg); + va_end(ap); + + return res < 0 ? -errno : res; +} + +static int impl_close(void *object, int fd) +{ + struct impl *impl = object; + int res = close(fd); + spa_log_debug(impl->log, "%p: close fd:%d", impl, fd); + return res < 0 ? -errno : res; +} + +/* clock */ +static int impl_clock_gettime(void *object, + int clockid, struct timespec *value) +{ + int res = clock_gettime(clockid, value); + return res < 0 ? -errno : res; +} + +static int impl_clock_getres(void *object, + int clockid, struct timespec *res) +{ + int r = clock_getres(clockid, res); + return r < 0 ? -errno : r; +} + +/* poll */ +static int impl_pollfd_create(void *object, int flags) +{ + struct impl *impl = object; + int fl = 0, res; + if (flags & SPA_FD_CLOEXEC) + fl |= EPOLL_CLOEXEC; + res = epoll_create1(fl); + spa_log_debug(impl->log, "%p: new fd:%d", impl, res); + return res < 0 ? -errno : res; +} + +static int impl_pollfd_add(void *object, int pfd, int fd, uint32_t events, void *data) +{ + struct epoll_event ep; + int res; + + spa_zero(ep); + ep.events = events; + ep.data.ptr = data; + + res = epoll_ctl(pfd, EPOLL_CTL_ADD, fd, &ep); + return res < 0 ? -errno : res; +} + +static int impl_pollfd_mod(void *object, int pfd, int fd, uint32_t events, void *data) +{ + struct epoll_event ep; + int res; + + spa_zero(ep); + ep.events = events; + ep.data.ptr = data; + + res = epoll_ctl(pfd, EPOLL_CTL_MOD, fd, &ep); + return res < 0 ? -errno : res; +} + +static int impl_pollfd_del(void *object, int pfd, int fd) +{ + int res = epoll_ctl(pfd, EPOLL_CTL_DEL, fd, NULL); + return res < 0 ? -errno : res; +} + +static int impl_pollfd_wait(void *object, int pfd, + struct spa_poll_event *ev, int n_ev, int timeout) +{ + struct epoll_event ep[n_ev]; + int i, nfds; + + if (SPA_UNLIKELY((nfds = epoll_wait(pfd, ep, n_ev, timeout)) < 0)) + return -errno; + + for (i = 0; i < nfds; i++) { + ev[i].events = ep[i].events; + ev[i].data = ep[i].data.ptr; + } + return nfds; +} + +/* timers */ +static int impl_timerfd_create(void *object, int clockid, int flags) +{ + struct impl *impl = object; + int fl = 0, res; + if (flags & SPA_FD_CLOEXEC) + fl |= TFD_CLOEXEC; + if (flags & SPA_FD_NONBLOCK) + fl |= TFD_NONBLOCK; + res = timerfd_create(clockid, fl); + spa_log_debug(impl->log, "%p: new fd:%d", impl, res); + return res < 0 ? -errno : res; +} + +static int impl_timerfd_settime(void *object, + int fd, int flags, + const struct itimerspec *new_value, + struct itimerspec *old_value) +{ + int fl = 0, res; + if (flags & SPA_FD_TIMER_ABSTIME) + fl |= TFD_TIMER_ABSTIME; + if (flags & SPA_FD_TIMER_CANCEL_ON_SET) + fl |= TFD_TIMER_CANCEL_ON_SET; + res = timerfd_settime(fd, fl, new_value, old_value); + return res < 0 ? -errno : res; +} + +static int impl_timerfd_gettime(void *object, + int fd, struct itimerspec *curr_value) +{ + int res = timerfd_gettime(fd, curr_value); + return res < 0 ? -errno : res; + +} +static int impl_timerfd_read(void *object, int fd, uint64_t *expirations) +{ + if (read(fd, expirations, sizeof(uint64_t)) != sizeof(uint64_t)) + return -errno; + return 0; +} + +/* events */ +static int impl_eventfd_create(void *object, int flags) +{ + struct impl *impl = object; + int fl = 0, res; + if (flags & SPA_FD_CLOEXEC) + fl |= EFD_CLOEXEC; + if (flags & SPA_FD_NONBLOCK) + fl |= EFD_NONBLOCK; + if (flags & SPA_FD_EVENT_SEMAPHORE) + fl |= EFD_SEMAPHORE; + res = eventfd(0, fl); + spa_log_debug(impl->log, "%p: new fd:%d", impl, res); + return res < 0 ? -errno : res; +} + +static int impl_eventfd_write(void *object, int fd, uint64_t count) +{ + if (write(fd, &count, sizeof(uint64_t)) != sizeof(uint64_t)) + return -errno; + return 0; +} + +static int impl_eventfd_read(void *object, int fd, uint64_t *count) +{ + if (read(fd, count, sizeof(uint64_t)) != sizeof(uint64_t)) + return -errno; + return 0; +} + +/* signals */ +static int impl_signalfd_create(void *object, int signal, int flags) +{ + struct impl *impl = object; + sigset_t mask; + int res, fl = 0; + + if (flags & SPA_FD_CLOEXEC) + fl |= SFD_CLOEXEC; + if (flags & SPA_FD_NONBLOCK) + fl |= SFD_NONBLOCK; + + sigemptyset(&mask); + sigaddset(&mask, signal); + res = signalfd(-1, &mask, fl); + sigprocmask(SIG_BLOCK, &mask, NULL); + spa_log_debug(impl->log, "%p: new fd:%d", impl, res); + + return res < 0 ? -errno : res; +} + +static int impl_signalfd_read(void *object, int fd, int *signal) +{ + struct signalfd_siginfo signal_info; + int len; + + len = read(fd, &signal_info, sizeof signal_info); + if (!(len == -1 && errno == EAGAIN) && len != sizeof signal_info) + return -errno; + + *signal = signal_info.ssi_signo; + + return 0; +} + +static const struct spa_system_methods impl_system = { + SPA_VERSION_SYSTEM_METHODS, + .read = impl_read, + .write = impl_write, + .ioctl = impl_ioctl, + .close = impl_close, + .clock_gettime = impl_clock_gettime, + .clock_getres = impl_clock_getres, + .pollfd_create = impl_pollfd_create, + .pollfd_add = impl_pollfd_add, + .pollfd_mod = impl_pollfd_mod, + .pollfd_del = impl_pollfd_del, + .pollfd_wait = impl_pollfd_wait, + .timerfd_create = impl_timerfd_create, + .timerfd_settime = impl_timerfd_settime, + .timerfd_gettime = impl_timerfd_gettime, + .timerfd_read = impl_timerfd_read, + .eventfd_create = impl_eventfd_create, + .eventfd_write = impl_eventfd_write, + .eventfd_read = impl_eventfd_read, + .signalfd_create = impl_signalfd_create, + .signalfd_read = impl_signalfd_read, +}; + +static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) +{ + struct impl *impl; + + spa_return_val_if_fail(handle != NULL, -EINVAL); + spa_return_val_if_fail(interface != NULL, -EINVAL); + + impl = (struct impl *) handle; + + if (spa_streq(type, SPA_TYPE_INTERFACE_System)) + *interface = &impl->system; + else + return -ENOENT; + + return 0; +} + +static int impl_clear(struct spa_handle *handle) +{ + spa_return_val_if_fail(handle != NULL, -EINVAL); + return 0; +} + +static size_t +impl_get_size(const struct spa_handle_factory *factory, + const struct spa_dict *params) +{ + return sizeof(struct impl); +} + +static int +impl_init(const struct spa_handle_factory *factory, + struct spa_handle *handle, + const struct spa_dict *info, + const struct spa_support *support, + uint32_t n_support) +{ + struct impl *impl; + + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(handle != NULL, -EINVAL); + + handle->get_interface = impl_get_interface; + handle->clear = impl_clear; + + impl = (struct impl *) handle; + impl->system.iface = SPA_INTERFACE_INIT( + SPA_TYPE_INTERFACE_System, + SPA_VERSION_SYSTEM, + &impl_system, impl); + + impl->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); + spa_log_topic_init(impl->log, &log_topic); + + spa_log_debug(impl->log, "%p: initialized", impl); + + return 0; +} + +static const struct spa_interface_info impl_interfaces[] = { + {SPA_TYPE_INTERFACE_System,}, +}; + +static int +impl_enum_interface_info(const struct spa_handle_factory *factory, + const struct spa_interface_info **info, + uint32_t *index) +{ + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(info != NULL, -EINVAL); + spa_return_val_if_fail(index != NULL, -EINVAL); + + if (*index >= SPA_N_ELEMENTS(impl_interfaces)) + return 0; + + *info = &impl_interfaces[(*index)++]; + return 1; +} + +const struct spa_handle_factory spa_support_system_factory = { + SPA_VERSION_HANDLE_FACTORY, + SPA_NAME_SUPPORT_SYSTEM, + NULL, + impl_get_size, + impl_init, + impl_enum_interface_info +}; |