diff mbox series

[net-next,v6,10/14] net: ethernet: oa_tc6: implement receive path to receive rx ethernet frames

Message ID 20240812102611.489550-11-Parthiban.Veerasooran@microchip.com (mailing list archive)
State Changes Requested
Delegated to: Netdev Maintainers
Headers show
Series Add support for OPEN Alliance 10BASE-T1x MACPHY Serial Interface | expand

Checks

Context Check Description
netdev/series_format success Posting correctly formatted
netdev/tree_selection success Clearly marked for net-next
netdev/ynl success Generated files up to date; no warnings/errors; no diff in generated;
netdev/fixes_present success Fixes tag not required for -next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 29 this patch: 29
netdev/build_tools success No tools touched, skip
netdev/cc_maintainers success CCed 5 of 5 maintainers
netdev/build_clang success Errors and warnings before: 29 this patch: 29
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 29 this patch: 29
netdev/checkpatch success total: 0 errors, 0 warnings, 0 checks, 346 lines checked
netdev/build_clang_rust success No Rust files in patch. Skipping build
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0
netdev/contest success net-next-2024-08-13--12-00 (tests: 706)

Commit Message

Parthiban Veerasooran Aug. 12, 2024, 10:26 a.m. UTC
SPI rx data buffer can contain one or more receive data chunks. A receive
data chunk consists a 64 bytes receive data chunk payload followed a
4 bytes data footer at the end. The data footer contains the information
needed to determine the validity and location of the receive frame data
within the receive data chunk payload and the host can use these
information to generate ethernet frame. Initially the receive chunks
available will be updated from the buffer status register and then it
will be updated from the footer received on each spi data transfer. Tx
data valid or empty chunks equal to the number receive chunks available
will be transmitted in the MOSI to receive all the rx chunks.
Additionally the receive data footer contains the below information as
well. The received footer will be examined for the receive errors if any.

Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: Parthiban Veerasooran <Parthiban.Veerasooran@microchip.com>
---
 drivers/net/ethernet/oa_tc6.c | 242 ++++++++++++++++++++++++++++++++--
 1 file changed, 234 insertions(+), 8 deletions(-)

Comments

Jakub Kicinski Aug. 16, 2024, 5:01 p.m. UTC | #1
On Mon, 12 Aug 2024 15:56:07 +0530 Parthiban Veerasooran wrote:
> +	if (netif_rx(tc6->rx_skb) == NET_RX_DROP)
> +		tc6->netdev->stats.rx_dropped++;

This is a bit unusual. If the core decides to drop the packet it will
count the drop towards the appropriate statistic. The drivers generally
only count their own drops, and call netif_rx() without checking the
return value.
Parthiban Veerasooran Aug. 19, 2024, 6:53 a.m. UTC | #2
Hi Jakub,

Thanks for reviewing the patches.

On 16/08/24 10:31 pm, Jakub Kicinski wrote:
> EXTERNAL EMAIL: Do not click links or open attachments unless you know the content is safe
> 
> On Mon, 12 Aug 2024 15:56:07 +0530 Parthiban Veerasooran wrote:
>> +     if (netif_rx(tc6->rx_skb) == NET_RX_DROP)
>> +             tc6->netdev->stats.rx_dropped++;
> 
> This is a bit unusual. If the core decides to drop the packet it will
> count the drop towards the appropriate statistic. The drivers generally
> only count their own drops, and call netif_rx() without checking the
> return value.
The first version of this patch series didn't have this check. There was 
a comment in the 1st version to check the return value and update the 
statistics.

https://lore.kernel.org/lkml/375fa9b4-0fb8-8d4b-8cb5-d8a9240d8f16@huawei.com/

That was the reason why it was introduced in the v2 of the patch series 
itself. It seems, somehow it got escaped from your RADAR from v2 to v5 
:D. Sorry, somehow I also missed to check it in the netdev core. Now I 
understand that the rx drop handled in the core itself in the below link 
using the function "dev_core_stats_rx_dropped_inc(skb->dev)".

