424 lines
12 KiB
C
424 lines
12 KiB
C
/*
|
|
* Copyright (c) 2024 BayLibre SAS
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_REGISTER(ptp_tlv, CONFIG_PTP_LOG_LEVEL);
|
|
|
|
#include <zephyr/kernel.h>
|
|
#include <zephyr/net/net_ip.h>
|
|
|
|
#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);
|
|
}
|