Skip to content
Snippets Groups Projects
flxnet_dev.c 27.6 KiB
Newer Older
/*

	This is a demonstration for establishing a network 
	between VMK180 and host PC over CPM. This is the common
	network interface device driver. 

	Elena Zhivun <elena.zhivun@cern.ch>

*/

#include <linux/kernel.h>
#include <linux/vmalloc.h>
#include <linux/errno.h>
#include <linux/io.h>
#include <linux/device.h>
#include <linux/timer.h>
Elena Zhivun's avatar
Elena Zhivun committed
#include <linux/types.h>
Elena Zhivun's avatar
Elena Zhivun committed
#include <linux/ethtool.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/kthread.h>
#include <linux/module.h>
#include <linux/version.h>
Elena Zhivun's avatar
Elena Zhivun committed
#include <linux/circ_buf.h>
#include "flxnet/flxnet.h"
Elena Zhivun's avatar
Elena Zhivun committed
#define TX_TIMEOUT_MS 6
Elena Zhivun's avatar
Elena Zhivun committed
#define RX_TIMEOUT_MS 3
#define POLL_PERIOD_MS 1
Elena Zhivun's avatar
Elena Zhivun committed
#define SLEEP_PERIOD_MS 1000
#define DRV_NAME "flxnet_dev"
Elena Zhivun's avatar
Elena Zhivun committed

Elena Zhivun's avatar
Elena Zhivun committed
/*
	TODO:
Elena Zhivun's avatar
Elena Zhivun committed
	- add watchdogs in firmware for "link detect" emulation
	- disable tx and rx on the peers without "link" present
Elena Zhivun's avatar
Elena Zhivun committed
	- it's possible to make TX queue circular buffer lockless
	- user interrupts instead of timer; use NAPI
Elena Zhivun's avatar
Elena Zhivun committed

	BUGs:
	- first ICMP packet takes 1-2 seconds to arrive?
Elena Zhivun's avatar
Elena Zhivun committed
*/

struct net_device * flxnet;
Elena Zhivun's avatar
Elena Zhivun committed
struct mutex flxnet_mutex;
Elena Zhivun's avatar
Elena Zhivun committed
u32 message_level;

Elena Zhivun's avatar
Elena Zhivun committed
typedef u32 t_fifo_word;
// MAX_PACKET_LEN is measured in FIFO words
#define MAX_PACKET_LEN 400
#define BYTES_IN_WORD 4
#define MAX_DATA_LEN MAX_PACKET_LEN * BYTES_IN_WORD
#define MAX_RETRY_ATTEMPTS 5

Elena Zhivun's avatar
Elena Zhivun committed

Elena Zhivun's avatar
Elena Zhivun committed
struct tx_packet {
	int	num_words;
	int attempts;
Elena Zhivun's avatar
Elena Zhivun committed
	char dest_mac[ETH_ALEN];
	char data[MAX_DATA_LEN];
Elena Zhivun's avatar
Elena Zhivun committed
};
Elena Zhivun's avatar
Elena Zhivun committed

Elena Zhivun's avatar
Elena Zhivun committed

Elena Zhivun's avatar
Elena Zhivun committed
int tx_queue_size = 256;
DEFINE_SPINLOCK(tx_queue_spinlock);
struct tx_queue {
Elena Zhivun's avatar
Elena Zhivun committed
	int head;
	int tail;
	struct tx_packet *packets;
Elena Zhivun's avatar
Elena Zhivun committed
};

Elena Zhivun's avatar
Elena Zhivun committed

struct tx_queue tx_queue; //[MAXCARDS]

static LIST_HEAD(peers);
struct mutex peer_mutex;
atomic_t peer_count;

wait_queue_head_t recv_queue;
Elena Zhivun's avatar
Elena Zhivun committed
struct timer_list recv_timer;
struct task_struct *recv_task;


#define FLXNET_SOP 0xCACE0000
#define FLXNET_SOP_MASK 0xFFFF0000
#define FLXNET_LEN_MASK 0x0000FFFF
#define FLXNET_EOP 0xFA18CECA
Elena Zhivun's avatar
Elena Zhivun committed

#define RX_SUCCESS 0
#define RX_LOW_BUFFER 1
#define RX_INVALID_ARG 2
#define RX_TIMEOUT 3
#define RX_INVALID_EOP 4
#define RX_NO_MEMORY 5
#define RX_TRUNCATED 6

#define TX_SUCCESS 0
#define TX_TRY_BROADCAST 1
#define TX_BUSY 2
Elena Zhivun's avatar
Elena Zhivun committed
#define TX_ERROR 3
Elena Zhivun's avatar
Elena Zhivun committed
/* Read/Write access to the MMAPped registers */
#if defined(CONFIG_ARCH_ZYNQ) || defined(CONFIG_X86)
# define register_read(offset)		readl(offset)
# define register_write(offset, val)	writel(val, offset)
#else
# define register_read(offset)		__raw_readl(offset)
# define register_write(offset, val)	__raw_writel(val, offset)
#endif

static void tx_all_packets(void);

Elena Zhivun's avatar
Elena Zhivun committed
static inline bool check_flag(void* __iomem reg, int bit_id) {
	return ((1 << bit_id) & register_read(reg)) != 0;
}

static inline void set_flag(void* __iomem reg, int bit_id) {
	uint32_t temp_reg = register_read(reg);
	temp_reg |= (1 << bit_id);
	pr_info("flxnet_dev: temp_reg is %u", temp_reg);
	register_write(reg, temp_reg);
	pr_info("flxnet_dev: flag set");
}

static inline void clear_flag(void* __iomem reg, int bit_id) {
	uint32_t temp_reg = register_read(reg);
	temp_reg &= ~(1 << bit_id);
	pr_info("flxnet_dev: temp_reg is %u", temp_reg);
	register_write(reg, temp_reg);
	pr_info("flxnet_dev: flag cleared");
}

