/* * Copyright (c) 2024 BayLibre SAS * * SPDX-License-Identifier: Apache-2.0 */ #include LOG_MODULE_REGISTER(ptp_tlv, CONFIG_PTP_LOG_LEVEL); #include #include #include "port.h" #include "tlv.h" #define TLV_MANUFACTURER_ID_LEN (3) #define TLV_PROFILE_ID_LEN (6) #define TLV_ADDR_LEN_MAX (16) K_MEM_SLAB_DEFINE_STATIC(tlv_slab, sizeof(struct ptp_tlv_container), 2 * CONFIG_PTP_MSG_POLL_SIZE, 8); static inline void tlv_ntohs(void *ptr) { uint16_t val = *(uint16_t *)ptr; val = ntohs(val); memcpy(ptr, &val, sizeof(val)); } static inline void tlv_htons(void *ptr) { uint16_t val = *(uint16_t *)ptr; val = htons(val); memcpy(ptr, &val, sizeof(val)); } static int tlv_mgmt_post_recv(struct ptp_tlv_mgmt *mgmt_tlv, uint16_t length) { struct ptp_tlv_mgmt_clock_desc *clock_desc; struct ptp_tlv_time_prop_ds *time_prop_ds; struct ptp_tlv_default_ds *default_ds; struct ptp_tlv_current_ds *current_ds; struct ptp_tlv_parent_ds *parent_ds; struct ptp_tlv_port_ds *port_ds; struct ptp_timestamp ts; enum ptp_mgmt_id id = (enum ptp_mgmt_id)mgmt_tlv->id; struct ptp_tlv_container *container; uint8_t *data; int32_t data_length; switch (id) { case PTP_MGMT_NULL_PTP_MANAGEMENT: __fallthrough; case PTP_MGMT_SAVE_IN_NON_VOLATILE_STORAGE: __fallthrough; case PTP_MGMT_RESET_NON_VOLATILE_STORAGE: __fallthrough; case PTP_MGMT_FAULT_LOG_RESET: __fallthrough; case PTP_MGMT_ENABLE_PORT: __fallthrough; case PTP_MGMT_DISABLE_PORT: if (length != 0) { return -EBADMSG; } break; case PTP_MGMT_CLOCK_DESCRIPTION: container = CONTAINER_OF((void *)mgmt_tlv, struct ptp_tlv_container, tlv); clock_desc = &container->clock_desc; data = mgmt_tlv->data; data_length = length; clock_desc->type = (uint16_t *)data; data += sizeof(*clock_desc->type); data_length -= sizeof(*clock_desc->type); if (data_length < 0) { return -EBADMSG; } tlv_ntohs(&clock_desc->type); clock_desc->phy_protocol = (struct ptp_text *)data; data += sizeof(*clock_desc->phy_protocol); data_length -= sizeof(*clock_desc->phy_protocol); if (data_length < 0) { return -EBADMSG; } data += clock_desc->phy_protocol->length; data_length -= clock_desc->phy_protocol->length; if (data_length < 0) { return -EBADMSG; } clock_desc->phy_addr_len = (uint16_t *)data; data += sizeof(*clock_desc->phy_addr_len); data_length -= sizeof(*clock_desc->phy_addr_len); if (data_length < 0) { return -EBADMSG; } tlv_ntohs(&clock_desc->phy_addr_len); if (*clock_desc->phy_addr_len > TLV_ADDR_LEN_MAX) { return -EBADMSG; } clock_desc->phy_addr = data; data += *clock_desc->phy_addr_len; data_length -= *clock_desc->phy_addr_len; if (data_length < 0) { return -EBADMSG; } clock_desc->protocol_addr = (struct ptp_port_addr *)data; data += sizeof(*clock_desc->protocol_addr); data_length -= sizeof(*clock_desc->protocol_addr); if (data_length < 0) { return -EBADMSG; } tlv_ntohs(&clock_desc->protocol_addr->protocol); tlv_ntohs(&clock_desc->protocol_addr->addr_len); if (clock_desc->protocol_addr->addr_len > TLV_ADDR_LEN_MAX) { return -EBADMSG; } data += clock_desc->protocol_addr->addr_len; data_length -= clock_desc->protocol_addr->addr_len; if (data_length < 0) { return -EBADMSG; } clock_desc->manufacturer_id = data; /* extra byte for reserved field - see IEEE 1588-2019 15.5.3.1.2 */ data += TLV_MANUFACTURER_ID_LEN + 1; data_length -= TLV_MANUFACTURER_ID_LEN + 1; if (data_length < 0) { return -EBADMSG; } clock_desc->product_desc = (struct ptp_text *)data; data += sizeof(*clock_desc->product_desc); data_length -= sizeof(*clock_desc->product_desc); if (data_length < 0) { return -EBADMSG; } data += clock_desc->product_desc->length; data_length -= clock_desc->product_desc->length; if (data_length < 0) { return -EBADMSG; } clock_desc->revision_data = (struct ptp_text *)data; data += sizeof(*clock_desc->revision_data); data_length -= sizeof(*clock_desc->revision_data); if (data_length < 0) { return -EBADMSG; } data += clock_desc->revision_data->length; data_length -= clock_desc->revision_data->length; if (data_length < 0) { return -EBADMSG; } clock_desc->user_desc = (struct ptp_text *)data; data += sizeof(*clock_desc->user_desc); data_length -= sizeof(*clock_desc->user_desc); if (data_length < 0) { return -EBADMSG; } data += clock_desc->user_desc->length; data_length -= clock_desc->user_desc->length; if (data_length < 0) { return -EBADMSG; } clock_desc->profile_id = data; data += TLV_PROFILE_ID_LEN; data_length -= TLV_PROFILE_ID_LEN; break; case PTP_MGMT_USER_DESCRIPTION: container = CONTAINER_OF((void *)mgmt_tlv, struct ptp_tlv_container, tlv); if (length < sizeof(struct ptp_text)) { return -EBADMSG; } container->clock_desc.user_desc = (struct ptp_text *)mgmt_tlv->data; break; case PTP_MGMT_DEFAULT_DATA_SET: if (length != sizeof(struct ptp_tlv_default_ds)) { return -EBADMSG; } default_ds = (struct ptp_tlv_default_ds *)mgmt_tlv->data; default_ds->n_ports = ntohs(default_ds->n_ports); default_ds->clk_quality.offset_scaled_log_variance = ntohs(default_ds->clk_quality.offset_scaled_log_variance); break; case PTP_MGMT_CURRENT_DATA_SET: if (length != sizeof(struct ptp_tlv_current_ds)) { return -EBADMSG; } current_ds = (struct ptp_tlv_current_ds *)mgmt_tlv->data; current_ds->steps_rm = ntohs(current_ds->steps_rm); current_ds->offset_from_tt = ntohll(current_ds->offset_from_tt); current_ds->mean_delay = ntohll(current_ds->mean_delay); break; case PTP_MGMT_PARENT_DATA_SET: if (length != sizeof(struct ptp_tlv_parent_ds)) { return -EBADMSG; } parent_ds = (struct ptp_tlv_parent_ds *)mgmt_tlv->data; parent_ds->port_id.port_number = ntohs(parent_ds->port_id.port_number); parent_ds->obsreved_parent_offset_scaled_log_variance = ntohs(parent_ds->obsreved_parent_offset_scaled_log_variance); parent_ds->obsreved_parent_clk_phase_change_rate = ntohl(parent_ds->obsreved_parent_clk_phase_change_rate); parent_ds->gm_clk_quality.offset_scaled_log_variance = ntohs(parent_ds->gm_clk_quality.offset_scaled_log_variance); break; case PTP_MGMT_TIME_PROPERTIES_DATA_SET: if (length != sizeof(struct ptp_tlv_time_prop_ds)) { return -EBADMSG; } time_prop_ds = (struct ptp_tlv_time_prop_ds *)mgmt_tlv->data; time_prop_ds->current_utc_offset = ntohs(time_prop_ds->current_utc_offset); break; case PTP_MGMT_PORT_DATA_SET: if (length != sizeof(struct ptp_tlv_port_ds)) { return -EBADMSG; } port_ds = (struct ptp_tlv_port_ds *)mgmt_tlv->data; port_ds->id.port_number = ntohs(port_ds->id.port_number); port_ds->mean_link_delay = ntohll(port_ds->mean_link_delay); break; case PTP_MGMT_TIME: ts = *(struct ptp_timestamp *)mgmt_tlv->data; ts.seconds_high = ntohs(ts.seconds_high); ts.seconds_low = ntohl(ts.seconds_low); ts.nanoseconds = ntohl(ts.nanoseconds); memcpy(mgmt_tlv->data, &ts, sizeof(ts)); break; default: break; } return 0; } static void tlv_mgmt_pre_send(struct ptp_tlv_mgmt *mgmt_tlv) { enum ptp_mgmt_id id = (enum ptp_mgmt_id)mgmt_tlv->id; struct ptp_tlv_mgmt_clock_desc *clock_desc; struct ptp_tlv_time_prop_ds *time_prop_ds; struct ptp_tlv_default_ds *default_ds; struct ptp_tlv_current_ds *current_ds; struct ptp_tlv_container *container; struct ptp_tlv_parent_ds *parent_ds; struct ptp_tlv_port_ds *port_ds; struct ptp_timestamp ts; switch (id) { case PTP_MGMT_CLOCK_DESCRIPTION: container = CONTAINER_OF((void *)mgmt_tlv, struct ptp_tlv_container, tlv); clock_desc = &container->clock_desc; tlv_htons(&clock_desc->type); tlv_htons(&clock_desc->phy_addr_len); tlv_htons(&clock_desc->protocol_addr->protocol); tlv_htons(&clock_desc->protocol_addr->addr_len); break; case PTP_MGMT_DEFAULT_DATA_SET: default_ds = (struct ptp_tlv_default_ds *)mgmt_tlv->data; default_ds->n_ports = htons(default_ds->n_ports); default_ds->clk_quality.offset_scaled_log_variance = htons(default_ds->clk_quality.offset_scaled_log_variance); break; case PTP_MGMT_CURRENT_DATA_SET: current_ds = (struct ptp_tlv_current_ds *)mgmt_tlv->data; current_ds->steps_rm = htons(current_ds->steps_rm); current_ds->offset_from_tt = htonll(current_ds->offset_from_tt); current_ds->mean_delay = htonll(current_ds->mean_delay); break; case PTP_MGMT_PARENT_DATA_SET: parent_ds = (struct ptp_tlv_parent_ds *)mgmt_tlv->data; parent_ds->port_id.port_number = htons(parent_ds->port_id.port_number); parent_ds->obsreved_parent_offset_scaled_log_variance = htons(parent_ds->obsreved_parent_offset_scaled_log_variance); parent_ds->obsreved_parent_clk_phase_change_rate = htons(parent_ds->obsreved_parent_clk_phase_change_rate); parent_ds->gm_clk_quality.offset_scaled_log_variance = htons(parent_ds->gm_clk_quality.offset_scaled_log_variance); break; case PTP_MGMT_TIME_PROPERTIES_DATA_SET: time_prop_ds = (struct ptp_tlv_time_prop_ds *)mgmt_tlv->data; time_prop_ds->current_utc_offset = htons(time_prop_ds->current_utc_offset); break; case PTP_MGMT_PORT_DATA_SET: port_ds = (struct ptp_tlv_port_ds *)mgmt_tlv->data; port_ds->id.port_number = htons(port_ds->id.port_number); port_ds->mean_link_delay = htonll(port_ds->mean_link_delay); break; case PTP_MGMT_TIME: ts = *(struct ptp_timestamp *)mgmt_tlv->data; ts.seconds_high = htons(ts.seconds_high); ts.seconds_low = htonl(ts.seconds_low); ts.nanoseconds = htonl(ts.nanoseconds); memcpy(mgmt_tlv->data, &ts, sizeof(ts)); break; default: break; } } struct ptp_tlv_container *ptp_tlv_alloc(void) { struct ptp_tlv_container *tlv_container = NULL; int ret = k_mem_slab_alloc(&tlv_slab, (void **)&tlv_container, K_FOREVER); if (ret) { LOG_ERR("Couldn't allocate memory for the TLV"); return NULL; } memset(tlv_container, 0, sizeof(*tlv_container)); return tlv_container; } void ptp_tlv_free(struct ptp_tlv_container *tlv_container) { k_mem_slab_free(&tlv_slab, (void *)tlv_container); } enum ptp_mgmt_op ptp_mgmt_action(struct ptp_msg *msg) { return (enum ptp_mgmt_op)msg->management.action; } enum ptp_tlv_type ptp_tlv_type(struct ptp_tlv *tlv) { return (enum ptp_tlv_type)tlv->type; } int ptp_tlv_post_recv(struct ptp_tlv *tlv) { struct ptp_tlv_mgmt_err *mgmt_err; struct ptp_tlv_mgmt *mgmt; int ret = 0; switch (ptp_tlv_type(tlv)) { case PTP_TLV_TYPE_MANAGEMENT: if (tlv->length < (sizeof(struct ptp_tlv_mgmt) - sizeof(struct ptp_tlv))) { return -EBADMSG; } mgmt = (struct ptp_tlv_mgmt *)tlv; mgmt->id = ntohs(mgmt->id); /* Value of length is 2 + N, where N is length of data field * based on IEEE 1588-2019 Section 15.5.2.2. */ if (tlv->length > sizeof(mgmt->id)) { ret = tlv_mgmt_post_recv(mgmt, tlv->length - 2); } break; case PTP_TLV_TYPE_MANAGEMENT_ERROR_STATUS: if (tlv->length < (sizeof(struct ptp_tlv_mgmt_err) - sizeof(struct ptp_tlv))) { return -EBADMSG; } mgmt_err = (struct ptp_tlv_mgmt_err *)tlv; mgmt_err->err_id = ntohs(mgmt_err->err_id); mgmt_err->id = ntohs(mgmt_err->id); break; default: break; } return ret; } void ptp_tlv_pre_send(struct ptp_tlv *tlv) { struct ptp_tlv_mgmt_err *mgmt_err; struct ptp_tlv_mgmt *mgmt; switch (ptp_tlv_type(tlv)) { case PTP_TLV_TYPE_MANAGEMENT: mgmt = (struct ptp_tlv_mgmt *)tlv; /* Check if management TLV contains data */ if (tlv->length > sizeof(mgmt->id)) { tlv_mgmt_pre_send(mgmt); } mgmt->id = htons(mgmt->id); break; case PTP_TLV_TYPE_MANAGEMENT_ERROR_STATUS: mgmt_err = (struct ptp_tlv_mgmt_err *)tlv; mgmt_err->err_id = htons(mgmt_err->err_id); mgmt_err->id = htons(mgmt_err->id); break; default: break; } tlv->length = htons(tlv->length); tlv->type = htons(tlv->type); }