Implement the RMU register operations Read and Write and setup a
proper bus to use as an alternative for SMI.

Signed-off-by: Vivien Didelot <vivien.dide...@gmail.com>
---
 drivers/net/dsa/mv88e6xxx/chip.c |  14 ++
 drivers/net/dsa/mv88e6xxx/chip.h |  10 ++
 drivers/net/dsa/mv88e6xxx/rmu.c  | 256 +++++++++++++++++++++++++++++++
 drivers/net/dsa/mv88e6xxx/rmu.h  |   1 +
 4 files changed, 281 insertions(+)

diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index 048fdaf1335e..68c247e951aa 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -4573,6 +4573,19 @@ static int mv88e6xxx_port_egress_floods(struct 
dsa_switch *ds, int port,
        return err;
 }
 
+static bool mv88e6xxx_rcv(struct dsa_switch *ds, struct sk_buff *skb)
+{
+       struct mv88e6xxx_chip *chip = ds->priv;
+
+       /* When this operation is called after a request,
+        * the mutex must already be held.
+        */
+       if (mutex_is_locked(&chip->reg_lock))
+               return !!mv88e6xxx_rmu_response(chip, skb);
+
+       return false;
+}
+
 static const struct dsa_switch_ops mv88e6xxx_switch_ops = {
        .get_tag_protocol       = mv88e6xxx_get_tag_protocol,
        .setup                  = mv88e6xxx_setup,
@@ -4617,6 +4630,7 @@ static const struct dsa_switch_ops mv88e6xxx_switch_ops = 
{
        .port_txtstamp          = mv88e6xxx_port_txtstamp,
        .port_rxtstamp          = mv88e6xxx_port_rxtstamp,
        .get_ts_info            = mv88e6xxx_get_ts_info,
+       .rcv                    = mv88e6xxx_rcv,
 };
 
 static int mv88e6xxx_register_switch(struct mv88e6xxx_chip *chip)
diff --git a/drivers/net/dsa/mv88e6xxx/chip.h b/drivers/net/dsa/mv88e6xxx/chip.h
index 2af574169e14..bf328cea67c8 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.h
+++ b/drivers/net/dsa/mv88e6xxx/chip.h
@@ -12,6 +12,7 @@
 #ifndef _MV88E6XXX_CHIP_H
 #define _MV88E6XXX_CHIP_H
 
+#include <linux/completion.h>
 #include <linux/if_vlan.h>
 #include <linux/irq.h>
 #include <linux/gpio/consumer.h>
@@ -214,6 +215,15 @@ struct mv88e6xxx_chip {
        struct mii_bus *bus;
        int sw_addr;
 
+       /* Register access through the Remote Management Unit */
+       const struct mv88e6xxx_bus_ops *rmu_ops;
+       struct completion rmu_response_received;
+       const unsigned char *rmu_response_data;
+       size_t rmu_response_data_len;
+       struct sk_buff *rmu_response;
+       struct net_device *rmu_dev;
+       u8 rmu_sequence_num;
+
        /* Handles automatic disabling and re-enabling of the PHY
         * polling unit.
         */
diff --git a/drivers/net/dsa/mv88e6xxx/rmu.c b/drivers/net/dsa/mv88e6xxx/rmu.c
index 71dabe6ecb46..b7dbd843753a 100644
--- a/drivers/net/dsa/mv88e6xxx/rmu.c
+++ b/drivers/net/dsa/mv88e6xxx/rmu.c
@@ -9,9 +9,255 @@
  * (at your option) any later version.
  */
 
+#include <linux/if_ether.h>
+
 #include "chip.h"
 #include "rmu.h"
 
+#define MV88E6XXX_RMU_TIMEOUT (msecs_to_jiffies(1000))
+
+static int mv88e6xxx_rmu_wait_response(struct mv88e6xxx_chip *chip)
+{
+       long timeout;
+
+       timeout = 
wait_for_completion_interruptible_timeout(&chip->rmu_response_received, 
MV88E6XXX_RMU_TIMEOUT);
+       if (timeout < 0)
+               return timeout;
+       if (timeout == 0)
+               return -ETIMEDOUT;
+
+       dev_dbg(chip->dev, "got RMU response for request %d in %d msecs\n",
+               chip->rmu_sequence_num, jiffies_to_msecs(MV88E6XXX_RMU_TIMEOUT 
- timeout));
+
+       return 0;
+}
+
+#define DSA_LEN                4
+
+struct edsahdr {
+       unsigned char   eth_dest_addr[ETH_ALEN];
+       unsigned char   eth_src_addr[ETH_ALEN];
+       __be16          edsa_ethertype;
+       __be16          edsa_reserved; /* 0x0000 */
+       unsigned char   dsa_tag[DSA_LEN];
+       __be16          eth_ethertype;
+} __attribute__((packed));
+
+struct dsahdr {
+       unsigned char   eth_dest_addr[ETH_ALEN];
+       unsigned char   eth_src_addr[ETH_ALEN];
+       unsigned char   dsa_tag[DSA_LEN];
+       __be16          eth_ethertype;
+} __attribute__((packed));
+
+struct mv88e6xxx_rmu_request {
+       __be16  format;
+       __be16  pad; /* 0x0000 on request, Prod Num/Rev on response */
+       __be16  code;
+} __attribute__((packed));
+
+static int mv88e6xxx_rmu_request(struct mv88e6xxx_chip *chip, u16 code, u8 
*data, size_t len)
+{
+       const unsigned char dest_addr[ETH_ALEN] = { 0x01, 0x50, 0x43, 0x00, 
0x00, 0x00 };
+       struct net_device *dev = chip->rmu_dev;
+       struct mv88e6xxx_rmu_request *req;
+       unsigned char *eth_dest_addr;
+       unsigned char *eth_src_addr;
+       unsigned char *dsa_tag;
+       __be16 *eth_ethertype;
+       struct edsahdr *edsa;
+       struct sk_buff *skb;
+       struct dsahdr *dsa;
+
+       if (!dev)
+               return -EOPNOTSUPP;
+
+       switch (dev->dsa_ptr->tag_ops->proto) {
+       case DSA_TAG_PROTO_DSA:
+               skb = alloc_skb(sizeof(*dsa) + sizeof(*req) + len, GFP_KERNEL);
+               if (!skb)
+                       return -ENOMEM;
+
+               dsa = skb_put(skb, sizeof(*dsa));
+               eth_dest_addr = dsa->eth_dest_addr;
+               eth_src_addr = dsa->eth_src_addr;
+               dsa_tag = dsa->dsa_tag;
+               eth_ethertype = &dsa->eth_ethertype;
+               break;
+       case DSA_TAG_PROTO_EDSA:
+               skb = alloc_skb(sizeof(*edsa) + sizeof(*req) + len, GFP_KERNEL);
+               if (!skb)
+                       return -ENOMEM;
+
+               edsa = skb_put(skb, sizeof(*edsa));
+               eth_dest_addr = edsa->eth_dest_addr;
+               eth_src_addr = edsa->eth_src_addr;
+               edsa->edsa_ethertype = htons(ETH_P_EDSA);
+               edsa->edsa_reserved = 0x0000;
+               dsa_tag = edsa->dsa_tag;
+               eth_ethertype = &edsa->eth_ethertype;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       ether_addr_copy(eth_dest_addr, dest_addr); /* Marvell broadcast or 
switch MAC */
+       ether_addr_copy(eth_src_addr, dev->dev_addr);
+       dsa_tag[0] = 0x40 | (chip->ds->index & 0x1f); /* From_CPU */
+       dsa_tag[1] = 0xfa;
+       dsa_tag[2] = 0xf;
+       dsa_tag[3] = ++chip->rmu_sequence_num;
+       *eth_ethertype = htons(ETH_P_EDSA); /* User defined, useless really */
+
+       req = skb_put(skb, sizeof(*req));
+       req->format = htons(MV88E6XXX_RMU_REQUEST_FORMAT_SOHO);
+       req->pad = 0x0000;
+       req->code = htons(code);
+
+       skb_put_data(skb, data, len);
+
+       skb->dev = dev;
+
+       dsa_switch_xmit(chip->ds, skb);
+
+       return mv88e6xxx_rmu_wait_response(chip);
+}
+
+int mv88e6xxx_rmu_response(struct mv88e6xxx_chip *chip, struct sk_buff *skb)
+{
+       struct net_device *dev = chip->rmu_dev;
+       struct mv88e6xxx_rmu_request *req;
+       unsigned char *dsa_tag;
+       size_t data_offset; 
+       size_t req_offset;
+
+       /* Check if RMU is enabled */
+       if (dev != skb->dev)
+               return -EOPNOTSUPP;
+
+       if (chip->rmu_response)
+               return -EBUSY;
+
+       switch (dev->dsa_ptr->tag_ops->proto) {
+       case DSA_TAG_PROTO_DSA:
+               dsa_tag = skb->data - 2;
+               req_offset = DSA_LEN + ETH_TLEN - 2;
+               break;
+       case DSA_TAG_PROTO_EDSA:
+               /* skb->data points to the end of the (EDSA) ethertype */
+               dsa_tag = skb->data + 2;
+               req_offset = 2 + DSA_LEN + ETH_TLEN;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       if ((dsa_tag[0] != chip->ds->index) ||
+            (dsa_tag[1] != 0x00) ||
+            ((dsa_tag[2] & 0x1f) != 0x1f) ||
+            (dsa_tag[3] != chip->rmu_sequence_num))
+               return -EINVAL;
+
+       data_offset = req_offset + sizeof(*req);
+       if (skb->len < data_offset)
+               return -EINVAL;
+
+       req = (struct mv88e6xxx_rmu_request *)(skb->data + req_offset);
+       if (ntohs(req->code) == 0xffff)
+               return -EINVAL;
+
+       chip->rmu_response_data_len = skb->len - data_offset;
+       if (chip->rmu_response_data_len > 0)
+               chip->rmu_response_data = skb->data + data_offset;
+       else
+               chip->rmu_response_data = NULL;
+
+       chip->rmu_response = skb_clone(skb, GFP_KERNEL);
+       if (!chip->rmu_response)
+               return -ENOMEM;
+
+       complete(&chip->rmu_response_received);
+
+       return 0;
+}
+
+static int mv88e6xxx_rmu_reg_read(struct mv88e6xxx_chip *chip,
+                                 int dev, int reg, u16 *data)
+{
+       unsigned char request_data[8];
+       int err;
+
+       request_data[0] = 0x08 | ((dev >> 3) & 0x03);
+       request_data[1] = ((dev << 5) & 0xe0) | (reg & 0x1f);
+       request_data[2] = 0x00;
+       request_data[3] = 0x00;
+
+       /* End Of List Command */
+       memset(&request_data[4], 0xff, 4);
+
+       err = mv88e6xxx_rmu_request(chip, 
MV88E6XXX_RMU_REQUEST_CODE_READ_WRITE, request_data, sizeof(request_data));
+       if (err)
+               return err;
+
+       if (chip->rmu_response_data_len < sizeof(request_data))
+               err = -EINVAL;
+       else
+               *data = (chip->rmu_response_data[2] << 8) | 
chip->rmu_response_data[3];
+
+       kfree_skb(chip->rmu_response);
+       chip->rmu_response = NULL;
+
+       return err;
+}
+
+static int mv88e6xxx_rmu_reg_write(struct mv88e6xxx_chip *chip,
+                                  int dev, int reg, u16 data)
+{
+       unsigned char request_data[8];
+       int err;
+
+       request_data[0] = 0x04 | ((dev >> 3) & 0x03);
+       request_data[1] = ((dev << 5) & 0xe0) | (reg & 0x1f);
+       request_data[2] = data >> 8;
+       request_data[3] = data & 0xff;
+
+       /* End Of List Command */
+       memset(&request_data[4], 0xff, 4);
+
+       err = mv88e6xxx_rmu_request(chip, 
MV88E6XXX_RMU_REQUEST_CODE_READ_WRITE, request_data, sizeof(request_data));
+       if (err)
+               return err;
+
+       if (chip->rmu_response_data_len < sizeof(request_data))
+               err = -EINVAL;
+
+       kfree_skb(chip->rmu_response);
+       chip->rmu_response = NULL;
+
+       return err;
+}
+
+static const struct mv88e6xxx_bus_ops mv88e6xxx_rmu_ops = {
+       .read = mv88e6xxx_rmu_reg_read,
+       .write = mv88e6xxx_rmu_reg_write,
+};
+
+static int mv88e6xxx_rmu_setup_bus(struct mv88e6xxx_chip *chip,
+                                     struct net_device *dev)
+{
+       chip->rmu_ops = &mv88e6xxx_rmu_ops;
+       chip->rmu_dev = dev;
+
+       init_completion(&chip->rmu_response_received);
+
+       dev_info(chip->dev, "RMU reachable via %s\n", netdev_name(dev));
+
+       if (!chip->ops)
+               chip->ops = chip->rmu_ops;
+
+       return 0;
+}
+
 static int mv88e6xxx_rmu_setup_port(struct mv88e6xxx_chip *chip, int port)
 {
        int err;
@@ -40,6 +286,7 @@ static int mv88e6xxx_rmu_setup_port(struct mv88e6xxx_chip 
*chip, int port)
 int mv88e6xxx_rmu_setup(struct mv88e6xxx_chip *chip)
 {
        struct dsa_switch *ds = chip->ds;
+       struct net_device *dev;
        int port;
        int err;
 
@@ -50,6 +297,15 @@ int mv88e6xxx_rmu_setup(struct mv88e6xxx_chip *chip)
                        if (err)
                                continue;
 
+                       /* When the control CPU is local, use the master 
interface */
+                       dev = dsa_to_master(ds, port);
+                       if (!dev)
+                               return -ENODEV;
+
+                       err = mv88e6xxx_rmu_setup_bus(chip, dev);
+                       if (err)
+                               return err;
+
                        return 0;
                }
        }
diff --git a/drivers/net/dsa/mv88e6xxx/rmu.h b/drivers/net/dsa/mv88e6xxx/rmu.h
index f7d849b169d2..099df0789bb5 100644
--- a/drivers/net/dsa/mv88e6xxx/rmu.h
+++ b/drivers/net/dsa/mv88e6xxx/rmu.h
@@ -29,5 +29,6 @@
 #include "chip.h"
 
 int mv88e6xxx_rmu_setup(struct mv88e6xxx_chip *chip);
+int mv88e6xxx_rmu_response(struct mv88e6xxx_chip *chip, struct sk_buff *skb);
 
 #endif /* _MV88E6XXX_RMU_H */
-- 
2.21.0

Reply via email to