[PATCH] Add PCINet Driver

Ira W. Snyder iws at ovro.caltech.edu
Tue Aug 19 00:40:40 CEST 2008


This adds a virtual network interface that uses the PCI bus for
communication. This is extremely useful for boards in PCISLAVE mode, so you
do not need extra cables to communicate with them.

There is a corresponding Linux driver to be run on the host system.

Signed-off-by: Ira W. Snyder <iws at ovro.caltech.edu>
---
 cpu/mpc83xx/cpu.c    |    4 +-
 drivers/net/Makefile |    1 +
 drivers/net/pcinet.c |  503 ++++++++++++++++++++++++++++++++++++++++++++++++++
 drivers/net/pcinet.h |   73 ++++++++
 4 files changed, 580 insertions(+), 1 deletions(-)
 create mode 100644 drivers/net/pcinet.c
 create mode 100644 drivers/net/pcinet.h

diff --git a/cpu/mpc83xx/cpu.c b/cpu/mpc83xx/cpu.c
index aa9b18d..79c0363 100644
--- a/cpu/mpc83xx/cpu.c
+++ b/cpu/mpc83xx/cpu.c
@@ -364,6 +364,8 @@ int cpu_eth_init(bd_t *bis)
 #if defined(CONFIG_TSEC_ENET)
 	tsec_standard_init(bis);
 #endif
-
+#ifdef CONFIG_PCINET_ETHERNET
+	pcinet_initialize(bis, 0, "PCINET");
+#endif
 	return 0;
 }
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index 439c354..2435e63 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -52,6 +52,7 @@ COBJS-$(CONFIG_NETCONSOLE) += netconsole.o
 COBJS-$(CONFIG_DRIVER_NS7520_ETHERNET) += ns7520_eth.o
 COBJS-$(CONFIG_NS8382X) += ns8382x.o
 COBJS-$(CONFIG_DRIVER_NS9750_ETHERNET) += ns9750_eth.o
+COBJS-$(CONFIG_PCINET_ETHERNET) += pcinet.o
 COBJS-$(CONFIG_PCNET) += pcnet.o
 COBJS-$(CONFIG_PLB2800_ETHER) += plb2800_eth.o
 COBJS-$(CONFIG_DRIVER_RTL8019) += rtl8019.o
diff --git a/drivers/net/pcinet.c b/drivers/net/pcinet.c
new file mode 100644
index 0000000..b86383f
--- /dev/null
+++ b/drivers/net/pcinet.c
@@ -0,0 +1,503 @@
+/*
+ * PCINet Virtual Ethernet over PCI driver
+ *
+ * This software may be used and distributed according to the
+ * terms of the GNU General Public License, Version 2, incorporated
+ * herein by reference.
+ *
+ * Copyright (c) 2008, Ira W. Snyder <iws at ovro.caltech.edu>
+ */
+
+#include <config.h>
+#include <common.h>
+#include <malloc.h>
+#include <net.h>
+#include <command.h>
+#include <asm/io.h>
+#include <asm/errno.h>
+
+#include <asm/mmu.h>
+#include <mpc83xx.h>
+#include <asm/mpc8349_pci.h>
+
+#include "pcinet.h"
+
+struct wqt_dev;
+typedef void (*wqt_irqhandler_t)(struct wqt_dev *);
+
+struct wqt_dev {
+	wqt_irqhandler_t net_rx_packet_handler;
+	wqt_irqhandler_t net_tx_complete_handler;
+	int state;
+
+	void *netregs;
+
+	cbd_t *rx_base;
+	cbd_t *tx_base;
+
+	cbd_t *cur_rx;
+	cbd_t *cur_tx;
+	cbd_t *dirty_tx;
+	int tx_free;
+};
+
+#define W32(addr, b) out_le32((volatile u32 *)(addr), (b))
+#define R32(addr) in_le32((volatile u32 *)(addr))
+
+#define W32BE(addr, b) out_be32((volatile u32 *)(addr), (b))
+#define R32BE(addr) in_be32((volatile u32 *)(addr))
+
+/*----------------------------------------------------------------------------*/
+/* Status Bits                                                                */
+/*----------------------------------------------------------------------------*/
+
+static void status_setbit(u32 bit)
+{
+	volatile immap_t   *immr = (volatile immap_t *)CONFIG_SYS_IMMR;
+	volatile dma83xx_t *dma = &immr->dma;
+
+	W32(&dma->omr1, R32(&dma->omr1) | bit);
+}
+
+static void status_clrbit(u32 bit)
+{
+	volatile immap_t   *immr = (volatile immap_t *)CONFIG_SYS_IMMR;
+	volatile dma83xx_t *dma = &immr->dma;
+
+	W32(&dma->omr1, R32(&dma->omr1) & ~bit);
+}
+
+static u32 status_remote_testbit(u32 bit)
+{
+	volatile immap_t   *immr = (volatile immap_t *)CONFIG_SYS_IMMR;
+	volatile dma83xx_t *dma = &immr->dma;
+
+	return R32(&dma->imr1) & bit;
+}
+
+/*----------------------------------------------------------------------------*/
+/* Doorbell                                                                   */
+/*----------------------------------------------------------------------------*/
+
+static void doorbell_set(u32 bit)
+{
+	volatile immap_t   *immr = (volatile immap_t *)CONFIG_SYS_IMMR;
+	volatile dma83xx_t *dma = &immr->dma;
+
+	W32(&dma->odr, bit);
+}
+
+/*----------------------------------------------------------------------------*/
+/* DMA                                                                        */
+/*----------------------------------------------------------------------------*/
+
+/*
+ * Set up a 1GB outbound window starting at PCI address 0x0
+ */
+static void setup_outbound_window(void)
+{
+	volatile immap_t *immr = (volatile immap_t *)CONFIG_SYS_IMMR;
+	volatile law83xx_t *pci_law = immr->sysconf.pcilaw;
+	volatile pot83xx_t *pci_pot = immr->ios.pot;
+
+	W32BE(&pci_law[0].ar, LAWAR_EN | LAWAR_SIZE_1G);
+	W32BE(&pci_pot[0].pocmr, POCMR_EN | POCMR_PREFETCH_EN
+			       | (POCMR_CM_1G & POCMR_CM_MASK));
+	W32BE(&pci_pot[0].potar, 0x0);
+}
+
+static void dma_pci_to_buf(u32 dst, u32 src, size_t len)
+{
+	volatile immap_t *immr = (volatile immap_t *)CONFIG_SYS_IMMR;
+	volatile dma83xx_t *dma = &immr->dma;
+
+	/* Wait for the DMA controller to become free */
+	while (R32(&dma->dmasr0) & DMA_CHANNEL_BUSY)
+		udelay(100);
+
+	/* Program the DMA controller */
+	W32(&dma->dmasar0, 0x80000000 + src);
+	W32(&dma->dmadar0, dst);
+	W32(&dma->dmabcr0, len);
+
+	/* Start the transfer */
+	W32(&dma->dmamr0, DMA_CHANNEL_TRANSFER_MODE_DIRECT | DMA_CHANNEL_SNOOP);
+	W32(&dma->dmamr0, DMA_CHANNEL_TRANSFER_MODE_DIRECT | DMA_CHANNEL_SNOOP
+			| DMA_CHANNEL_START);
+
+	while (R32(&dma->dmasr0) & DMA_CHANNEL_BUSY)
+		udelay(100);
+}
+
+static void dma_buf_to_pci(u32 dst, u32 src, size_t len)
+{
+	volatile immap_t *immr = (volatile immap_t *)CONFIG_SYS_IMMR;
+	volatile dma83xx_t *dma = &immr->dma;
+
+	/* Wait for the DMA controller to become free */
+	while (R32(&dma->dmasr0) & DMA_CHANNEL_BUSY)
+		udelay(100);
+
+	/* Program the DMA controller */
+	W32(&dma->dmasar0, src);
+	W32(&dma->dmadar0, 0x80000000 + dst);
+	W32(&dma->dmabcr0, len);
+
+	/* Start the transfer */
+	W32(&dma->dmamr0, DMA_CHANNEL_TRANSFER_MODE_DIRECT | DMA_CHANNEL_SNOOP);
+	W32(&dma->dmamr0, DMA_CHANNEL_TRANSFER_MODE_DIRECT | DMA_CHANNEL_SNOOP
+			| DMA_CHANNEL_START);
+
+	while (R32(&dma->dmasr0) & DMA_CHANNEL_BUSY)
+		udelay(100);
+}
+
+/*----------------------------------------------------------------------------*/
+/* Helper Functions                                                           */
+/*----------------------------------------------------------------------------*/
+
+static int pcinet_init_netregs(struct wqt_dev *priv)
+{
+	volatile immap_t   *immr = (volatile immap_t *)CONFIG_SYS_IMMR;
+	volatile pcictrl83xx_t *pci_ctrl = &immr->pci_ctrl[0];
+	u32 val;
+
+	/* Check the PCI Inbound Window Attributes Register 0 for a 4k window
+	 * This is PCI BAR1, and will be used as network device registers */
+	val = R32BE(&pci_ctrl->piwar0) & (PIWAR_EN | PIWAR_IWS_4K);
+
+	if (val != (PIWAR_EN | PIWAR_IWS_4K)) {
+		printf("PIWAR0 set up incorrectly (was %x should be %x)\n",
+				val, (PIWAR_EN | PIWAR_IWS_4K));
+		return -ENODEV;
+	}
+
+	/* Allocate the 4k of registers, 4k aligned */
+	priv->netregs = memalign(4096, 4096);
+
+	if (!priv->netregs)
+		return -ENOMEM;
+
+	memset(priv->netregs, 0, 4096);
+
+	/* Write the address into the inbound window address register */
+	W32BE(&pci_ctrl->pitar0, (u32)priv->netregs >> 12);
+
+	return 0;
+}
+
+/*----------------------------------------------------------------------------*/
+/* Interrupt Handling                                                         */
+/*----------------------------------------------------------------------------*/
+
+static void net_rx_packet_handler(struct wqt_dev *priv);
+static void net_tx_complete_handler(struct wqt_dev *priv);
+static void empty_handler(struct wqt_dev *priv);
+
+static void do_reinitialize_ring_pointers(struct wqt_dev *priv)
+{
+	priv->cur_rx = priv->rx_base;
+	priv->cur_tx = priv->tx_base;
+	priv->dirty_tx = priv->tx_base;
+	priv->tx_free = PH_RING_SIZE;
+}
+
+static void do_start_queues(struct wqt_dev *priv)
+{
+	priv->net_rx_packet_handler = net_rx_packet_handler;
+	priv->net_tx_complete_handler = net_tx_complete_handler;
+}
+
+static void do_stop_queues(struct wqt_dev *priv)
+{
+	priv->net_rx_packet_handler = empty_handler;
+	priv->net_tx_complete_handler = empty_handler;
+}
+
+static void empty_handler(struct wqt_dev *priv)
+{
+}
+
+static void net_start_handler(struct wqt_dev *priv)
+{
+	if (priv->state == NET_STATE_RUNNING)
+		return;
+
+	do_reinitialize_ring_pointers(priv);
+	do_start_queues(priv);
+
+	priv->state = NET_STATE_RUNNING;
+}
+
+static void net_stop_handler(struct wqt_dev *priv)
+{
+	if (priv->state == NET_STATE_STOPPED)
+		return;
+
+	do_stop_queues(priv);
+
+	priv->state = NET_STATE_STOPPED;
+}
+
+static void net_rx_packet_handler(struct wqt_dev *priv)
+{
+	cbd_t *bdp;
+	int dirty_idx;
+	u32 pkt_len, src_addr;
+	uchar *pkt;
+
+	bdp = priv->cur_rx;
+
+	while (CBDR_SC(bdp) == BD_MEM_DIRTY) {
+		dirty_idx = bdp - priv->rx_base;
+
+		pkt_len = CBDR_LEN(bdp);
+		src_addr = CBDR_ADDR(bdp);
+
+		pkt = malloc(pkt_len);
+
+		if (pkt != NULL) {
+			dma_pci_to_buf((u32)pkt, src_addr, pkt_len);
+			NetReceive(pkt, pkt_len);
+			free(pkt);
+		}
+
+		CBDW_SC(bdp, BD_MEM_FREE);
+
+		if (dirty_idx == PH_RING_SIZE - 1)
+			bdp = priv->rx_base;
+		else
+			bdp++;
+	}
+
+	priv->cur_rx = bdp;
+
+	doorbell_set(NET_TX_COMPLETE_DBELL);
+}
+
+static void net_tx_complete_handler(struct wqt_dev *priv)
+{
+	cbd_t *bdp;
+	int dirty_idx;
+
+	bdp = priv->dirty_tx;
+
+	while (CBDR_SC(bdp) == BD_MEM_FREE) {
+		dirty_idx = bdp - priv->tx_base;
+
+		priv->tx_free++;
+		CBDW_SC(bdp, BD_MEM_READY);
+
+		if (dirty_idx == PH_RING_SIZE - 1)
+			bdp = priv->tx_base;
+		else
+			bdp++;
+	}
+
+	priv->dirty_tx = bdp;
+}
+
+static void pcinet_interrupt(struct wqt_dev *priv)
+{
+	volatile immap_t   *immr = (volatile immap_t *)CONFIG_SYS_IMMR;
+	volatile dma83xx_t *dma = &immr->dma;
+	u32 idr;
+
+	idr = R32(&dma->idr);
+
+	/* Clear all of the network doorbells */
+	W32(&dma->idr, NET_START_REQ_DBELL | NET_START_ACK_DBELL
+		     | NET_STOP_REQ_DBELL  | NET_STOP_ACK_DBELL
+		     | NET_RX_PACKET_DBELL | NET_TX_COMPLETE_DBELL);
+
+	if (idr & NET_START_REQ_DBELL) {
+		net_start_handler(priv);
+		doorbell_set(NET_START_ACK_DBELL);
+	}
+
+	if (idr & NET_START_ACK_DBELL)
+		net_start_handler(priv);
+
+	if (idr & NET_STOP_REQ_DBELL) {
+		net_stop_handler(priv);
+		doorbell_set(NET_STOP_ACK_DBELL);
+	}
+
+	if (idr & NET_STOP_ACK_DBELL)
+		net_stop_handler(priv);
+
+	if (idr & NET_RX_PACKET_DBELL)
+		priv->net_rx_packet_handler(priv);
+
+	if (idr & NET_TX_COMPLETE_DBELL)
+		priv->net_tx_complete_handler(priv);
+
+	/* This slows down the interrupt routine a bit, since we don't need
+	 * to be running it as absolutely fast as the CPU can handle */
+	udelay(250);
+}
+
+/*----------------------------------------------------------------------------*/
+/* Ethernet Device Functions                                                  */
+/*----------------------------------------------------------------------------*/
+
+/* Initializes data structures and registers for the controller,
+ * and brings the interface up. Returns the link status, meaning:
+ * SUCCESS if the link is up
+ * FAILURE otherwise
+ *
+ * This allows u-boot to find the first active controller
+ */
+static int pcinet_open(struct eth_device *dev, bd_t *bd)
+{
+	struct wqt_dev *priv = dev->priv;
+	int i;
+
+	status_setbit(PCINET_NET_STATUS_RUNNING);
+	do_stop_queues(priv);
+
+	/* If the other side is not running, there isn't much we can do. It's not like we
+	 * can just magically start it */
+	if (!status_remote_testbit(PCINET_NET_STATUS_RUNNING))
+		return 0;
+
+	/* Notify the other side to start */
+	doorbell_set(NET_START_REQ_DBELL);
+
+	/* Wait for 5 seconds to start */
+	for (i=0; priv->state == NET_STATE_STOPPED && i<5000; ++i) {
+		pcinet_interrupt(priv);
+		udelay(1000);
+	}
+
+	if (priv->state == NET_STATE_RUNNING)
+		return 1;
+
+	return 0;
+}
+
+/* Stop the interface completely */
+static void pcinet_stop(struct eth_device *dev)
+{
+	struct wqt_dev *priv = dev->priv;
+	int i;
+
+	status_clrbit(PCINET_NET_STATUS_RUNNING);
+	do_stop_queues(priv);
+
+	if (priv->state == NET_STATE_RUNNING) {
+		doorbell_set(NET_STOP_REQ_DBELL);
+
+		/* Wait 5 seconds to stop */
+		for (i=0; priv->state == NET_STATE_RUNNING && i<5000; ++i) {
+			pcinet_interrupt(priv);
+			udelay(1000);
+		}
+	}
+}
+
+static int pcinet_xmit(struct eth_device *dev, volatile void *packet, int length)
+{
+	struct wqt_dev *priv = dev->priv;
+	u32 dst_addr;
+	int dirty_idx;
+	cbd_t *bdp;
+
+	bdp = priv->cur_tx;
+	dirty_idx = bdp - priv->tx_base;
+
+	if (priv->state == NET_STATE_STOPPED)
+		return 1;
+
+	/* Check for no free TX slots */
+	if (priv->tx_free == 0 || CBDR_SC(bdp) != BD_MEM_READY)
+		return 1;
+
+	dst_addr = CBDR_ADDR(bdp);
+
+	/* DMA the packet into the remote memory */
+	dma_buf_to_pci(dst_addr, (u32)packet, length);
+
+	CBDW_LEN(bdp, length);
+	CBDW_SC(bdp, BD_MEM_DIRTY);
+
+	if (dirty_idx == PH_RING_SIZE - 1)
+		bdp = priv->tx_base;
+	else
+		bdp++;
+
+	priv->cur_tx = bdp;
+	priv->tx_free--;
+
+	if (!status_remote_testbit(PCINET_NET_RXINT_OFF))
+		doorbell_set(NET_RX_PACKET_DBELL);
+
+	return 0;
+}
+
+static int pcinet_recv(struct eth_device *dev)
+{
+	struct wqt_dev *priv = dev->priv;
+	pcinet_interrupt(priv);
+	return -1;
+}
+
+/* Called from net/eth.c to start the ethernet controller */
+int pcinet_initialize(bd_t *bis, int index, char *devname)
+{
+	struct eth_device *dev;
+	struct wqt_dev *priv;
+	int ret;
+
+	/* Allocate the ethernet device and driver-private data */
+	dev = malloc(sizeof(*dev));
+
+	if (!dev)
+		goto out_alloc_eth_device;
+
+	priv = malloc(sizeof(*priv));
+
+	if (!priv)
+		goto out_alloc_priv;
+
+	memset(dev, 0, sizeof(*dev));
+	memset(priv, 0, sizeof(*priv));
+
+	/* Allocate and setup network registers */
+	ret = pcinet_init_netregs(priv);
+
+	if (ret)
+		goto out_init_netregs;
+
+	status_setbit(PCINET_NET_REGISTERS_VALID);
+	priv->rx_base = priv->netregs + PCINET_TXBD_BASE;
+	priv->tx_base = priv->netregs + PCINET_RXBD_BASE;
+
+	/* Set up the outbound window */
+	setup_outbound_window();
+
+	/* Setup ethernet device */
+	sprintf (dev->name, devname);
+	dev->iobase = 0;
+	dev->priv = priv;
+	dev->init = pcinet_open;
+	dev->halt = pcinet_stop;
+	dev->send = pcinet_xmit;
+	dev->recv = pcinet_recv;
+
+	/* Tell u-boot to get the addr from the env */
+	memset(dev->enetaddr, 0, 6);
+
+	eth_register (dev);
+
+	return 1;
+
+out_init_netregs:
+	free(priv);
+out_alloc_priv:
+	free(dev);
+out_alloc_eth_device:
+	return 0;
+}
+
+/* vim: set ts=8 sts=8 sw=8 noet tw=92: */
diff --git a/drivers/net/pcinet.h b/drivers/net/pcinet.h
new file mode 100644
index 0000000..b1851fe
--- /dev/null
+++ b/drivers/net/pcinet.h
@@ -0,0 +1,73 @@
+/*
+ * ONE-LINE DESCRIPTION
+ *
+ * Copyright (c) 2008 Ira W. Snyder <iws at ovro.caltech.edu>
+ *
+ * Heavily inspired by the drivers/net/fs_enet driver
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#ifndef PCINET_H
+#define PCINET_H
+
+//#define PH_MAX_MTU	(1508)
+//#define PH_MAX_MTU	(4082)
+//#define PH_MAX_MTU	(8178)
+//#define PH_MAX_MTU	(16370)
+//#define PH_MAX_MTU	(32754)
+#define PH_MAX_MTU	(65522)
+//#define PH_MAX_MTU	(131058)
+#define PH_MAX_FRSIZE	(PH_MAX_MTU+ETHER_HDR_SIZE)
+
+#define PH_RING_SIZE	64
+
+struct circ_buf_desc {
+	u32 sc;
+	u32 len;
+	u32 addr;
+} __attribute__((__packed__));
+typedef struct circ_buf_desc cbd_t;
+
+/* Buffer Descriptor Accessors */
+#define CBDW_SC(_cbd, _sc) out_le32((volatile u32 *)&(_cbd)->sc, (_sc))
+#define CBDW_LEN(_cbd, _len) out_le32((volatile u32 *)&(_cbd)->len, (_len))
+#define CBDW_ADDR(_cbd, _addr) out_le32((volatile u32 *)&(_cbd)->addr, (_addr))
+
+#define CBDR_SC(_cbd) in_le32((volatile u32 *)&(_cbd)->sc)
+#define CBDR_LEN(_cbd) in_le32((volatile u32 *)&(_cbd)->len)
+#define CBDR_ADDR(_cbd) in_le32((volatile u32 *)&(_cbd)->addr)
+
+/* Buffer Descriptor Registers */
+#define PCINET_TXBD_BASE	0x400
+#define PCINET_RXBD_BASE	0x800
+
+/* Buffer Descriptor Status */
+#define BD_MEM_READY		0x1
+#define BD_MEM_DIRTY		0x2
+#define BD_MEM_FREE		0x3
+
+#define PCINET_UART_RX_ENABLED		(1<<0)
+#define PCINET_NET_STATUS_RUNNING	(1<<1)
+#define PCINET_NET_RXINT_OFF		(1<<2)
+#define PCINET_NET_REGISTERS_VALID	(1<<3)
+
+/* Driver State Bits */
+#define NET_STATE_STOPPED	0
+#define NET_STATE_RUNNING	1
+
+/* Doorbell Registers */
+#define UART_RX_READY_DBELL	(1<<0)
+#define UART_TX_EMPTY_DBELL	(1<<1)
+#define NET_RX_PACKET_DBELL	(1<<2)
+#define NET_TX_COMPLETE_DBELL	(1<<3)
+#define NET_START_REQ_DBELL	(1<<4)
+#define NET_START_ACK_DBELL	(1<<5)
+#define NET_STOP_REQ_DBELL	(1<<6)
+#define NET_STOP_ACK_DBELL	(1<<7)
+
+#endif /* PCINET_H */
+
+/* vim: set ts=8 sts=8 sw=8 noet tw=92: */
-- 
1.5.4.3











More information about the U-Boot mailing list