Commit 9a06cce6 authored by David S. Miller's avatar David S. Miller

Merge branch 'dsa-microchip-ptp'

Arun Ramadoss says:

====================
net: dsa: microchip: add PTP support for KSZ9563/KSZ8563 and LAN937x

KSZ9563/KSZ8563 and  LAN937x switch are capable for supporting IEEE 1588 PTP
protocol.  LAN937x has the same PTP register set similar to KSZ9563, hence the
implementation has been made common for the KSZ switches.  KSZ9563 does not
support two step timestamping but LAN937x supports both.  Tested the 1step &
2step p2p timestamping in LAN937x and p2p1step timestamping in KSZ9563.

This patch series is based on the Christian Eggers PTP support for KSZ9563.
Applied the Christian patch and updated as per the latest refactoring of KSZ
series code. The features added on top are PTP packet Interrupt
implementation based on nested handler, LAN937x two step timestamping and
programmable per_out pins.

Link: https://www.spinics.net/lists/netdev/msg705531.html

Patch v7 -> v8
- set skb->ip_summed = CHECKSUM_NONE after updating the checksum

Patch v6 -> v7
- Corrected the misplaced spaces and tabs
- Added mutex lock in do_aux_work
- Replaced 0/1 with false/true for ts_en
- SKB_TX_INPROGRESS flag is set before dsa_enqueue_skb
- Removed the fallthrough keyword
- pdelay_resp header correction is performed based on
  KSZ_SKB_CB(skb)->update_correction instead of clone

Patch v5 -> v6
- Rebased to latest net-next and renamed from RFC to patch net-next.

Patch v4 -> v5
- Replaced irq_domain_add_simple with irq_doamin_add_linear
- Used the helper diff_by_scaled_ppm() for adjfine.

Patch v3 -> v4
- removed IRQF_TRIGGER_FALLING from the request_threaded_irq of ptp msg
- addressed review comments on patch 10 periodic output
- added sign off in patch 6 & 9
- reverted to set PTP_1STEP bit for lan937x which is missed during v3 regression

Patch v2-> v3
- used port_rxtstamp for reconstructing the absolute timestamp instead of
tagger function pointer.
- Reverted to setting of 802.1As bit.

Patch v1 -> v2
- GPIO perout enable bit is different for LAN937x and KSZ9x. Added new patch
for configuring LAN937x programmable pins.
- PTP enabled in hardware based on both tx and rx timestamping of all the user
ports.
- Replaced setting of 802.1AS bit with P2P bit in PTP_MSG_CONF1 register.

RFC v2 -> Patch v1
- Changed the patch author based on past patch submission
- Changed the commit message prefix as net: dsa: microchip: ptp
Individual patch changes are listed in correspondig commits.

RFC v1 -> v2
- Added the p2p1step timestamping and conditional execution of 2 step for
  LAN937x only.