/* static inline void print_status_reg(struct flxnet_peer *peer) {
	uint32_t temp_reg = register_read(peer->regs + peer->reg_status);
	pr_info("flxnet_dev: SEND_EOP: %d\nRECV_EOP: %d\nROOM_FOR_BLOCK: %d\nROOM_FOR_WORD: %d\nBLOCK_AVAILABLE: %d\nWORD_AVAILABLE: %d"
          (temp_reg & (1<<SEND_IS_EOP) >0),
          (temp_reg & (1<<RECV_IS_EOP) >0),
          (temp_reg & (1<<HAS_ROOM_FOR_BLOCK) >0),
          (temp_reg & (1<<HAS_ROOM_FOR_WORD) >0),
          (temp_reg & (1<<BLOCK_AVAILABLE) >0),
          (temp_reg & (1<<WORD_AVAILABLE) >0)
	)
} */

static inline bool is_word_available(struct flxnet_peer *peer) {
Elena Zhivun's avatar
Elena Zhivun committed
	return check_flag(peer->regs + peer->reg_status, WORD_AVAILABLE);
}

static inline bool is_block_available(struct flxnet_peer *peer) {
Elena Zhivun's avatar
Elena Zhivun committed
	return check_flag(peer->regs + peer->reg_status, BLOCK_AVAILABLE);
}

static inline bool can_accept_word(struct flxnet_peer *peer) {
Elena Zhivun's avatar
Elena Zhivun committed
	return check_flag(peer->regs + peer->reg_status, HAS_ROOM_FOR_WORD);
}

static inline bool can_accept_block(struct flxnet_peer *peer) {
Elena Zhivun's avatar
Elena Zhivun committed
	return check_flag(peer->regs + peer->reg_status, HAS_ROOM_FOR_BLOCK);
}
static inline bool recv_eop(struct flxnet_peer *peer) {
	return check_flag(peer->regs + peer->reg_status, RECV_IS_EOP);
}

Elena Zhivun's avatar
Elena Zhivun committed
// Send a single word to the device
static inline void send_word(struct flxnet_peer* peer, u32 data) {
	register_write(peer->regs + peer->reg_send, data);
}

// Receive a single word
Elena Zhivun's avatar
Elena Zhivun committed
static inline t_fifo_word recv_word(struct flxnet_peer* peer) {
	return (t_fifo_word)register_read(peer->regs + peer->reg_recv);
Elena Zhivun's avatar
Elena Zhivun committed
}

// send end of packet
static inline void send_eop(struct flxnet_peer *peer) {
	set_flag(peer->regs + peer->reg_status, SEND_IS_EOP);
	send_word(peer, FLXNET_EOP);
	pr_info("flxnet_dev: end of packet sent");
	clear_flag(peer->regs + peer->reg_status, SEND_IS_EOP);
}

Elena Zhivun's avatar
Elena Zhivun committed
// Verify that the device magic number is correct
inline bool is_magic_correct(struct flxnet_peer* peer) {
	#ifdef IS_ROLE_TARGET
		return (register_read(peer->regs + TARGET_MAGIC_REG) == FLX_DEV_MAGIC);
	#else
		return (register_read(peer->regs + HOST_MAGIC_REG) == FLX_DEV_MAGIC);
	#endif
Elena Zhivun's avatar
Elena Zhivun committed
/* 
	Removes the peer from the network, assumes
	that peer_mutex is held
*/
Elena Zhivun's avatar
Elena Zhivun committed
void flxnet_remove_peer(struct flxnet_peer *peer) {
Elena Zhivun's avatar
Elena Zhivun committed
	int rv;

Elena Zhivun's avatar
Elena Zhivun committed
	if (message_level & NETIF_MSG_LINK)
		pr_info("flxnet_dev: removing peer %p from the network\n", peer);
Elena Zhivun's avatar
Elena Zhivun committed
	rv = atomic_dec_return(&peer_count);
	if (rv < 0) {
		pr_err("flxnet_dev: BUG in %s: peer_count = %d, less than 0\n",
Elena Zhivun's avatar
Elena Zhivun committed
			__FUNCTION__, rv);
		atomic_set(&peer_count, 0);
Elena Zhivun's avatar
Elena Zhivun committed

	if (peer->reserved) module_put(peer->owner);
Elena Zhivun's avatar
Elena Zhivun committed
	list_del(&peer->list_head);
	kfree(peer);
}


// Removes all peers from the network
Elena Zhivun's avatar
Elena Zhivun committed
void remove_all_peers(void) {
	struct flxnet_peer *peer;
Elena Zhivun's avatar
Elena Zhivun committed
	struct flxnet_peer *tmp;
Elena Zhivun's avatar
Elena Zhivun committed
	if (message_level & NETIF_MSG_LINK)
		pr_info("flxnet_dev: removing all peers from the network\n");
	mutex_lock(&peer_mutex);
Elena Zhivun's avatar
Elena Zhivun committed
	list_for_each_entry_safe(peer, tmp, &peers, list_head) {
Elena Zhivun's avatar
Elena Zhivun committed
		flxnet_remove_peer(peer);
Elena Zhivun's avatar
Elena Zhivun committed
// mark as not reserved so that peer modules can be removed
void flxnet_release_all_peers(void) {
	struct flxnet_peer *peer;
	struct flxnet_peer *tmp;
	
	mutex_lock(&peer_mutex);
	list_for_each_entry_safe(peer, tmp, &peers, list_head) {
		if (peer->reserved) {
			module_put(peer->owner);
			peer->reserved = false;
		} else {
Elena Zhivun's avatar
Elena Zhivun committed
			if (message_level & NETIF_MSG_DRV)
				pr_info("flxnet_dev: trying to release peer %p, which is not reserved, skipping\n", peer);
Elena Zhivun's avatar
Elena Zhivun committed
		}
	}
	mutex_unlock(&peer_mutex);
}


// mark as reserved so that peer modules cannot be removed and iomem stay there
void flxnet_reserve_all_peers(void) {
	struct flxnet_peer *peer;
	struct flxnet_peer *tmp;

	mutex_lock(&peer_mutex);
	list_for_each_entry_safe(peer, tmp, &peers, list_head) {
		if (!peer->reserved) {
			peer->reserved = try_module_get(peer->owner);
			if (!peer->reserved) {
				pr_err("flxnet_dev: failed to reserve peer %p, owner module is missing\n", peer);
Elena Zhivun's avatar
Elena Zhivun committed
				flxnet_remove_peer(peer);
			}
		} else {
Elena Zhivun's avatar
Elena Zhivun committed
			if (message_level & NETIF_MSG_DRV)
				pr_info("flxnet_dev: trying to reserve peer %p, which is already reserved, skipping\n", peer);
Elena Zhivun's avatar
Elena Zhivun committed
		}
	}
	mutex_unlock(&peer_mutex);
}

Elena Zhivun's avatar
Elena Zhivun committed
static bool tx_queue_is_full(void) {
	bool rv;
	unsigned long flags;
	spin_lock_irqsave(&tx_queue_spinlock, flags);
	rv = CIRC_SPACE(tx_queue.head, tx_queue.tail, tx_queue_size) == 0;
	spin_unlock_irqrestore(&tx_queue_spinlock, flags);
	return rv;
Elena Zhivun's avatar
Elena Zhivun committed

Elena Zhivun's avatar
Elena Zhivun committed
static bool tx_queue_is_empty(void) {
	bool rv;
	unsigned long flags;
	spin_lock_irqsave(&tx_queue_spinlock, flags);
	rv = CIRC_CNT(tx_queue.head, tx_queue.tail, tx_queue_size) == 0;
	spin_unlock_irqrestore(&tx_queue_spinlock, flags);
	return rv;
Elena Zhivun's avatar
Elena Zhivun committed
	
Elena Zhivun's avatar
Elena Zhivun committed
static int tx_queue_try_push(struct sk_buff *skb) {
	struct tx_packet *packet;
	struct ethhdr *mac_hdr;
Elena Zhivun's avatar
Elena Zhivun committed
	unsigned long flags;
	if (tx_queue_is_full()) {
		if (message_level & NETIF_MSG_TX_ERR)
			pr_info("flxnet_dev: trying to add a packet to TX queue, but it is full\n");
		return TX_BUSY;
	if (skb->len > MAX_DATA_LEN) {
		pr_err("flxnet_dev: (BUG) TX packet payload is too large "
Elena Zhivun's avatar
Elena Zhivun committed
			"(%d bytes, maximum is %d)\n", skb->len, MAX_DATA_LEN);
		return TX_ERROR;
	mac_hdr = eth_hdr(skb);

Elena Zhivun's avatar
Elena Zhivun committed
	spin_lock_irqsave(&tx_queue_spinlock, flags);
	packet = &tx_queue.packets[tx_queue.head];
	packet->num_words = skb->len / BYTES_IN_WORD;
	memcpy(packet->data, skb->data, skb->len);
Elena Zhivun's avatar
Elena Zhivun committed
	if (skb->len % BYTES_IN_WORD) {
Elena Zhivun's avatar
Elena Zhivun committed
		packet->num_words += 1;
		memset(packet->data + skb->len, 0, BYTES_IN_WORD - (skb->len % BYTES_IN_WORD));
Elena Zhivun's avatar
Elena Zhivun committed
	memcpy(packet->dest_mac, mac_hdr->h_dest, ETH_ALEN);
	packet->attempts = 0;
	tx_queue.head = (tx_queue.head + 1) & (tx_queue_size - 1);
Elena Zhivun's avatar
Elena Zhivun committed
	spin_unlock_irqrestore(&tx_queue_spinlock, flags);
	return TX_SUCCESS;
}
Elena Zhivun's avatar
Elena Zhivun committed

static int tx_queue_pop(void) {
Elena Zhivun's avatar
Elena Zhivun committed
	unsigned long flags;

	if (tx_queue_is_empty()) {
		pr_warn("flxnet_dev: trying to remove a packet from TX queue, but it is empty\n");
		return -1;
Elena Zhivun's avatar
Elena Zhivun committed
	spin_lock_irqsave(&tx_queue_spinlock, flags);
	tx_queue.tail = (tx_queue.tail + 1) & (tx_queue_size - 1);
Elena Zhivun's avatar
Elena Zhivun committed
	spin_unlock_irqrestore(&tx_queue_spinlock, flags);
	return 0;
Elena Zhivun's avatar
Elena Zhivun committed

void tx_queue_reset(void) {
Elena Zhivun's avatar
Elena Zhivun committed
	unsigned long flags;
Elena Zhivun's avatar
Elena Zhivun committed
	flxnet->stats.tx_dropped += CIRC_CNT(tx_queue.head, tx_queue.tail, tx_queue_size);
Elena Zhivun's avatar
Elena Zhivun committed
	spin_lock_irqsave(&tx_queue_spinlock, flags);
	tx_queue.head = 0;
	tx_queue.tail = 0;
Elena Zhivun's avatar
Elena Zhivun committed
	spin_unlock_irqrestore(&tx_queue_spinlock, flags);	
Elena Zhivun's avatar
Elena Zhivun committed

Elena Zhivun's avatar
Elena Zhivun committed
static struct tx_packet * tx_queue_get_next(void) {
	struct tx_packet * result;
Elena Zhivun's avatar
Elena Zhivun committed
	unsigned long flags;
Elena Zhivun's avatar
Elena Zhivun committed
	if (tx_queue_is_empty())
		return NULL;

Elena Zhivun's avatar
Elena Zhivun committed
	spin_lock_irqsave(&tx_queue_spinlock, flags);
	result = &tx_queue.packets[tx_queue.tail];
	spin_unlock_irqrestore(&tx_queue_spinlock, flags);
	return result;
Elena Zhivun's avatar
Elena Zhivun committed
// waits for EOP, returns 0 if it is received correctly, -1 otherwise
Elena Zhivun's avatar
Elena Zhivun committed
bool is_valid_eop(struct flxnet_peer *peer, unsigned long timeout) {
Elena Zhivun's avatar
Elena Zhivun committed
	t_fifo_word eop;
	eop = 0;
	while (true) {
		if (is_word_available(peer)) {
			eop = recv_word(peer);
			break;
Elena Zhivun's avatar
Elena Zhivun committed
		}

Elena Zhivun's avatar
Elena Zhivun committed
		if (jiffies > timeout) {
Elena Zhivun's avatar
Elena Zhivun committed
			break;
Elena Zhivun's avatar
Elena Zhivun committed
	if (jiffies > timeout || eop != FLXNET_EOP) {
Elena Zhivun's avatar
Elena Zhivun committed
		return false;
Elena Zhivun's avatar
Elena Zhivun committed
	return true;
}


// check the mac address of the received packet
// and update the peer mac if valid
Elena Zhivun's avatar
Elena Zhivun committed
void update_mac(struct flxnet_peer *peer, struct sk_buff *skb) {
	struct ethhdr *mac_hdr;

	mac_hdr = eth_hdr(skb);
	if (!is_valid_ether_addr(mac_hdr->h_source)) {
		pr_info("flxnet_dev: mac address of received packet is invalid\n");
Elena Zhivun's avatar
Elena Zhivun committed
	if (memcmp(peer->mac_addr, mac_hdr->h_source, ETH_ALEN) != 0) {
Elena Zhivun's avatar
Elena Zhivun committed
		if (message_level & NETIF_MSG_DRV) {
			pr_info("flxnet_dev: updating peer mac address: was %pM, new %pM\n",
Elena Zhivun's avatar
Elena Zhivun committed
				peer->mac_addr, mac_hdr->h_source);
		}
		memcpy(peer->mac_addr, mac_hdr->h_source, ETH_ALEN);
	}	
}


// there is no memory to create skb, so deque the packet
// and drop it
Elena Zhivun's avatar
Elena Zhivun committed
int drop_rx_packet(struct flxnet_peer *peer, int length_words) {
	int num_words;
	unsigned long timeout;

Elena Zhivun's avatar
Elena Zhivun committed
	if (length_words == 0) {
Elena Zhivun's avatar
Elena Zhivun committed
		if (message_level & NETIF_MSG_DRV)
			pr_err("flxnet_dev: attempting to drop 0-length packet\n");
Elena Zhivun's avatar
Elena Zhivun committed
		flxnet->stats.rx_frame_errors += 1;
Elena Zhivun's avatar
Elena Zhivun committed
		return RX_INVALID_ARG;
	}

Elena Zhivun's avatar
Elena Zhivun committed
	timeout = jiffies + msecs_to_jiffies(RX_TIMEOUT_MS);

	num_words = 0;
	while (true) {
Elena Zhivun's avatar
Elena Zhivun committed
		while (is_word_available(peer)) {
			recv_word(peer);
			num_words += 1;
Elena Zhivun's avatar
Elena Zhivun committed
			if (num_words == length_words) {
				goto packet_rx_done;
			}
Elena Zhivun's avatar
Elena Zhivun committed
		}

Elena Zhivun's avatar
Elena Zhivun committed
		if (jiffies > timeout) {
Elena Zhivun's avatar
Elena Zhivun committed
			goto packet_rx_done;
Elena Zhivun's avatar
Elena Zhivun committed
packet_rx_done:

Elena Zhivun's avatar
Elena Zhivun committed
	flxnet->stats.rx_bytes += num_words * BYTES_IN_WORD;
Elena Zhivun's avatar
Elena Zhivun committed
	if (jiffies > timeout) {
		return RX_TIMEOUT;	
	}

	if (num_words != length_words) {
		return RX_TRUNCATED;
	}

Elena Zhivun's avatar
Elena Zhivun committed
	if (!is_valid_eop(peer, timeout)) {
Elena Zhivun's avatar
Elena Zhivun committed
		flxnet->stats.rx_frame_errors += 1;
		return RX_INVALID_EOP;
	}
	
	return RX_NO_MEMORY;
}


Elena Zhivun's avatar
Elena Zhivun committed
int recv_packet(struct flxnet_peer *peer, int length_words) {
	struct sk_buff *skb;	
	int num_words;
Elena Zhivun's avatar
Elena Zhivun committed
	t_fifo_word rx_word;
	unsigned long timeout;
Elena Zhivun's avatar
Elena Zhivun committed
	bool low_buffer;	
Elena Zhivun's avatar
Elena Zhivun committed
	if (length_words == 0 || length_words > MAX_PACKET_LEN) {
Elena Zhivun's avatar
Elena Zhivun committed
		if (message_level & NETIF_MSG_DRV)
			pr_err("flxnet_dev: invalid packet length (%d words)\n",
Elena Zhivun's avatar
Elena Zhivun committed
				length_words);
Elena Zhivun's avatar
Elena Zhivun committed
		flxnet->stats.rx_frame_errors += 1;
		return RX_INVALID_ARG;
	}	

Elena Zhivun's avatar
Elena Zhivun committed
	skb = netdev_alloc_skb(flxnet, length_words * BYTES_IN_WORD);
Elena Zhivun's avatar
Elena Zhivun committed
		flxnet->stats.rx_dropped++;
Elena Zhivun's avatar
Elena Zhivun committed
		if (message_level & NETIF_MSG_RX_ERR)
			pr_err("flxnet_dev: dropped RX packet, low memory\n");
Elena Zhivun's avatar
Elena Zhivun committed
		drop_rx_packet(peer, length_words);
		return RX_NO_MEMORY;
Elena Zhivun's avatar
Elena Zhivun committed
	// socket buffer is available, receive the packet
Elena Zhivun's avatar
Elena Zhivun committed
	timeout = jiffies + msecs_to_jiffies(RX_TIMEOUT_MS);
Elena Zhivun's avatar
Elena Zhivun committed
	low_buffer = false;
Elena Zhivun's avatar
Elena Zhivun committed
	if (message_level & NETIF_MSG_PKTDATA)
		pr_info("flxnet_dev: received packet data:\n");
Elena Zhivun's avatar
Elena Zhivun committed

	while (true) {
Elena Zhivun's avatar
Elena Zhivun committed
		while (is_word_available(peer)) {
			rx_word = recv_word(peer);
Elena Zhivun's avatar
Elena Zhivun committed
			memcpy(skb_put(skb, BYTES_IN_WORD), &rx_word, BYTES_IN_WORD);
			num_words += 1;
Elena Zhivun's avatar
Elena Zhivun committed
			if (message_level & NETIF_MSG_PKTDATA)
				pr_info("%08X\n", rx_word);
			if (num_words == length_words)
				goto packet_rx_done;
Elena Zhivun's avatar
Elena Zhivun committed
		}

Elena Zhivun's avatar
Elena Zhivun committed
		low_buffer = true;

Elena Zhivun's avatar
Elena Zhivun committed
		if (jiffies > timeout) {
Elena Zhivun's avatar
Elena Zhivun committed
			goto packet_rx_done;
Elena Zhivun's avatar
Elena Zhivun committed
packet_rx_done:

Elena Zhivun's avatar
Elena Zhivun committed
	flxnet->stats.rx_bytes += num_words * BYTES_IN_WORD;
Elena Zhivun's avatar
Elena Zhivun committed
	if (jiffies > timeout) {
		dev_kfree_skb(skb);
Elena Zhivun's avatar
Elena Zhivun committed
		flxnet->stats.rx_errors++;
Elena Zhivun's avatar
Elena Zhivun committed
		if (message_level & NETIF_MSG_RX_ERR)
			pr_err("flxnet_dev: error receiving packet, timeout\n");
		return RX_TIMEOUT;
	}

	if (num_words != length_words) {
		dev_kfree_skb(skb);
Elena Zhivun's avatar
Elena Zhivun committed
		flxnet->stats.rx_errors++;
Elena Zhivun's avatar
Elena Zhivun committed
		if (message_level & NETIF_MSG_RX_ERR)
			pr_err_ratelimited("flxnet_dev: error receiving packet, missing data\n");
Elena Zhivun's avatar
Elena Zhivun committed
	if (!is_valid_eop(peer, timeout)) {
		dev_kfree_skb(skb);
Elena Zhivun's avatar
Elena Zhivun committed
		flxnet->stats.rx_errors++;
Elena Zhivun's avatar
Elena Zhivun committed
		if (message_level & NETIF_MSG_RX_ERR)
			pr_err_ratelimited("flxnet_dev: received a packet with invalid trailer\n");
Elena Zhivun's avatar
Elena Zhivun committed
		flxnet->stats.rx_frame_errors += 1;
		return RX_INVALID_EOP;
	}
	
	skb->protocol = eth_type_trans(skb, flxnet);
Elena Zhivun's avatar
Elena Zhivun committed
	skb->ip_summed = CHECKSUM_UNNECESSARY;
Elena Zhivun's avatar
Elena Zhivun committed
	flxnet->stats.rx_packets += 1;	
	update_mac(peer, skb);

Elena Zhivun's avatar
Elena Zhivun committed
	if (message_level & NETIF_MSG_RX_STATUS)
		pr_info("flxnet_dev: received packet (%d bytes, %d words) from peer %p\n",
Elena Zhivun's avatar
Elena Zhivun committed
			skb->len, length_words, peer);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,14,0)
	netif_rx(skb);
#else
Elena Zhivun's avatar
Elena Zhivun committed
	netif_rx_ni(skb);
Elena Zhivun's avatar
Elena Zhivun committed

	if (low_buffer)
		return RX_LOW_BUFFER;
	else
		return RX_SUCCESS;
}

/*
	Timer to wake up FIFO polling thread periodically
*/
void recv_timer_function(struct timer_list *timer_list) {
Elena Zhivun's avatar
Elena Zhivun committed
	wake_up_interruptible(&recv_queue);	
	mod_timer(timer_list, jiffies + msecs_to_jiffies(POLL_PERIOD_MS));
Elena Zhivun's avatar
Elena Zhivun committed
}


// returns true if the packet can be received by at least one peer
bool can_transmit_packets(void) {
Elena Zhivun's avatar
Elena Zhivun committed
	struct flxnet_peer *peer;

	// if there are no peers, packets can be marked as "sent" and 
	// dropped immediately
Elena Zhivun's avatar
Elena Zhivun committed
	if (atomic_read(&peer_count) == 0)
Elena Zhivun's avatar
Elena Zhivun committed
		return true;
Elena Zhivun's avatar
Elena Zhivun committed

	mutex_lock(&peer_mutex);
	list_for_each_entry(peer, &peers, list_head) {
Elena Zhivun's avatar
Elena Zhivun committed
		if (!peer->reserved) continue;
Elena Zhivun's avatar
Elena Zhivun committed
		if (is_magic_correct(peer) && can_accept_block(peer)) {
			mutex_unlock(&peer_mutex);
			return true;
		}
	}
	mutex_unlock(&peer_mutex);
	return false;
Elena Zhivun's avatar
Elena Zhivun committed
/*
	Iterate over all peers, retrieve all packets under the assumption that
	CPU can receive them faster than FIFO fills up
*/
void rx_all_packets(void) {	
Elena Zhivun's avatar
Elena Zhivun committed
	struct flxnet_peer *peer;
	struct flxnet_peer *tmp;
	int length_words, rv;
Elena Zhivun's avatar
Elena Zhivun committed
	t_fifo_word sop;
Elena Zhivun's avatar
Elena Zhivun committed

Elena Zhivun's avatar
Elena Zhivun committed
	mutex_lock(&peer_mutex);
	list_for_each_entry_safe(peer, tmp, &peers, list_head) {
		if (!peer->reserved) continue;	

		if (!is_magic_correct(peer)) {
			pr_err("flxnet_dev: invalid magic in %s, removing peer %p from the network\n",
Elena Zhivun's avatar
Elena Zhivun committed
				__FUNCTION__, peer);
			flxnet_remove_peer(peer);
			continue;
		}

		while (is_block_available(peer)) {
			if (message_level & NETIF_MSG_RX_STATUS)
				pr_info("flxnet_dev: peer %p has data block available\n", peer);
Elena Zhivun's avatar
Elena Zhivun committed

			sop = recv_word(peer);
			if ((sop & FLXNET_SOP_MASK) != FLXNET_SOP) {
				if (message_level & NETIF_MSG_RX_ERR)
					pr_err("flxnet_dev: invalid SOP: %#010X\n", sop);
Elena Zhivun's avatar
Elena Zhivun committed
				flxnet->stats.rx_frame_errors += 1;
				continue;
			}

			// found the beginning of a packet
			length_words = sop & FLXNET_LEN_MASK;
			rv = recv_packet(peer, length_words); 				
			if (rv == RX_TIMEOUT || rv == RX_LOW_BUFFER) {
				// receiving this packet took too long
				// move on to the next peer
				if (message_level & NETIF_MSG_RX_STATUS)
					pr_err("flxnet_dev: low buffer on peer %p, abort reading\n", peer);
Elena Zhivun's avatar
Elena Zhivun committed
				break;
			} else if (rv == RX_NO_MEMORY) {
				mutex_unlock(&peer_mutex);
				pr_err("flxnet_dev: RX thread is out of memory\n");
Elena Zhivun's avatar
Elena Zhivun committed
				return;
Elena Zhivun's avatar
Elena Zhivun committed
			}
		}
	}	
	mutex_unlock(&peer_mutex);
}

Elena Zhivun's avatar
Elena Zhivun committed

/*
	Background task for polling the FIFOs and reading out packets
	I don't want these in NAPI poll because this can be slow and
	hence potentially interfere with other network devices
*/
int send_recv_thread(void *data) {
	DEFINE_WAIT(wait);

Elena Zhivun's avatar
Elena Zhivun committed
	if (message_level & NETIF_MSG_DRV)
		pr_info("flxnet_dev: entered FIFO polling thread\n");

	while (!kthread_should_stop()) {
Elena Zhivun's avatar
Elena Zhivun committed
		if (tx_queue_is_empty()) {
			prepare_to_wait(&recv_queue, &wait, TASK_INTERRUPTIBLE);
			schedule();
			finish_wait(&recv_queue, &wait);
		}		
Elena Zhivun's avatar
Elena Zhivun committed
		// if (message_level & NETIF_MSG_INTR)
Elena Zhivun's avatar
Elena Zhivun committed
		// 	pr_info("flxnet: wake up worker thread %s\n", __FUNCTION__);

		tx_all_packets();
Elena Zhivun's avatar
Elena Zhivun committed
		if (kthread_should_stop())
			break;
Elena Zhivun's avatar
Elena Zhivun committed
		if (atomic_read(&peer_count) == 0)
Elena Zhivun's avatar
Elena Zhivun committed
			continue;		
Elena Zhivun's avatar
Elena Zhivun committed

		// restart TX queue if it's stalled
		if (!netif_running(flxnet) && can_transmit_packets()) {
Elena Zhivun's avatar
Elena Zhivun committed
			if (message_level & NETIF_MSG_DRV)
				pr_info("flxnet_dev: wake up net if queue (%s)\n", __FUNCTION__);
Elena Zhivun's avatar
Elena Zhivun committed
			netif_wake_queue(flxnet);
Elena Zhivun's avatar
Elena Zhivun committed
		}

Elena Zhivun's avatar
Elena Zhivun committed
		rx_all_packets();
Elena Zhivun's avatar
Elena Zhivun committed
	if (message_level & NETIF_MSG_IFDOWN)
		pr_info("flxnet_dev: stopping %s\n", __FUNCTION__);
	return 0;
}

int open(struct net_device *dev) {
Elena Zhivun's avatar
Elena Zhivun committed
	mutex_lock(&flxnet_mutex);
Elena Zhivun's avatar
Elena Zhivun committed

Elena Zhivun's avatar
Elena Zhivun committed
	flxnet_reserve_all_peers(); // reserve the modules that supplied the peers	
	tx_queue_reset(); // clear TX queue
	// enable RX side
Elena Zhivun's avatar
Elena Zhivun committed
	if (message_level & NETIF_MSG_IFUP)
		pr_info("flxnet_dev: initialize RX thread and timer");
	recv_task = kthread_create(send_recv_thread, NULL, "flxnet RX thread");
	if (IS_ERR(recv_task)) {
Elena Zhivun's avatar
Elena Zhivun committed
		mutex_unlock(&flxnet_mutex);
		pr_err("flxnet_dev: failed to create RX thread in %s", __FUNCTION__);
Elena Zhivun's avatar
Elena Zhivun committed
	}
	wake_up_process(recv_task);
Elena Zhivun's avatar
Elena Zhivun committed
	mod_timer(&recv_timer, jiffies + msecs_to_jiffies(POLL_PERIOD_MS));
Elena Zhivun's avatar
Elena Zhivun committed
	if (message_level & NETIF_MSG_IFUP)
		pr_info("flxnet_dev: start netif queue");
	netif_start_queue(dev);
Elena Zhivun's avatar
Elena Zhivun committed

Elena Zhivun's avatar
Elena Zhivun committed
	mutex_unlock(&flxnet_mutex);
Elena Zhivun's avatar
Elena Zhivun committed
int stop(struct net_device *dev) {
	mutex_lock(&flxnet_mutex);
Elena Zhivun's avatar
Elena Zhivun committed

	// disable RX side	
Elena Zhivun's avatar
Elena Zhivun committed
	if (message_level & NETIF_MSG_IFDOWN)
		pr_info("flxnet_dev: stop RX thread");	
	wake_up_interruptible(&recv_queue);
	kthread_stop(recv_task);
	del_timer_sync(&recv_timer);
Elena Zhivun's avatar
Elena Zhivun committed

	// disable TX side
Elena Zhivun's avatar
Elena Zhivun committed
	if (message_level & NETIF_MSG_IFDOWN)
		pr_info("flxnet_dev: stop netif queue");
Elena Zhivun's avatar
Elena Zhivun committed
	netif_stop_queue(dev);
Elena Zhivun's avatar
Elena Zhivun committed

Elena Zhivun's avatar
Elena Zhivun committed
	tx_queue_reset(); // clear TX queue
	flxnet_release_all_peers(); // free the modules that supplied the peers
Elena Zhivun's avatar
Elena Zhivun committed

Elena Zhivun's avatar
Elena Zhivun committed
	mutex_unlock(&flxnet_mutex);
Elena Zhivun's avatar
Elena Zhivun committed
static inline void drop_this_packet(void) {
	tx_queue_pop();
	flxnet->stats.tx_dropped += 1;
}


int transmit_packet(struct flxnet_peer *peer, struct tx_packet *packet) {
Elena Zhivun's avatar
Elena Zhivun committed

	int num_words, i;
Elena Zhivun's avatar
Elena Zhivun committed
	t_fifo_word data;
Elena Zhivun's avatar
Elena Zhivun committed

	if (!is_magic_correct(peer)) {
		pr_err("flxnet_dev: invalid magic in %s, removing peer %p from the network,"
			" and dropping the packet", __FUNCTION__, peer);
Elena Zhivun's avatar
Elena Zhivun committed
		flxnet_remove_peer(peer);		
Elena Zhivun's avatar
Elena Zhivun committed
		flxnet->stats.tx_dropped += 1;
		return TX_ERROR;
	if (!can_accept_block(peer))
		return TX_BUSY;
	num_words = packet->num_words;
Elena Zhivun's avatar
Elena Zhivun committed

Elena Zhivun's avatar
Elena Zhivun committed
	if (message_level & NETIF_MSG_PKTDATA)
		pr_info("flxnet_dev: transmitting data:");
	send_word(peer, FLXNET_SOP | num_words);
	for (i = 0; i < num_words; i++) {
Elena Zhivun's avatar
Elena Zhivun committed
		memcpy(&data, &packet->data[i * BYTES_IN_WORD], BYTES_IN_WORD);
		send_word(peer, data);
		if (message_level & NETIF_MSG_PKTDATA)
			pr_info("%08x\n", data);
	send_eop(peer);
	if (message_level & NETIF_MSG_TX_DONE)
		pr_info("flxnet_dev: sent packet (%d words) to peer %p", num_words, peer);

	flxnet->stats.tx_packets += 1;
	flxnet->stats.tx_bytes += num_words * BYTES_IN_WORD;
	return TX_SUCCESS;
int send_broadcast(struct tx_packet *packet) {
Elena Zhivun's avatar
Elena Zhivun committed
	int rv;	
Elena Zhivun's avatar
Elena Zhivun committed
	struct flxnet_peer *peer, *tmp;
Elena Zhivun's avatar
Elena Zhivun committed

	rv = TX_BUSY;
	mutex_lock(&peer_mutex);
Elena Zhivun's avatar
Elena Zhivun committed
	list_for_each_entry_safe(peer, tmp, &peers, list_head) {
Elena Zhivun's avatar
Elena Zhivun committed
		if (!peer->reserved) continue;

Elena Zhivun's avatar
Elena Zhivun committed
		if (transmit_packet(peer, packet) != TX_BUSY)			
			rv = TX_SUCCESS;
	}
	mutex_unlock(&peer_mutex);
Elena Zhivun's avatar
Elena Zhivun committed
	if (rv == TX_SUCCESS) tx_queue_pop();

	if ((rv == TX_BUSY) && (message_level & NETIF_MSG_TX_ERR)) {
		pr_info("flxnet_dev: no peer has space in FIFO (total %d peers)",
Elena Zhivun's avatar
Elena Zhivun committed
			atomic_read(&peer_count));
// send out the latest buffered packet 
int send_packet(struct tx_packet *packet) {
	int num_peers, rv;
Elena Zhivun's avatar
Elena Zhivun committed
	struct flxnet_peer *peer, *tmp;
Elena Zhivun's avatar
Elena Zhivun committed

	num_peers = atomic_read(&peer_count);
	if (num_peers == 0) {
Elena Zhivun's avatar
Elena Zhivun committed
		drop_this_packet();
		if (message_level & NETIF_MSG_TX_ERR)
			pr_info("flxnet_dev: discard packet - no peers available");
		return TX_SUCCESS;
	}
Elena Zhivun's avatar
Elena Zhivun committed

Elena Zhivun's avatar
Elena Zhivun committed
	packet->attempts += 1;
	if (packet->attempts > MAX_RETRY_ATTEMPTS) {
Elena Zhivun's avatar
Elena Zhivun committed
		drop_this_packet();
		if (message_level & NETIF_MSG_TX_ERR)
			pr_info("flxnet_dev: packet %p is dropped after %d attempts",
Elena Zhivun's avatar
Elena Zhivun committed
				packet, packet->attempts);
		return TX_ERROR;
	}
Elena Zhivun's avatar
Elena Zhivun committed

	if (num_peers == 1 || is_multicast_ether_addr(packet->dest_mac))
		return send_broadcast(packet);

	mutex_lock(&peer_mutex);
Elena Zhivun's avatar
Elena Zhivun committed
	list_for_each_entry_safe(peer, tmp, &peers, list_head) {
		if (!peer->reserved) continue;

		if (memcmp(peer->mac_addr, packet->dest_mac, ETH_ALEN) == 0) {
			// matching peer is found in the table
Elena Zhivun's avatar
Elena Zhivun committed
			rv = transmit_packet(peer, packet);
			mutex_unlock(&peer_mutex);
Elena Zhivun's avatar
Elena Zhivun committed
			if (rv != TX_BUSY) tx_queue_pop();
			return rv;
Elena Zhivun's avatar
Elena Zhivun committed
		}
	}
	mutex_unlock(&peer_mutex);
Elena Zhivun's avatar
Elena Zhivun committed

	// matching peer was not found on the list
	return send_broadcast(packet);
Elena Zhivun's avatar
Elena Zhivun committed

static void tx_all_packets(void) {
	struct tx_packet *packet;

	while (true) {
		packet = tx_queue_get_next();
		if (packet == NULL) break;
		if (send_packet(packet) == TX_BUSY) break;
	}
}
Elena Zhivun's avatar
Elena Zhivun committed

Elena Zhivun's avatar
Elena Zhivun committed

int hard_start_xmit(struct sk_buff *skb, struct net_device *dev) {

Elena Zhivun's avatar
Elena Zhivun committed
	netif_trans_update(dev);
Elena Zhivun's avatar
Elena Zhivun committed
	if (tx_queue_try_push(skb) == TX_BUSY) {
Elena Zhivun's avatar
Elena Zhivun committed
		if (message_level & NETIF_MSG_TX_ERR)
			pr_info("flxnet_dev: TX packet queue is full");
		netif_stop_queue(flxnet);
		return NETDEV_TX_BUSY;
	}
	dev_kfree_skb(skb);
	return NETDEV_TX_OK;
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,18,0)
Elena Zhivun's avatar
Elena Zhivun committed
static void tx_timeout (struct net_device *dev, unsigned int txqueue)
#else
static void tx_timeout (struct net_device *dev)
#endif
Elena Zhivun's avatar
Elena Zhivun committed
	flxnet->stats.tx_errors++;
Elena Zhivun's avatar
Elena Zhivun committed
	flxnet->stats.tx_dropped++;
Elena Zhivun's avatar
Elena Zhivun committed
	netif_trans_update(dev);
Elena Zhivun's avatar
Elena Zhivun committed
	if (message_level & NETIF_MSG_DRV)
		pr_info("flxnet_dev: wake up netif queue (%s)", __FUNCTION__);
Elena Zhivun's avatar
Elena Zhivun committed
	netif_wake_queue(dev);
Elena Zhivun's avatar
Elena Zhivun committed
static struct net_device_stats * get_stats(struct net_device *dev)
{
	return &dev->stats;
}
Elena Zhivun's avatar
Elena Zhivun committed

static void flx_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *info)
{
	strlcpy(info->driver, DRV_NAME, sizeof(info->driver));
}

static int flx_get_link_ksettings(struct net_device *dev,
				  struct ethtool_link_ksettings *cmd)
{
	cmd->base.speed = SPEED_10;
	cmd->base.duplex = DUPLEX_FULL;
	cmd->base.port = PORT_OTHER;
	return 0;
}

static int flx_set_link_ksettings(struct net_device *dev,
				  const struct ethtool_link_ksettings *cmd)
{
	return -EINVAL;
}

static u32 flx_get_link(struct net_device *dev)
{
	return (u32)(atomic_read(&peer_count) != 0);
}

static u32 flx_get_msglevel(struct net_device *dev)
{
Elena Zhivun's avatar
Elena Zhivun committed
	return message_level;
Elena Zhivun's avatar
Elena Zhivun committed
}

static void flx_set_msglevel(struct net_device *dev, u32 v)
{
Elena Zhivun's avatar
Elena Zhivun committed
	message_level = v;
Elena Zhivun's avatar
Elena Zhivun committed
}


static const struct ethtool_ops ethtool_ops = {
	.get_drvinfo = flx_get_drvinfo,
	.get_link = flx_get_link,
	.get_msglevel = flx_get_msglevel,
	.set_msglevel = flx_set_msglevel,
	.get_link_ksettings = flx_get_link_ksettings,
	.set_link_ksettings = flx_set_link_ksettings,
};


static const struct net_device_ops flx_netdev_ops = {
	.ndo_open            = open,
	.ndo_stop            = stop,
	.ndo_start_xmit      = hard_start_xmit,
Elena Zhivun's avatar
Elena Zhivun committed
	.ndo_get_stats       = get_stats,
	.ndo_tx_timeout 	 = tx_timeout,
	.ndo_set_mac_address = eth_mac_addr,
	.ndo_validate_addr	 = eth_validate_addr,
Elena Zhivun's avatar
Elena Zhivun committed
};

union zynq_hw_addr {
	char hw_addr[ETH_ALEN];
	struct {
		u32 version;
Elena Zhivun's avatar
Elena Zhivun committed
		u32 idcode;
union zynq_hw_addr zynq_data;
Elena Zhivun's avatar
Elena Zhivun committed

// register the network device
int register_network(int index) {
Elena Zhivun's avatar
Elena Zhivun committed
	int rv;
Elena Zhivun's avatar
Elena Zhivun committed
	mutex_init(&peer_mutex);
	mutex_init(&flxnet_mutex);
	atomic_set(&peer_count, 0);	
Elena Zhivun's avatar
Elena Zhivun committed
	message_level = 0;
Elena Zhivun's avatar
Elena Zhivun committed
	// allocate ring buffer for outgoing packets
	tx_queue.packets = kcalloc(tx_queue_size, sizeof(struct tx_packet), GFP_KERNEL);
	if (IS_ERR(tx_queue.packets)) {
		pr_err("flxnet_dev: can't allocate memory for the TX packet queue");
		rv = -ENODEV;
		goto err_alloc_tx_queue;
	}
Elena Zhivun's avatar
Elena Zhivun committed
	tx_queue.head = 0;
	tx_queue.tail = 0;
Elena Zhivun's avatar
Elena Zhivun committed
	flxnet = alloc_etherdev(0);
	if (IS_ERR(flxnet)) {
		pr_err("flxnet_dev: can't allocate memory for the network device structure");
Elena Zhivun's avatar
Elena Zhivun committed
		rv = -ENODEV;
		goto err_alloc_etherdev;
	strlcpy(flxnet->name, "flxnet%d", index);
Elena Zhivun's avatar
Elena Zhivun committed
	flxnet->netdev_ops = &flx_netdev_ops;
	flxnet->ethtool_ops = &ethtool_ops;
Elena Zhivun's avatar
Elena Zhivun committed
	flxnet->watchdog_timeo = msecs_to_jiffies(TX_TIMEOUT_MS);