diff --git a/spa/plugins/support/node-driver.c b/spa/plugins/support/node-driver.c index e7b2c702b..fa9cf3426 100644 --- a/spa/plugins/support/node-driver.c +++ b/spa/plugins/support/node-driver.c @@ -26,6 +26,8 @@ #include #include #include +#include +#include SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.driver"); @@ -48,12 +50,16 @@ SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.driver"); #define BW_PERIOD (3 * SPA_NSEC_PER_SEC) #define MAX_ERROR_MS 1 +#define CLOCK_NAME_MAX 64 + struct props { bool freewheel; - char clock_name[64]; + char clock_name[CLOCK_NAME_MAX]; clockid_t clock_id; uint32_t freewheel_wait; float resync_ms; + char clock_device[CLOCK_NAME_MAX]; + char clock_interface[CLOCK_NAME_MAX]; }; struct clock_offset { @@ -73,7 +79,10 @@ struct impl { uint64_t info_all; struct spa_node_info info; - struct spa_param_info params[1]; +#define NODE_PropInfo 0 +#define NODE_Props 1 +#define N_NODE_PARAMS 2 + struct spa_param_info params[N_NODE_PARAMS]; struct spa_hook_list hooks; struct spa_callbacks callbacks; @@ -99,13 +108,20 @@ struct impl { struct clock_offset nsec_offset; }; +static void reset_props_strings(struct props *props) +{ + spa_zero(props->clock_name); + spa_zero(props->clock_device); + spa_zero(props->clock_interface); +} + static void reset_props(struct props *props) { props->freewheel = DEFAULT_FREEWHEEL; - spa_zero(props->clock_name); props->clock_id = CLOCK_MONOTONIC; props->freewheel_wait = DEFAULT_FREEWHEEL_WAIT; props->resync_ms = DEFAULT_RESYNC_MS; + reset_props_strings(props); } static const struct clock_info { @@ -598,10 +614,280 @@ static int impl_node_process(void *object) return SPA_STATUS_HAVE_DATA | SPA_STATUS_NEED_DATA; } +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[4096]; + 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_PropInfo: + { + struct props *p = &this->props; + + switch (result.index) { + case 0: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_clockId), + SPA_PROP_INFO_description, SPA_POD_String("The clock id (monotonic, realtime, etc.)"), + SPA_PROP_INFO_type, SPA_POD_String(clock_id_to_name(p->clock_id))); + break; + case 1: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_clockDevice), + SPA_PROP_INFO_description, SPA_POD_String("The clock device (eg. /dev/ptp0)"), + SPA_PROP_INFO_type, SPA_POD_Stringn(p->clock_device, sizeof(p->clock_device))); + break; + case 2: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_clockInterface), + SPA_PROP_INFO_description, SPA_POD_String("The clock network interface (eg. eth0)"), + SPA_PROP_INFO_type, SPA_POD_Stringn(p->clock_interface, sizeof(p->clock_interface))); + break; + default: + return 0; + } + + break; + } + + case SPA_PARAM_Props: + { + struct props *p = &this->props; + + switch (result.index) { + case 0: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_Props, id, + SPA_PROP_clockId, SPA_POD_String(clock_id_to_name(p->clock_id)) + ); + break; + case 1: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_Props, id, + SPA_PROP_clockDevice, SPA_POD_Stringn(p->clock_device, sizeof(p->clock_device)) + ); + break; + case 2: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_Props, id, + SPA_PROP_clockInterface, SPA_POD_Stringn(p->clock_interface, sizeof(p->clock_interface)) + ); + 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 get_phc_index(struct spa_system *s, const char *name) { +#ifdef ETHTOOL_GET_TS_INFO + struct ethtool_ts_info info = {0}; + struct ifreq ifr = {0}; + int fd, err; + + info.cmd = ETHTOOL_GET_TS_INFO; + strncpy(ifr.ifr_name, name, IFNAMSIZ - 1); + ifr.ifr_data = (char *) &info; + + fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) + return -errno; + + err = spa_system_ioctl(s, fd, SIOCETHTOOL, &ifr); + close(fd); + if (err < 0) + return -errno; + + return info.phc_index; +#else + return -ENOTSUP; +#endif +} + +static bool parse_clock_id(struct impl *this, const char *s) +{ + int id = clock_name_to_id(s); + if (id == -1) { + spa_log_info(this->log, "unknown clock id '%s'", s); + return false; + } + this->props.clock_id = id; + if (this->clock_fd >= 0) { + close(this->clock_fd); + this->clock_fd = -1; + } + return true; +} + +static bool parse_clock_device(struct impl *this, const char *s) +{ + int fd = open(s, O_RDONLY); + if (fd == -1) { + spa_log_info(this->log, "failed to open clock device '%s': %m", s); + return false; + } + if (this->clock_fd >= 0) { + close(this->clock_fd); + } + this->clock_fd = fd; + this->props.clock_id = FD_TO_CLOCKID(this->clock_fd); + return true; +} + +static bool parse_clock_interface(struct impl *this, const char *s) +{ + int phc_index = get_phc_index(this->data_system, s); + if (phc_index < 0) { + spa_log_info(this->log, "failed to get phc device index for interface '%s': %s", + s, spa_strerror(phc_index)); + return false; + } else { + char dev[19]; + spa_scnprintf(dev, sizeof(dev), "/dev/ptp%d", phc_index); + if (!parse_clock_device(this, dev)) { + spa_log_info(this->log, "failed to open clock device '%s' " + "for interface '%s': %m", dev, s); + return false; + } + } + return true; +} + +static void ensure_clock_name(struct impl *this) +{ + struct props *p = &this->props; + if (p->clock_name[0] == '\0') { + const char *name = clock_id_to_name(p->clock_id); + if (p->clock_device[0]) + name = p->clock_device; + if (p->clock_interface[0]) + name = p->clock_interface; + spa_scnprintf(p->clock_name, sizeof(p->clock_name), + "%s.%s", DEFAULT_CLOCK_PREFIX, name); + } +} + +static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, + const struct spa_pod *param) +{ + struct impl *this = object; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + switch (id) { + case SPA_PARAM_Props: + { + struct props *p = &this->props; + bool notify = false; + char buffer[CLOCK_NAME_MAX]; + int count; + + if (param == NULL) { + return 0; + } + + /* Note that the length passed to SPA_POD_OPT_Stringn() also + * includes room for the null terminator, so the content of the + * buffer variable is always guaranteed to be null terminated. */ + + spa_zero(buffer); + count = spa_pod_parse_object(param, + SPA_TYPE_OBJECT_Props, NULL, + SPA_PROP_clockId, SPA_POD_OPT_Stringn(buffer, sizeof(buffer)) + ); + if (count && parse_clock_id(this, buffer)) + { + reset_props_strings(p); + notify = true; + } + + spa_zero(buffer); + count = spa_pod_parse_object(param, + SPA_TYPE_OBJECT_Props, NULL, + SPA_PROP_clockDevice, SPA_POD_OPT_Stringn(buffer, sizeof(buffer)) + ); + if (count && parse_clock_device(this, buffer)) + { + reset_props_strings(p); + strncpy(p->clock_device, buffer, sizeof(p->clock_device)); + notify = true; + } + + spa_zero(buffer); + count = spa_pod_parse_object(param, + SPA_TYPE_OBJECT_Props, NULL, + SPA_PROP_clockInterface, SPA_POD_OPT_Stringn(buffer, sizeof(buffer)) + ); + if (count && parse_clock_interface(this, buffer)) + { + reset_props_strings(p); + strncpy(p->clock_interface, buffer, sizeof(p->clock_interface)); + notify = true; + } + + if (notify) + { + ensure_clock_name(this); + spa_log_info(this->log, "%p: setting clock to '%s'", this, p->clock_name); + if (this->started) { + do_stop(this); + do_start(this); + } + emit_node_info(this, true); + } + + break; + } + + default: + return -ENOENT; + break; + } + + return 0; +} + 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_param = impl_node_set_param, .set_io = impl_node_set_io, .send_command = impl_node_send_command, .process = impl_node_process, @@ -655,31 +941,6 @@ impl_get_size(const struct spa_handle_factory *factory, return sizeof(struct impl); } -static int get_phc_index(struct spa_system *s, const char *name) { -#ifdef ETHTOOL_GET_TS_INFO - struct ethtool_ts_info info = {0}; - struct ifreq ifr = {0}; - int fd, err; - - info.cmd = ETHTOOL_GET_TS_INFO; - strncpy(ifr.ifr_name, name, IFNAMSIZ - 1); - ifr.ifr_data = (char *) &info; - - fd = socket(AF_INET, SOCK_DGRAM, 0); - if (fd < 0) - return -errno; - - err = spa_system_ioctl(s, fd, SIOCETHTOOL, &ifr); - close(fd); - if (err < 0) - return -errno; - - return info.phc_index; -#else - return -ENOTSUP; -#endif -} - static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, @@ -727,9 +988,10 @@ impl_init(const struct spa_handle_factory *factory, 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->params[NODE_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); + this->params[NODE_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); this->info.params = this->params; - this->info.n_params = 0; + this->info.n_params = N_NODE_PARAMS; reset_props(&this->props); @@ -742,37 +1004,17 @@ impl_init(const struct spa_handle_factory *factory, spa_scnprintf(this->props.clock_name, sizeof(this->props.clock_name), "%s", s); } else if (spa_streq(k, "clock.id") && this->clock_fd < 0) { - this->props.clock_id = clock_name_to_id(s); - if (this->props.clock_id == -1) { - spa_log_warn(this->log, "unknown clock id '%s'", s); - this->props.clock_id = DEFAULT_CLOCK_ID; - } + if (parse_clock_id(this, s)) + reset_props_strings(&this->props); } else if (spa_streq(k, "clock.device")) { - if (this->clock_fd >= 0) { - close(this->clock_fd); - } - this->clock_fd = open(s, O_RDONLY); - - if (this->clock_fd == -1) { - spa_log_warn(this->log, "failed to open clock device '%s': %m", s); - } else { - this->props.clock_id = FD_TO_CLOCKID(this->clock_fd); + if (parse_clock_device(this, s)) { + reset_props_strings(&this->props); + strncpy(this->props.clock_device, s, sizeof(this->props.clock_device)-1); } } else if (spa_streq(k, "clock.interface") && this->clock_fd < 0) { - int phc_index = get_phc_index(this->data_system, s); - if (phc_index < 0) { - spa_log_warn(this->log, "failed to get phc device index for interface '%s': %s", - s, spa_strerror(phc_index)); - } else { - char dev[19]; - spa_scnprintf(dev, sizeof(dev), "/dev/ptp%d", phc_index); - this->clock_fd = open(dev, O_RDONLY); - if (this->clock_fd == -1) { - spa_log_warn(this->log, "failed to open clock device '%s' " - "for interface '%s': %m", dev, s); - } else { - this->props.clock_id = FD_TO_CLOCKID(this->clock_fd); - } + if (parse_clock_interface(this, s)) { + reset_props_strings(&this->props); + strncpy(this->props.clock_interface, s, sizeof(this->props.clock_interface)-1); } } else if (spa_streq(k, "freewheel.wait")) { this->props.freewheel_wait = atoi(s); @@ -785,6 +1027,7 @@ impl_init(const struct spa_handle_factory *factory, "%s.%s", DEFAULT_CLOCK_PREFIX, clock_id_to_name(this->props.clock_id)); } + ensure_clock_name(this); this->tracking = !clock_for_timerfd(this->props.clock_id); this->timer_clockid = this->tracking ? CLOCK_MONOTONIC : this->props.clock_id;