Commit 08ef8bc8 authored by Danielle Ratson's avatar Danielle Ratson Committed by David S. Miller

mlxsw: spectrum_ptp: Support SIOCGHWTSTAMP, SIOCSHWTSTAMP ioctls

The SIOCSHWTSTAMP ioctl configures HW timestamping on a given port. In
Spectrum-2 and above, each packet gets time stamp by default, but in
order to provide an accurate time stamp, software should configure to
update the correction field. In addition, the PTP traps are not enabled
by default, software should enable it per port or for all ports.

The switch behaves like a transparent clock between CPU port and each
front panel port. If ingress correction is set on a port for a given packet
type, then when such a packet is received via the port, the current time
stamp is subtracted from the correction field. If egress correction is set
on a port for a given packet type, then when such a packet is transmitted
via the port, the current time stamp is added to the correction field.

The result is that as the packet ingresses through a port with ingress
correction enabled, and egresses through a port with egress correction
enabled, the PTP correction field is updated to reflect the time that the
packet spent in the ASIC.

This can be used to update the correction field of trapped packets by
enabling ingress correction on a port where time stamping was enabled,
and egress correction on the CPU port. Similarly, for packets transmitted
from the host, ingress correction should be enabled on the CPU port, and
egress correction on a front-panel port.

However, since the correction fields will be updated for all PTP packets
crossing the CPU port, in order not to mangle the correction field, the
front panel port involved in the packet transfer must have the
corresponding correction enabled as well.

Therefore, when HW timestamping is enabled on at least one port, we have
to configure hardware to update the correction field and trap PTP event
packets on all ports.

Add reference count as part of 'struct mlxsw_sp_ptp_state', to maintain
how many ports use HW timestamping. Handle the correction field
configuration only when the first port enables time stamping and when the
last port disables time stamping. Store the configuration as part of
'struct mlxsw_sp_ptp_state', as it is global for all ports.