https://github.com/torvalds/linux/blob/master/net/core/dev.c#L4894

Is my understanding correct? if so then I will remove this check in the 
next version.

Best regards,
Parthiban V
Jakub Kicinski Aug. 19, 2024, 10:48 p.m. UTC | #3
On Mon, 19 Aug 2024 06:53:51 +0000 Parthiban.Veerasooran@microchip.com
wrote:
> > This is a bit unusual. If the core decides to drop the packet it will
> > count the drop towards the appropriate statistic. The drivers generally
> > only count their own drops, and call netif_rx() without checking the
> > return value.  
>
> The first version of this patch series didn't have this check. There was 
> a comment in the 1st version to check the return value and update the 
> statistics.
> 
> https://lore.kernel.org/lkml/375fa9b4-0fb8-8d4b-8cb5-d8a9240d8f16@huawei.com/
> 
> That was the reason why it was introduced in the v2 of the patch series 
> itself. It seems, somehow it got escaped from your RADAR from v2 to v5 
> :D.

Sorry about that :( There's definitely a gap in terms of reviewing 
the work of reviewers :(

> Sorry, somehow I also missed to check it in the netdev core. Now I 
> understand that the rx drop handled in the core itself in the below link 
> using the function "dev_core_stats_rx_dropped_inc(skb->dev)".
> 
> https://github.com/torvalds/linux/blob/master/net/core/dev.c#L4894
> 
> Is my understanding correct? if so then I will remove this check in the 
> next version.

Yes!
Parthiban Veerasooran Aug. 20, 2024, 5:10 a.m. UTC | #4
Hi Jakub,

On 20/08/24 4:18 am, Jakub Kicinski wrote:
> EXTERNAL EMAIL: Do not click links or open attachments unless you know the content is safe
> 
> On Mon, 19 Aug 2024 06:53:51 +0000 Parthiban.Veerasooran@microchip.com
> wrote:
>>> This is a bit unusual. If the core decides to drop the packet it will
>>> count the drop towards the appropriate statistic. The drivers generally
>>> only count their own drops, and call netif_rx() without checking the
>>> return value.
>>
>> The first version of this patch series didn't have this check. There was
>> a comment in the 1st version to check the return value and update the
>> statistics.
>>
>> https://lore.kernel.org/lkml/375fa9b4-0fb8-8d4b-8cb5-d8a9240d8f16@huawei.com/
>>
>> That was the reason why it was introduced in the v2 of the patch series
>> itself. It seems, somehow it got escaped from your RADAR from v2 to v5
>> :D.
> 
> Sorry about that :( There's definitely a gap in terms of reviewing
> the work of reviewers :(
No problem, I understand that, I just wanted to let you know the reason. 
Thanks a lot for reviewing the patches and the feedback to bring the 
patches to a good shape. Please keep supporting.
> 
>> Sorry, somehow I also missed to check it in the netdev core. Now I
>> understand that the rx drop handled in the core itself in the below link
>> using the function "dev_core_stats_rx_dropped_inc(skb->dev)".
>>
>> https://github.com/torvalds/linux/blob/master/net/core/dev.c#L4894
>>
>> Is my understanding correct? if so then I will remove this check in the
>> next version.
> 
> Yes!
O.K. Thanks for the confirmation.

Best regards,
Parthiban V
diff mbox series

Patch

diff --git a/drivers/net/ethernet/oa_tc6.c b/drivers/net/ethernet/oa_tc6.c
index 2a7180045394..fa1f9e74ceb8 100644
--- a/drivers/net/ethernet/oa_tc6.c
+++ b/drivers/net/ethernet/oa_tc6.c
@@ -29,11 +29,13 @@ 
 #define STATUS0_RESETC				BIT(6)	/* Reset Complete */
 #define STATUS0_HEADER_ERROR			BIT(5)
 #define STATUS0_LOSS_OF_FRAME_ERROR		BIT(4)
+#define STATUS0_RX_BUFFER_OVERFLOW_ERROR	BIT(3)
 #define STATUS0_TX_PROTOCOL_ERROR		BIT(0)
 
 /* Buffer Status Register */
 #define OA_TC6_REG_BUFFER_STATUS		0x000B
 #define BUFFER_STATUS_TX_CREDITS_AVAILABLE	GENMASK(15, 8)
+#define BUFFER_STATUS_RX_CHUNKS_AVAILABLE	GENMASK(7, 0)
 
 /* Interrupt Mask Register #0 */
 #define OA_TC6_REG_INT_MASK0			0x000C
@@ -67,6 +69,12 @@ 
 #define OA_TC6_DATA_FOOTER_EXTENDED_STS		BIT(31)
 #define OA_TC6_DATA_FOOTER_RXD_HEADER_BAD	BIT(30)
 #define OA_TC6_DATA_FOOTER_CONFIG_SYNC		BIT(29)
+#define OA_TC6_DATA_FOOTER_RX_CHUNKS		GENMASK(28, 24)
+#define OA_TC6_DATA_FOOTER_DATA_VALID		BIT(21)
+#define OA_TC6_DATA_FOOTER_START_VALID		BIT(20)
+#define OA_TC6_DATA_FOOTER_START_WORD_OFFSET	GENMASK(19, 16)
+#define OA_TC6_DATA_FOOTER_END_VALID		BIT(14)
+#define OA_TC6_DATA_FOOTER_END_BYTE_OFFSET	GENMASK(13, 8)
 #define OA_TC6_DATA_FOOTER_TX_CREDITS		GENMASK(5, 1)
 
 /* PHY – Clause 45 registers memory map selector (MMS) as per table 6 in the
@@ -111,11 +119,14 @@  struct oa_tc6 {
 	void *spi_data_rx_buf;
 	struct sk_buff_head tx_skb_q;
 	struct sk_buff *tx_skb;
+	struct sk_buff *rx_skb;
 	struct task_struct *spi_thread;
 	wait_queue_head_t spi_wq;
 	u16 tx_skb_offset;
 	u16 spi_data_tx_buf_offset;
 	u16 tx_credits;
+	u8 rx_chunks_available;
+	bool rx_buf_overflow;
 };
 
 enum oa_tc6_header_type {
@@ -638,6 +649,15 @@  static int oa_tc6_enable_data_transfer(struct oa_tc6 *tc6)
 	return oa_tc6_write_register(tc6, OA_TC6_REG_CONFIG0, value);
 }
 
+static void oa_tc6_cleanup_ongoing_rx_skb(struct oa_tc6 *tc6)
+{
+	if (tc6->rx_skb) {
+		tc6->netdev->stats.rx_dropped++;
+		kfree_skb(tc6->rx_skb);
+		tc6->rx_skb = NULL;
+	}
+}
+
 static void oa_tc6_cleanup_ongoing_tx_skb(struct oa_tc6 *tc6)
 {
 	if (tc6->tx_skb) {
@@ -667,6 +687,13 @@  static int oa_tc6_process_extended_status(struct oa_tc6 *tc6)
 		return ret;
 	}
 
+	if (FIELD_GET(STATUS0_RX_BUFFER_OVERFLOW_ERROR, value)) {
+		tc6->rx_buf_overflow = true;
+		oa_tc6_cleanup_ongoing_rx_skb(tc6);
+		net_err_ratelimited("%s: Receive buffer overflow error\n",
+				    tc6->netdev->name);
+		return -EAGAIN;
+	}
 	if (FIELD_GET(STATUS0_TX_PROTOCOL_ERROR, value)) {
 		netdev_err(tc6->netdev, "Transmit protocol error\n");
 		return -ENODEV;
@@ -691,8 +718,11 @@  static int oa_tc6_process_rx_chunk_footer(struct oa_tc6 *tc6, u32 footer)
 	/* Process rx chunk footer for the following,
 	 * 1. tx credits
 	 * 2. errors if any from MAC-PHY
+	 * 3. receive chunks available
 	 */
 	tc6->tx_credits = FIELD_GET(OA_TC6_DATA_FOOTER_TX_CREDITS, footer);
+	tc6->rx_chunks_available = FIELD_GET(OA_TC6_DATA_FOOTER_RX_CHUNKS,
+					     footer);
 
 	if (FIELD_GET(OA_TC6_DATA_FOOTER_EXTENDED_STS, footer)) {
 		int ret = oa_tc6_process_extended_status(tc6);
@@ -718,6 +748,142 @@  static int oa_tc6_process_rx_chunk_footer(struct oa_tc6 *tc6, u32 footer)
 	return 0;
 }
 
+static void oa_tc6_submit_rx_skb(struct oa_tc6 *tc6)
+{
+	tc6->rx_skb->protocol = eth_type_trans(tc6->rx_skb, tc6->netdev);
+	tc6->netdev->stats.rx_packets++;
+	tc6->netdev->stats.rx_bytes += tc6->rx_skb->len;
+
+	if (netif_rx(tc6->rx_skb) == NET_RX_DROP)
+		tc6->netdev->stats.rx_dropped++;
+
+	tc6->rx_skb = NULL;
+}
+
+static void oa_tc6_update_rx_skb(struct oa_tc6 *tc6, u8 *payload, u8 length)
+{
+	memcpy(skb_put(tc6->rx_skb, length), payload, length);
+}
+
+static int oa_tc6_allocate_rx_skb(struct oa_tc6 *tc6)
+{
+	tc6->rx_skb = netdev_alloc_skb_ip_align(tc6->netdev, tc6->netdev->mtu +
+						ETH_HLEN + ETH_FCS_LEN);
+	if (!tc6->rx_skb) {
+		tc6->netdev->stats.rx_dropped++;
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+static int oa_tc6_prcs_complete_rx_frame(struct oa_tc6 *tc6, u8 *payload,
+					 u16 size)
+{
+	int ret;
+
+	ret = oa_tc6_allocate_rx_skb(tc6);
+	if (ret)
+		return ret;
+
+	oa_tc6_update_rx_skb(tc6, payload, size);
+
+	oa_tc6_submit_rx_skb(tc6);
+
+	return 0;
+}
+
+static int oa_tc6_prcs_rx_frame_start(struct oa_tc6 *tc6, u8 *payload, u16 size)
+{
+	int ret;
+
+	ret = oa_tc6_allocate_rx_skb(tc6);
+	if (ret)
+		return ret;
+
+	oa_tc6_update_rx_skb(tc6, payload, size);
+
+	return 0;
+}
+
+static void oa_tc6_prcs_rx_frame_end(struct oa_tc6 *tc6, u8 *payload, u16 size)
+{
+	oa_tc6_update_rx_skb(tc6, payload, size);
+
+	oa_tc6_submit_rx_skb(tc6);
+}
+
+static void oa_tc6_prcs_ongoing_rx_frame(struct oa_tc6 *tc6, u8 *payload,
+					 u32 footer)
+{
+	oa_tc6_update_rx_skb(tc6, payload, OA_TC6_CHUNK_PAYLOAD_SIZE);
+}
+
+static int oa_tc6_prcs_rx_chunk_payload(struct oa_tc6 *tc6, u8 *data,
+					u32 footer)
+{
+	u8 start_byte_offset = FIELD_GET(OA_TC6_DATA_FOOTER_START_WORD_OFFSET,
+					 footer) * sizeof(u32);
+	u8 end_byte_offset = FIELD_GET(OA_TC6_DATA_FOOTER_END_BYTE_OFFSET,
+				       footer);
+	bool start_valid = FIELD_GET(OA_TC6_DATA_FOOTER_START_VALID, footer);
+	bool end_valid = FIELD_GET(OA_TC6_DATA_FOOTER_END_VALID, footer);
+	u16 size;
+
+	/* Restart the new rx frame after receiving rx buffer overflow error */
+	if (start_valid && tc6->rx_buf_overflow)
+		tc6->rx_buf_overflow = false;
+
+	if (tc6->rx_buf_overflow)
+		return 0;
+
+	/* Process the chunk with complete rx frame */
+	if (start_valid && end_valid && start_byte_offset < end_byte_offset) {
+		size = end_byte_offset + 1 - start_byte_offset;
+		return oa_tc6_prcs_complete_rx_frame(tc6,
+						     &data[start_byte_offset],
+						     size);
+	}
+
+	/* Process the chunk with only rx frame start */
+	if (start_valid && !end_valid) {
+		size = OA_TC6_CHUNK_PAYLOAD_SIZE - start_byte_offset;
+		return oa_tc6_prcs_rx_frame_start(tc6,
+						  &data[start_byte_offset],
+						  size);
+	}
+
+	/* Process the chunk with only rx frame end */
+	if (end_valid && !start_valid) {
+		size = end_byte_offset + 1;
+		oa_tc6_prcs_rx_frame_end(tc6, data, size);
+		return 0;
+	}
+
+	/* Process the chunk with previous rx frame end and next rx frame
+	 * start.
+	 */
+	if (start_valid && end_valid && start_byte_offset > end_byte_offset) {
+		/* After rx buffer overflow error received, there might be a
+		 * possibility of getting an end valid of a previously
+		 * incomplete rx frame along with the new rx frame start valid.
+		 */
+		if (tc6->rx_skb) {
+			size = end_byte_offset + 1;
+			oa_tc6_prcs_rx_frame_end(tc6, data, size);
+		}
+		size = OA_TC6_CHUNK_PAYLOAD_SIZE - start_byte_offset;
+		return oa_tc6_prcs_rx_frame_start(tc6,
+						  &data[start_byte_offset],
+						  size);
+	}
+
+	/* Process the chunk with ongoing rx frame data */
+	oa_tc6_prcs_ongoing_rx_frame(tc6, data, footer);
+
+	return 0;
+}
+
 static u32 oa_tc6_get_rx_chunk_footer(struct oa_tc6 *tc6, u16 footer_offset)
 {
 	u8 *rx_buf = tc6->spi_data_rx_buf;
@@ -743,6 +909,20 @@  static int oa_tc6_process_spi_data_rx_buf(struct oa_tc6 *tc6, u16 length)
 		ret = oa_tc6_process_rx_chunk_footer(tc6, footer);
 		if (ret)
 			return ret;
+
+		/* If there is a data valid chunks then process it for the
+		 * information needed to determine the validity and the location
+		 * of the receive frame data.
+		 */
+		if (FIELD_GET(OA_TC6_DATA_FOOTER_DATA_VALID, footer)) {
+			u8 *payload = tc6->spi_data_rx_buf + i *
+				      OA_TC6_CHUNK_SIZE;
+
+			ret = oa_tc6_prcs_rx_chunk_payload(tc6, payload,
+							   footer);
+			if (ret)
+				return ret;
+		}
 	}
 
 	return 0;
@@ -833,31 +1013,74 @@  static u16 oa_tc6_prepare_spi_tx_buf_for_tx_skbs(struct oa_tc6 *tc6)
 	return used_tx_credits * OA_TC6_CHUNK_SIZE;
 }
 
+static void oa_tc6_add_empty_chunks_to_spi_buf(struct oa_tc6 *tc6,
+					       u16 needed_empty_chunks)
+{
+	__be32 header;
+
+	header = oa_tc6_prepare_data_header(OA_TC6_DATA_INVALID,
+					    OA_TC6_DATA_START_INVALID,
+					    OA_TC6_DATA_END_INVALID, 0);
+
+	while (needed_empty_chunks--) {
+		__be32 *tx_buf = tc6->spi_data_tx_buf +
+				 tc6->spi_data_tx_buf_offset;
+
+		*tx_buf = header;
+		tc6->spi_data_tx_buf_offset += OA_TC6_CHUNK_SIZE;
+	}
+}
+
+static u16 oa_tc6_prepare_spi_tx_buf_for_rx_chunks(struct oa_tc6 *tc6, u16 len)
+{
+	u16 tx_chunks = len / OA_TC6_CHUNK_SIZE;
+	u16 needed_empty_chunks;
+
+	/* If there are more chunks to receive than to transmit, we need to add
+	 * enough empty tx chunks to allow the reception of the excess rx
+	 * chunks.
+	 */
+	if (tx_chunks >= tc6->rx_chunks_available)
+		return len;
+
+	needed_empty_chunks = tc6->rx_chunks_available - tx_chunks;
+
+	oa_tc6_add_empty_chunks_to_spi_buf(tc6, needed_empty_chunks);
+
+	return needed_empty_chunks * OA_TC6_CHUNK_SIZE + len;
+}
+
 static int oa_tc6_try_spi_transfer(struct oa_tc6 *tc6)
 {
 	int ret;
 
 	while (true) {
-		u16 spi_length = 0;
+		u16 spi_len = 0;
 
 		tc6->spi_data_tx_buf_offset = 0;
 
 		if (tc6->tx_skb || !skb_queue_empty(&tc6->tx_skb_q))
-			spi_length = oa_tc6_prepare_spi_tx_buf_for_tx_skbs(tc6);
+			spi_len = oa_tc6_prepare_spi_tx_buf_for_tx_skbs(tc6);
 
-		if (spi_length == 0)
+		spi_len = oa_tc6_prepare_spi_tx_buf_for_rx_chunks(tc6, spi_len);
+
+		if (spi_len == 0)
 			break;
 
-		ret = oa_tc6_spi_transfer(tc6, OA_TC6_DATA_HEADER, spi_length);
+		ret = oa_tc6_spi_transfer(tc6, OA_TC6_DATA_HEADER, spi_len);
 		if (ret) {
 			netdev_err(tc6->netdev, "SPI data transfer failed: %d\n",
 				   ret);
 			return ret;
 		}
 
-		ret = oa_tc6_process_spi_data_rx_buf(tc6, spi_length);
+		ret = oa_tc6_process_spi_data_rx_buf(tc6, spi_len);
 		if (ret) {
+			if (ret == -EAGAIN)
+				continue;
+
 			oa_tc6_cleanup_ongoing_tx_skb(tc6);
+			oa_tc6_cleanup_ongoing_rx_skb(tc6);
 			netdev_err(tc6->netdev, "Device error: %d\n", ret);
 			return ret;
 		}
@@ -897,15 +1120,17 @@  static int oa_tc6_update_buffer_status_from_register(struct oa_tc6 *tc6)
 	u32 value;
 	int ret;
 
-	/* Initially tx credits to be updated from the register as there is no
-	 * data transfer performed yet. Later it will be updated from the rx
-	 * footer.
+	/* Initially tx credits and rx chunks available to be updated from the
+	 * register as there is no data transfer performed yet. Later they will
+	 * be updated from the rx footer.
 	 */
 	ret = oa_tc6_read_register(tc6, OA_TC6_REG_BUFFER_STATUS, &value);
 	if (ret)
 		return ret;
 
 	tc6->tx_credits = FIELD_GET(BUFFER_STATUS_TX_CREDITS_AVAILABLE, value);
+	tc6->rx_chunks_available = FIELD_GET(BUFFER_STATUS_RX_CHUNKS_AVAILABLE,
+					     value);
 
 	return 0;
 }
@@ -1055,6 +1280,7 @@  void oa_tc6_exit(struct oa_tc6 *tc6)
 	oa_tc6_phy_exit(tc6);
 	kthread_stop(tc6->spi_thread);
 	dev_kfree_skb_any(tc6->tx_skb);
+	dev_kfree_skb_any(tc6->rx_skb);
 	skb_queue_purge(&tc6->tx_skb_q);
 }
 EXPORT_SYMBOL_GPL(oa_tc6_exit);