mirror of
https://github.com/Fishwaldo/linux-bl808.git
synced 2025-04-02 20:33:54 +00:00
net: dsa: sja1105: Add a state machine for RX timestamping
Meta frame reception relies on the hardware keeping its promise that it will send no other traffic towards the CPU port between a link-local frame and a meta frame. Otherwise there is no other way to associate the meta frame with the link-local frame it's holding a timestamp of. The receive function is made stateful, and buffers a timestampable frame until its meta frame arrives, then merges the two, drops the meta and releases the link-local frame up the stack. Signed-off-by: Vladimir Oltean <olteanv@gmail.com> Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
parent
08fde09a0d
commit
f3097be21b
3 changed files with 189 additions and 1 deletions
|
@ -1600,6 +1600,14 @@ static int sja1105_setup(struct dsa_switch *ds)
|
||||||
return sja1105_setup_8021q_tagging(ds, true);
|
return sja1105_setup_8021q_tagging(ds, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void sja1105_teardown(struct dsa_switch *ds)
|
||||||
|
{
|
||||||
|
struct sja1105_private *priv = ds->priv;
|
||||||
|
|
||||||
|
cancel_work_sync(&priv->tagger_data.rxtstamp_work);
|
||||||
|
skb_queue_purge(&priv->tagger_data.skb_rxtstamp_queue);
|
||||||
|
}
|
||||||
|
|
||||||
static int sja1105_mgmt_xmit(struct dsa_switch *ds, int port, int slot,
|
static int sja1105_mgmt_xmit(struct dsa_switch *ds, int port, int slot,
|
||||||
struct sk_buff *skb, bool takets)
|
struct sk_buff *skb, bool takets)
|
||||||
{
|
{
|
||||||
|
@ -1747,6 +1755,57 @@ static int sja1105_set_ageing_time(struct dsa_switch *ds,
|
||||||
return sja1105_static_config_reload(priv);
|
return sja1105_static_config_reload(priv);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define to_tagger(d) \
|
||||||
|
container_of((d), struct sja1105_tagger_data, rxtstamp_work)
|
||||||
|
#define to_sja1105(d) \
|
||||||
|
container_of((d), struct sja1105_private, tagger_data)
|
||||||
|
|
||||||
|
static void sja1105_rxtstamp_work(struct work_struct *work)
|
||||||
|
{
|
||||||
|
struct sja1105_tagger_data *data = to_tagger(work);
|
||||||
|
struct sja1105_private *priv = to_sja1105(data);
|
||||||
|
struct sk_buff *skb;
|
||||||
|
u64 now;
|
||||||
|
|
||||||
|
mutex_lock(&priv->ptp_lock);
|
||||||
|
|
||||||
|
now = priv->tstamp_cc.read(&priv->tstamp_cc);
|
||||||
|
|
||||||
|
while ((skb = skb_dequeue(&data->skb_rxtstamp_queue)) != NULL) {
|
||||||
|
struct skb_shared_hwtstamps *shwt = skb_hwtstamps(skb);
|
||||||
|
u64 ts;
|
||||||
|
|
||||||
|
*shwt = (struct skb_shared_hwtstamps) {0};
|
||||||
|
|
||||||
|
ts = SJA1105_SKB_CB(skb)->meta_tstamp;
|
||||||
|
ts = sja1105_tstamp_reconstruct(priv, now, ts);
|
||||||
|
ts = timecounter_cyc2time(&priv->tstamp_tc, ts);
|
||||||
|
|
||||||
|
shwt->hwtstamp = ns_to_ktime(ts);
|
||||||
|
netif_rx_ni(skb);
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex_unlock(&priv->ptp_lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Called from dsa_skb_defer_rx_timestamp */
|
||||||
|
bool sja1105_port_rxtstamp(struct dsa_switch *ds, int port,
|
||||||
|
struct sk_buff *skb, unsigned int type)
|
||||||
|
{
|
||||||
|
struct sja1105_private *priv = ds->priv;
|
||||||
|
struct sja1105_tagger_data *data = &priv->tagger_data;
|
||||||
|
|
||||||
|
if (!data->hwts_rx_en)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* We need to read the full PTP clock to reconstruct the Rx
|
||||||
|
* timestamp. For that we need a sleepable context.
|
||||||
|
*/
|
||||||
|
skb_queue_tail(&data->skb_rxtstamp_queue, skb);
|
||||||
|
schedule_work(&data->rxtstamp_work);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/* Called from dsa_skb_tx_timestamp. This callback is just to make DSA clone
|
/* Called from dsa_skb_tx_timestamp. This callback is just to make DSA clone
|
||||||
* the skb and have it available in DSA_SKB_CB in the .port_deferred_xmit
|
* the skb and have it available in DSA_SKB_CB in the .port_deferred_xmit
|
||||||
* callback, where we will timestamp it synchronously.
|
* callback, where we will timestamp it synchronously.
|
||||||
|
@ -1766,6 +1825,7 @@ bool sja1105_port_txtstamp(struct dsa_switch *ds, int port,
|
||||||
static const struct dsa_switch_ops sja1105_switch_ops = {
|
static const struct dsa_switch_ops sja1105_switch_ops = {
|
||||||
.get_tag_protocol = sja1105_get_tag_protocol,
|
.get_tag_protocol = sja1105_get_tag_protocol,
|
||||||
.setup = sja1105_setup,
|
.setup = sja1105_setup,
|
||||||
|
.teardown = sja1105_teardown,
|
||||||
.set_ageing_time = sja1105_set_ageing_time,
|
.set_ageing_time = sja1105_set_ageing_time,
|
||||||
.phylink_validate = sja1105_phylink_validate,
|
.phylink_validate = sja1105_phylink_validate,
|
||||||
.phylink_mac_config = sja1105_mac_config,
|
.phylink_mac_config = sja1105_mac_config,
|
||||||
|
@ -1787,6 +1847,7 @@ static const struct dsa_switch_ops sja1105_switch_ops = {
|
||||||
.port_mdb_add = sja1105_mdb_add,
|
.port_mdb_add = sja1105_mdb_add,
|
||||||
.port_mdb_del = sja1105_mdb_del,
|
.port_mdb_del = sja1105_mdb_del,
|
||||||
.port_deferred_xmit = sja1105_port_deferred_xmit,
|
.port_deferred_xmit = sja1105_port_deferred_xmit,
|
||||||
|
.port_rxtstamp = sja1105_port_rxtstamp,
|
||||||
.port_txtstamp = sja1105_port_txtstamp,
|
.port_txtstamp = sja1105_port_txtstamp,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1885,6 +1946,7 @@ static int sja1105_probe(struct spi_device *spi)
|
||||||
|
|
||||||
tagger_data = &priv->tagger_data;
|
tagger_data = &priv->tagger_data;
|
||||||
skb_queue_head_init(&tagger_data->skb_rxtstamp_queue);
|
skb_queue_head_init(&tagger_data->skb_rxtstamp_queue);
|
||||||
|
INIT_WORK(&tagger_data->rxtstamp_work, sja1105_rxtstamp_work);
|
||||||
|
|
||||||
/* Connections between dsa_port and sja1105_port */
|
/* Connections between dsa_port and sja1105_port */
|
||||||
for (i = 0; i < SJA1105_NUM_PORTS; i++) {
|
for (i = 0; i < SJA1105_NUM_PORTS; i++) {
|
||||||
|
|
|
@ -45,6 +45,13 @@ struct sja1105_tagger_data {
|
||||||
bool hwts_rx_en;
|
bool hwts_rx_en;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct sja1105_skb_cb {
|
||||||
|
u32 meta_tstamp;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define SJA1105_SKB_CB(skb) \
|
||||||
|
((struct sja1105_skb_cb *)DSA_SKB_CB_PRIV(skb))
|
||||||
|
|
||||||
struct sja1105_port {
|
struct sja1105_port {
|
||||||
struct sja1105_tagger_data *data;
|
struct sja1105_tagger_data *data;
|
||||||
struct dsa_port *dp;
|
struct dsa_port *dp;
|
||||||
|
|
|
@ -110,6 +110,124 @@ static struct sk_buff *sja1105_xmit(struct sk_buff *skb,
|
||||||
((pcp << VLAN_PRIO_SHIFT) | tx_vid));
|
((pcp << VLAN_PRIO_SHIFT) | tx_vid));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void sja1105_transfer_meta(struct sk_buff *skb,
|
||||||
|
const struct sja1105_meta *meta)
|
||||||
|
{
|
||||||
|
struct ethhdr *hdr = eth_hdr(skb);
|
||||||
|
|
||||||
|
hdr->h_dest[3] = meta->dmac_byte_3;
|
||||||
|
hdr->h_dest[4] = meta->dmac_byte_4;
|
||||||
|
SJA1105_SKB_CB(skb)->meta_tstamp = meta->tstamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This is a simple state machine which follows the hardware mechanism of
|
||||||
|
* generating RX timestamps:
|
||||||
|
*
|
||||||
|
* After each timestampable skb (all traffic for which send_meta1 and
|
||||||
|
* send_meta0 is true, aka all MAC-filtered link-local traffic) a meta frame
|
||||||
|
* containing a partial timestamp is immediately generated by the switch and
|
||||||
|
* sent as a follow-up to the link-local frame on the CPU port.
|
||||||
|
*
|
||||||
|
* The meta frames have no unique identifier (such as sequence number) by which
|
||||||
|
* one may pair them to the correct timestampable frame.
|
||||||
|
* Instead, the switch has internal logic that ensures no frames are sent on
|
||||||
|
* the CPU port between a link-local timestampable frame and its corresponding
|
||||||
|
* meta follow-up. It also ensures strict ordering between ports (lower ports
|
||||||
|
* have higher priority towards the CPU port). For this reason, a per-port
|
||||||
|
* data structure is not needed/desirable.
|
||||||
|
*
|
||||||
|
* This function pairs the link-local frame with its partial timestamp from the
|
||||||
|
* meta follow-up frame. The full timestamp will be reconstructed later in a
|
||||||
|
* work queue.
|
||||||
|
*/
|
||||||
|
static struct sk_buff
|
||||||
|
*sja1105_rcv_meta_state_machine(struct sk_buff *skb,
|
||||||
|
struct sja1105_meta *meta,
|
||||||
|
bool is_link_local,
|
||||||
|
bool is_meta)
|
||||||
|
{
|
||||||
|
struct sja1105_port *sp;
|
||||||
|
struct dsa_port *dp;
|
||||||
|
|
||||||
|
dp = dsa_slave_to_port(skb->dev);
|
||||||
|
sp = dp->priv;
|
||||||
|
|
||||||
|
/* Step 1: A timestampable frame was received.
|
||||||
|
* Buffer it until we get its meta frame.
|
||||||
|
*/
|
||||||
|
if (is_link_local && sp->data->hwts_rx_en) {
|
||||||
|
spin_lock(&sp->data->meta_lock);
|
||||||
|
/* Was this a link-local frame instead of the meta
|
||||||
|
* that we were expecting?
|
||||||
|
*/
|
||||||
|
if (sp->data->stampable_skb) {
|
||||||
|
dev_err_ratelimited(dp->ds->dev,
|
||||||
|
"Expected meta frame, is %12llx "
|
||||||
|
"in the DSA master multicast filter?\n",
|
||||||
|
SJA1105_META_DMAC);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hold a reference to avoid dsa_switch_rcv
|
||||||
|
* from freeing the skb.
|
||||||
|
*/
|
||||||
|
sp->data->stampable_skb = skb_get(skb);
|
||||||
|
spin_unlock(&sp->data->meta_lock);
|
||||||
|
|
||||||
|
/* Tell DSA we got nothing */
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
/* Step 2: The meta frame arrived.
|
||||||
|
* Time to take the stampable skb out of the closet, annotate it
|
||||||
|
* with the partial timestamp, and pretend that we received it
|
||||||
|
* just now (basically masquerade the buffered frame as the meta
|
||||||
|
* frame, which serves no further purpose).
|
||||||
|
*/
|
||||||
|
} else if (is_meta) {
|
||||||
|
struct sk_buff *stampable_skb;
|
||||||
|
|
||||||
|
spin_lock(&sp->data->meta_lock);
|
||||||
|
|
||||||
|
stampable_skb = sp->data->stampable_skb;
|
||||||
|
sp->data->stampable_skb = NULL;
|
||||||
|
|
||||||
|
/* Was this a meta frame instead of the link-local
|
||||||
|
* that we were expecting?
|
||||||
|
*/
|
||||||
|
if (!stampable_skb) {
|
||||||
|
dev_err_ratelimited(dp->ds->dev,
|
||||||
|
"Unexpected meta frame\n");
|
||||||
|
spin_unlock(&sp->data->meta_lock);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stampable_skb->dev != skb->dev) {
|
||||||
|
dev_err_ratelimited(dp->ds->dev,
|
||||||
|
"Meta frame on wrong port\n");
|
||||||
|
spin_unlock(&sp->data->meta_lock);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Free the meta frame and give DSA the buffered stampable_skb
|
||||||
|
* for further processing up the network stack.
|
||||||
|
*/
|
||||||
|
kfree_skb(skb);
|
||||||
|
|
||||||
|
skb = skb_copy(stampable_skb, GFP_ATOMIC);
|
||||||
|
if (!skb) {
|
||||||
|
dev_err_ratelimited(dp->ds->dev,
|
||||||
|
"Failed to copy stampable skb\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
sja1105_transfer_meta(skb, meta);
|
||||||
|
/* The cached copy will be freed now */
|
||||||
|
skb_unref(stampable_skb);
|
||||||
|
|
||||||
|
spin_unlock(&sp->data->meta_lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
return skb;
|
||||||
|
}
|
||||||
|
|
||||||
static struct sk_buff *sja1105_rcv(struct sk_buff *skb,
|
static struct sk_buff *sja1105_rcv(struct sk_buff *skb,
|
||||||
struct net_device *netdev,
|
struct net_device *netdev,
|
||||||
struct packet_type *pt)
|
struct packet_type *pt)
|
||||||
|
@ -167,7 +285,8 @@ static struct sk_buff *sja1105_rcv(struct sk_buff *skb,
|
||||||
if (is_tagged)
|
if (is_tagged)
|
||||||
skb = dsa_8021q_remove_header(skb);
|
skb = dsa_8021q_remove_header(skb);
|
||||||
|
|
||||||
return skb;
|
return sja1105_rcv_meta_state_machine(skb, &meta, is_link_local,
|
||||||
|
is_meta);
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct dsa_device_ops sja1105_netdev_ops = {
|
static struct dsa_device_ops sja1105_netdev_ops = {
|
||||||
|
|
Loading…
Add table
Reference in a new issue