| /** |
| * @file pmc_agent.c |
| * @note Copyright (C) 2012 Richard Cochran <richardcochran@gmail.com> |
| * @note Copyright (C) 2013 Miroslav Lichvar <mlichvar@redhat.com> |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License along |
| * with this program; if not, write to the Free Software Foundation, Inc., |
| * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| */ |
| #include <errno.h> |
| #include <net/if.h> |
| #include <poll.h> |
| #include <stdlib.h> |
| |
| #include "notification.h" |
| #include "pmc_agent.h" |
| #include "print.h" |
| #include "util.h" |
| |
| /* The subscription duration needs to be longer than the update interval to be |
| able to process the messages in time without losing notifications. Define |
| a minimum interval to avoid renewing the subscription too frequently. */ |
| #define UPDATES_PER_SUBSCRIPTION 3 |
| #define MIN_UPDATE_INTERVAL 10 |
| |
| /* Update interval if the agent not subscribed, just polling the UTC offset */ |
| #define DEFAULT_UPDATE_INTERVAL 60 |
| |
| struct pmc_agent { |
| struct pmc *pmc; |
| uint64_t pmc_last_update; |
| uint64_t update_interval; |
| int update_phase; |
| |
| struct defaultDS dds; |
| bool dds_valid; |
| int leap; |
| int pmc_ds_requested; |
| bool stay_subscribed; |
| int sync_offset; |
| int utc_offset_traceable; |
| |
| /* Callback on message reception */ |
| pmc_node_recv_subscribed_t *recv_subscribed; |
| void *recv_context; |
| }; |
| |
| static void send_subscription(struct pmc_agent *node) |
| { |
| struct subscribe_events_np sen; |
| |
| memset(&sen, 0, sizeof(sen)); |
| sen.duration = UPDATES_PER_SUBSCRIPTION * node->update_interval; |
| event_bitmask_set(sen.bitmask, NOTIFY_PORT_STATE, TRUE); |
| pmc_send_set_action(node->pmc, MID_SUBSCRIBE_EVENTS_NP, &sen, sizeof(sen)); |
| } |
| |
| static int check_clock_identity(struct pmc_agent *node, struct ptp_message *msg) |
| { |
| if (!node->dds_valid) { |
| return 1; |
| } |
| return cid_eq(&node->dds.clockIdentity, |
| &msg->header.sourcePortIdentity.clockIdentity); |
| } |
| |
| static int is_msg_mgt(struct ptp_message *msg) |
| { |
| struct tlv_extra *extra; |
| struct TLV *tlv; |
| |
| if (msg_type(msg) != MANAGEMENT) |
| return 0; |
| if (management_action(msg) != RESPONSE) |
| return 0; |
| switch (msg_tlv_count(msg)) { |
| case 1: |
| break; |
| case 2: |
| extra = TAILQ_LAST(&msg->tlv_list, tlv_list); |
| if (extra->tlv->type == TLV_AUTHENTICATION) { |
| break; |
| } |
| default: |
| return 0; |
| } |
| tlv = (struct TLV *) msg->management.suffix; |
| if (tlv->type == TLV_MANAGEMENT) |
| return 1; |
| if (tlv->type == TLV_MANAGEMENT_ERROR_STATUS) |
| return -1; |
| return 0; |
| } |
| |
| static int get_mgt_err_id(struct ptp_message *msg) |
| { |
| struct management_error_status *mgt; |
| |
| mgt = (struct management_error_status *)msg->management.suffix; |
| return mgt->id; |
| } |
| |
| #define RUN_PMC_OKAY 1 |
| #define RUN_PMC_TMO 0 |
| #define RUN_PMC_NODEV -1 |
| #define RUN_PMC_INTR -2 |
| |
| static bool is_run_pmc_error(int code) |
| { |
| return code != RUN_PMC_OKAY; |
| } |
| |
| static int run_pmc_err2errno(int code) |
| { |
| switch (code) { |
| case RUN_PMC_TMO: |
| return -ETIMEDOUT; |
| case RUN_PMC_NODEV: |
| return -ENODEV; |
| case RUN_PMC_INTR: |
| return -EINTR; |
| case RUN_PMC_OKAY: |
| default: |
| return 0; |
| } |
| } |
| |
| static int run_pmc(struct pmc_agent *node, int timeout, int ds_id, |
| struct ptp_message **msg) |
| { |
| #define N_FD 1 |
| struct pollfd pollfd[N_FD]; |
| int cnt, res; |
| |
| while (1) { |
| pollfd[0].fd = pmc_get_transport_fd(node->pmc); |
| pollfd[0].events = POLLIN|POLLPRI; |
| if (!node->pmc_ds_requested && ds_id >= 0) |
| pollfd[0].events |= POLLOUT; |
| |
| cnt = poll(pollfd, N_FD, timeout); |
| if (cnt < 0) { |
| pr_err("poll failed"); |
| return RUN_PMC_INTR; |
| } |
| if (!cnt) { |
| /* Request the data set again in the next run. */ |
| node->pmc_ds_requested = 0; |
| return RUN_PMC_TMO; |
| } |
| |
| /* Send a new request if there are no pending messages. */ |
| if ((pollfd[0].revents & POLLOUT) && |
| !(pollfd[0].revents & (POLLIN|POLLPRI))) { |
| switch (ds_id) { |
| case MID_SUBSCRIBE_EVENTS_NP: |
| send_subscription(node); |
| break; |
| default: |
| pmc_send_get_action(node->pmc, ds_id); |
| break; |
| } |
| node->pmc_ds_requested = 1; |
| } |
| |
| if (!(pollfd[0].revents & (POLLIN|POLLPRI))) |
| continue; |
| |
| *msg = pmc_recv(node->pmc); |
| |
| if (!*msg) |
| continue; |
| |
| if (!check_clock_identity(node, *msg)) { |
| msg_put(*msg); |
| *msg = NULL; |
| continue; |
| } |
| |
| res = is_msg_mgt(*msg); |
| if (res < 0 && get_mgt_err_id(*msg) == ds_id) { |
| node->pmc_ds_requested = 0; |
| return RUN_PMC_NODEV; |
| } |
| if (res <= 0 || |
| node->recv_subscribed(node->recv_context, *msg, ds_id) || |
| management_tlv_id(*msg) != ds_id) { |
| msg_put(*msg); |
| *msg = NULL; |
| continue; |
| } |
| node->pmc_ds_requested = 0; |
| return RUN_PMC_OKAY; |
| } |
| } |
| |
| static int renew_subscription(struct pmc_agent *node, int timeout) |
| { |
| struct ptp_message *msg; |
| int res; |
| |
| res = run_pmc(node, timeout, MID_SUBSCRIBE_EVENTS_NP, &msg); |
| if (is_run_pmc_error(res)) { |
| return run_pmc_err2errno(res); |
| } |
| msg_put(msg); |
| return 0; |
| } |
| |
| int run_pmc_wait_sync(struct pmc_agent *node, int timeout) |
| { |
| struct ptp_message *msg; |
| Enumeration8 portState; |
| void *data; |
| int res; |
| |
| while (1) { |
| res = run_pmc(node, timeout, MID_PORT_DATA_SET, &msg); |
| if (res <= 0) |
| return res; |
| |
| data = management_tlv_data(msg); |
| portState = ((struct portDS *)data)->portState; |
| msg_put(msg); |
| |
| switch (portState) { |
| case PS_MASTER: |
| case PS_SLAVE: |
| return 1; |
| } |
| /* try to get more data sets (for other ports) */ |
| node->pmc_ds_requested = 1; |
| } |
| } |
| |
| int init_pmc_node(struct config *cfg, struct pmc_agent *node, const char *uds, |
| pmc_node_recv_subscribed_t *recv_subscribed, void *context) |
| { |
| node->pmc = pmc_create(cfg, TRANS_UDS, uds, |
| config_get_string(cfg, NULL, "uds_address"), 0, |
| config_get_int(cfg, NULL, "domainNumber"), |
| config_get_int(cfg, NULL, "transportSpecific") << 4, 0, 1); |
| if (!node->pmc) { |
| pr_err("failed to create pmc"); |
| return -1; |
| } |
| node->recv_subscribed = recv_subscribed; |
| node->recv_context = context; |
| node->update_interval = DEFAULT_UPDATE_INTERVAL * NS_PER_SEC; |
| |
| return 0; |
| } |
| |
| struct pmc_agent *pmc_agent_create(void) |
| { |
| struct pmc_agent *agent = calloc(1, sizeof(*agent)); |
| return agent; |
| } |
| |
| void pmc_agent_destroy(struct pmc_agent *agent) |
| { |
| if (agent->pmc) { |
| pmc_destroy(agent->pmc); |
| } |
| free(agent); |
| } |
| |
| void pmc_agent_disable(struct pmc_agent *agent) |
| { |
| if (agent->pmc) { |
| pmc_destroy(agent->pmc); |
| } |
| agent->pmc = NULL; |
| } |
| |
| int pmc_agent_get_leap(struct pmc_agent *agent) |
| { |
| return agent->leap; |
| } |
| |
| int pmc_agent_get_sync_offset(struct pmc_agent *agent) |
| { |
| return agent->sync_offset; |
| } |
| |
| int pmc_agent_get_number_ports(struct pmc_agent *node) |
| { |
| if (!node->dds_valid) { |
| return -1; |
| } |
| return node->dds.numberPorts; |
| } |
| |
| int pmc_agent_query_dds(struct pmc_agent *node, int timeout) |
| { |
| struct ptp_message *msg; |
| struct defaultDS *dds; |
| int res; |
| |
| res = run_pmc(node, timeout, MID_DEFAULT_DATA_SET, &msg); |
| if (is_run_pmc_error(res)) { |
| return run_pmc_err2errno(res); |
| } |
| dds = (struct defaultDS *) management_tlv_data(msg); |
| memcpy(&node->dds, dds, sizeof(node->dds)); |
| node->dds_valid = true; |
| msg_put(msg); |
| return 0; |
| } |
| |
| int pmc_agent_query_port_properties(struct pmc_agent *node, int timeout, |
| unsigned int port, enum port_state *state, |
| int *tstamping, int *phc_index, char *iface) |
| { |
| struct port_properties_np *ppn; |
| struct port_hwclock_np *phn; |
| struct ptp_message *msg; |
| int res, len; |
| |
| pmc_target_port(node->pmc, port); |
| while (1) { |
| res = run_pmc(node, timeout, MID_PORT_PROPERTIES_NP, &msg); |
| if (is_run_pmc_error(res)) { |
| goto out; |
| } |
| ppn = management_tlv_data(msg); |
| if (ppn->portIdentity.portNumber != port) { |
| msg_put(msg); |
| continue; |
| } |
| *state = ppn->port_state; |
| *tstamping = ppn->timestamping; |
| len = ppn->interface.length; |
| if (len > IFNAMSIZ - 1) { |
| len = IFNAMSIZ - 1; |
| } |
| memcpy(iface, ppn->interface.text, len); |
| iface[len] = '\0'; |
| |
| msg_put(msg); |
| break; |
| } |
| while (1) { |
| res = run_pmc(node, timeout, MID_PORT_HWCLOCK_NP, &msg); |
| if (is_run_pmc_error(res)) { |
| goto out; |
| } |
| phn = management_tlv_data(msg); |
| if (phn->portIdentity.portNumber != port) { |
| msg_put(msg); |
| continue; |
| } |
| *phc_index = phn->phc_index; |
| |
| msg_put(msg); |
| res = RUN_PMC_OKAY; |
| break; |
| } |
| out: |
| pmc_target_all(node->pmc); |
| return run_pmc_err2errno(res); |
| } |
| |
| int pmc_agent_query_utc_offset(struct pmc_agent *node, int timeout) |
| { |
| struct timePropertiesDS *tds; |
| struct ptp_message *msg; |
| int res; |
| |
| res = run_pmc(node, timeout, MID_TIME_PROPERTIES_DATA_SET, &msg); |
| if (is_run_pmc_error(res)) { |
| return run_pmc_err2errno(res); |
| } |
| |
| tds = (struct timePropertiesDS *) management_tlv_data(msg); |
| if (tds->flags & PTP_TIMESCALE) { |
| node->sync_offset = tds->currentUtcOffset; |
| if (tds->flags & LEAP_61) |
| node->leap = 1; |
| else if (tds->flags & LEAP_59) |
| node->leap = -1; |
| else |
| node->leap = 0; |
| node->utc_offset_traceable = tds->flags & UTC_OFF_VALID && |
| tds->flags & TIME_TRACEABLE; |
| } else { |
| node->sync_offset = 0; |
| node->leap = 0; |
| node->utc_offset_traceable = 0; |
| } |
| msg_put(msg); |
| return 0; |
| } |
| |
| void pmc_agent_set_sync_offset(struct pmc_agent *agent, int offset) |
| { |
| agent->sync_offset = offset; |
| } |
| |
| int pmc_agent_subscribe(struct pmc_agent *node, int timeout, int interval) |
| { |
| node->stay_subscribed = true; |
| if (interval < MIN_UPDATE_INTERVAL) |
| interval = MIN_UPDATE_INTERVAL; |
| node->update_interval = interval * NS_PER_SEC; |
| return renew_subscription(node, timeout); |
| } |
| |
| int pmc_agent_update(struct pmc_agent *node) |
| { |
| struct ptp_message *msg; |
| struct timespec tp; |
| uint64_t ts; |
| |
| if (!node->pmc) { |
| return 0; |
| } |
| if (clock_gettime(CLOCK_MONOTONIC, &tp)) { |
| pr_err("failed to read clock: %m"); |
| return -errno; |
| } |
| ts = tp.tv_sec * NS_PER_SEC + tp.tv_nsec; |
| |
| if (ts - node->pmc_last_update >= node->update_interval) { |
| switch (node->update_phase) { |
| case 0: |
| if (node->stay_subscribed && |
| renew_subscription(node, 0)) |
| break; |
| node->update_phase++; |
| /* Fall through */ |
| case 1: |
| if (pmc_agent_query_utc_offset(node, 0)) |
| break; |
| node->update_phase++; |
| /* Fall through */ |
| default: |
| node->pmc_last_update = ts; |
| node->update_phase = 0; |
| break; |
| } |
| } else { |
| run_pmc(node, 0, -1, &msg); |
| } |
| |
| return 0; |
| } |
| |
| int pmc_agent_is_subscribed(struct pmc_agent *agent) |
| { |
| struct timespec tp; |
| uint64_t ts; |
| |
| if (clock_gettime(CLOCK_MONOTONIC, &tp)) { |
| pr_err("failed to read clock: %m"); |
| return 0; |
| } |
| ts = tp.tv_sec * NS_PER_SEC + tp.tv_nsec; |
| |
| return agent->pmc_last_update > 0 && |
| ts - agent->pmc_last_update <= UPDATES_PER_SUBSCRIPTION * |
| agent->update_interval; |
| } |
| |
| bool pmc_agent_utc_offset_traceable(struct pmc_agent *agent) |
| { |
| return agent->utc_offset_traceable; |
| } |