symbian-qemu-0.9.1-12/qemu-symbian-svp/hw/etraxfs_eth.c
changeset 1 2fb8b9db1c86
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/symbian-qemu-0.9.1-12/qemu-symbian-svp/hw/etraxfs_eth.c	Fri Jul 31 15:01:17 2009 +0100
@@ -0,0 +1,607 @@
+/*
+ * QEMU ETRAX Ethernet Controller.
+ *
+ * Copyright (c) 2008 Edgar E. Iglesias, Axis Communications AB.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include "hw.h"
+#include "net.h"
+
+#include "etraxfs_dma.h"
+
+#define D(x)
+
+/* Advertisement control register. */
+#define ADVERTISE_10HALF        0x0020  /* Try for 10mbps half-duplex  */
+#define ADVERTISE_10FULL        0x0040  /* Try for 10mbps full-duplex  */
+#define ADVERTISE_100HALF       0x0080  /* Try for 100mbps half-duplex */
+#define ADVERTISE_100FULL       0x0100  /* Try for 100mbps full-duplex */
+
+/* 
+ * The MDIO extensions in the TDK PHY model were reversed engineered from the 
+ * linux driver (PHYID and Diagnostics reg).
+ * TODO: Add friendly names for the register nums.
+ */
+struct qemu_phy
+{
+	uint32_t regs[32];
+
+	unsigned int (*read)(struct qemu_phy *phy, unsigned int req);
+	void (*write)(struct qemu_phy *phy, unsigned int req, 
+		      unsigned int data);
+};
+
+static unsigned int tdk_read(struct qemu_phy *phy, unsigned int req)
+{
+	int regnum;
+	unsigned r = 0;
+
+	regnum = req & 0x1f;
+
+	switch (regnum) {
+		case 1:
+			/* MR1.	 */
+			/* Speeds and modes.  */
+			r |= (1 << 13) | (1 << 14);
+			r |= (1 << 11) | (1 << 12);
+			r |= (1 << 5); /* Autoneg complete.  */
+			r |= (1 << 3); /* Autoneg able.	 */
+			r |= (1 << 2); /* Link.	 */
+			break;
+		case 5:
+			/* Link partner ability.
+			   We are kind; always agree with whatever best mode
+			   the guest advertises.  */
+			r = 1 << 14; /* Success.  */
+			/* Copy advertised modes.  */
+			r |= phy->regs[4] & (15 << 5);
+			/* Autoneg support.  */
+			r |= 1;
+			break;
+		case 18:
+		{
+			/* Diagnostics reg.  */
+			int duplex = 0;
+			int speed_100 = 0;
+
+			/* Are we advertising 100 half or 100 duplex ? */
+			speed_100 = !!(phy->regs[4] & ADVERTISE_100HALF);
+			speed_100 |= !!(phy->regs[4] & ADVERTISE_100FULL);
+
+			/* Are we advertising 10 duplex or 100 duplex ? */
+			duplex = !!(phy->regs[4] & ADVERTISE_100FULL);
+			duplex |= !!(phy->regs[4] & ADVERTISE_10FULL);
+			r = (speed_100 << 10) | (duplex << 11);
+		}
+		break;
+
+		default:
+			r = phy->regs[regnum];
+			break;
+	}
+	D(printf("\n%s %x = reg[%d]\n", __func__, r, regnum));
+	return r;
+}
+
+static void 
+tdk_write(struct qemu_phy *phy, unsigned int req, unsigned int data)
+{
+	int regnum;
+
+	regnum = req & 0x1f;
+	D(printf("%s reg[%d] = %x\n", __func__, regnum, data));
+	switch (regnum) {
+		default:
+			phy->regs[regnum] = data;
+			break;
+	}
+}
+
+static void 
+tdk_init(struct qemu_phy *phy)
+{
+	phy->regs[0] = 0x3100;
+	/* PHY Id.  */
+	phy->regs[2] = 0x0300;
+	phy->regs[3] = 0xe400;
+	/* Autonegotiation advertisement reg.  */
+	phy->regs[4] = 0x01E1;
+
+	phy->read = tdk_read;
+	phy->write = tdk_write;
+}
+
+struct qemu_mdio
+{
+	/* bus.	 */
+	int mdc;
+	int mdio;
+
+	/* decoder.  */
+	enum {
+		PREAMBLE,
+		SOF,
+		OPC,
+		ADDR,
+		REQ,
+		TURNAROUND,
+		DATA
+	} state;
+	unsigned int drive;
+
+	unsigned int cnt;
+	unsigned int addr;
+	unsigned int opc;
+	unsigned int req;
+	unsigned int data;
+
+	struct qemu_phy *devs[32];
+};
+
+static void 
+mdio_attach(struct qemu_mdio *bus, struct qemu_phy *phy, unsigned int addr)
+{
+	bus->devs[addr & 0x1f] = phy;
+}
+
+#ifdef USE_THIS_DEAD_CODE
+static void 
+mdio_detach(struct qemu_mdio *bus, struct qemu_phy *phy, unsigned int addr)
+{
+	bus->devs[addr & 0x1f] = NULL;	
+}
+#endif
+
+static void mdio_read_req(struct qemu_mdio *bus)
+{
+	struct qemu_phy *phy;
+
+	phy = bus->devs[bus->addr];
+	if (phy && phy->read)
+		bus->data = phy->read(phy, bus->req);
+	else 
+		bus->data = 0xffff;
+}
+
+static void mdio_write_req(struct qemu_mdio *bus)
+{
+	struct qemu_phy *phy;
+
+	phy = bus->devs[bus->addr];
+	if (phy && phy->write)
+		phy->write(phy, bus->req, bus->data);
+}
+
+static void mdio_cycle(struct qemu_mdio *bus)
+{
+	bus->cnt++;
+
+	D(printf("mdc=%d mdio=%d state=%d cnt=%d drv=%d\n",
+		bus->mdc, bus->mdio, bus->state, bus->cnt, bus->drive));
+#if 0
+	if (bus->mdc)
+		printf("%d", bus->mdio);
+#endif
+	switch (bus->state)
+	{
+		case PREAMBLE:
+			if (bus->mdc) {
+				if (bus->cnt >= (32 * 2) && !bus->mdio) {
+					bus->cnt = 0;
+					bus->state = SOF;
+					bus->data = 0;
+				}
+			}
+			break;
+		case SOF:
+			if (bus->mdc) {
+				if (bus->mdio != 1)
+					printf("WARNING: no SOF\n");
+				if (bus->cnt == 1*2) {
+					bus->cnt = 0;
+					bus->opc = 0;
+					bus->state = OPC;
+				}
+			}
+			break;
+		case OPC:
+			if (bus->mdc) {
+				bus->opc <<= 1;
+				bus->opc |= bus->mdio & 1;
+				if (bus->cnt == 2*2) {
+					bus->cnt = 0;
+					bus->addr = 0;
+					bus->state = ADDR;
+				}
+			}
+			break;
+		case ADDR:
+			if (bus->mdc) {
+				bus->addr <<= 1;
+				bus->addr |= bus->mdio & 1;
+
+				if (bus->cnt == 5*2) {
+					bus->cnt = 0;
+					bus->req = 0;
+					bus->state = REQ;
+				}
+			}
+			break;
+		case REQ:
+			if (bus->mdc) {
+				bus->req <<= 1;
+				bus->req |= bus->mdio & 1;
+				if (bus->cnt == 5*2) {
+					bus->cnt = 0;
+					bus->state = TURNAROUND;
+				}
+			}
+			break;
+		case TURNAROUND:
+			if (bus->mdc && bus->cnt == 2*2) {
+				bus->mdio = 0;
+				bus->cnt = 0;
+
+				if (bus->opc == 2) {
+					bus->drive = 1;
+					mdio_read_req(bus);
+					bus->mdio = bus->data & 1;
+				}
+				bus->state = DATA;
+			}
+			break;
+		case DATA:			
+			if (!bus->mdc) {
+				if (bus->drive) {
+					bus->mdio = !!(bus->data & (1 << 15));
+					bus->data <<= 1;
+				}
+			} else {
+				if (!bus->drive) {
+					bus->data <<= 1;
+					bus->data |= bus->mdio;
+				}
+				if (bus->cnt == 16 * 2) {
+					bus->cnt = 0;
+					bus->state = PREAMBLE;
+					if (!bus->drive)
+						mdio_write_req(bus);
+					bus->drive = 0;
+				}
+			}
+			break;
+		default:
+			break;
+	}
+}
+
+/* ETRAX-FS Ethernet MAC block starts here.  */
+
+#define RW_MA0_LO	  0x00
+#define RW_MA0_HI	  0x04
+#define RW_MA1_LO	  0x08
+#define RW_MA1_HI	  0x0c
+#define RW_GA_LO	  0x10
+#define RW_GA_HI	  0x14
+#define RW_GEN_CTRL	  0x18
+#define RW_REC_CTRL	  0x1c
+#define RW_TR_CTRL	  0x20
+#define RW_CLR_ERR	  0x24
+#define RW_MGM_CTRL	  0x28
+#define R_STAT		  0x2c
+#define FS_ETH_MAX_REGS	  0x5c
+
+struct fs_eth
+{
+	CPUState *env;
+	qemu_irq *irq;
+	VLANClientState *vc;
+	int ethregs;
+
+	/* Two addrs in the filter.  */
+	uint8_t macaddr[2][6];
+	uint32_t regs[FS_ETH_MAX_REGS];
+
+	struct etraxfs_dma_client *dma_out;
+	struct etraxfs_dma_client *dma_in;
+
+	/* MDIO bus.  */
+	struct qemu_mdio mdio_bus;
+	unsigned int phyaddr;
+	int duplex_mismatch;
+
+	/* PHY.	 */
+	struct qemu_phy phy;
+};
+
+static void eth_validate_duplex(struct fs_eth *eth)
+{
+	struct qemu_phy *phy;
+	unsigned int phy_duplex;
+	unsigned int mac_duplex;
+	int new_mm = 0;
+
+	phy = eth->mdio_bus.devs[eth->phyaddr];
+	phy_duplex = !!(phy->read(phy, 18) & (1 << 11));
+	mac_duplex = !!(eth->regs[RW_REC_CTRL] & 128);
+
+	if (mac_duplex != phy_duplex)
+		new_mm = 1;
+
+	if (eth->regs[RW_GEN_CTRL] & 1) {
+		if (new_mm != eth->duplex_mismatch) {
+			if (new_mm)
+				printf("HW: WARNING "
+				       "ETH duplex mismatch MAC=%d PHY=%d\n",
+				       mac_duplex, phy_duplex);
+			else
+				printf("HW: ETH duplex ok.\n");
+		}
+		eth->duplex_mismatch = new_mm;
+	}
+}
+
+static uint32_t eth_rinvalid (void *opaque, target_phys_addr_t addr)
+{
+	struct fs_eth *eth = opaque;
+	CPUState *env = eth->env;
+	cpu_abort(env, "Unsupported short access. reg=" TARGET_FMT_plx "\n",
+		  addr);
+	return 0;
+}
+
+static uint32_t eth_readl (void *opaque, target_phys_addr_t addr)
+{
+	struct fs_eth *eth = opaque;
+	uint32_t r = 0;
+
+	switch (addr) {
+		case R_STAT:
+			/* Attach an MDIO/PHY abstraction.  */
+			r = eth->mdio_bus.mdio & 1;
+			break;
+	default:
+		r = eth->regs[addr];
+		D(printf ("%s %x\n", __func__, addr));
+		break;
+	}
+	return r;
+}
+
+static void
+eth_winvalid (void *opaque, target_phys_addr_t addr, uint32_t value)
+{
+	struct fs_eth *eth = opaque;
+	CPUState *env = eth->env;
+	cpu_abort(env, "Unsupported short access. reg=" TARGET_FMT_plx "\n",
+		  addr);
+}
+
+static void eth_update_ma(struct fs_eth *eth, int ma)
+{
+	int reg;
+	int i = 0;
+
+	ma &= 1;
+
+	reg = RW_MA0_LO;
+	if (ma)
+		reg = RW_MA1_LO;
+
+	eth->macaddr[ma][i++] = eth->regs[reg];
+	eth->macaddr[ma][i++] = eth->regs[reg] >> 8;
+	eth->macaddr[ma][i++] = eth->regs[reg] >> 16;
+	eth->macaddr[ma][i++] = eth->regs[reg] >> 24;
+	eth->macaddr[ma][i++] = eth->regs[reg + 4];
+	eth->macaddr[ma][i++] = eth->regs[reg + 4] >> 8;
+
+	D(printf("set mac%d=%x.%x.%x.%x.%x.%x\n", ma,
+		 eth->macaddr[ma][0], eth->macaddr[ma][1],
+		 eth->macaddr[ma][2], eth->macaddr[ma][3],
+		 eth->macaddr[ma][4], eth->macaddr[ma][5]));
+}
+
+static void
+eth_writel (void *opaque, target_phys_addr_t addr, uint32_t value)
+{
+	struct fs_eth *eth = opaque;
+
+	switch (addr)
+	{
+		case RW_MA0_LO:
+			eth->regs[addr] = value;
+			eth_update_ma(eth, 0);
+			break;
+		case RW_MA0_HI:
+			eth->regs[addr] = value;
+			eth_update_ma(eth, 0);
+			break;
+		case RW_MA1_LO:
+			eth->regs[addr] = value;
+			eth_update_ma(eth, 1);
+			break;
+		case RW_MA1_HI:
+			eth->regs[addr] = value;
+			eth_update_ma(eth, 1);
+			break;
+
+		case RW_MGM_CTRL:
+			/* Attach an MDIO/PHY abstraction.  */
+			if (value & 2)
+				eth->mdio_bus.mdio = value & 1;
+			if (eth->mdio_bus.mdc != (value & 4)) {
+				mdio_cycle(&eth->mdio_bus);
+				eth_validate_duplex(eth);
+			}
+			eth->mdio_bus.mdc = !!(value & 4);
+			break;
+
+		case RW_REC_CTRL:
+			eth->regs[addr] = value;
+			eth_validate_duplex(eth);
+			break;
+
+		default:
+			eth->regs[addr] = value;
+			D(printf ("%s %x %x\n",
+				  __func__, addr, value));
+			break;
+	}
+}
+
+/* The ETRAX FS has a groupt address table (GAT) which works like a k=1 bloom
+   filter dropping group addresses we have not joined.	The filter has 64
+   bits (m). The has function is a simple nible xor of the group addr.	*/
+static int eth_match_groupaddr(struct fs_eth *eth, const unsigned char *sa)
+{
+	unsigned int hsh;
+	int m_individual = eth->regs[RW_REC_CTRL] & 4;
+	int match;
+
+	/* First bit on the wire of a MAC address signals multicast or
+	   physical address.  */
+	if (!m_individual && !sa[0] & 1)
+		return 0;
+
+	/* Calculate the hash index for the GA registers. */
+	hsh = 0;
+	hsh ^= (*sa) & 0x3f;
+	hsh ^= ((*sa) >> 6) & 0x03;
+	++sa;
+	hsh ^= ((*sa) << 2) & 0x03c;
+	hsh ^= ((*sa) >> 4) & 0xf;
+	++sa;
+	hsh ^= ((*sa) << 4) & 0x30;
+	hsh ^= ((*sa) >> 2) & 0x3f;
+	++sa;
+	hsh ^= (*sa) & 0x3f;
+	hsh ^= ((*sa) >> 6) & 0x03;
+	++sa;
+	hsh ^= ((*sa) << 2) & 0x03c;
+	hsh ^= ((*sa) >> 4) & 0xf;
+	++sa;
+	hsh ^= ((*sa) << 4) & 0x30;
+	hsh ^= ((*sa) >> 2) & 0x3f;
+
+	hsh &= 63;
+	if (hsh > 31)
+		match = eth->regs[RW_GA_HI] & (1 << (hsh - 32));
+	else
+		match = eth->regs[RW_GA_LO] & (1 << hsh);
+	D(printf("hsh=%x ga=%x.%x mtch=%d\n", hsh,
+		 eth->regs[RW_GA_HI], eth->regs[RW_GA_LO], match));
+	return match;
+}
+
+static int eth_can_receive(void *opaque)
+{
+	return 1;
+}
+
+static void eth_receive(void *opaque, const uint8_t *buf, int size)
+{
+	unsigned char sa_bcast[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
+	struct fs_eth *eth = opaque;
+	int use_ma0 = eth->regs[RW_REC_CTRL] & 1;
+	int use_ma1 = eth->regs[RW_REC_CTRL] & 2;
+	int r_bcast = eth->regs[RW_REC_CTRL] & 8;
+
+	if (size < 12)
+		return;
+
+	D(printf("%x.%x.%x.%x.%x.%x ma=%d %d bc=%d\n",
+		 buf[0], buf[1], buf[2], buf[3], buf[4], buf[5],
+		 use_ma0, use_ma1, r_bcast));
+	       
+	/* Does the frame get through the address filters?  */
+	if ((!use_ma0 || memcmp(buf, eth->macaddr[0], 6))
+	    && (!use_ma1 || memcmp(buf, eth->macaddr[1], 6))
+	    && (!r_bcast || memcmp(buf, sa_bcast, 6))
+	    && !eth_match_groupaddr(eth, buf))
+		return;
+
+	/* FIXME: Find another way to pass on the fake csum.  */
+	etraxfs_dmac_input(eth->dma_in, (void *)buf, size + 4, 1);
+}
+
+static int eth_tx_push(void *opaque, unsigned char *buf, int len)
+{
+	struct fs_eth *eth = opaque;
+
+	D(printf("%s buf=%p len=%d\n", __func__, buf, len));
+	qemu_send_packet(eth->vc, buf, len);
+	return len;
+}
+
+static CPUReadMemoryFunc *eth_read[] = {
+	&eth_rinvalid,
+	&eth_rinvalid,
+	&eth_readl,
+};
+
+static CPUWriteMemoryFunc *eth_write[] = {
+	&eth_winvalid,
+	&eth_winvalid,
+	&eth_writel,
+};
+
+void *etraxfs_eth_init(NICInfo *nd, CPUState *env, 
+		       qemu_irq *irq, target_phys_addr_t base)
+{
+	struct etraxfs_dma_client *dma = NULL;	
+	struct fs_eth *eth = NULL;
+
+	dma = qemu_mallocz(sizeof *dma * 2);
+	if (!dma)
+		return NULL;
+
+	eth = qemu_mallocz(sizeof *eth);
+	if (!eth)
+		goto err;
+
+	dma[0].client.push = eth_tx_push;
+	dma[0].client.opaque = eth;
+	dma[1].client.opaque = eth;
+	dma[1].client.pull = NULL;
+
+	eth->env = env;
+	eth->irq = irq;
+	eth->dma_out = dma;
+	eth->dma_in = dma + 1;
+
+	/* Connect the phy.  */
+	eth->phyaddr = 1;
+	tdk_init(&eth->phy);
+	mdio_attach(&eth->mdio_bus, &eth->phy, eth->phyaddr);
+
+	eth->ethregs = cpu_register_io_memory(0, eth_read, eth_write, eth);
+	cpu_register_physical_memory (base, 0x5c, eth->ethregs);
+
+	eth->vc = qemu_new_vlan_client(nd->vlan, 
+				       eth_receive, eth_can_receive, eth);
+
+	return dma;
+  err:
+	qemu_free(eth);
+	qemu_free(dma);
+	return NULL;
+}