Re: [RFC PATCH net-next 2/2] net: ethernet: ti: inter-core-virt-eth: Register as network device

From: Simon Horman
Date: Thu Feb 01 2024 - 08:19:44 EST


On Tue, Jan 30, 2024 at 04:39:44PM +0530, Ravi Gunasekaran wrote:
> Register the RPMsg driver as network device and add support for
> basic ethernet functionality by using the shared memory for data
> plane.
>
> The shared memory layout is as below, with the region between
> PKT_1_LEN to PKT_N modelled as circular buffer.
>
> -------------------------
> | HEAD |
> -------------------------
> | TAIL |
> -------------------------
> | PKT_1_LEN |
> | PKT_1 |
> -------------------------
> | PKT_2_LEN |
> | PKT_2 |
> -------------------------
> | . |
> | . |
> -------------------------
> | PKT_N_LEN |
> | PKT_N |
> -------------------------
>
> The offset between the HEAD and TAIL is polled to process the Rx packets.
>
> Signed-off-by: Siddharth Vadapalli <s-vadapalli@xxxxxx>
> Signed-off-by: Ravi Gunasekaran <r-gunasekaran@xxxxxx>

Hi Ravi,

some feedback, mainly regarding issues flagged by linters and static analysis.

> ---
> drivers/net/ethernet/ti/inter-core-virt-eth.c | 316 ++++++++++++++++++
> drivers/net/ethernet/ti/inter-core-virt-eth.h | 16 +
> 2 files changed, 332 insertions(+)
>
> diff --git a/drivers/net/ethernet/ti/inter-core-virt-eth.c b/drivers/net/ethernet/ti/inter-core-virt-eth.c
> index d3b689eab1c0..735482001f4d 100644
> --- a/drivers/net/ethernet/ti/inter-core-virt-eth.c
> +++ b/drivers/net/ethernet/ti/inter-core-virt-eth.c
> @@ -6,6 +6,50 @@
>
> #include "inter-core-virt-eth.h"
>
> +#define ICVE_MIN_PACKET_SIZE ETH_ZLEN
> +#define ICVE_MAX_PACKET_SIZE (ETH_FRAME_LEN + ETH_FCS_LEN)
> +#define ICVE_MAX_TX_QUEUES 1
> +#define ICVE_MAX_RX_QUEUES 1
> +
> +#define TEST_DEBUG 1

Please remove TEST_DEBUG and associated code.
This does not seem appropriate for upstream.