- Added the periodic output support
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 72863e08 168a5940
...@@ -13710,6 +13710,7 @@ S: Maintained ...@@ -13710,6 +13710,7 @@ S: Maintained
F: Documentation/devicetree/bindings/net/dsa/microchip,ksz.yaml F: Documentation/devicetree/bindings/net/dsa/microchip,ksz.yaml
F: Documentation/devicetree/bindings/net/dsa/microchip,lan937x.yaml F: Documentation/devicetree/bindings/net/dsa/microchip,lan937x.yaml
F: drivers/net/dsa/microchip/* F: drivers/net/dsa/microchip/*
F: include/linux/dsa/ksz_common.h
F: include/linux/platform_data/microchip-ksz.h F: include/linux/platform_data/microchip-ksz.h
F: net/dsa/tag_ksz.c F: net/dsa/tag_ksz.c
......
...@@ -11,6 +11,7 @@ menuconfig NET_DSA_MICROCHIP_KSZ_COMMON ...@@ -11,6 +11,7 @@ menuconfig NET_DSA_MICROCHIP_KSZ_COMMON
config NET_DSA_MICROCHIP_KSZ9477_I2C config NET_DSA_MICROCHIP_KSZ9477_I2C
tristate "KSZ series I2C connected switch driver" tristate "KSZ series I2C connected switch driver"
depends on NET_DSA_MICROCHIP_KSZ_COMMON && I2C depends on NET_DSA_MICROCHIP_KSZ_COMMON && I2C
depends on PTP_1588_CLOCK_OPTIONAL
select REGMAP_I2C select REGMAP_I2C
help help
Select to enable support for registering switches configured through I2C. Select to enable support for registering switches configured through I2C.
...@@ -18,10 +19,20 @@ config NET_DSA_MICROCHIP_KSZ9477_I2C ...@@ -18,10 +19,20 @@ config NET_DSA_MICROCHIP_KSZ9477_I2C
config NET_DSA_MICROCHIP_KSZ_SPI config NET_DSA_MICROCHIP_KSZ_SPI
tristate "KSZ series SPI connected switch driver" tristate "KSZ series SPI connected switch driver"
depends on NET_DSA_MICROCHIP_KSZ_COMMON && SPI depends on NET_DSA_MICROCHIP_KSZ_COMMON && SPI
depends on PTP_1588_CLOCK_OPTIONAL
select REGMAP_SPI select REGMAP_SPI
help help
Select to enable support for registering switches configured through SPI. Select to enable support for registering switches configured through SPI.
config NET_DSA_MICROCHIP_KSZ_PTP
bool "Support for the PTP clock on the KSZ9563/LAN937x Ethernet Switch"
depends on NET_DSA_MICROCHIP_KSZ_COMMON && PTP_1588_CLOCK
help
Select to enable support for timestamping & PTP clock manipulation in
KSZ8563/KSZ9563/LAN937x series of switches. KSZ9563/KSZ8563 supports
only one step timestamping. LAN937x switch supports both one step and
two step timestamping.
config NET_DSA_MICROCHIP_KSZ8863_SMI config NET_DSA_MICROCHIP_KSZ8863_SMI
tristate "KSZ series SMI connected switch driver" tristate "KSZ series SMI connected switch driver"
depends on NET_DSA_MICROCHIP_KSZ_COMMON depends on NET_DSA_MICROCHIP_KSZ_COMMON
......
...@@ -4,6 +4,11 @@ ksz_switch-objs := ksz_common.o ...@@ -4,6 +4,11 @@ ksz_switch-objs := ksz_common.o
ksz_switch-objs += ksz9477.o ksz_switch-objs += ksz9477.o
ksz_switch-objs += ksz8795.o ksz_switch-objs += ksz8795.o
ksz_switch-objs += lan937x_main.o ksz_switch-objs += lan937x_main.o
ifdef CONFIG_NET_DSA_MICROCHIP_KSZ_PTP
ksz_switch-objs += ksz_ptp.o
endif
obj-$(CONFIG_NET_DSA_MICROCHIP_KSZ9477_I2C) += ksz9477_i2c.o obj-$(CONFIG_NET_DSA_MICROCHIP_KSZ9477_I2C) += ksz9477_i2c.o
obj-$(CONFIG_NET_DSA_MICROCHIP_KSZ_SPI) += ksz_spi.o obj-$(CONFIG_NET_DSA_MICROCHIP_KSZ_SPI) += ksz_spi.o
obj-$(CONFIG_NET_DSA_MICROCHIP_KSZ8863_SMI) += ksz8863_smi.o obj-$(CONFIG_NET_DSA_MICROCHIP_KSZ8863_SMI) += ksz8863_smi.o
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
*/ */
#include <linux/delay.h> #include <linux/delay.h>
#include <linux/dsa/ksz_common.h>
#include <linux/export.h> #include <linux/export.h>
#include <linux/gpio/consumer.h> #include <linux/gpio/consumer.h>
#include <linux/kernel.h> #include <linux/kernel.h>
...@@ -25,6 +26,7 @@ ...@@ -25,6 +26,7 @@
#include <net/switchdev.h> #include <net/switchdev.h>
#include "ksz_common.h" #include "ksz_common.h"
#include "ksz_ptp.h"
#include "ksz8.h" #include "ksz8.h"
#include "ksz9477.h" #include "ksz9477.h"
#include "lan937x.h" #include "lan937x.h"
...@@ -2099,13 +2101,23 @@ static int ksz_setup(struct dsa_switch *ds) ...@@ -2099,13 +2101,23 @@ static int ksz_setup(struct dsa_switch *ds)
ret = ksz_pirq_setup(dev, dp->index); ret = ksz_pirq_setup(dev, dp->index);
if (ret) if (ret)
goto out_girq; goto out_girq;
ret = ksz_ptp_irq_setup(ds, dp->index);
if (ret)
goto out_pirq;
} }
} }
ret = ksz_ptp_clock_register(ds);
if (ret) {
dev_err(dev->dev, "Failed to register PTP clock: %d\n", ret);
goto out_ptpirq;
}
ret = ksz_mdio_register(dev); ret = ksz_mdio_register(dev);
if (ret < 0) { if (ret < 0) {
dev_err(dev->dev, "failed to register the mdio"); dev_err(dev->dev, "failed to register the mdio");
goto out_pirq; goto out_ptp_clock_unregister;
} }
/* start switch */ /* start switch */
...@@ -2114,6 +2126,12 @@ static int ksz_setup(struct dsa_switch *ds) ...@@ -2114,6 +2126,12 @@ static int ksz_setup(struct dsa_switch *ds)
return 0; return 0;
out_ptp_clock_unregister:
ksz_ptp_clock_unregister(ds);
out_ptpirq:
if (dev->irq > 0)
dsa_switch_for_each_user_port(dp, dev->ds)
ksz_ptp_irq_free(ds, dp->index);
out_pirq: out_pirq:
if (dev->irq > 0) if (dev->irq > 0)
dsa_switch_for_each_user_port(dp, dev->ds) dsa_switch_for_each_user_port(dp, dev->ds)
...@@ -2130,9 +2148,14 @@ static void ksz_teardown(struct dsa_switch *ds) ...@@ -2130,9 +2148,14 @@ static void ksz_teardown(struct dsa_switch *ds)
struct ksz_device *dev = ds->priv; struct ksz_device *dev = ds->priv;
struct dsa_port *dp; struct dsa_port *dp;
ksz_ptp_clock_unregister(ds);
if (dev->irq > 0) { if (dev->irq > 0) {
dsa_switch_for_each_user_port(dp, dev->ds) dsa_switch_for_each_user_port(dp, dev->ds) {
ksz_ptp_irq_free(ds, dp->index);
ksz_irq_free(&dev->ports[dp->index].pirq); ksz_irq_free(&dev->ports[dp->index].pirq);
}
ksz_irq_free(&dev->girq); ksz_irq_free(&dev->girq);
} }
...@@ -2517,6 +2540,17 @@ static enum dsa_tag_protocol ksz_get_tag_protocol(struct dsa_switch *ds, ...@@ -2517,6 +2540,17 @@ static enum dsa_tag_protocol ksz_get_tag_protocol(struct dsa_switch *ds,
return proto; return proto;
} }
static int ksz_connect_tag_protocol(struct dsa_switch *ds,
enum dsa_tag_protocol proto)
{
struct ksz_tagger_data *tagger_data;
tagger_data = ksz_tagger_data(ds);
tagger_data->xmit_work_fn = ksz_port_deferred_xmit;
return 0;
}
static int ksz_port_vlan_filtering(struct dsa_switch *ds, int port, static int ksz_port_vlan_filtering(struct dsa_switch *ds, int port,
bool flag, struct netlink_ext_ack *extack) bool flag, struct netlink_ext_ack *extack)
{ {
...@@ -2932,6 +2966,7 @@ static int ksz_switch_detect(struct ksz_device *dev) ...@@ -2932,6 +2966,7 @@ static int ksz_switch_detect(struct ksz_device *dev)
static const struct dsa_switch_ops ksz_switch_ops = { static const struct dsa_switch_ops ksz_switch_ops = {
.get_tag_protocol = ksz_get_tag_protocol, .get_tag_protocol = ksz_get_tag_protocol,
.connect_tag_protocol = ksz_connect_tag_protocol,
.get_phy_flags = ksz_get_phy_flags, .get_phy_flags = ksz_get_phy_flags,
.setup = ksz_setup, .setup = ksz_setup,
.teardown = ksz_teardown, .teardown = ksz_teardown,
...@@ -2966,6 +3001,11 @@ static const struct dsa_switch_ops ksz_switch_ops = { ...@@ -2966,6 +3001,11 @@ static const struct dsa_switch_ops ksz_switch_ops = {
.get_pause_stats = ksz_get_pause_stats, .get_pause_stats = ksz_get_pause_stats,
.port_change_mtu = ksz_change_mtu, .port_change_mtu = ksz_change_mtu,
.port_max_mtu = ksz_max_mtu, .port_max_mtu = ksz_max_mtu,
.get_ts_info = ksz_get_ts_info,
.port_hwtstamp_get = ksz_hwtstamp_get,
.port_hwtstamp_set = ksz_hwtstamp_set,
.port_txtstamp = ksz_port_txtstamp,
.port_rxtstamp = ksz_port_rxtstamp,
}; };
struct ksz_device *ksz_switch_alloc(struct device *base, void *priv) struct ksz_device *ksz_switch_alloc(struct device *base, void *priv)
......
...@@ -15,9 +15,12 @@ ...@@ -15,9 +15,12 @@
#include <net/dsa.h> #include <net/dsa.h>
#include <linux/irq.h> #include <linux/irq.h>
#include "ksz_ptp.h"
#define KSZ_MAX_NUM_PORTS 8 #define KSZ_MAX_NUM_PORTS 8
struct ksz_device; struct ksz_device;
struct ksz_port;
struct vlan_table { struct vlan_table {
u32 table[3]; u32 table[3];
...@@ -81,6 +84,14 @@ struct ksz_irq { ...@@ -81,6 +84,14 @@ struct ksz_irq {
struct ksz_device *dev; struct ksz_device *dev;
}; };
struct ksz_ptp_irq {
struct ksz_port *port;
u16 ts_reg;
bool ts_en;
char name[16];
int num;
};
struct ksz_port { struct ksz_port {
bool remove_tag; /* Remove Tag flag set, for ksz8795 only */ bool remove_tag; /* Remove Tag flag set, for ksz8795 only */
bool learning; bool learning;
...@@ -100,6 +111,15 @@ struct ksz_port { ...@@ -100,6 +111,15 @@ struct ksz_port {
struct ksz_device *ksz_dev; struct ksz_device *ksz_dev;
struct ksz_irq pirq; struct ksz_irq pirq;
u8 num; u8 num;
#if IS_ENABLED(CONFIG_NET_DSA_MICROCHIP_KSZ_PTP)
struct hwtstamp_config tstamp_config;
bool hwts_tx_en;
bool hwts_rx_en;
struct ksz_irq ptpirq;
struct ksz_ptp_irq ptpmsg_irq[3];
ktime_t tstamp_msg;
struct completion tstamp_msg_comp;
#endif
}; };
struct ksz_device { struct ksz_device {
...@@ -140,6 +160,7 @@ struct ksz_device { ...@@ -140,6 +160,7 @@ struct ksz_device {
u16 port_mask; u16 port_mask;
struct mutex lock_irq; /* IRQ Access */ struct mutex lock_irq; /* IRQ Access */
struct ksz_irq girq; struct ksz_irq girq;
struct ksz_ptp_data ptp_data;
}; };
/* List of supported models */ /* List of supported models */
...@@ -443,6 +464,32 @@ static inline int ksz_write32(struct ksz_device *dev, u32 reg, u32 value) ...@@ -443,6 +464,32 @@ static inline int ksz_write32(struct ksz_device *dev, u32 reg, u32 value)
return ret; return ret;
} }
static inline int ksz_rmw16(struct ksz_device *dev, u32 reg, u16 mask,
u16 value)
{
int ret;
ret = regmap_update_bits(dev->regmap[1], reg, mask, value);
if (ret)
dev_err(dev->dev, "can't rmw 16bit reg 0x%x: %pe\n", reg,
ERR_PTR(ret));
return ret;
}
static inline int ksz_rmw32(struct ksz_device *dev, u32 reg, u32 mask,
u32 value)
{
int ret;
ret = regmap_update_bits(dev->regmap[2], reg, mask, value);
if (ret)
dev_err(dev->dev, "can't rmw 32bit reg 0x%x: %pe\n", reg,
ERR_PTR(ret));
return ret;
}
static inline int ksz_write64(struct ksz_device *dev, u32 reg, u64 value) static inline int ksz_write64(struct ksz_device *dev, u32 reg, u64 value)
{ {
u32 val[2]; u32 val[2];
...@@ -591,6 +638,7 @@ static inline int is_lan937x(struct ksz_device *dev) ...@@ -591,6 +638,7 @@ static inline int is_lan937x(struct ksz_device *dev)
#define REG_PORT_INT_MASK 0x001F #define REG_PORT_INT_MASK 0x001F
#define PORT_SRC_PHY_INT 1 #define PORT_SRC_PHY_INT 1
#define PORT_SRC_PTP_INT 2
#define KSZ8795_HUGE_PACKET_SIZE 2000 #define KSZ8795_HUGE_PACKET_SIZE 2000
#define KSZ8863_HUGE_PACKET_SIZE 1916 #define KSZ8863_HUGE_PACKET_SIZE 1916
......
This diff is collapsed.
/* SPDX-License-Identifier: GPL-2.0 */
/* Microchip KSZ PTP Implementation
*
* Copyright (C) 2020 ARRI Lighting
* Copyright (C) 2022 Microchip Technology Inc.
*/
#ifndef _NET_DSA_DRIVERS_KSZ_PTP_H
#define _NET_DSA_DRIVERS_KSZ_PTP_H
#if IS_ENABLED(CONFIG_NET_DSA_MICROCHIP_KSZ_PTP)
#include <linux/ptp_clock_kernel.h>
#define KSZ_PTP_N_GPIO 2
enum ksz_ptp_tou_mode {
KSZ_PTP_TOU_IDLE,
KSZ_PTP_TOU_PEROUT,
};
struct ksz_ptp_data {
struct ptp_clock_info caps;
struct ptp_clock *clock;
struct ptp_pin_desc pin_config[KSZ_PTP_N_GPIO];
/* Serializes all operations on the PTP hardware clock */
struct mutex lock;
/* lock for accessing the clock_time */
spinlock_t clock_lock;
struct timespec64 clock_time;
enum ksz_ptp_tou_mode tou_mode;
struct timespec64 perout_target_time_first; /* start of first pulse */
struct timespec64 perout_period;
};
int ksz_ptp_clock_register(struct dsa_switch *ds);
void ksz_ptp_clock_unregister(struct dsa_switch *ds);
int ksz_get_ts_info(struct dsa_switch *ds, int port,
struct ethtool_ts_info *ts);
int ksz_hwtstamp_get(struct dsa_switch *ds, int port, struct ifreq *ifr);
int ksz_hwtstamp_set(struct dsa_switch *ds, int port, struct ifreq *ifr);
void ksz_port_txtstamp(struct dsa_switch *ds, int port, struct sk_buff *skb);
void ksz_port_deferred_xmit(struct kthread_work *work);
bool ksz_port_rxtstamp(struct dsa_switch *ds, int port, struct sk_buff *skb,
unsigned int type);
int ksz_ptp_irq_setup(struct dsa_switch *ds, u8 p);
void ksz_ptp_irq_free(struct dsa_switch *ds, u8 p);
#else
struct ksz_ptp_data {
/* Serializes all operations on the PTP hardware clock */
struct mutex lock;
};
static inline int ksz_ptp_clock_register(struct dsa_switch *ds)
{
return 0;
}
static inline void ksz_ptp_clock_unregister(struct dsa_switch *ds) { }
static inline int ksz_ptp_irq_setup(struct dsa_switch *ds, u8 p)
{
return 0;
}
static inline void ksz_ptp_irq_free(struct dsa_switch *ds, u8 p) {}
#define ksz_get_ts_info NULL
#define ksz_hwtstamp_get NULL
#define ksz_hwtstamp_set NULL
#define ksz_port_rxtstamp NULL
#define ksz_port_txtstamp NULL
#define ksz_port_deferred_xmit NULL
#endif /* End of CONFIG_NET_DSA_MICROCHIP_KSZ_PTP */
#endif
/* SPDX-License-Identifier: GPL-2.0 */
/* Microchip KSZ PTP register definitions
* Copyright (C) 2022 Microchip Technology Inc.
*/
#ifndef __KSZ_PTP_REGS_H
#define __KSZ_PTP_REGS_H
#define REG_SW_GLOBAL_LED_OVR__4 0x0120
#define LED_OVR_2 BIT(1)
#define LED_OVR_1 BIT(0)
#define REG_SW_GLOBAL_LED_SRC__4 0x0128
#define LED_SRC_PTP_GPIO_1 BIT(3)
#define LED_SRC_PTP_GPIO_2 BIT(2)
/* 5 - PTP Clock */
#define REG_PTP_CLK_CTRL 0x0500
#define PTP_STEP_ADJ BIT(6)
#define PTP_STEP_DIR BIT(5)
#define PTP_READ_TIME BIT(4)
#define PTP_LOAD_TIME BIT(3)
#define PTP_CLK_ADJ_ENABLE BIT(2)
#define PTP_CLK_ENABLE BIT(1)
#define PTP_CLK_RESET BIT(0)
#define REG_PTP_RTC_SUB_NANOSEC__2 0x0502
#define PTP_RTC_SUB_NANOSEC_M 0x0007
#define PTP_RTC_0NS 0x00
#define REG_PTP_RTC_NANOSEC 0x0504
#define REG_PTP_RTC_SEC 0x0508
#define REG_PTP_SUBNANOSEC_RATE 0x050C
#define PTP_SUBNANOSEC_M 0x3FFFFFFF
#define PTP_RATE_DIR BIT(31)
#define PTP_TMP_RATE_ENABLE BIT(30)
#define REG_PTP_SUBNANOSEC_RATE_L 0x050E
#define REG_PTP_RATE_DURATION 0x0510
#define REG_PTP_RATE_DURATION_H 0x0510
#define REG_PTP_RATE_DURATION_L 0x0512
#define REG_PTP_MSG_CONF1 0x0514
#define PTP_802_1AS BIT(7)
#define PTP_ENABLE BIT(6)
#define PTP_ETH_ENABLE BIT(5)
#define PTP_IPV4_UDP_ENABLE BIT(4)
#define PTP_IPV6_UDP_ENABLE BIT(3)
#define PTP_TC_P2P BIT(2)
#define PTP_MASTER BIT(1)
#define PTP_1STEP BIT(0)
#define REG_PTP_UNIT_INDEX__4 0x0520
#define PTP_GPIO_INDEX GENMASK(19, 16)
#define PTP_TSI_INDEX BIT(8)
#define PTP_TOU_INDEX GENMASK(1, 0)
#define REG_PTP_TRIG_STATUS__4 0x0524
#define TRIG_ERROR_M GENMASK(18, 16)
#define TRIG_DONE_M GENMASK(2, 0)
#define REG_PTP_INT_STATUS__4 0x0528
#define TRIG_INT_M GENMASK(18, 16)
#define TS_INT_M GENMASK(1, 0)
#define REG_PTP_CTRL_STAT__4 0x052C
#define GPIO_IN BIT(7)
#define GPIO_OUT BIT(6)
#define TS_INT_ENABLE BIT(5)
#define TRIG_ACTIVE BIT(4)
#define TRIG_ENABLE BIT(3)
#define TRIG_RESET BIT(2)
#define TS_ENABLE BIT(1)
#define TS_RESET BIT(0)
#define REG_TRIG_TARGET_NANOSEC 0x0530
#define REG_TRIG_TARGET_SEC 0x0534
#define REG_TRIG_CTRL__4 0x0538
#define TRIG_CASCADE_ENABLE BIT(31)
#define TRIG_CASCADE_TAIL BIT(30)
#define TRIG_CASCADE_UPS_M GENMASK(29, 26)
#define TRIG_NOW BIT(25)
#define TRIG_NOTIFY BIT(24)
#define TRIG_EDGE BIT(23)
#define TRIG_PATTERN_M GENMASK(22, 20)
#define TRIG_NEG_EDGE 0
#define TRIG_POS_EDGE 1
#define TRIG_NEG_PULSE 2
#define TRIG_POS_PULSE 3
#define TRIG_NEG_PERIOD 4
#define TRIG_POS_PERIOD 5
#define TRIG_REG_OUTPUT 6
#define TRIG_GPO_M GENMASK(19, 16)
#define TRIG_CASCADE_ITERATE_CNT_M GENMASK(15, 0)
#define REG_TRIG_CYCLE_WIDTH 0x053C
#define TRIG_CYCLE_WIDTH_M GENMASK(31, 0)
#define REG_TRIG_CYCLE_CNT 0x0540
#define TRIG_CYCLE_CNT_M GENMASK(31, 16)
#define TRIG_BIT_PATTERN_M GENMASK(15, 0)
#define REG_TRIG_ITERATE_TIME 0x0544
#define REG_TRIG_PULSE_WIDTH__4 0x0548
#define TRIG_PULSE_WIDTH_M GENMASK(23, 0)
/* Port PTP Register */
#define REG_PTP_PORT_RX_DELAY__2 0x0C00
#define REG_PTP_PORT_TX_DELAY__2 0x0C02
#define REG_PTP_PORT_ASYM_DELAY__2 0x0C04
#define REG_PTP_PORT_XDELAY_TS 0x0C08
#define REG_PTP_PORT_SYNC_TS 0x0C0C
#define REG_PTP_PORT_PDRESP_TS 0x0C10
#define REG_PTP_PORT_TX_INT_STATUS__2 0x0C14
#define REG_PTP_PORT_TX_INT_ENABLE__2 0x0C16
#define PTP_PORT_SYNC_INT BIT(15)
#define PTP_PORT_XDELAY_REQ_INT BIT(14)
#define PTP_PORT_PDELAY_RESP_INT BIT(13)
#define KSZ_SYNC_MSG 2
#define KSZ_XDREQ_MSG 1
#define KSZ_PDRES_MSG 0
#endif
/* SPDX-License-Identifier: GPL-2.0 */
/* Microchip switch tag common header
*
* Copyright (C) 2022 Microchip Technology Inc.
*/
#ifndef _NET_DSA_KSZ_COMMON_H_
#define _NET_DSA_KSZ_COMMON_H_
#include <net/dsa.h>
/* All time stamps from the KSZ consist of 2 bits for seconds and 30 bits for
* nanoseconds. This is NOT the same as 32 bits for nanoseconds.
*/
#define KSZ_TSTAMP_SEC_MASK GENMASK(31, 30)
#define KSZ_TSTAMP_NSEC_MASK GENMASK(29, 0)
static inline ktime_t ksz_decode_tstamp(u32 tstamp)
{
u64 ns = FIELD_GET(KSZ_TSTAMP_SEC_MASK, tstamp) * NSEC_PER_SEC +
FIELD_GET(KSZ_TSTAMP_NSEC_MASK, tstamp);
return ns_to_ktime(ns);
}
struct ksz_deferred_xmit_work {
struct dsa_port *dp;
struct sk_buff *skb;
struct kthread_work work;
};
struct ksz_tagger_data {
void (*xmit_work_fn)(struct kthread_work *work);
void (*hwtstamp_set_state)(struct dsa_switch *ds, bool on);
};
struct ksz_skb_cb {
struct sk_buff *clone;
unsigned int ptp_type;
bool update_correction;
u32 tstamp;
};
#define KSZ_SKB_CB(skb) \
((struct ksz_skb_cb *)((skb)->cb))
static inline struct ksz_tagger_data *
ksz_tagger_data(struct dsa_switch *ds)
{
return ds->tagger_data;
}
#endif /* _NET_DSA_KSZ_COMMON_H_ */
...@@ -10,8 +10,12 @@ ...@@ -10,8 +10,12 @@
#ifndef _PTP_CLASSIFY_H_ #ifndef _PTP_CLASSIFY_H_
#define _PTP_CLASSIFY_H_ #define _PTP_CLASSIFY_H_
#include <asm/unaligned.h>
#include <linux/ip.h> #include <linux/ip.h>
#include <linux/ktime.h>
#include <linux/skbuff.h> #include <linux/skbuff.h>
#include <linux/udp.h>
#include <net/checksum.h>
#define PTP_CLASS_NONE 0x00 /* not a PTP event message */ #define PTP_CLASS_NONE 0x00 /* not a PTP event message */
#define PTP_CLASS_V1 0x01 /* protocol version 1 */ #define PTP_CLASS_V1 0x01 /* protocol version 1 */
...@@ -129,6 +133,69 @@ static inline u8 ptp_get_msgtype(const struct ptp_header *hdr, ...@@ -129,6 +133,69 @@ static inline u8 ptp_get_msgtype(const struct ptp_header *hdr,
return msgtype; return msgtype;
} }
/**
* ptp_check_diff8 - Computes new checksum (when altering a 64-bit field)
* @old: old field value
* @new: new field value
* @oldsum: previous checksum
*
* This function can be used to calculate a new checksum when only a single
* field is changed. Similar as ip_vs_check_diff*() in ip_vs.h.
*
* Return: Updated checksum
*/
static inline __wsum ptp_check_diff8(__be64 old, __be64 new, __wsum oldsum)
{
__be64 diff[2] = { ~old, new };
return csum_partial(diff, sizeof(diff), oldsum);
}
/**
* ptp_header_update_correction - Update PTP header's correction field
* @skb: packet buffer
* @type: type of the packet (see ptp_classify_raw())
* @hdr: ptp header
* @correction: new correction value
*
* This updates the correction field of a PTP header and updates the UDP
* checksum (if UDP is used as transport). It is needed for hardware capable of
* one-step P2P that does not already modify the correction field of Pdelay_Req
* event messages on ingress.
*/
static inline
void ptp_header_update_correction(struct sk_buff *skb, unsigned int type,
struct ptp_header *hdr, s64 correction)
{
__be64 correction_old;
struct udphdr *uhdr;
/* previous correction value is required for checksum update. */
memcpy(&correction_old, &hdr->correction, sizeof(correction_old));
/* write new correction value */
put_unaligned_be64((u64)correction, &hdr->correction);
switch (type & PTP_CLASS_PMASK) {
case PTP_CLASS_IPV4:
case PTP_CLASS_IPV6:
/* locate udp header */
uhdr = (struct udphdr *)((char *)hdr - sizeof(struct udphdr));
break;
default:
return;
}
/* update checksum */
uhdr->check = csum_fold(ptp_check_diff8(correction_old,
hdr->correction,
~csum_unfold(uhdr->check)));
if (!uhdr->check)
uhdr->check = CSUM_MANGLED_0;
skb->ip_summed = CHECKSUM_NONE;
}
/** /**
* ptp_msg_is_sync - Evaluates whether the given skb is a PTP Sync message * ptp_msg_is_sync - Evaluates whether the given skb is a PTP Sync message
* @skb: packet buffer * @skb: packet buffer
...@@ -166,5 +233,11 @@ static inline bool ptp_msg_is_sync(struct sk_buff *skb, unsigned int type) ...@@ -166,5 +233,11 @@ static inline bool ptp_msg_is_sync(struct sk_buff *skb, unsigned int type)
{ {
return false; return false;
} }
static inline
void ptp_header_update_correction(struct sk_buff *skb, unsigned int type,
struct ptp_header *hdr, s64 correction)
{
}
#endif #endif
#endif /* _PTP_CLASSIFY_H_ */ #endif /* _PTP_CLASSIFY_H_ */
...@@ -4,8 +4,10 @@ ...@@ -4,8 +4,10 @@
* Copyright (c) 2017 Microchip Technology * Copyright (c) 2017 Microchip Technology
*/ */
#include <linux/dsa/ksz_common.h>
#include <linux/etherdevice.h> #include <linux/etherdevice.h>
#include <linux/list.h> #include <linux/list.h>
#include <linux/ptp_classify.h>
#include <net/dsa.h> #include <net/dsa.h>
#include "tag.h" #include "tag.h"
...@@ -16,9 +18,71 @@ ...@@ -16,9 +18,71 @@
#define LAN937X_NAME "lan937x" #define LAN937X_NAME "lan937x"
/* Typically only one byte is used for tail tag. */ /* Typically only one byte is used for tail tag. */
#define KSZ_PTP_TAG_LEN 4
#define KSZ_EGRESS_TAG_LEN 1 #define KSZ_EGRESS_TAG_LEN 1
#define KSZ_INGRESS_TAG_LEN 1 #define KSZ_INGRESS_TAG_LEN 1
#define KSZ_HWTS_EN 0
struct ksz_tagger_private {
struct ksz_tagger_data data; /* Must be first */
unsigned long state;
struct kthread_worker *xmit_worker;
};
static struct ksz_tagger_private *
ksz_tagger_private(struct dsa_switch *ds)
{
return ds->tagger_data;
}
static void ksz_hwtstamp_set_state(struct dsa_switch *ds, bool on)
{
struct ksz_tagger_private *priv = ksz_tagger_private(ds);
if (on)
set_bit(KSZ_HWTS_EN, &priv->state);
else
clear_bit(KSZ_HWTS_EN, &priv->state);
}
static void ksz_disconnect(struct dsa_switch *ds)
{
struct ksz_tagger_private *priv = ds->tagger_data;
kthread_destroy_worker(priv->xmit_worker);
kfree(priv);
ds->tagger_data = NULL;
}
static int ksz_connect(struct dsa_switch *ds)
{
struct ksz_tagger_data *tagger_data;
struct kthread_worker *xmit_worker;
struct ksz_tagger_private *priv;
int ret;
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
xmit_worker = kthread_create_worker(0, "dsa%d:%d_xmit",
ds->dst->index, ds->index);
if (IS_ERR(xmit_worker)) {
ret = PTR_ERR(xmit_worker);
kfree(priv);
return ret;
}
priv->xmit_worker = xmit_worker;
/* Export functions for switch driver use */
tagger_data = &priv->data;
tagger_data->hwtstamp_set_state = ksz_hwtstamp_set_state;
ds->tagger_data = priv;
return 0;
}
static struct sk_buff *ksz_common_rcv(struct sk_buff *skb, static struct sk_buff *ksz_common_rcv(struct sk_buff *skb,
struct net_device *dev, struct net_device *dev,
unsigned int port, unsigned int len) unsigned int port, unsigned int len)
...@@ -92,17 +156,20 @@ DSA_TAG_DRIVER(ksz8795_netdev_ops); ...@@ -92,17 +156,20 @@ DSA_TAG_DRIVER(ksz8795_netdev_ops);
MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_KSZ8795, KSZ8795_NAME); MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_KSZ8795, KSZ8795_NAME);
/* /*
* For Ingress (Host -> KSZ9477), 2 bytes are added before FCS. * For Ingress (Host -> KSZ9477), 2/6 bytes are added before FCS.
* --------------------------------------------------------------------------- * ---------------------------------------------------------------------------
* DA(6bytes)|SA(6bytes)|....|Data(nbytes)|tag0(1byte)|tag1(1byte)|FCS(4bytes) * DA(6bytes)|SA(6bytes)|....|Data(nbytes)|ts(4bytes)|tag0(1byte)|tag1(1byte)|
* FCS(4bytes)
* --------------------------------------------------------------------------- * ---------------------------------------------------------------------------
* ts : time stamp (Present only if PTP is enabled in the Hardware)
* tag0 : Prioritization (not used now) * tag0 : Prioritization (not used now)
* tag1 : each bit represents port (eg, 0x01=port1, 0x02=port2, 0x10=port5) * tag1 : each bit represents port (eg, 0x01=port1, 0x02=port2, 0x10=port5)
* *
* For Egress (KSZ9477 -> Host), 1 byte is added before FCS. * For Egress (KSZ9477 -> Host), 1/5 bytes is added before FCS.
* --------------------------------------------------------------------------- * ---------------------------------------------------------------------------
* DA(6bytes)|SA(6bytes)|....|Data(nbytes)|tag0(1byte)|FCS(4bytes) * DA(6bytes)|SA(6bytes)|....|Data(nbytes)|ts(4bytes)|tag0(1byte)|FCS(4bytes)
* --------------------------------------------------------------------------- * ---------------------------------------------------------------------------
* ts : time stamp (Present only if bit 7 of tag0 is set)
* tag0 : zero-based value represents port * tag0 : zero-based value represents port
* (eg, 0x00=port1, 0x02=port3, 0x06=port7) * (eg, 0x00=port1, 0x02=port3, 0x06=port7)
*/ */
...@@ -114,6 +181,91 @@ MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_KSZ8795, KSZ8795_NAME); ...@@ -114,6 +181,91 @@ MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_KSZ8795, KSZ8795_NAME);
#define KSZ9477_TAIL_TAG_OVERRIDE BIT(9) #define KSZ9477_TAIL_TAG_OVERRIDE BIT(9)
#define KSZ9477_TAIL_TAG_LOOKUP BIT(10) #define KSZ9477_TAIL_TAG_LOOKUP BIT(10)
static void ksz_rcv_timestamp(struct sk_buff *skb, u8 *tag)
{
u8 *tstamp_raw = tag - KSZ_PTP_TAG_LEN;
ktime_t tstamp;
tstamp = ksz_decode_tstamp(get_unaligned_be32(tstamp_raw));
KSZ_SKB_CB(skb)->tstamp = tstamp;
}
/* Time stamp tag *needs* to be inserted if PTP is enabled in hardware.
* Regardless of Whether it is a PTP frame or not.
*/
static void ksz_xmit_timestamp(struct dsa_port *dp, struct sk_buff *skb)
{
struct ksz_tagger_private *priv;
struct ptp_header *ptp_hdr;
unsigned int ptp_type;
u32 tstamp_raw = 0;
s64 correction;
priv = ksz_tagger_private(dp->ds);
if (!test_bit(KSZ_HWTS_EN, &priv->state))
return;
if (!KSZ_SKB_CB(skb)->update_correction)
goto output_tag;
ptp_type = KSZ_SKB_CB(skb)->ptp_type;
ptp_hdr = ptp_parse_header(skb, ptp_type);
if (!ptp_hdr)
goto output_tag;
correction = (s64)get_unaligned_be64(&ptp_hdr->correction);
if (correction < 0) {
struct timespec64 ts;
ts = ns_to_timespec64(-correction >> 16);
tstamp_raw = ((ts.tv_sec & 3) << 30) | ts.tv_nsec;
/* Set correction field to 0 and update UDP checksum */
ptp_header_update_correction(skb, ptp_type, ptp_hdr, 0);
}
output_tag:
put_unaligned_be32(tstamp_raw, skb_put(skb, KSZ_PTP_TAG_LEN));
}
/* Defer transmit if waiting for egress time stamp is required. */
static struct sk_buff *ksz_defer_xmit(struct dsa_port *dp, struct sk_buff *skb)
{
struct ksz_tagger_data *tagger_data = ksz_tagger_data(dp->ds);
struct ksz_tagger_private *priv = ksz_tagger_private(dp->ds);
void (*xmit_work_fn)(struct kthread_work *work);
struct sk_buff *clone = KSZ_SKB_CB(skb)->clone;
struct ksz_deferred_xmit_work *xmit_work;
struct kthread_worker *xmit_worker;
if (!clone)
return skb; /* no deferred xmit for this packet */
xmit_work_fn = tagger_data->xmit_work_fn;
xmit_worker = priv->xmit_worker;
if (!xmit_work_fn || !xmit_worker)
return NULL;
xmit_work = kzalloc(sizeof(*xmit_work), GFP_ATOMIC);
if (!xmit_work)
return NULL;
kthread_init_work(&xmit_work->work, xmit_work_fn);
/* Increase refcount so the kfree_skb in dsa_slave_xmit
* won't really free the packet.
*/
xmit_work->dp = dp;
xmit_work->skb = skb_get(skb);
kthread_queue_work(xmit_worker, &xmit_work->work);
return NULL;
}
static struct sk_buff *ksz9477_xmit(struct sk_buff *skb, static struct sk_buff *ksz9477_xmit(struct sk_buff *skb,
struct net_device *dev) struct net_device *dev)
{ {
...@@ -126,6 +278,8 @@ static struct sk_buff *ksz9477_xmit(struct sk_buff *skb, ...@@ -126,6 +278,8 @@ static struct sk_buff *ksz9477_xmit(struct sk_buff *skb,
return NULL; return NULL;
/* Tag encoding */ /* Tag encoding */
ksz_xmit_timestamp(dp, skb);
tag = skb_put(skb, KSZ9477_INGRESS_TAG_LEN); tag = skb_put(skb, KSZ9477_INGRESS_TAG_LEN);
addr = skb_mac_header(skb); addr = skb_mac_header(skb);
...@@ -136,7 +290,7 @@ static struct sk_buff *ksz9477_xmit(struct sk_buff *skb, ...@@ -136,7 +290,7 @@ static struct sk_buff *ksz9477_xmit(struct sk_buff *skb,
*tag = cpu_to_be16(val); *tag = cpu_to_be16(val);
return skb; return ksz_defer_xmit(dp, skb);
} }
static struct sk_buff *ksz9477_rcv(struct sk_buff *skb, struct net_device *dev) static struct sk_buff *ksz9477_rcv(struct sk_buff *skb, struct net_device *dev)
...@@ -147,8 +301,10 @@ static struct sk_buff *ksz9477_rcv(struct sk_buff *skb, struct net_device *dev) ...@@ -147,8 +301,10 @@ static struct sk_buff *ksz9477_rcv(struct sk_buff *skb, struct net_device *dev)
unsigned int len = KSZ_EGRESS_TAG_LEN; unsigned int len = KSZ_EGRESS_TAG_LEN;
/* Extra 4-bytes PTP timestamp */ /* Extra 4-bytes PTP timestamp */
if (tag[0] & KSZ9477_PTP_TAG_INDICATION) if (tag[0] & KSZ9477_PTP_TAG_INDICATION) {
len += KSZ9477_PTP_TAG_LEN; ksz_rcv_timestamp(skb, tag);
len += KSZ_PTP_TAG_LEN;
}
return ksz_common_rcv(skb, dev, port, len); return ksz_common_rcv(skb, dev, port, len);
} }
...@@ -158,7 +314,9 @@ static const struct dsa_device_ops ksz9477_netdev_ops = { ...@@ -158,7 +314,9 @@ static const struct dsa_device_ops ksz9477_netdev_ops = {
.proto = DSA_TAG_PROTO_KSZ9477, .proto = DSA_TAG_PROTO_KSZ9477,
.xmit = ksz9477_xmit, .xmit = ksz9477_xmit,
.rcv = ksz9477_rcv, .rcv = ksz9477_rcv,
.needed_tailroom = KSZ9477_INGRESS_TAG_LEN, .connect = ksz_connect,
.disconnect = ksz_disconnect,
.needed_tailroom = KSZ9477_INGRESS_TAG_LEN + KSZ_PTP_TAG_LEN,
}; };
DSA_TAG_DRIVER(ksz9477_netdev_ops); DSA_TAG_DRIVER(ksz9477_netdev_ops);
...@@ -178,6 +336,8 @@ static struct sk_buff *ksz9893_xmit(struct sk_buff *skb, ...@@ -178,6 +336,8 @@ static struct sk_buff *ksz9893_xmit(struct sk_buff *skb,
return NULL; return NULL;
/* Tag encoding */ /* Tag encoding */
ksz_xmit_timestamp(dp, skb);
tag = skb_put(skb, KSZ_INGRESS_TAG_LEN); tag = skb_put(skb, KSZ_INGRESS_TAG_LEN);
addr = skb_mac_header(skb); addr = skb_mac_header(skb);
...@@ -186,7 +346,7 @@ static struct sk_buff *ksz9893_xmit(struct sk_buff *skb, ...@@ -186,7 +346,7 @@ static struct sk_buff *ksz9893_xmit(struct sk_buff *skb,
if (is_link_local_ether_addr(addr)) if (is_link_local_ether_addr(addr))
*tag |= KSZ9893_TAIL_TAG_OVERRIDE; *tag |= KSZ9893_TAIL_TAG_OVERRIDE;
return skb; return ksz_defer_xmit(dp, skb);
} }
static const struct dsa_device_ops ksz9893_netdev_ops = { static const struct dsa_device_ops ksz9893_netdev_ops = {
...@@ -194,23 +354,28 @@ static const struct dsa_device_ops ksz9893_netdev_ops = { ...@@ -194,23 +354,28 @@ static const struct dsa_device_ops ksz9893_netdev_ops = {
.proto = DSA_TAG_PROTO_KSZ9893, .proto = DSA_TAG_PROTO_KSZ9893,
.xmit = ksz9893_xmit, .xmit = ksz9893_xmit,
.rcv = ksz9477_rcv, .rcv = ksz9477_rcv,
.needed_tailroom = KSZ_INGRESS_TAG_LEN, .connect = ksz_connect,
.disconnect = ksz_disconnect,
.needed_tailroom = KSZ_INGRESS_TAG_LEN + KSZ_PTP_TAG_LEN,
}; };
DSA_TAG_DRIVER(ksz9893_netdev_ops); DSA_TAG_DRIVER(ksz9893_netdev_ops);
MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_KSZ9893, KSZ9893_NAME); MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_KSZ9893, KSZ9893_NAME);
/* For xmit, 2 bytes are added before FCS. /* For xmit, 2/6 bytes are added before FCS.
* --------------------------------------------------------------------------- * ---------------------------------------------------------------------------
* DA(6bytes)|SA(6bytes)|....|Data(nbytes)|tag0(1byte)|tag1(1byte)|FCS(4bytes) * DA(6bytes)|SA(6bytes)|....|Data(nbytes)|ts(4bytes)|tag0(1byte)|tag1(1byte)|
* FCS(4bytes)
* --------------------------------------------------------------------------- * ---------------------------------------------------------------------------
* ts : time stamp (Present only if PTP is enabled in the Hardware)
* tag0 : represents tag override, lookup and valid * tag0 : represents tag override, lookup and valid
* tag1 : each bit represents port (eg, 0x01=port1, 0x02=port2, 0x80=port8) * tag1 : each bit represents port (eg, 0x01=port1, 0x02=port2, 0x80=port8)
* *
* For rcv, 1 byte is added before FCS. * For rcv, 1/5 bytes is added before FCS.
* --------------------------------------------------------------------------- * ---------------------------------------------------------------------------
* DA(6bytes)|SA(6bytes)|....|Data(nbytes)|tag0(1byte)|FCS(4bytes) * DA(6bytes)|SA(6bytes)|....|Data(nbytes)|ts(4bytes)|tag0(1byte)|FCS(4bytes)
* --------------------------------------------------------------------------- * ---------------------------------------------------------------------------
* ts : time stamp (Present only if bit 7 of tag0 is set)
* tag0 : zero-based value represents port * tag0 : zero-based value represents port
* (eg, 0x00=port1, 0x02=port3, 0x07=port8) * (eg, 0x00=port1, 0x02=port3, 0x07=port8)
*/ */
...@@ -232,6 +397,8 @@ static struct sk_buff *lan937x_xmit(struct sk_buff *skb, ...@@ -232,6 +397,8 @@ static struct sk_buff *lan937x_xmit(struct sk_buff *skb,
if (skb->ip_summed == CHECKSUM_PARTIAL && skb_checksum_help(skb)) if (skb->ip_summed == CHECKSUM_PARTIAL && skb_checksum_help(skb))
return NULL; return NULL;
ksz_xmit_timestamp(dp, skb);
tag = skb_put(skb, LAN937X_EGRESS_TAG_LEN); tag = skb_put(skb, LAN937X_EGRESS_TAG_LEN);
val = BIT(dp->index); val = BIT(dp->index);
...@@ -244,7 +411,7 @@ static struct sk_buff *lan937x_xmit(struct sk_buff *skb, ...@@ -244,7 +411,7 @@ static struct sk_buff *lan937x_xmit(struct sk_buff *skb,
put_unaligned_be16(val, tag); put_unaligned_be16(val, tag);
return skb; return ksz_defer_xmit(dp, skb);
} }
static const struct dsa_device_ops lan937x_netdev_ops = { static const struct dsa_device_ops lan937x_netdev_ops = {
...@@ -252,7 +419,9 @@ static const struct dsa_device_ops lan937x_netdev_ops = { ...@@ -252,7 +419,9 @@ static const struct dsa_device_ops lan937x_netdev_ops = {
.proto = DSA_TAG_PROTO_LAN937X, .proto = DSA_TAG_PROTO_LAN937X,
.xmit = lan937x_xmit, .xmit = lan937x_xmit,
.rcv = ksz9477_rcv, .rcv = ksz9477_rcv,
.needed_tailroom = LAN937X_EGRESS_TAG_LEN, .connect = ksz_connect,
.disconnect = ksz_disconnect,
.needed_tailroom = LAN937X_EGRESS_TAG_LEN + KSZ_PTP_TAG_LEN,
}; };
DSA_TAG_DRIVER(lan937x_netdev_ops); DSA_TAG_DRIVER(lan937x_netdev_ops);
......
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