Commit a2550d3c authored by Christian Marangi's avatar Christian Marangi Committed by David S. Miller
Browse files

net: dsa: qca8k: fix inband mgmt for big-endian systems



The header and the data of the skb for the inband mgmt requires
to be in little-endian. This is problematic for big-endian system
as the mgmt header is written in the cpu byte order.

Fix this by converting each value for the mgmt header and data to
little-endian, and convert to cpu byte order the mgmt header and
data sent by the switch.

Fixes: 5950c7c0 ("net: dsa: qca8k: add support for mgmt read/write in Ethernet packet")
Tested-by: default avatarPawel Dembicki <paweldembicki@gmail.com>
Tested-by: default avatarLech Perczak <lech.perczak@gmail.com>
Signed-off-by: default avatarChristian Marangi <ansuelsmth@gmail.com>
Reviewed-by: default avatarLech Perczak <lech.perczak@gmail.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 777ecaab
Loading
Loading
Loading
Loading
+47 −16
Original line number Diff line number Diff line
@@ -137,27 +137,42 @@ static void qca8k_rw_reg_ack_handler(struct dsa_switch *ds, struct sk_buff *skb)
	struct qca8k_mgmt_eth_data *mgmt_eth_data;
	struct qca8k_priv *priv = ds->priv;
	struct qca_mgmt_ethhdr *mgmt_ethhdr;
	u32 command;
	u8 len, cmd;
	int i;

	mgmt_ethhdr = (struct qca_mgmt_ethhdr *)skb_mac_header(skb);
	mgmt_eth_data = &priv->mgmt_eth_data;

	cmd = FIELD_GET(QCA_HDR_MGMT_CMD, mgmt_ethhdr->command);
	len = FIELD_GET(QCA_HDR_MGMT_LENGTH, mgmt_ethhdr->command);
	command = get_unaligned_le32(&mgmt_ethhdr->command);
	cmd = FIELD_GET(QCA_HDR_MGMT_CMD, command);
	len = FIELD_GET(QCA_HDR_MGMT_LENGTH, command);

	/* Make sure the seq match the requested packet */
	if (mgmt_ethhdr->seq == mgmt_eth_data->seq)
	if (get_unaligned_le32(&mgmt_ethhdr->seq) == mgmt_eth_data->seq)
		mgmt_eth_data->ack = true;

	if (cmd == MDIO_READ) {
		mgmt_eth_data->data[0] = mgmt_ethhdr->mdio_data;
		u32 *val = mgmt_eth_data->data;

		*val = get_unaligned_le32(&mgmt_ethhdr->mdio_data);

		/* Get the rest of the 12 byte of data.
		 * The read/write function will extract the requested data.
		 */
		if (len > QCA_HDR_MGMT_DATA1_LEN)
			memcpy(mgmt_eth_data->data + 1, skb->data,
			       QCA_HDR_MGMT_DATA2_LEN);
		if (len > QCA_HDR_MGMT_DATA1_LEN) {
			__le32 *data2 = (__le32 *)skb->data;
			int data_len = min_t(int, QCA_HDR_MGMT_DATA2_LEN,
					     len - QCA_HDR_MGMT_DATA1_LEN);

			val++;

			for (i = sizeof(u32); i <= data_len; i += sizeof(u32)) {
				*val = get_unaligned_le32(data2);
				val++;
				data2++;
			}
		}
	}

	complete(&mgmt_eth_data->rw_done);