> +
> +#ifdef TEST_DEBUG
> +#define ICVE_MAX_BUFFERS 100 //TODO : Set to power of 2 to leverage shift operations
> +#endif
> +
> +#define PKT_LEN_SIZE_TYPE sizeof(u32)
> +
> +/* 4 bytes to hold packet length and ICVE_MAX_PACKET_SIZE to hold packet */
> +#define ICVE_BUFFER_SIZE (ICVE_MAX_PACKET_SIZE + PKT_LEN_SIZE_TYPE)
> +
> +#define RX_POLL_TIMEOUT 250
> +
> +#define icve_ndev_to_priv(ndev) \
> + ((struct icve_ndev_priv *)netdev_priv(ndev))
> +#define icve_ndev_to_port(ndev) (icve_ndev_to_priv(ndev)->port)
> +#define icve_ndev_to_common(ndev) (icve_ndev_to_port(ndev)->common)
> +
> +static void icve_rx_timer(struct timer_list *timer)
> +{
> + struct icve_port *port = from_timer(port, timer, rx_timer);
> + struct napi_struct *napi;
> + int num_pkts = 0;
> + u32 head, tail;
> +
> + head = port->rx_buffer->head;
> + tail = port->rx_buffer->tail;
> +
> + num_pkts = tail - head;
> + num_pkts = num_pkts >= 0 ? num_pkts : (num_pkts + port->icve_max_buffers);

Networking (still) prefers code to be < 80 columns wide.
In this case I'd probably go for (completely untested!):

num_pkts = tail - head;
if (num_pkts <= 0)
num_pkts = num_pkts + port->icve_max_buffers;

Please consider running checkpatch as it is run by the CI.

[1] https://github.com/linux-netdev/nipa/blob/main/tests/patch/checkpatch/checkpatch.sh`

> +
> + napi = &port->rx_napi;
> + if (num_pkts && likely(napi_schedule_prep(napi))) {
> + __napi_schedule(napi);
> + } else {
> + mod_timer(&port->rx_timer, RX_POLL_TIMEOUT);

Smatch warns that mod_timer takes an absolute time rather than an offset.
So, perhaps (completely untested!):

mod_timer(&port->rx_timer, jiffies + RX_POLL_TIMEOUT);

> + }

If any arm of an if statement has {} then all should,
but {} is not needed unless there is more than one statement
convered by conditions.

So (completely untested!):

if (num_pkts && likely(napi_schedule_prep(napi)))
__napi_schedule(napi);
else
mod_timer(&port->rx_timer, jiffies + RX_POLL_TIMEOUT);

..

> @@ -85,11 +145,262 @@ static int create_request(struct icve_common *common, enum icve_rpmsg_type rpmsg
> return ret;
> }
>
> +static int icve_rx_packets(struct napi_struct *napi, int budget)
> +{
> + struct icve_port *port = container_of(napi, struct icve_port, rx_napi);
> + u32 count, process_pkts;
> + struct sk_buff *skb;
> + u32 head, tail;
> + u32 pkt_len;
> + int num_pkts;

nit: Please use reverse xmas tree - longest line to shortest - for local
variables in Networking code.

This tool can help achieve that.
* https://github.com/ecree-solarflare/xmastree

> +
> + head = port->rx_buffer->head;
> + tail = port->rx_buffer->tail;
> +
> + num_pkts = tail - head;
> + num_pkts = num_pkts >= 0 ? num_pkts : (num_pkts + port->icve_max_buffers);
> + process_pkts = min(num_pkts, budget);
> + count = 0;
> + while (count < process_pkts) {
> + memcpy((void *)&pkt_len,
> + (void *)port->rx_buffer->base_addr + ((head + count) * ICVE_BUFFER_SIZE),
> + PKT_LEN_SIZE_TYPE);

I don't think there is any need to cast to (void *) like this.

..

> +static netdev_tx_t icve_start_xmit(struct sk_buff *skb, struct net_device *ndev)
> +{
> + struct icve_port *port = icve_ndev_to_port(ndev);
> + struct ethhdr *ether;
> + u32 head, tail;
> + u32 num_pkts;
> + u32 len;
> +
> + ether = eth_hdr(skb);

ether is assigned but otherwise unused in this function.

Flaged by W=1 builds using both gcc-13 and clang-17.

> + len = skb_headlen(skb);
> +
> + head = port->tx_buffer->head;
> + tail = port->tx_buffer->tail;
> +
> + /* If the buffer queue is full, then drop packet */
> + num_pkts = tail - head;
> + num_pkts = num_pkts >= 0 ? num_pkts : (num_pkts + port->icve_max_buffers);

num_pkts is unsigned, so the condition above is always true.

Flagged by Coccinelle and Smatch.

> + if ((num_pkts + 1) == port->icve_max_buffers) {
> + netdev_warn(ndev, "Tx buffer full\n");
> + goto ring_full;
> + }
> +
> + /* Copy length */
> + memcpy((void *)port->tx_buffer->base_addr + (port->tx_buffer->tail * ICVE_BUFFER_SIZE),
> + (void *)&len, PKT_LEN_SIZE_TYPE);
> +
> + /* Copy data to shared mem */
> + memcpy((void *)(port->tx_buffer->base_addr + PKT_LEN_SIZE_TYPE) +
> + (port->tx_buffer->tail * ICVE_BUFFER_SIZE),
> + (void *)skb->data, len);
> +
> +#ifdef TEST_DEBUG
> + /* For quick Rx path testing, inject Tx pkt back into network */
> + test_tx_rx_path(skb, ndev);
> +#endif
> + port->tx_buffer->tail = (port->tx_buffer->tail + 1) % port->icve_max_buffers;
> +
> + dev_consume_skb_any(skb);
> +
> + return NETDEV_TX_OK;
> +
> +ring_full:
> + return NETDEV_TX_BUSY;
> +}
> +
> +static int icve_set_mac_address(struct net_device *ndev, void *addr)
> +{
> + eth_mac_addr(ndev, addr);
> +
> + /* TODO : Inform remote core about MAC address change */
> + return 0;
> +}
> +
> +static const struct net_device_ops icve_netdev_ops = {
> + .ndo_open = icve_ndo_open,
> + .ndo_stop = icve_ndo_stop,
> + .ndo_start_xmit = icve_start_xmit,
> + .ndo_set_mac_address = icve_set_mac_address,
> +};
> +
> +static int icve_init_ndev(struct icve_common *common)
> +{
> + struct device *dev = &common->rpdev->dev;
> + struct icve_ndev_priv *ndev_priv;
> + struct icve_port *port;
> + static u32 port_id;
> + int err;
> +
> + port = common->port;
> + port->common = common;
> + port->port_id = port_id++;
> +
> + port->ndev = devm_alloc_etherdev_mqs(common->dev, sizeof(*ndev_priv),
> + ICVE_MAX_TX_QUEUES,
> + ICVE_MAX_RX_QUEUES);
> +
> + if (!port->ndev) {
> + dev_err(dev, "error allocating net_device\n");
> + return -ENOMEM;
> + }
> +
> + ndev_priv = netdev_priv(port->ndev);
> + ndev_priv->port = port;
> + SET_NETDEV_DEV(port->ndev, dev);
> +
> + port->ndev->min_mtu = ICVE_MIN_PACKET_SIZE;
> + port->ndev->max_mtu = ICVE_MAX_PACKET_SIZE;
> + port->ndev->netdev_ops = &icve_netdev_ops;
> +
> +#ifdef TEST_DEBUG
> + /* Allocate memory to test without actual RPMsg handshaking */
> + port->tx_buffer = devm_kzalloc(dev, sizeof(port->tx_buffer),
> + GFP_KERNEL);

(I think this code should be removed, but in any case I'll flag
the problems that I am aware of.)

This allocates the size of the port->tx_buffer pointer, rather than the
size of port->tx_buffer itself (4 or 8 bytes instead of 12 or 16 bytes).
So perhaps it should be (completely untested!):

port->tx_buffer = devm_kzalloc(dev, sizeof(*port->tx_buffer),
GFP_KERNEL);

Flagged by Sparse.


> + if (!port->tx_buffer) {
> + dev_err(dev, "Memory not available\n");

Out of memory messages will be logged by the code.
So there is no need for the dev_err() call above.

> + return -ENOMEM;
> + }
> +
> + port->tx_buffer->base_addr = devm_kzalloc(dev, ICVE_BUFFER_SIZE * ICVE_MAX_BUFFERS,
> + GFP_KERNEL);
> + if (!port->tx_buffer->base_addr) {
> + dev_err(dev, "Memory not available\n");
> + return -ENOMEM;
> + }
> +
> + port->rx_buffer = devm_kzalloc(dev, sizeof(port->rx_buffer),
> + GFP_KERNEL);
> + if (!port->rx_buffer) {
> + dev_err(dev, "Memory not available\n");
> + return -ENOMEM;
> + };
> +
> + port->rx_buffer->base_addr = devm_kzalloc(dev, ICVE_BUFFER_SIZE * ICVE_MAX_BUFFERS,
> + GFP_KERNEL);
> + if (!port->rx_buffer->base_addr) {
> + dev_err(dev, "Memory not available\n");
> + return -ENOMEM;
> + }
> +
> + port->icve_max_buffers = ICVE_MAX_BUFFERS;
> +#else
> + /* Shared memory details will be sent by the remote core.
> + * So turn off the carrier, until both the virtual port and
> + * remote core is ready
> + */
> + netif_carrier_off(port->ndev);
> +
> +#endif
> + err = register_netdev(port->ndev);
> +
> + if (err)
> + dev_err(dev, "error registering icve net device %d\n", err);
> +
> + return 0;
> +}
> +

..

> diff --git a/drivers/net/ethernet/ti/inter-core-virt-eth.h b/drivers/net/ethernet/ti/inter-core-virt-eth.h

..

> @@ -70,7 +78,11 @@ struct shared_mem {
> struct icve_port {
> struct shared_mem *tx_buffer; /* Write buffer for data to be consumed remote side */
> struct shared_mem *rx_buffer; /* Read buffer for data to be consumed by this driver */
> + struct timer_list rx_timer;
> struct icve_common *common;
> + struct napi_struct rx_napi;
> + u8 local_mac_addr[ETH_ALEN];

local_mac_addr does not appear to be used in this patch.
If so, please drop it and add it when it is needed.

> + struct net_device *ndev;
> u32 icve_max_buffers;
> u32 port_id; /* Unique ID for the port : TODO: Define range for use by Linux and non linux */
>

..