The SIOCGHWTSTAMP ioctl is a getter for the current configuration,
implement it and use the global configuration.
Signed-off-by: default avatarDanielle Ratson <danieller@nvidia.com>
Signed-off-by: default avatarAmit Cohen <amcohen@nvidia.com>
Reviewed-by: default avatarPetr Machata <petrm@nvidia.com>
Signed-off-by: default avatarIdo Schimmel <idosch@nvidia.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 382ad0d9
......@@ -11,6 +11,7 @@
#include <linux/if_ether.h>
#include <linux/if_vlan.h>
#include <linux/net_tstamp.h>
#include <linux/refcount.h>
#include "spectrum.h"
#include "spectrum_ptp.h"
......@@ -41,6 +42,10 @@ struct mlxsw_sp1_ptp_state {
struct mlxsw_sp2_ptp_state {
struct mlxsw_sp_ptp_state common;
refcount_t ptp_port_enabled_ref; /* Number of ports with time stamping
* enabled.
*/
struct hwtstamp_config config;
};
struct mlxsw_sp1_ptp_key {
......@@ -1368,6 +1373,7 @@ struct mlxsw_sp_ptp_state *mlxsw_sp2_ptp_init(struct mlxsw_sp *mlxsw_sp)
if (err)
goto err_ptp_traps_set;
refcount_set(&ptp_state->ptp_port_enabled_ref, 0);
return &ptp_state->common;
err_ptp_traps_set:
......@@ -1448,6 +1454,208 @@ void mlxsw_sp2_ptp_transmitted(struct mlxsw_sp *mlxsw_sp,
dev_kfree_skb_any(skb);
}
int mlxsw_sp2_ptp_hwtstamp_get(struct mlxsw_sp_port *mlxsw_sp_port,
struct hwtstamp_config *config)
{
struct mlxsw_sp2_ptp_state *ptp_state;
ptp_state = mlxsw_sp2_ptp_state(mlxsw_sp_port->mlxsw_sp);
*config = ptp_state->config;
return 0;
}
static int
mlxsw_sp2_ptp_get_message_types(const struct hwtstamp_config *config,
u16 *p_ing_types, u16 *p_egr_types,
enum hwtstamp_rx_filters *p_rx_filter)
{
enum hwtstamp_rx_filters rx_filter = config->rx_filter;
enum hwtstamp_tx_types tx_type = config->tx_type;
u16 ing_types = 0x00;
u16 egr_types = 0x00;
*p_rx_filter = rx_filter;
switch (rx_filter) {
case HWTSTAMP_FILTER_NONE:
ing_types = 0x00;
break;
case HWTSTAMP_FILTER_PTP_V1_L4_SYNC:
case HWTSTAMP_FILTER_PTP_V2_L4_SYNC:
case HWTSTAMP_FILTER_PTP_V2_L2_SYNC:
case HWTSTAMP_FILTER_PTP_V2_SYNC:
case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ:
case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ:
case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ:
case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ:
case HWTSTAMP_FILTER_PTP_V1_L4_EVENT:
case HWTSTAMP_FILTER_PTP_V2_L4_EVENT:
case HWTSTAMP_FILTER_PTP_V2_L2_EVENT:
case HWTSTAMP_FILTER_PTP_V2_EVENT:
/* In Spectrum-2 and above, all packets get time stamp by
* default and the driver fill the time stamp only for event
* packets. Return all event types even if only specific types
* were required.
*/
ing_types = 0x0f;
*p_rx_filter = HWTSTAMP_FILTER_SOME;
break;
case HWTSTAMP_FILTER_ALL:
case HWTSTAMP_FILTER_SOME:
case HWTSTAMP_FILTER_NTP_ALL:
return -ERANGE;
default:
return -EINVAL;
}
switch (tx_type) {
case HWTSTAMP_TX_OFF:
egr_types = 0x00;
break;
case HWTSTAMP_TX_ON:
egr_types = 0x0f;
break;
case HWTSTAMP_TX_ONESTEP_SYNC:
case HWTSTAMP_TX_ONESTEP_P2P:
return -ERANGE;
default:
return -EINVAL;
}
*p_ing_types = ing_types;
*p_egr_types = egr_types;
return 0;
}
static int mlxsw_sp2_ptp_mtpcpc_set(struct mlxsw_sp *mlxsw_sp, bool ptp_trap_en,
u16 ing_types, u16 egr_types)
{
char mtpcpc_pl[MLXSW_REG_MTPCPC_LEN];
mlxsw_reg_mtpcpc_pack(mtpcpc_pl, false, 0, ptp_trap_en, ing_types,
egr_types);
return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(mtpcpc), mtpcpc_pl);
}
static int mlxsw_sp2_ptp_enable(struct mlxsw_sp *mlxsw_sp, u16 ing_types,
u16 egr_types,
struct hwtstamp_config new_config)
{
struct mlxsw_sp2_ptp_state *ptp_state = mlxsw_sp2_ptp_state(mlxsw_sp);
int err;
err = mlxsw_sp2_ptp_mtpcpc_set(mlxsw_sp, true, ing_types, egr_types);
if (err)
return err;
ptp_state->config = new_config;
return 0;
}
static int mlxsw_sp2_ptp_disable(struct mlxsw_sp *mlxsw_sp,
struct hwtstamp_config new_config)
{
struct mlxsw_sp2_ptp_state *ptp_state = mlxsw_sp2_ptp_state(mlxsw_sp);
int err;
err = mlxsw_sp2_ptp_mtpcpc_set(mlxsw_sp, false, 0, 0);
if (err)
return err;
ptp_state->config = new_config;
return 0;
}
static int mlxsw_sp2_ptp_configure_port(struct mlxsw_sp_port *mlxsw_sp_port,
u16 ing_types, u16 egr_types,
struct hwtstamp_config new_config)
{
struct mlxsw_sp2_ptp_state *ptp_state;
int err;
ASSERT_RTNL();
ptp_state = mlxsw_sp2_ptp_state(mlxsw_sp_port->mlxsw_sp);
if (refcount_inc_not_zero(&ptp_state->ptp_port_enabled_ref))
return 0;
err = mlxsw_sp2_ptp_enable(mlxsw_sp_port->mlxsw_sp, ing_types,
egr_types, new_config);
if (err)
return err;
refcount_set(&ptp_state->ptp_port_enabled_ref, 1);
return 0;
}
static int mlxsw_sp2_ptp_deconfigure_port(struct mlxsw_sp_port *mlxsw_sp_port,
struct hwtstamp_config new_config)
{
struct mlxsw_sp2_ptp_state *ptp_state;
int err;
ASSERT_RTNL();
ptp_state = mlxsw_sp2_ptp_state(mlxsw_sp_port->mlxsw_sp);
if (!refcount_dec_and_test(&ptp_state->ptp_port_enabled_ref))
return 0;
err = mlxsw_sp2_ptp_disable(mlxsw_sp_port->mlxsw_sp, new_config);
if (err)
goto err_ptp_disable;
return 0;
err_ptp_disable:
refcount_set(&ptp_state->ptp_port_enabled_ref, 1);
return err;
}
int mlxsw_sp2_ptp_hwtstamp_set(struct mlxsw_sp_port *mlxsw_sp_port,
struct hwtstamp_config *config)
{
enum hwtstamp_rx_filters rx_filter;
struct hwtstamp_config new_config;
u16 new_ing_types, new_egr_types;
bool ptp_enabled;
int err;
err = mlxsw_sp2_ptp_get_message_types(config, &new_ing_types,
&new_egr_types, &rx_filter);
if (err)
return err;
new_config.flags = config->flags;
new_config.tx_type = config->tx_type;
new_config.rx_filter = rx_filter;
ptp_enabled = mlxsw_sp_port->ptp.ing_types ||
mlxsw_sp_port->ptp.egr_types;
if ((new_ing_types || new_egr_types) && !ptp_enabled) {
err = mlxsw_sp2_ptp_configure_port(mlxsw_sp_port, new_ing_types,
new_egr_types, new_config);
if (err)
return err;
} else if (!new_ing_types && !new_egr_types && ptp_enabled) {
err = mlxsw_sp2_ptp_deconfigure_port(mlxsw_sp_port, new_config);
if (err)
return err;
}
mlxsw_sp_port->ptp.ing_types = new_ing_types;
mlxsw_sp_port->ptp.egr_types = new_egr_types;
/* Notify the ioctl caller what we are actually timestamping. */
config->rx_filter = rx_filter;
return 0;
}
int mlxsw_sp_ptp_txhdr_construct(struct mlxsw_core *mlxsw_core,
struct mlxsw_sp_port *mlxsw_sp_port,
struct sk_buff *skb,
......
......@@ -77,6 +77,12 @@ void mlxsw_sp2_ptp_receive(struct mlxsw_sp *mlxsw_sp, struct sk_buff *skb,
void mlxsw_sp2_ptp_transmitted(struct mlxsw_sp *mlxsw_sp,
struct sk_buff *skb, u16 local_port);
int mlxsw_sp2_ptp_hwtstamp_get(struct mlxsw_sp_port *mlxsw_sp_port,
struct hwtstamp_config *config);
int mlxsw_sp2_ptp_hwtstamp_set(struct mlxsw_sp_port *mlxsw_sp_port,
struct hwtstamp_config *config);
int mlxsw_sp2_ptp_txhdr_construct(struct mlxsw_core *mlxsw_core,
struct mlxsw_sp_port *mlxsw_sp_port,
struct sk_buff *skb,
......@@ -202,15 +208,6 @@ static inline void mlxsw_sp2_ptp_transmitted(struct mlxsw_sp *mlxsw_sp,
dev_kfree_skb_any(skb);
}
int mlxsw_sp2_ptp_txhdr_construct(struct mlxsw_core *mlxsw_core,
struct mlxsw_sp_port *mlxsw_sp_port,
struct sk_buff *skb,
const struct mlxsw_tx_info *tx_info)
{
return -EOPNOTSUPP;
}
#endif
static inline int
mlxsw_sp2_ptp_hwtstamp_get(struct mlxsw_sp_port *mlxsw_sp_port,
struct hwtstamp_config *config)
......@@ -225,6 +222,15 @@ mlxsw_sp2_ptp_hwtstamp_set(struct mlxsw_sp_port *mlxsw_sp_port,
return -EOPNOTSUPP;
}
int mlxsw_sp2_ptp_txhdr_construct(struct mlxsw_core *mlxsw_core,
struct mlxsw_sp_port *mlxsw_sp_port,
struct sk_buff *skb,
const struct mlxsw_tx_info *tx_info)
{
return -EOPNOTSUPP;
}
#endif
static inline void mlxsw_sp2_ptp_shaper_work(struct work_struct *work)
{
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment