Re: [RFC next-next v2 1/5] net: marvell: prestera: Add driver for Prestera family ASIC devices
From: Jiri Pirko <jiri@resnulli.us>
Date: 2020-05-11 12:57:31
[...]
quoted hunk ↗ jump to hunk
diff --git a/drivers/net/ethernet/marvell/prestera/prestera_dsa.c b/drivers/net/ethernet/marvell/prestera/prestera_dsa.c new file mode 100644 index 000000000000..c4f7d9f6edcb --- /dev/null +++ b/drivers/net/ethernet/marvell/prestera/prestera_dsa.c@@ -0,0 +1,134 @@ +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 +/* Copyright (c) 2020 Marvell International Ltd. All rights reserved */ + +#include "prestera_dsa.h" + +#include <linux/string.h> +#include <linux/bitops.h> +#include <linux/bitfield.h> +#include <linux/errno.h> + +#define W0_MASK_IS_TAGGED BIT(29) + +/* TrgDev[4:0] = {Word0[28:24]} */ +#define W0_MASK_HW_DEV_NUM GENMASK(28, 24) + +/* SrcPort/TrgPort extended to 8b + * SrcPort/TrgPort[7:0] = {Word2[20], Word1[11:10], Word0[23:19]} + */ +#define W0_MASK_IFACE_PORT_NUM GENMASK(23, 19) + +/* bits 30:31 - TagCommand 1 = FROM_CPU */ +#define W0_MASK_DSA_CMD GENMASK(31, 30) + +/* bits 13:15 -- UP */ +#define W0_MASK_VPT GENMASK(15, 13) + +#define W0_MASK_EXT_BIT BIT(12) + +/* bits 0:11 -- VID */ +#define W0_MASK_VID GENMASK(11, 0) + +/* SrcPort/TrgPort extended to 8b + * SrcPort/TrgPort[7:0] = {Word2[20], Word1[11:10], Word0[23:19]} + */ +#define W1_MASK_IFACE_PORT_NUM GENMASK(11, 10) + +#define W1_MASK_EXT_BIT BIT(31) +#define W1_MASK_CFI_BIT BIT(30) + +/* SrcPort/TrgPort extended to 8b + * SrcPort/TrgPort[7:0] = {Word2[20], Word1[11:10], Word0[23:19]} + */ +#define W2_MASK_IFACE_PORT_NUM BIT(20) + +#define W2_MASK_EXT_BIT BIT(31) + +/* trgHwDev and trgPort + * TrgDev[11:5] = {Word3[6:0]} + */ +#define W3_MASK_HW_DEV_NUM GENMASK(6, 0) + +/* VID 16b [15:0] = {Word3[30:27], Word0[11:0]} */ +#define W3_MASK_VID GENMASK(30, 27) + +/* TRGePort[16:0] = {Word3[23:7]} */ +#define W3_MASK_DST_EPORT GENMASK(23, 7) + +#define DEV_NUM_MASK GENMASK(11, 5) +#define VID_MASK GENMASK(15, 12)
Looks like you forgot to add the "prestera" prefix here.
quoted hunk ↗ jump to hunk
+ +int prestera_dsa_parse(struct prestera_dsa *dsa, const u8 *dsa_buf) +{ + u32 *dsa_words = (u32 *)dsa_buf; + enum prestera_dsa_cmd cmd; + u32 words[4] = { 0 }; + u32 field; + + words[0] = ntohl((__force __be32)dsa_words[0]); + words[1] = ntohl((__force __be32)dsa_words[1]); + words[2] = ntohl((__force __be32)dsa_words[2]); + words[3] = ntohl((__force __be32)dsa_words[3]); + + /* set the common parameters */ + cmd = (enum prestera_dsa_cmd)FIELD_GET(W0_MASK_DSA_CMD, words[0]); + + /* only to CPU is supported */ + if (unlikely(cmd != PRESTERA_DSA_CMD_TO_CPU_E)) + return -EINVAL; + + if (FIELD_GET(W0_MASK_EXT_BIT, words[0]) == 0) + return -EINVAL; + if (FIELD_GET(W1_MASK_EXT_BIT, words[1]) == 0) + return -EINVAL; + if (FIELD_GET(W2_MASK_EXT_BIT, words[2]) == 0) + return -EINVAL; + + field = FIELD_GET(W3_MASK_VID, words[3]); + + dsa->vlan.is_tagged = (bool)FIELD_GET(W0_MASK_IS_TAGGED, words[0]); + dsa->vlan.cfi_bit = (u8)FIELD_GET(W1_MASK_CFI_BIT, words[1]); + dsa->vlan.vpt = (u8)FIELD_GET(W0_MASK_VPT, words[0]); + dsa->vlan.vid = (u16)FIELD_GET(W0_MASK_VID, words[0]); + dsa->vlan.vid &= ~VID_MASK; + dsa->vlan.vid |= FIELD_PREP(VID_MASK, field); + + field = FIELD_GET(W3_MASK_HW_DEV_NUM, words[3]); + + dsa->hw_dev_num = FIELD_GET(W0_MASK_HW_DEV_NUM, words[0]); + dsa->hw_dev_num &= W3_MASK_HW_DEV_NUM; + dsa->hw_dev_num |= FIELD_PREP(DEV_NUM_MASK, field); + + dsa->port_num = (FIELD_GET(W0_MASK_IFACE_PORT_NUM, words[0]) << 0) | + (FIELD_GET(W1_MASK_IFACE_PORT_NUM, words[1]) << 5) | + (FIELD_GET(W2_MASK_IFACE_PORT_NUM, words[2]) << 7); + return 0; +} + +int prestera_dsa_build(const struct prestera_dsa *dsa, u8 *dsa_buf) +{ + __be32 *dsa_words = (__be32 *)dsa_buf; + u32 words[4] = { 0 }; + + if (dsa->hw_dev_num >= BIT(12)) + return -EINVAL; + if (dsa->port_num >= BIT(17)) + return -EINVAL; + + words[0] |= FIELD_PREP(W0_MASK_DSA_CMD, PRESTERA_DSA_CMD_FROM_CPU_E); + + words[0] |= FIELD_PREP(W0_MASK_HW_DEV_NUM, dsa->hw_dev_num); + words[3] |= FIELD_PREP(W3_MASK_HW_DEV_NUM, (dsa->hw_dev_num >> 5)); + words[3] |= FIELD_PREP(W3_MASK_DST_EPORT, dsa->port_num); + + words[0] |= FIELD_PREP(W0_MASK_EXT_BIT, 1); + words[1] |= FIELD_PREP(W1_MASK_EXT_BIT, 1); + words[2] |= FIELD_PREP(W2_MASK_EXT_BIT, 1); + + dsa_words[0] = htonl(words[0]); + dsa_words[1] = htonl(words[1]); + dsa_words[2] = htonl(words[2]); + dsa_words[3] = htonl(words[3]); + + return 0; +}diff --git a/drivers/net/ethernet/marvell/prestera/prestera_dsa.h b/drivers/net/ethernet/marvell/prestera/prestera_dsa.h new file mode 100644 index 000000000000..34cb260f1a74 --- /dev/null +++ b/drivers/net/ethernet/marvell/prestera/prestera_dsa.h@@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 + * + * Copyright (c) 2020 Marvell International Ltd. All rights reserved. + * + */ +#ifndef __PRESTERA_DSA_H_ +#define __PRESTERA_DSA_H_ + +#include <linux/types.h> + +#define PRESTERA_DSA_HLEN 16 + +enum prestera_dsa_cmd { + /* DSA command is "To CPU" */ + PRESTERA_DSA_CMD_TO_CPU_E = 0, + + /* DSA command is "FROM CPU" */ + PRESTERA_DSA_CMD_FROM_CPU_E, +}; + +struct prestera_dsa_vlan { + u16 vid; + u8 vpt; + u8 cfi_bit; + bool is_tagged; +}; + +struct prestera_dsa { + struct prestera_dsa_vlan vlan; + u32 hw_dev_num; + u32 port_num; +}; + +int prestera_dsa_parse(struct prestera_dsa *dsa, const u8 *dsa_buf); +int prestera_dsa_build(const struct prestera_dsa *dsa, u8 *dsa_buf); + +#endif /* _PRESTERA_DSA_H_ */diff --git a/drivers/net/ethernet/marvell/prestera/prestera_hw.c b/drivers/net/ethernet/marvell/prestera/prestera_hw.c new file mode 100644 index 000000000000..b4626cf288b6 --- /dev/null +++ b/drivers/net/ethernet/marvell/prestera/prestera_hw.c@@ -0,0 +1,614 @@ +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 +/* Copyright (c) 2019-2020 Marvell International Ltd. All rights reserved */ + +#include <linux/etherdevice.h> +#include <linux/ethtool.h> +#include <linux/netdevice.h> +#include <linux/list.h> + +#include "prestera.h" +#include "prestera_hw.h" + +#define PRESTERA_SWITCH_INIT_TIMEOUT 30000000 /* 30sec */ +#define PRESTERA_MIN_MTU 64 + +enum prestera_cmd_type_t { + PRESTERA_CMD_TYPE_SWITCH_INIT = 0x1, + PRESTERA_CMD_TYPE_SWITCH_ATTR_SET = 0x2, + + PRESTERA_CMD_TYPE_PORT_ATTR_SET = 0x100, + PRESTERA_CMD_TYPE_PORT_ATTR_GET = 0x101, + PRESTERA_CMD_TYPE_PORT_INFO_GET = 0x110, + + PRESTERA_CMD_TYPE_RXTX_INIT = 0x800, + PRESTERA_CMD_TYPE_RXTX_PORT_INIT = 0x801, + + PRESTERA_CMD_TYPE_ACK = 0x10000, + PRESTERA_CMD_TYPE_MAX +}; + +enum { + PRESTERA_CMD_PORT_ATTR_ADMIN_STATE = 1, + PRESTERA_CMD_PORT_ATTR_MTU = 3, + PRESTERA_CMD_PORT_ATTR_MAC = 4, + PRESTERA_CMD_PORT_ATTR_CAPABILITY = 9, + PRESTERA_CMD_PORT_ATTR_AUTONEG = 15, + PRESTERA_CMD_PORT_ATTR_STATS = 17, +}; + +enum { + PRESTERA_CMD_SWITCH_ATTR_MAC = 1, +}; + +enum { + PRESTERA_CMD_ACK_OK, + PRESTERA_CMD_ACK_FAILED, + + PRESTERA_CMD_ACK_MAX +}; + +enum { + PRESTERA_PORT_GOOD_OCTETS_RCV_CNT, + PRESTERA_PORT_BAD_OCTETS_RCV_CNT, + PRESTERA_PORT_MAC_TRANSMIT_ERR_CNT, + PRESTERA_PORT_BRDC_PKTS_RCV_CNT, + PRESTERA_PORT_MC_PKTS_RCV_CNT, + PRESTERA_PORT_PKTS_64L_CNT, + PRESTERA_PORT_PKTS_65TO127L_CNT, + PRESTERA_PORT_PKTS_128TO255L_CNT, + PRESTERA_PORT_PKTS_256TO511L_CNT, + PRESTERA_PORT_PKTS_512TO1023L_CNT, + PRESTERA_PORT_PKTS_1024TOMAXL_CNT, + PRESTERA_PORT_EXCESSIVE_COLLISIONS_CNT, + PRESTERA_PORT_MC_PKTS_SENT_CNT, + PRESTERA_PORT_BRDC_PKTS_SENT_CNT, + PRESTERA_PORT_FC_SENT_CNT, + PRESTERA_PORT_GOOD_FC_RCV_CNT, + PRESTERA_PORT_DROP_EVENTS_CNT, + PRESTERA_PORT_UNDERSIZE_PKTS_CNT, + PRESTERA_PORT_FRAGMENTS_PKTS_CNT, + PRESTERA_PORT_OVERSIZE_PKTS_CNT, + PRESTERA_PORT_JABBER_PKTS_CNT, + PRESTERA_PORT_MAC_RCV_ERROR_CNT, + PRESTERA_PORT_BAD_CRC_CNT, + PRESTERA_PORT_COLLISIONS_CNT, + PRESTERA_PORT_LATE_COLLISIONS_CNT, + PRESTERA_PORT_GOOD_UC_PKTS_RCV_CNT, + PRESTERA_PORT_GOOD_UC_PKTS_SENT_CNT, + PRESTERA_PORT_MULTIPLE_PKTS_SENT_CNT, + PRESTERA_PORT_DEFERRED_PKTS_SENT_CNT, + PRESTERA_PORT_PKTS_1024TO1518L_CNT, + PRESTERA_PORT_PKTS_1519TOMAXL_CNT, + PRESTERA_PORT_GOOD_OCTETS_SENT_CNT, + + PRESTERA_PORT_CNT_MAX, +}; + +struct prestera_fw_event_handler { + struct list_head list; + enum prestera_event_type type; + prestera_event_cb_t func; + void *arg; +}; + +struct prestera_msg_cmd { + u32 type; +} __packed __aligned(4); + +struct prestera_msg_ret { + struct prestera_msg_cmd cmd; + u32 status; +} __packed __aligned(4); + +struct prestera_msg_common_req { + struct prestera_msg_cmd cmd; +} __packed __aligned(4); + +struct prestera_msg_common_resp { + struct prestera_msg_ret ret; +} __packed __aligned(4); + +union prestera_msg_switch_param { + u8 mac[ETH_ALEN]; +}; + +struct prestera_msg_switch_attr_req { + struct prestera_msg_cmd cmd; + u32 attr; + union prestera_msg_switch_param param; +} __packed __aligned(4); + +struct prestera_msg_switch_init_resp { + struct prestera_msg_ret ret; + u32 port_count; + u32 mtu_max; + u8 switch_id; +} __packed __aligned(4); + +struct prestera_msg_port_autoneg_param { + u64 link_mode; + u8 enable; + u8 fec; +}; + +struct prestera_msg_port_cap_param { + u64 link_mode; + u8 type; + u8 fec; + u8 transceiver; +}; + +union prestera_msg_port_param { + u8 admin_state; + u8 oper_state; + u32 mtu; + u8 mac[ETH_ALEN]; + struct prestera_msg_port_autoneg_param autoneg; + struct prestera_msg_port_cap_param cap; +}; + +struct prestera_msg_port_attr_req { + struct prestera_msg_cmd cmd; + u32 attr; + u32 port; + u32 dev; + union prestera_msg_port_param param; +} __packed __aligned(4); + +struct prestera_msg_port_attr_resp { + struct prestera_msg_ret ret; + union prestera_msg_port_param param; +} __packed __aligned(4); + +struct prestera_msg_port_stats_resp { + struct prestera_msg_ret ret; + u64 stats[PRESTERA_PORT_CNT_MAX]; +} __packed __aligned(4); + +struct prestera_msg_port_info_req { + struct prestera_msg_cmd cmd; + u32 port; +} __packed __aligned(4); + +struct prestera_msg_port_info_resp { + struct prestera_msg_ret ret; + u32 hw_id; + u32 dev_id; + u16 fp_id; +} __packed __aligned(4); + +struct prestera_msg_rxtx_req { + struct prestera_msg_cmd cmd; + u8 use_sdma; +} __packed __aligned(4); + +struct prestera_msg_rxtx_resp { + struct prestera_msg_ret ret; + u32 map_addr; +} __packed __aligned(4); + +struct prestera_msg_rxtx_port_req { + struct prestera_msg_cmd cmd; + u32 port; + u32 dev; +} __packed __aligned(4); + +struct prestera_msg_event { + u16 type; + u16 id; +} __packed __aligned(4); + +union prestera_msg_event_port_param { + u32 oper_state; +}; + +struct prestera_msg_event_port { + struct prestera_msg_event id; + u32 port_id; + union prestera_msg_event_port_param param; +} __packed __aligned(4); + +static int __prestera_cmd_ret(struct prestera_switch *sw, + enum prestera_cmd_type_t type, + struct prestera_msg_cmd *cmd, size_t clen, + struct prestera_msg_ret *ret, size_t rlen, + int wait) +{ + struct prestera_device *dev = sw->dev; + int err; + + cmd->type = type; + + err = dev->send_req(dev, (u8 *)cmd, clen, (u8 *)ret, rlen, wait); + if (err) + return err; + + if (ret->cmd.type != PRESTERA_CMD_TYPE_ACK) + return -EBADE; + if (ret->status != PRESTERA_CMD_ACK_OK) + return -EINVAL; + + return 0; +} + +static int prestera_cmd_ret(struct prestera_switch *sw, + enum prestera_cmd_type_t type, + struct prestera_msg_cmd *cmd, size_t clen, + struct prestera_msg_ret *ret, size_t rlen) +{ + return __prestera_cmd_ret(sw, type, cmd, clen, ret, rlen, 0); +} + +static int prestera_cmd_ret_wait(struct prestera_switch *sw, + enum prestera_cmd_type_t type, + struct prestera_msg_cmd *cmd, size_t clen, + struct prestera_msg_ret *ret, size_t rlen, + int wait) +{ + return __prestera_cmd_ret(sw, type, cmd, clen, ret, rlen, wait); +} + +static int prestera_cmd(struct prestera_switch *sw, + enum prestera_cmd_type_t type, + struct prestera_msg_cmd *cmd, size_t clen) +{ + struct prestera_msg_common_resp resp; + + return prestera_cmd_ret(sw, type, cmd, clen, &resp.ret, sizeof(resp)); +} + +static int prestera_fw_parse_port_evt(u8 *msg, struct prestera_event *evt) +{ + struct prestera_msg_event_port *hw_evt; + + hw_evt = (struct prestera_msg_event_port *)msg; + + evt->port_evt.port_id = hw_evt->port_id; + + if (evt->id == PRESTERA_PORT_EVENT_STATE_CHANGED) + evt->port_evt.data.oper_state = hw_evt->param.oper_state; + else + return -EINVAL; + + return 0; +} + +static struct prestera_fw_evt_parser { + int (*func)(u8 *msg, struct prestera_event *evt); +} fw_event_parsers[PRESTERA_EVENT_TYPE_MAX] = { + [PRESTERA_EVENT_TYPE_PORT] = {.func = prestera_fw_parse_port_evt}, +}; + +static struct prestera_fw_event_handler * +__find_event_handler(const struct prestera_switch *sw, + enum prestera_event_type type) +{ + struct prestera_fw_event_handler *eh; + + list_for_each_entry_rcu(eh, &sw->event_handlers, list) { + if (eh->type == type) + return eh; + } + + return NULL; +} + +static int prestera_find_event_handler(const struct prestera_switch *sw, + enum prestera_event_type type, + struct prestera_fw_event_handler *eh) +{ + struct prestera_fw_event_handler *tmp; + int err = 0; + + rcu_read_lock(); + tmp = __find_event_handler(sw, type); + if (tmp) + *eh = *tmp; + else + err = -EEXIST; + rcu_read_unlock(); + + return err; +} + +static int prestera_evt_recv(struct prestera_device *dev, u8 *buf, size_t size) +{ + struct prestera_msg_event *msg = (struct prestera_msg_event *)buf; + struct prestera_switch *sw = dev->priv; + struct prestera_fw_event_handler eh; + struct prestera_event evt; + int err; + + if (msg->type >= PRESTERA_EVENT_TYPE_MAX) + return -EINVAL; + + err = prestera_find_event_handler(sw, msg->type, &eh); + + if (err || !fw_event_parsers[msg->type].func) + return 0; + + evt.id = msg->id; + + err = fw_event_parsers[msg->type].func(buf, &evt); + if (!err) + eh.func(sw, &evt, eh.arg); + + return err; +} + +static void prestera_pkt_recv(struct prestera_device *dev) +{ + struct prestera_switch *sw = dev->priv; + struct prestera_fw_event_handler eh; + struct prestera_event ev; + int err; + + ev.id = PRESTERA_RXTX_EVENT_RCV_PKT; + + err = prestera_find_event_handler(sw, PRESTERA_EVENT_TYPE_RXTX, &eh); + if (err) + return; + + eh.func(sw, &ev, eh.arg); +} + +int prestera_hw_port_info_get(const struct prestera_port *port, + u16 *fp_id, u32 *hw_id, u32 *dev_id) +{ + struct prestera_msg_port_info_resp resp; + struct prestera_msg_port_info_req req = { + .port = port->id + }; + int err; + + err = prestera_cmd_ret(port->sw, PRESTERA_CMD_TYPE_PORT_INFO_GET, + &req.cmd, sizeof(req), &resp.ret, sizeof(resp)); + if (err) + return err; + + *hw_id = resp.hw_id; + *dev_id = resp.dev_id; + *fp_id = resp.fp_id; + + return 0; +} + +int prestera_hw_switch_mac_set(struct prestera_switch *sw, char *mac) +{ + struct prestera_msg_switch_attr_req req = { + .attr = PRESTERA_CMD_SWITCH_ATTR_MAC, + }; + + memcpy(req.param.mac, mac, sizeof(req.param.mac)); + + return prestera_cmd(sw, PRESTERA_CMD_TYPE_SWITCH_ATTR_SET, + &req.cmd, sizeof(req)); +} + +int prestera_hw_switch_init(struct prestera_switch *sw) +{ + struct prestera_msg_switch_init_resp resp; + struct prestera_msg_common_req req; + int err; + + INIT_LIST_HEAD(&sw->event_handlers); + + err = prestera_cmd_ret_wait(sw, PRESTERA_CMD_TYPE_SWITCH_INIT, + &req.cmd, sizeof(req), + &resp.ret, sizeof(resp), + PRESTERA_SWITCH_INIT_TIMEOUT); + if (err) + return err; + + sw->id = resp.switch_id; + sw->port_count = resp.port_count; + sw->mtu_min = PRESTERA_MIN_MTU; + sw->mtu_max = resp.mtu_max; + sw->dev->recv_msg = prestera_evt_recv; + sw->dev->recv_pkt = prestera_pkt_recv; + + return 0; +} + +int prestera_hw_port_state_set(const struct prestera_port *port, + bool admin_state) +{ + struct prestera_msg_port_attr_req req = { + .attr = PRESTERA_CMD_PORT_ATTR_ADMIN_STATE, + .port = port->hw_id, + .dev = port->dev_id, + .param = {.admin_state = admin_state} + }; + + return prestera_cmd(port->sw, PRESTERA_CMD_TYPE_PORT_ATTR_SET, + &req.cmd, sizeof(req)); +} + +int prestera_hw_port_mtu_set(const struct prestera_port *port, u32 mtu) +{ + struct prestera_msg_port_attr_req req = { + .attr = PRESTERA_CMD_PORT_ATTR_MTU, + .port = port->hw_id, + .dev = port->dev_id, + .param = {.mtu = mtu} + }; + + return prestera_cmd(port->sw, PRESTERA_CMD_TYPE_PORT_ATTR_SET, + &req.cmd, sizeof(req)); +} + +int prestera_hw_port_mac_set(const struct prestera_port *port, char *mac) +{ + struct prestera_msg_port_attr_req req = { + .attr = PRESTERA_CMD_PORT_ATTR_MAC, + .port = port->hw_id, + .dev = port->dev_id + }; + memcpy(&req.param.mac, mac, sizeof(req.param.mac)); + + return prestera_cmd(port->sw, PRESTERA_CMD_TYPE_PORT_ATTR_SET, + &req.cmd, sizeof(req)); +} + +int prestera_hw_port_cap_get(const struct prestera_port *port, + struct prestera_port_caps *caps) +{ + struct prestera_msg_port_attr_resp resp; + struct prestera_msg_port_attr_req req = { + .attr = PRESTERA_CMD_PORT_ATTR_CAPABILITY, + .port = port->hw_id, + .dev = port->dev_id + }; + int err; + + err = prestera_cmd_ret(port->sw, PRESTERA_CMD_TYPE_PORT_ATTR_GET, + &req.cmd, sizeof(req), &resp.ret, sizeof(resp)); + if (err) + return err; + + caps->supp_link_modes = resp.param.cap.link_mode; + caps->supp_fec = resp.param.cap.fec; + caps->type = resp.param.cap.type; + caps->transceiver = resp.param.cap.transceiver; + + return err; +} + +int prestera_hw_port_autoneg_set(const struct prestera_port *port, + bool autoneg, u64 link_modes, u8 fec) +{ + struct prestera_msg_port_attr_req req = { + .attr = PRESTERA_CMD_PORT_ATTR_AUTONEG, + .port = port->hw_id, + .dev = port->dev_id, + .param = {.autoneg = {.link_mode = link_modes, + .enable = autoneg, + .fec = fec} + } + }; + + return prestera_cmd(port->sw, PRESTERA_CMD_TYPE_PORT_ATTR_SET, + &req.cmd, sizeof(req)); +} + +int prestera_hw_port_stats_get(const struct prestera_port *port, + struct prestera_port_stats *st) +{ + struct prestera_msg_port_stats_resp resp; + struct prestera_msg_port_attr_req req = { + .attr = PRESTERA_CMD_PORT_ATTR_STATS, + .port = port->hw_id, + .dev = port->dev_id + }; + u64 *hw = resp.stats; + int err; + + err = prestera_cmd_ret(port->sw, PRESTERA_CMD_TYPE_PORT_ATTR_GET, + &req.cmd, sizeof(req), &resp.ret, sizeof(resp)); + if (err) + return err; + + st->good_octets_received = hw[PRESTERA_PORT_GOOD_OCTETS_RCV_CNT]; + st->bad_octets_received = hw[PRESTERA_PORT_BAD_OCTETS_RCV_CNT]; + st->mac_trans_error = hw[PRESTERA_PORT_MAC_TRANSMIT_ERR_CNT]; + st->broadcast_frames_received = hw[PRESTERA_PORT_BRDC_PKTS_RCV_CNT]; + st->multicast_frames_received = hw[PRESTERA_PORT_MC_PKTS_RCV_CNT]; + st->frames_64_octets = hw[PRESTERA_PORT_PKTS_64L_CNT]; + st->frames_65_to_127_octets = hw[PRESTERA_PORT_PKTS_65TO127L_CNT]; + st->frames_128_to_255_octets = hw[PRESTERA_PORT_PKTS_128TO255L_CNT]; + st->frames_256_to_511_octets = hw[PRESTERA_PORT_PKTS_256TO511L_CNT]; + st->frames_512_to_1023_octets = hw[PRESTERA_PORT_PKTS_512TO1023L_CNT]; + st->frames_1024_to_max_octets = hw[PRESTERA_PORT_PKTS_1024TOMAXL_CNT]; + st->excessive_collision = hw[PRESTERA_PORT_EXCESSIVE_COLLISIONS_CNT]; + st->multicast_frames_sent = hw[PRESTERA_PORT_MC_PKTS_SENT_CNT]; + st->broadcast_frames_sent = hw[PRESTERA_PORT_BRDC_PKTS_SENT_CNT]; + st->fc_sent = hw[PRESTERA_PORT_FC_SENT_CNT]; + st->fc_received = hw[PRESTERA_PORT_GOOD_FC_RCV_CNT]; + st->buffer_overrun = hw[PRESTERA_PORT_DROP_EVENTS_CNT]; + st->undersize = hw[PRESTERA_PORT_UNDERSIZE_PKTS_CNT]; + st->fragments = hw[PRESTERA_PORT_FRAGMENTS_PKTS_CNT]; + st->oversize = hw[PRESTERA_PORT_OVERSIZE_PKTS_CNT]; + st->jabber = hw[PRESTERA_PORT_JABBER_PKTS_CNT]; + st->rx_error_frame_received = hw[PRESTERA_PORT_MAC_RCV_ERROR_CNT]; + st->bad_crc = hw[PRESTERA_PORT_BAD_CRC_CNT]; + st->collisions = hw[PRESTERA_PORT_COLLISIONS_CNT]; + st->late_collision = hw[PRESTERA_PORT_LATE_COLLISIONS_CNT]; + st->unicast_frames_received = hw[PRESTERA_PORT_GOOD_UC_PKTS_RCV_CNT]; + st->unicast_frames_sent = hw[PRESTERA_PORT_GOOD_UC_PKTS_SENT_CNT]; + st->sent_multiple = hw[PRESTERA_PORT_MULTIPLE_PKTS_SENT_CNT]; + st->sent_deferred = hw[PRESTERA_PORT_DEFERRED_PKTS_SENT_CNT]; + st->frames_1024_to_1518_octets = hw[PRESTERA_PORT_PKTS_1024TO1518L_CNT]; + st->frames_1519_to_max_octets = hw[PRESTERA_PORT_PKTS_1519TOMAXL_CNT]; + st->good_octets_sent = hw[PRESTERA_PORT_GOOD_OCTETS_SENT_CNT]; + + return 0; +} + +int prestera_hw_rxtx_init(struct prestera_switch *sw, + struct prestera_rxtx_params *params) +{ + struct prestera_msg_rxtx_resp resp; + struct prestera_msg_rxtx_req req; + int err; + + req.use_sdma = params->use_sdma; + + err = prestera_cmd_ret(sw, PRESTERA_CMD_TYPE_RXTX_INIT, + &req.cmd, sizeof(req), &resp.ret, sizeof(resp)); + if (err) + return err; + + params->map_addr = resp.map_addr; + return 0; +} + +int prestera_hw_rxtx_port_init(struct prestera_port *port) +{ + struct prestera_msg_rxtx_port_req req = { + .port = port->hw_id, + .dev = port->dev_id, + }; + + return prestera_cmd(port->sw, PRESTERA_CMD_TYPE_RXTX_PORT_INIT, + &req.cmd, sizeof(req)); +} + +int prestera_hw_event_handler_register(struct prestera_switch *sw, + enum prestera_event_type type, + prestera_event_cb_t fn, + void *arg) +{ + struct prestera_fw_event_handler *eh; + + eh = __find_event_handler(sw, type); + if (eh) + return -EEXIST; + eh = kmalloc(sizeof(*eh), GFP_KERNEL); + if (!eh) + return -ENOMEM; + + eh->type = type; + eh->func = fn; + eh->arg = arg; + + INIT_LIST_HEAD(&eh->list); + + list_add_rcu(&eh->list, &sw->event_handlers); + + return 0; +} + +void prestera_hw_event_handler_unregister(struct prestera_switch *sw, + enum prestera_event_type type, + prestera_event_cb_t fn) +{ + struct prestera_fw_event_handler *eh; + + eh = __find_event_handler(sw, type); + if (!eh) + return; + + list_del_rcu(&eh->list); + synchronize_rcu(); + kfree(eh); +}diff --git a/drivers/net/ethernet/marvell/prestera/prestera_hw.h b/drivers/net/ethernet/marvell/prestera/prestera_hw.h new file mode 100644 index 000000000000..acb0e31d6684 --- /dev/null +++ b/drivers/net/ethernet/marvell/prestera/prestera_hw.h@@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 + * + * Copyright (c) 2019-2020 Marvell International Ltd. All rights reserved. + * + */ + +#ifndef _PRESTERA_HW_H_ +#define _PRESTERA_HW_H_ + +#include <linux/types.h> + +enum { + PRESTERA_PORT_TYPE_NONE, + PRESTERA_PORT_TYPE_TP, + + PRESTERA_PORT_TYPE_MAX, +}; + +enum { + PRESTERA_PORT_FEC_OFF, + + PRESTERA_PORT_FEC_MAX, +}; + +struct prestera_switch; +struct prestera_port; +struct prestera_port_stats; +struct prestera_port_caps; +enum prestera_event_type; +struct prestera_event; + +typedef void (*prestera_event_cb_t) + (struct prestera_switch *sw, struct prestera_event *evt, void *arg); + +struct prestera_rxtx_params; + +/* Switch API */ +int prestera_hw_switch_init(struct prestera_switch *sw); +int prestera_hw_switch_mac_set(struct prestera_switch *sw, char *mac); + +/* Port API */ +int prestera_hw_port_info_get(const struct prestera_port *port, + u16 *fp_id, u32 *hw_id, u32 *dev_id); +int prestera_hw_port_state_set(const struct prestera_port *port, + bool admin_state); +int prestera_hw_port_mtu_set(const struct prestera_port *port, u32 mtu); +int prestera_hw_port_mtu_get(const struct prestera_port *port, u32 *mtu); +int prestera_hw_port_mac_set(const struct prestera_port *port, char *mac); +int prestera_hw_port_mac_get(const struct prestera_port *port, char *mac); +int prestera_hw_port_cap_get(const struct prestera_port *port, + struct prestera_port_caps *caps); +int prestera_hw_port_autoneg_set(const struct prestera_port *port, + bool autoneg, u64 link_modes, u8 fec); +int prestera_hw_port_stats_get(const struct prestera_port *port, + struct prestera_port_stats *stats); + +/* Event handlers */ +int prestera_hw_event_handler_register(struct prestera_switch *sw, + enum prestera_event_type type, + prestera_event_cb_t fn, + void *arg); +void prestera_hw_event_handler_unregister(struct prestera_switch *sw, + enum prestera_event_type type, + prestera_event_cb_t fn); + +/* RX/TX */ +int prestera_hw_rxtx_init(struct prestera_switch *sw, + struct prestera_rxtx_params *params); +int prestera_hw_rxtx_port_init(struct prestera_port *port); + +#endif /* _PRESTERA_HW_H_ */diff --git a/drivers/net/ethernet/marvell/prestera/prestera_rxtx.c b/drivers/net/ethernet/marvell/prestera/prestera_rxtx.c new file mode 100644 index 000000000000..556941d97d4d --- /dev/null +++ b/drivers/net/ethernet/marvell/prestera/prestera_rxtx.c@@ -0,0 +1,825 @@ +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 +/* Copyright (c) 2019-2020 Marvell International Ltd. All rights reserved */ + +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_device.h> +#include <linux/dmapool.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/if_vlan.h> + +#include "prestera.h" +#include "prestera_hw.h" +#include "prestera_dsa.h" + +struct prestera_sdma_desc { + __le32 word1; + __le32 word2; + __le32 buff; + __le32 next; +} __packed __aligned(16); + +#define SDMA_BUFF_SIZE_MAX 1544 + +#define SDMA_RX_DESC_PKT_LEN(desc) \ + ((le32_to_cpu((desc)->word2) >> 16) & 0x3FFF) + +#define SDMA_RX_DESC_OWNER(desc) \ + ((le32_to_cpu((desc)->word1) & BIT(31)) >> 31) + +#define SDMA_RX_DESC_CPU_OWN 0 +#define SDMA_RX_DESC_DMA_OWN 1 + +#define SDMA_RX_QUEUE_NUM 8 + +#define SDMA_RX_DESC_PER_Q 1000 + +#define SDMA_TX_DESC_PER_Q 1000 +#define SDMA_TX_MAX_BURST 64 + +#define SDMA_TX_DESC_OWNER(desc) \ + ((le32_to_cpu((desc)->word1) & BIT(31)) >> 31) + +#define SDMA_TX_DESC_CPU_OWN 0 +#define SDMA_TX_DESC_DMA_OWN 1 + +#define SDMA_TX_DESC_IS_SENT(desc) \ + (SDMA_TX_DESC_OWNER(desc) == SDMA_TX_DESC_CPU_OWN) + +#define SDMA_TX_DESC_LAST BIT(20) +#define SDMA_TX_DESC_FIRST BIT(21) +#define SDMA_TX_DESC_SINGLE (SDMA_TX_DESC_FIRST | SDMA_TX_DESC_LAST) +#define SDMA_TX_DESC_CALC_CRC BIT(12) + +#define SDMA_RX_INTR_MASK_REG 0x2814 +#define SDMA_RX_QUEUE_STATUS_REG 0x2680 +#define SDMA_RX_QUEUE_DESC_REG(n) (0x260C + (n) * 16) + +#define SDMA_TX_QUEUE_DESC_REG 0x26C0 +#define SDMA_TX_QUEUE_START_REG 0x2868
You forgot to prefix these.
+
+struct prestera_sdma_buf {
+ struct prestera_sdma_desc *desc;
+ dma_addr_t desc_dma;
+ struct sk_buff *skb;
+ dma_addr_t buf_dma;
+ bool is_used;
+};
+
+struct prestera_rx_ring {
+ struct prestera_sdma_buf *bufs;
+ int next_rx;
+};
+
+struct prestera_tx_ring {
+ struct prestera_sdma_buf *bufs;
+ int next_tx;
+ int max_burst;
+ int burst;
+};
+
+struct prestera_sdma {
+ struct prestera_rx_ring rx_ring[SDMA_RX_QUEUE_NUM];
+ struct prestera_tx_ring tx_ring;
+ const struct prestera_switch *sw;
+ struct dma_pool *desc_pool;
+ struct work_struct tx_work;
+ struct napi_struct rx_napi;
+ struct net_device napi_dev;
+ u32 map_addr;
+ u64 dma_mask;
+};
+
+struct prestera_rxtx {
+ struct prestera_sdma sdma;
+};
+
+static int prestera_sdma_buf_init(struct prestera_sdma *sdma,
+ struct prestera_sdma_buf *buf)
+{
+ struct device *dma_dev = sdma->sw->dev->dev;
+ struct prestera_sdma_desc *desc;
+ dma_addr_t dma;
+
+ desc = dma_pool_alloc(sdma->desc_pool, GFP_DMA | GFP_KERNEL, &dma);
+ if (!desc)
+ return -ENOMEM;
+
+ if (dma + sizeof(struct prestera_sdma_desc) > sdma->dma_mask) {
+ dev_err(dma_dev, "failed to alloc desc\n");
+ dma_pool_free(sdma->desc_pool, desc, dma);
+ return -ENOMEM;
+ }
+
+ buf->buf_dma = DMA_MAPPING_ERROR;
+ buf->desc_dma = dma;
+ buf->desc = desc;
+ buf->skb = NULL;
+
+ return 0;
+}
+
+static u32 prestera_sdma_map(struct prestera_sdma *sdma, dma_addr_t pa)
+{
+ return sdma->map_addr + pa;
+}
+
+static void prestera_sdma_rx_desc_set_len(struct prestera_sdma_desc *desc,
+ size_t val)
+{
+ u32 word = le32_to_cpu(desc->word2);
+
+ word = (word & ~GENMASK(15, 0)) | val;
+ desc->word2 = cpu_to_le32(word);
+}
+
+static void prestera_sdma_rx_desc_init(struct prestera_sdma *sdma,
+ struct prestera_sdma_desc *desc,
+ dma_addr_t buf)
+{
+ prestera_sdma_rx_desc_set_len(desc, SDMA_BUFF_SIZE_MAX);
+ desc->buff = cpu_to_le32(prestera_sdma_map(sdma, buf));
+
+ /* make sure buffer is set before reset the descriptor */
+ wmb();
+
+ desc->word1 = cpu_to_le32(0xA0000000);
+}
+
+static void prestera_sdma_rx_desc_set_next(struct prestera_sdma *sdma,
+ struct prestera_sdma_desc *desc,
+ dma_addr_t next)
+{
+ desc->next = cpu_to_le32(prestera_sdma_map(sdma, next));
+}
+
+static int prestera_sdma_rx_skb_alloc(struct prestera_sdma *sdma,
+ struct prestera_sdma_buf *buf)
+{
+ struct device *dev = sdma->sw->dev->dev;
+ struct sk_buff *skb;
+ dma_addr_t dma;
+
+ skb = alloc_skb(SDMA_BUFF_SIZE_MAX, GFP_DMA | GFP_ATOMIC);
+ if (!skb)
+ return -ENOMEM;
+
+ dma = dma_map_single(dev, skb->data, skb->len, DMA_FROM_DEVICE);
+
+ if (dma_mapping_error(dev, dma))
+ goto err_dma_map;
+ if (dma + skb->len > sdma->dma_mask)
+ goto err_dma_range;
+
+ if (buf->skb)
+ dma_unmap_single(dev, buf->buf_dma, buf->skb->len,
+ DMA_FROM_DEVICE);
+
+ buf->buf_dma = dma;
+ buf->skb = skb;
+ return 0;
+
+err_dma_range:
+ dma_unmap_single(dev, dma, skb->len, DMA_FROM_DEVICE);
+err_dma_map:
+ kfree_skb(skb);
+
+ return -ENOMEM;
+}
+
+static struct sk_buff *prestera_sdma_rx_skb_get(struct prestera_sdma *sdma,
+ struct prestera_sdma_buf *buf)
+{
+ dma_addr_t buf_dma = buf->buf_dma;
+ struct sk_buff *skb = buf->skb;
+ u32 len = skb->len;
+ int err;
+
+ err = prestera_sdma_rx_skb_alloc(sdma, buf);
+ if (err) {
+ buf->buf_dma = buf_dma;
+ buf->skb = skb;
+
+ skb = alloc_skb(skb->len, GFP_ATOMIC);
+ if (skb) {
+ skb_put(skb, len);
+ skb_copy_from_linear_data(buf->skb, skb->data, len);
+ }
+ }
+
+ prestera_sdma_rx_desc_init(sdma, buf->desc, buf->buf_dma);
+
+ return skb;
+}
+
+static int prestera_rxtx_process_skb(struct sk_buff *skb)
+{
+ const struct prestera_port *port;
+ struct prestera_dsa dsa;
+ u32 hw_port, hw_id;
+ int err;
+
+ skb_pull(skb, ETH_HLEN);
+
+ /* ethertype field is part of the dsa header */
+ err = prestera_dsa_parse(&dsa, skb->data - ETH_TLEN);
+ if (err)
+ return err;
+
+ hw_port = dsa.port_num;
+ hw_id = dsa.hw_dev_num;
+
+ port = prestera_port_find_by_hwid(hw_id, hw_port);
+ if (unlikely(!port)) {
+ pr_warn_ratelimited("prestera: received pkt for non-existent port(%u, %u)\n",
+ hw_id, hw_port);
+ return -EEXIST;
+ }
+
+ if (unlikely(!pskb_may_pull(skb, PRESTERA_DSA_HLEN)))
+ return -EINVAL;
+
+ /* remove DSA tag and update checksum */
+ skb_pull_rcsum(skb, PRESTERA_DSA_HLEN);
+
+ memmove(skb->data - ETH_HLEN, skb->data - ETH_HLEN - PRESTERA_DSA_HLEN,
+ ETH_ALEN * 2);
+
+ skb_push(skb, ETH_HLEN);
+
+ skb->protocol = eth_type_trans(skb, port->dev);
+
+ if (dsa.vlan.is_tagged) {
+ u16 tci = dsa.vlan.vid & VLAN_VID_MASK;
+
+ tci |= dsa.vlan.vpt << VLAN_PRIO_SHIFT;
+ if (dsa.vlan.cfi_bit)
+ tci |= VLAN_CFI_MASK;
+
+ __vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), tci);
+ }
+
+ return 0;
+}
+
+static int prestera_sdma_rx_poll(struct napi_struct *napi, int budget)
+{
+ unsigned int qmask = GENMASK(SDMA_RX_QUEUE_NUM - 1, 0);
+ struct prestera_sdma *sdma;
+ unsigned int rxq_done_map = 0;
+ struct list_head rx_list;
+ int pkts_done = 0;
+ int q;
+
+ INIT_LIST_HEAD(&rx_list);
+
+ sdma = container_of(napi, struct prestera_sdma, rx_napi);
+
+ while (pkts_done < budget && rxq_done_map != qmask) {
+ for (q = 0; q < SDMA_RX_QUEUE_NUM && pkts_done < budget; q++) {
+ struct prestera_rx_ring *ring = &sdma->rx_ring[q];
+ int buf_idx = ring->next_rx;
+ struct prestera_sdma_desc *desc;
+ struct prestera_sdma_buf *buf;
+ struct sk_buff *skb;
+
+ buf = &ring->bufs[buf_idx];
+ desc = buf->desc;
+
+ if (SDMA_RX_DESC_OWNER(desc) != SDMA_RX_DESC_CPU_OWN) {
+ rxq_done_map |= BIT(q);
+ continue;
+ } else {
+ rxq_done_map &= ~BIT(q);
+ }
+
+ pkts_done++;
+
+ __skb_trim(buf->skb, SDMA_RX_DESC_PKT_LEN(desc));
+
+ skb = prestera_sdma_rx_skb_get(sdma, buf);
+ if (!skb)
+ goto rx_next_buf;
+
+ if (unlikely(prestera_rxtx_process_skb(skb)))
+ goto rx_next_buf;
+
+ list_add_tail(&skb->list, &rx_list);
+rx_next_buf:
+ ring->next_rx = (buf_idx + 1) % SDMA_RX_DESC_PER_Q;
+ }
+ }
+
+ if (pkts_done < budget && napi_complete_done(napi, pkts_done))
+ prestera_write(sdma->sw, SDMA_RX_INTR_MASK_REG, 0xff << 2);
+
+ netif_receive_skb_list(&rx_list);
+
+ return pkts_done;
+}
+
+static void prestera_sdma_rx_fini(struct prestera_sdma *sdma)
+{
+ int q, b;
+
+ /* disable all rx queues */
+ prestera_write(sdma->sw, SDMA_RX_QUEUE_STATUS_REG, 0xff00);
+
+ for (q = 0; q < SDMA_RX_QUEUE_NUM; q++) {
+ struct prestera_rx_ring *ring = &sdma->rx_ring[q];
+
+ if (!ring->bufs)
+ break;
+
+ for (b = 0; b < SDMA_RX_DESC_PER_Q; b++) {
+ struct prestera_sdma_buf *buf = &ring->bufs[b];
+
+ if (buf->desc_dma)
+ dma_pool_free(sdma->desc_pool, buf->desc,
+ buf->desc_dma);
+
+ if (!buf->skb)
+ continue;
+
+ if (buf->buf_dma != DMA_MAPPING_ERROR)
+ dma_unmap_single(sdma->sw->dev->dev,
+ buf->buf_dma, buf->skb->len,
+ DMA_FROM_DEVICE);
+ kfree_skb(buf->skb);
+ }
+ }
+}
+
+static int prestera_sdma_rx_init(struct prestera_sdma *sdma)
+{
+ int q, b;
+ int err;
+
+ /* disable all rx queues */
+ prestera_write(sdma->sw, SDMA_RX_QUEUE_STATUS_REG, 0xff00);
+
+ for (q = 0; q < SDMA_RX_QUEUE_NUM; q++) {
+ struct prestera_rx_ring *ring = &sdma->rx_ring[q];
+ struct prestera_sdma_buf *head;
+
+ ring->bufs = kmalloc_array(SDMA_RX_DESC_PER_Q, sizeof(*head),
+ GFP_KERNEL);
+ if (!ring->bufs)
+ return -ENOMEM;
+
+ head = &ring->bufs[0];
+ ring->next_rx = 0;
+
+ for (b = 0; b < SDMA_RX_DESC_PER_Q; b++) {
+ struct prestera_sdma_buf *buf = &ring->bufs[b];
+
+ err = prestera_sdma_buf_init(sdma, buf);
+ if (err)
+ return err;
+
+ err = prestera_sdma_rx_skb_alloc(sdma, buf);
+ if (err)
+ return err;
+
+ prestera_sdma_rx_desc_init(sdma, buf->desc,
+ buf->buf_dma);
+
+ if (b == 0)
+ continue;
+
+ prestera_sdma_rx_desc_set_next(sdma,
+ ring->bufs[b - 1].desc,
+ buf->desc_dma);
+
+ if (b == SDMA_RX_DESC_PER_Q - 1)
+ prestera_sdma_rx_desc_set_next(sdma, buf->desc,
+ head->desc_dma);
+ }
+
+ prestera_write(sdma->sw, SDMA_RX_QUEUE_DESC_REG(q),
+ prestera_sdma_map(sdma, head->desc_dma));
+ }
+
+ /* make sure all rx descs are filled before enabling all rx queues */
+ wmb();
+
+ prestera_write(sdma->sw, SDMA_RX_QUEUE_STATUS_REG, 0xff);
+
+ return 0;
+}
+
+static void prestera_sdma_tx_desc_init(struct prestera_sdma *sdma,
+ struct prestera_sdma_desc *desc)
+{
+ desc->word1 = cpu_to_le32(SDMA_TX_DESC_SINGLE | SDMA_TX_DESC_CALC_CRC);
+ desc->word2 = 0;
+}
+
+static void prestera_sdma_tx_desc_set_next(struct prestera_sdma *sdma,
+ struct prestera_sdma_desc *desc,
+ dma_addr_t next)
+{
+ desc->next = cpu_to_le32(prestera_sdma_map(sdma, next));
+}
+
+static void prestera_sdma_tx_desc_set_buf(struct prestera_sdma *sdma,
+ struct prestera_sdma_desc *desc,
+ dma_addr_t buf, size_t len)
+{
+ u32 word = le32_to_cpu(desc->word2);
+
+ word = (word & ~GENMASK(30, 16)) | ((len + ETH_FCS_LEN) << 16);
+
+ desc->buff = cpu_to_le32(prestera_sdma_map(sdma, buf));
+ desc->word2 = cpu_to_le32(word);
+}
+
+static void prestera_sdma_tx_desc_xmit(struct prestera_sdma_desc *desc)
+{
+ u32 word = le32_to_cpu(desc->word1);
+
+ word |= (SDMA_TX_DESC_DMA_OWN << 31);Drop the ()s here.
+
+ /* make sure everything is written before enable xmit */
+ wmb();
+
+ desc->word1 = cpu_to_le32(word);
+}
+
+static int prestera_sdma_tx_buf_map(struct prestera_sdma *sdma,
+ struct prestera_sdma_buf *buf,
+ struct sk_buff *skb)
+{
+ struct device *dma_dev = sdma->sw->dev->dev;
+ struct sk_buff *new_skb;
+ size_t len = skb->len;
+ dma_addr_t dma;
+
+ dma = dma_map_single(dma_dev, skb->data, len, DMA_TO_DEVICE);
+ if (!dma_mapping_error(dma_dev, dma) && dma + len <= sdma->dma_mask) {
+ buf->buf_dma = dma;
+ buf->skb = skb;
+ return 0;
+ }
+
+ if (!dma_mapping_error(dma_dev, dma))
+ dma_unmap_single(dma_dev, dma, len, DMA_TO_DEVICE);
+
+ new_skb = alloc_skb(len, GFP_ATOMIC | GFP_DMA);
+ if (!new_skb)
+ goto err_alloc_skb;
+
+ dma = dma_map_single(dma_dev, new_skb->data, len, DMA_TO_DEVICE);
+ if (dma_mapping_error(dma_dev, dma))
+ goto err_dma_map;
+ if (dma + len > sdma->dma_mask)
+ goto err_dma_range;
+
+ skb_copy_from_linear_data(skb, skb_put(new_skb, len), len);
+
+ dev_consume_skb_any(skb);
+
+ buf->skb = new_skb;
+ buf->buf_dma = dma;
+
+ return 0;
+
+err_dma_range:
+ dma_unmap_single(dma_dev, dma, len, DMA_TO_DEVICE);
+err_dma_map:
+ dev_kfree_skb(new_skb);
+err_alloc_skb:
+ dev_kfree_skb(skb);
+
+ return -ENOMEM;
+}
+
+static void prestera_sdma_tx_buf_unmap(struct prestera_sdma *sdma,
+ struct prestera_sdma_buf *buf)
+{
+ struct device *dma_dev = sdma->sw->dev->dev;
+
+ dma_unmap_single(dma_dev, buf->buf_dma, buf->skb->len, DMA_TO_DEVICE);
+}
+
+static void prestera_sdma_tx_recycle_work_fn(struct work_struct *work)
+{
+ struct prestera_tx_ring *tx_ring;
+ struct prestera_sdma *sdma;
+ struct device *dma_dev;
+ int b;
+
+ sdma = container_of(work, struct prestera_sdma, tx_work);
+
+ dma_dev = sdma->sw->dev->dev;
+ tx_ring = &sdma->tx_ring;
+
+ for (b = 0; b < SDMA_TX_DESC_PER_Q; b++) {
+ struct prestera_sdma_buf *buf = &tx_ring->bufs[b];
+
+ if (!buf->is_used)
+ continue;
+
+ if (!SDMA_TX_DESC_IS_SENT(buf->desc))
+ continue;
+
+ prestera_sdma_tx_buf_unmap(sdma, buf);
+ dev_consume_skb_any(buf->skb);
+ buf->skb = NULL;
+
+ /* make sure everything is cleaned up */
+ wmb();
+
+ buf->is_used = false;
+ }
+}
+
+static int prestera_sdma_tx_init(struct prestera_sdma *sdma)
+{
+ struct prestera_tx_ring *tx_ring = &sdma->tx_ring;
+ struct prestera_sdma_buf *head;
+ int err;
+ int b;
+
+ INIT_WORK(&sdma->tx_work, prestera_sdma_tx_recycle_work_fn);
+
+ tx_ring->bufs = kmalloc_array(SDMA_TX_DESC_PER_Q, sizeof(*head),
+ GFP_KERNEL);
+ if (!tx_ring->bufs)
+ return -ENOMEM;
+
+ head = &tx_ring->bufs[0];
+
+ tx_ring->max_burst = SDMA_TX_MAX_BURST;
+ tx_ring->burst = tx_ring->max_burst;
+ tx_ring->next_tx = 0;
+
+ for (b = 0; b < SDMA_TX_DESC_PER_Q; b++) {
+ struct prestera_sdma_buf *buf = &tx_ring->bufs[b];
+
+ err = prestera_sdma_buf_init(sdma, buf);
+ if (err)
+ return err;
+
+ prestera_sdma_tx_desc_init(sdma, buf->desc);
+
+ buf->is_used = false;
+
+ if (b == 0)
+ continue;
+
+ prestera_sdma_tx_desc_set_next(sdma, tx_ring->bufs[b - 1].desc,
+ buf->desc_dma);
+
+ if (b == SDMA_TX_DESC_PER_Q - 1)
+ prestera_sdma_tx_desc_set_next(sdma, buf->desc,
+ head->desc_dma);
+ }
+
+ /* make sure descriptors are written */
+ wmb();
+
+ prestera_write(sdma->sw, SDMA_TX_QUEUE_DESC_REG,
+ prestera_sdma_map(sdma, head->desc_dma));
+
+ return 0;
+}
+
+static void prestera_sdma_tx_fini(struct prestera_sdma *sdma)
+{
+ struct prestera_tx_ring *ring = &sdma->tx_ring;
+ int b;
+
+ cancel_work_sync(&sdma->tx_work);
+
+ if (!ring->bufs)
+ return;
+
+ for (b = 0; b < SDMA_TX_DESC_PER_Q; b++) {
+ struct prestera_sdma_buf *buf = &ring->bufs[b];
+
+ if (buf->desc)
+ dma_pool_free(sdma->desc_pool, buf->desc,
+ buf->desc_dma);
+
+ if (!buf->skb)
+ continue;
+
+ dma_unmap_single(sdma->sw->dev->dev, buf->buf_dma,
+ buf->skb->len, DMA_TO_DEVICE);
+
+ dev_consume_skb_any(buf->skb);
+ }
+}
+
+static void prestera_rxtx_handle_event(struct prestera_switch *sw,
+ struct prestera_event *evt,
+ void *arg)
+{
+ struct prestera_sdma *sdma = arg;
+
+ if (evt->id != PRESTERA_RXTX_EVENT_RCV_PKT)
+ return;
+
+ prestera_write(sdma->sw, SDMA_RX_INTR_MASK_REG, 0);
+ napi_schedule(&sdma->rx_napi);
+}
+
+int prestera_sdma_switch_init(struct prestera_switch *sw)
+{
+ struct prestera_sdma *sdma = &sw->rxtx->sdma;
+ struct device *dev = sw->dev->dev;
+ struct prestera_rxtx_params p;
+ int err;
+
+ p.use_sdma = true;
+
+ err = prestera_hw_rxtx_init(sw, &p);
+ if (err) {
+ dev_err(dev, "failed to init rxtx by hw\n");
+ return err;
+ }
+
+ sdma->dma_mask = dma_get_mask(dev);
+ sdma->map_addr = p.map_addr;
+ sdma->sw = sw;
+
+ sdma->desc_pool = dma_pool_create("desc_pool", dev,
+ sizeof(struct prestera_sdma_desc),
+ 16, 0);
+ if (!sdma->desc_pool)
+ return -ENOMEM;
+
+ err = prestera_sdma_rx_init(sdma);
+ if (err) {
+ dev_err(dev, "failed to init rx ring\n");
+ goto err_rx_init;
+ }
+
+ err = prestera_sdma_tx_init(sdma);
+ if (err) {
+ dev_err(dev, "failed to init tx ring\n");
+ goto err_tx_init;
+ }
+
+ err = prestera_hw_event_handler_register(sw, PRESTERA_EVENT_TYPE_RXTX,
+ prestera_rxtx_handle_event,
+ sdma);
+ if (err)
+ goto err_evt_register;
+
+ init_dummy_netdev(&sdma->napi_dev);
+
+ netif_napi_add(&sdma->napi_dev, &sdma->rx_napi, prestera_sdma_rx_poll, 64);
+ napi_enable(&sdma->rx_napi);
+
+ return 0;
+
+err_evt_register:
+err_tx_init:
+ prestera_sdma_tx_fini(sdma);
+err_rx_init:
+ prestera_sdma_rx_fini(sdma);
+
+ dma_pool_destroy(sdma->desc_pool);
+ return err;
+}
+
+void prestera_sdma_switch_fini(struct prestera_switch *sw)
+{
+ struct prestera_sdma *sdma = &sw->rxtx->sdma;
+
+ prestera_hw_event_handler_unregister(sw, PRESTERA_EVENT_TYPE_RXTX,
+ prestera_rxtx_handle_event);
+ napi_disable(&sdma->rx_napi);
+ netif_napi_del(&sdma->rx_napi);
+ prestera_sdma_rx_fini(sdma);
+ prestera_sdma_tx_fini(sdma);
+ dma_pool_destroy(sdma->desc_pool);
+}
+
+static int prestera_sdma_tx_wait(struct prestera_sdma *sdma,
+ struct prestera_tx_ring *tx_ring)
+{
+ int tx_retry_num = 10 * tx_ring->max_burst;
+
+ while (--tx_retry_num) {
+ if (!(prestera_read(sdma->sw, SDMA_TX_QUEUE_START_REG) & 1))
+ return 0;
+
+ udelay(1);
+ }
+
+ return -EBUSY;
+}
+
+static void prestera_sdma_tx_start(struct prestera_sdma *sdma)
+{
+ prestera_write(sdma->sw, SDMA_TX_QUEUE_START_REG, 1);
+ schedule_work(&sdma->tx_work);
+}
+
+netdev_tx_t prestera_sdma_xmit(struct prestera_sdma *sdma, struct sk_buff *skb)
+{
+ struct device *dma_dev = sdma->sw->dev->dev;
+ struct prestera_tx_ring *tx_ring;
+ struct net_device *dev = skb->dev;
+ struct prestera_sdma_buf *buf;
+ int err;
+
+ tx_ring = &sdma->tx_ring;
+
+ buf = &tx_ring->bufs[tx_ring->next_tx];
+ if (buf->is_used) {
+ schedule_work(&sdma->tx_work);
+ goto drop_skb;
+ }What is preventing 2 CPUs to get here and work with the same buf?
+
+ if (unlikely(eth_skb_pad(skb)))
+ goto drop_skb_nofree;
+
+ err = prestera_sdma_tx_buf_map(sdma, buf, skb);
+ if (err)
+ goto drop_skb;
+
+ prestera_sdma_tx_desc_set_buf(sdma, buf->desc, buf->buf_dma, skb->len);
+
+ dma_sync_single_for_device(dma_dev, buf->buf_dma, skb->len,
+ DMA_TO_DEVICE);
+
+ if (!tx_ring->burst--) {
+ tx_ring->burst = tx_ring->max_burst;
+
+ err = prestera_sdma_tx_wait(sdma, tx_ring);
+ if (err)
+ goto drop_skb_unmap;
+ }
+
+ tx_ring->next_tx = (tx_ring->next_tx + 1) % SDMA_TX_DESC_PER_Q;
+ prestera_sdma_tx_desc_xmit(buf->desc);
+ buf->is_used = true;
+
+ prestera_sdma_tx_start(sdma);
+
+ return NETDEV_TX_OK;
+
+drop_skb_unmap:
+ prestera_sdma_tx_buf_unmap(sdma, buf);
+drop_skb:
+ dev_consume_skb_any(skb);
+drop_skb_nofree:
+ dev->stats.tx_dropped++;
+ return NETDEV_TX_OK;
+}
+
+int prestera_rxtx_switch_init(struct prestera_switch *sw)
+{
+ struct prestera_rxtx *rxtx;
+
+ rxtx = kzalloc(sizeof(*rxtx), GFP_KERNEL);
+ if (!rxtx)
+ return -ENOMEM;
+
+ sw->rxtx = rxtx;
+
+ return prestera_sdma_switch_init(sw);
+}
+
+void prestera_rxtx_switch_fini(struct prestera_switch *sw)
+{
+ prestera_sdma_switch_fini(sw);
+ kfree(sw->rxtx);
+}
+
+int prestera_rxtx_port_init(struct prestera_port *port)
+{
+ int err;
+
+ err = prestera_hw_rxtx_port_init(port);
+ if (err)
+ return err;
+
+ port->dev->needed_headroom = PRESTERA_DSA_HLEN + ETH_FCS_LEN;
+ return 0;
+}
+
+netdev_tx_t prestera_rxtx_xmit(struct prestera_port *port, struct sk_buff *skb)Why this has "rx" in the name??
quoted hunk ↗ jump to hunk
+{ + struct prestera_dsa dsa; + + dsa.hw_dev_num = port->dev_id; + dsa.port_num = port->hw_id; + + if (skb_cow_head(skb, PRESTERA_DSA_HLEN) < 0) + return NET_XMIT_DROP; + + skb_push(skb, PRESTERA_DSA_HLEN); + memmove(skb->data, skb->data + PRESTERA_DSA_HLEN, 2 * ETH_ALEN); + + if (prestera_dsa_build(&dsa, skb->data + 2 * ETH_ALEN) != 0) + return NET_XMIT_DROP; + + return prestera_sdma_xmit(&port->sw->rxtx->sdma, skb); +}diff --git a/drivers/net/ethernet/marvell/prestera/prestera_rxtx.h b/drivers/net/ethernet/marvell/prestera/prestera_rxtx.h new file mode 100644 index 000000000000..bbbadfa5accf --- /dev/null +++ b/drivers/net/ethernet/marvell/prestera/prestera_rxtx.h@@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 + * + * Copyright (c) 2019-2020 Marvell International Ltd. All rights reserved. + * + */ + +#ifndef _PRESTERA_RXTX_H_ +#define _PRESTERA_RXTX_H_ + +#include <linux/netdevice.h> + +#include "prestera.h" + +int prestera_rxtx_switch_init(struct prestera_switch *sw); +void prestera_rxtx_switch_fini(struct prestera_switch *sw); + +int prestera_rxtx_port_init(struct prestera_port *port); + +netdev_tx_t prestera_rxtx_xmit(struct prestera_port *port, struct sk_buff *skb); + +#endif /* _PRESTERA_RXTX_H_ */-- 2.17.1