@@ -169,8 +184,10 @@ static struct sk_buff *qca8k_alloc_mdio_header(enum mdio_cmd cmd, u32 reg, u32 *
	struct qca_mgmt_ethhdr *mgmt_ethhdr;
	unsigned int real_len;
	struct sk_buff *skb;
	u32 *data2;
	__le32 *data2;
	u32 command;
	u16 hdr;
	int i;

	skb = dev_alloc_skb(QCA_HDR_MGMT_PKT_LEN);
	if (!skb)
@@ -199,20 +216,32 @@ static struct sk_buff *qca8k_alloc_mdio_header(enum mdio_cmd cmd, u32 reg, u32 *
	hdr |= FIELD_PREP(QCA_HDR_XMIT_DP_BIT, BIT(0));
	hdr |= FIELD_PREP(QCA_HDR_XMIT_CONTROL, QCA_HDR_XMIT_TYPE_RW_REG);

	mgmt_ethhdr->command = FIELD_PREP(QCA_HDR_MGMT_ADDR, reg);
	mgmt_ethhdr->command |= FIELD_PREP(QCA_HDR_MGMT_LENGTH, real_len);
	mgmt_ethhdr->command |= FIELD_PREP(QCA_HDR_MGMT_CMD, cmd);
	mgmt_ethhdr->command |= FIELD_PREP(QCA_HDR_MGMT_CHECK_CODE,
	command = FIELD_PREP(QCA_HDR_MGMT_ADDR, reg);
	command |= FIELD_PREP(QCA_HDR_MGMT_LENGTH, real_len);
	command |= FIELD_PREP(QCA_HDR_MGMT_CMD, cmd);
	command |= FIELD_PREP(QCA_HDR_MGMT_CHECK_CODE,
					   QCA_HDR_MGMT_CHECK_CODE_VAL);

	put_unaligned_le32(command, &mgmt_ethhdr->command);

	if (cmd == MDIO_WRITE)
		mgmt_ethhdr->mdio_data = *val;
		put_unaligned_le32(*val, &mgmt_ethhdr->mdio_data);

	mgmt_ethhdr->hdr = htons(hdr);

	data2 = skb_put_zero(skb, QCA_HDR_MGMT_DATA2_LEN + QCA_HDR_MGMT_PADDING_LEN);
	if (cmd == MDIO_WRITE && len > QCA_HDR_MGMT_DATA1_LEN)
		memcpy(data2, val + 1, len - QCA_HDR_MGMT_DATA1_LEN);
	if (cmd == MDIO_WRITE && len > QCA_HDR_MGMT_DATA1_LEN) {
		int data_len = min_t(int, QCA_HDR_MGMT_DATA2_LEN,
				     len - QCA_HDR_MGMT_DATA1_LEN);

		val++;

		for (i = sizeof(u32); i <= data_len; i += sizeof(u32)) {
			put_unaligned_le32(*val, data2);
			data2++;
			val++;
		}
	}

	return skb;
}
@@ -220,9 +249,11 @@ static struct sk_buff *qca8k_alloc_mdio_header(enum mdio_cmd cmd, u32 reg, u32 *
static void qca8k_mdio_header_fill_seq_num(struct sk_buff *skb, u32 seq_num)
{
	struct qca_mgmt_ethhdr *mgmt_ethhdr;
	u32 seq;

	seq = FIELD_PREP(QCA_HDR_MGMT_SEQ_NUM, seq_num);
	mgmt_ethhdr = (struct qca_mgmt_ethhdr *)skb->data;
	mgmt_ethhdr->seq = FIELD_PREP(QCA_HDR_MGMT_SEQ_NUM, seq_num);
	put_unaligned_le32(seq, &mgmt_ethhdr->seq);
}

static int qca8k_read_eth(struct qca8k_priv *priv, u32 reg, u32 *val, int len)
+3 −3
Original line number Diff line number Diff line
@@ -61,9 +61,9 @@ struct sk_buff;

/* Special struct emulating a Ethernet header */
struct qca_mgmt_ethhdr {
	u32 command;		/* command bit 31:0 */
	u32 seq;		/* seq 63:32 */
	u32 mdio_data;		/* first 4byte mdio */
	__le32 command;		/* command bit 31:0 */
	__le32 seq;		/* seq 63:32 */
	__le32 mdio_data;		/* first 4byte mdio */
	__be16 hdr;		/* qca hdr */
} __packed;